diff --git a/_SETUP.md b/_SETUP.md index 8c080252e5b..ebd501013be 100644 --- a/_SETUP.md +++ b/_SETUP.md @@ -51,6 +51,12 @@ or in watch mode yarn test:unit --watch ``` +### To update Jest snapshots use + +```bash +yarn test:update-snapshots +``` + ### To run code formatter (prettier) run ```bash diff --git a/examples/dll-app-and-vendor/0-vendor/README.md b/examples/dll-app-and-vendor/0-vendor/README.md index 0db675ca871..7d737145f29 100644 --- a/examples/dll-app-and-vendor/0-vendor/README.md +++ b/examples/dll-app-and-vendor/0-vendor/README.md @@ -4,7 +4,7 @@ It's built separately from the app part. The vendors dll is only built when the The DllPlugin in combination with the `output.library` option exposes the internal require function as global variable in the target environment. -A manifest is creates which includes mappings from module names to internal ids. +A manifest is created which includes mappings from module names to internal ids. ### webpack.config.js diff --git a/examples/dll-app-and-vendor/0-vendor/template.md b/examples/dll-app-and-vendor/0-vendor/template.md index 662ea88e909..358db39d4c7 100644 --- a/examples/dll-app-and-vendor/0-vendor/template.md +++ b/examples/dll-app-and-vendor/0-vendor/template.md @@ -4,7 +4,7 @@ It's built separately from the app part. The vendors dll is only built when the The DllPlugin in combination with the `output.library` option exposes the internal require function as global variable in the target environment. -A manifest is creates which includes mappings from module names to internal ids. +A manifest is created which includes mappings from module names to internal ids. ### webpack.config.js diff --git a/lib/Compilation.js b/lib/Compilation.js index f388cf4dc09..635ced193e5 100644 --- a/lib/Compilation.js +++ b/lib/Compilation.js @@ -45,8 +45,10 @@ const ModuleDependency = require("./dependencies/ModuleDependency"); /** @typedef {import("./dependencies/SingleEntryDependency")} SingleEntryDependency */ /** @typedef {import("./dependencies/MultiEntryDependency")} MultiEntryDependency */ /** @typedef {import("./dependencies/DllEntryDependency")} DllEntryDependency */ +/** @typedef {import("./dependencies/DependencyReference")} DependencyReference */ /** @typedef {import("./DependenciesBlock")} DependenciesBlock */ /** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */ +/** @typedef {import("./Dependency")} Dependency */ /** @typedef {import("./Dependency").DependencyLocation} DependencyLocation */ /** @typedef {import("./Dependency").DependencyTemplate} DependencyTemplate */ /** @typedef {import("./util/createHash").Hash} Hash */ @@ -213,7 +215,15 @@ class Compilation extends Tapable { failedModule: new SyncHook(["module", "error"]), /** @type {SyncHook} */ succeedModule: new SyncHook(["module"]), - /** @type {SyncHook} */ + + /** @type {SyncWaterfallHook} */ + dependencyReference: new SyncWaterfallHook([ + "dependencyReference", + "dependency", + "module" + ]), + + /** @type {SyncHook} */ finishModules: new SyncHook(["modules"]), /** @type {SyncHook} */ finishRebuildingModule: new SyncHook(["module"]), @@ -1429,6 +1439,19 @@ class Compilation extends Tapable { } } + /** + * @param {Module} module the module containing the dependency + * @param {Dependency} dependency the dependency + * @returns {DependencyReference} a reference for the dependency + */ + getDependencyReference(module, dependency) { + // TODO remove dep.getReference existance check in webpack 5 + if (typeof dependency.getReference !== "function") return null; + const ref = dependency.getReference(); + if (!ref) return null; + return this.hooks.dependencyReference.call(ref, dependency, module); + } + /** * This method creates the Chunk graph from the Module graph * @private @@ -1458,7 +1481,7 @@ class Compilation extends Tapable { */ const iteratorDependency = d => { // We skip Dependencies without Reference - const ref = d.getReference(); + const ref = this.getDependencyReference(currentModule, d); if (!ref) { return; } @@ -1484,6 +1507,8 @@ class Compilation extends Tapable { blockQueue.push(b); }; + /** @type {Module} */ + let currentModule; /** @type {DependenciesBlock} */ let block; /** @type {DependenciesBlock[]} */ @@ -1495,6 +1520,7 @@ class Compilation extends Tapable { for (const module of this.modules) { blockQueue = [module]; + currentModule = module; while (blockQueue.length > 0) { block = blockQueue.pop(); blockInfoModules = new Set(); @@ -2246,6 +2272,8 @@ class Compilation extends Tapable { createChunkAssets() { const outputOptions = this.outputOptions; const cachedSourceMap = new Map(); + /** @type {Map} */ + const alreadyWrittenFiles = new Map(); for (let i = 0; i < this.chunks.length; i++) { const chunk = this.chunks[i]; chunk.files = []; @@ -2268,6 +2296,28 @@ class Compilation extends Tapable { const cacheName = fileManifest.identifier; const usedHash = fileManifest.hash; filenameTemplate = fileManifest.filenameTemplate; + file = this.getPath(filenameTemplate, fileManifest.pathOptions); + + // check if the same filename was already written by another chunk + const alreadyWritten = alreadyWrittenFiles.get(file); + if (alreadyWritten !== undefined) { + if (alreadyWritten.hash === usedHash) { + if (this.cache) { + this.cache[cacheName] = { + hash: usedHash, + source: alreadyWritten.source + }; + } + chunk.files.push(file); + this.hooks.chunkAsset.call(chunk, file); + continue; + } else { + throw new Error( + `Conflict: Multiple chunks emit assets to the same filename ${file}` + + ` (chunks ${alreadyWritten.chunk.id} and ${chunk.id})` + ); + } + } if ( this.cache && this.cache[cacheName] && @@ -2294,7 +2344,6 @@ class Compilation extends Tapable { }; } } - file = this.getPath(filenameTemplate, fileManifest.pathOptions); if (this.assets[file] && this.assets[file] !== source) { throw new Error( `Conflict: Multiple assets emit to the same filename ${file}` @@ -2303,6 +2352,11 @@ class Compilation extends Tapable { this.assets[file] = source; chunk.files.push(file); this.hooks.chunkAsset.call(chunk, file); + alreadyWrittenFiles.set(file, { + hash: usedHash, + source, + chunk + }); } } catch (err) { this.errors.push( diff --git a/lib/DefinePlugin.js b/lib/DefinePlugin.js index 4d485a9eb95..98d5aab3d0d 100644 --- a/lib/DefinePlugin.js +++ b/lib/DefinePlugin.js @@ -9,26 +9,44 @@ const BasicEvaluatedExpression = require("./BasicEvaluatedExpression"); const ParserHelpers = require("./ParserHelpers"); const NullFactory = require("./NullFactory"); -const stringifyObj = obj => { +class RuntimeValue { + constructor(fn, fileDependencies) { + this.fn = fn; + this.fileDependencies = fileDependencies || []; + } + + exec(parser) { + for (const fileDependency of this.fileDependencies) { + parser.state.module.buildInfo.fileDependencies.add(fileDependency); + } + + return this.fn(); + } +} + +const stringifyObj = (obj, parser) => { return ( "Object({" + Object.keys(obj) .map(key => { const code = obj[key]; - return JSON.stringify(key) + ":" + toCode(code); + return JSON.stringify(key) + ":" + toCode(code, parser); }) .join(",") + "})" ); }; -const toCode = code => { +const toCode = (code, parser) => { if (code === null) { return "null"; } if (code === undefined) { return "undefined"; } + if (code instanceof RuntimeValue) { + return toCode(code.exec(parser), parser); + } if (code instanceof RegExp && code.toString) { return code.toString(); } @@ -36,7 +54,7 @@ const toCode = code => { return "(" + code.toString() + ")"; } if (typeof code === "object") { - return stringifyObj(code); + return stringifyObj(code, parser); } return code + ""; }; @@ -46,6 +64,10 @@ class DefinePlugin { this.definitions = definitions; } + static runtimeValue(fn, fileDependencies) { + return new RuntimeValue(fn, fileDependencies); + } + apply(compiler) { const definitions = this.definitions; compiler.hooks.compilation.tap( @@ -64,6 +86,7 @@ class DefinePlugin { if ( code && typeof code === "object" && + !(code instanceof RuntimeValue) && !(code instanceof RegExp) ) { walkDefinitions(code, prefix + key + "."); @@ -90,7 +113,6 @@ class DefinePlugin { if (isTypeof) key = key.replace(/^typeof\s+/, ""); let recurse = false; let recurseTypeof = false; - code = toCode(code); if (!isTypeof) { parser.hooks.canRename .for(key) @@ -108,24 +130,25 @@ class DefinePlugin { */ if (recurse) return; recurse = true; - const res = parser.evaluate(code); + const res = parser.evaluate(toCode(code, parser)); recurse = false; res.setRange(expr.range); return res; }); - parser.hooks.expression - .for(key) - .tap( - "DefinePlugin", - /__webpack_require__/.test(code) - ? ParserHelpers.toConstantDependencyWithWebpackRequire( - parser, - code - ) - : ParserHelpers.toConstantDependency(parser, code) - ); + parser.hooks.expression.for(key).tap("DefinePlugin", expr => { + const strCode = toCode(code, parser); + if (/__webpack_require__/.test(strCode)) { + return ParserHelpers.toConstantDependencyWithWebpackRequire( + parser, + strCode + )(expr); + } else { + return ParserHelpers.toConstantDependency(parser, strCode)( + expr + ); + } + }); } - const typeofCode = isTypeof ? code : "typeof (" + code + ")"; parser.hooks.evaluateTypeof.for(key).tap("DefinePlugin", expr => { /** * this is needed in case there is a recursion in the DefinePlugin @@ -137,12 +160,18 @@ class DefinePlugin { */ if (recurseTypeof) return; recurseTypeof = true; + const typeofCode = isTypeof + ? toCode(code, parser) + : "typeof (" + toCode(code, parser) + ")"; const res = parser.evaluate(typeofCode); recurseTypeof = false; res.setRange(expr.range); return res; }); parser.hooks.typeof.for(key).tap("DefinePlugin", expr => { + const typeofCode = isTypeof + ? toCode(code, parser) + : "typeof (" + toCode(code, parser) + ")"; const res = parser.evaluate(typeofCode); if (!res.isString()) return; return ParserHelpers.toConstantDependency( @@ -153,7 +182,6 @@ class DefinePlugin { }; const applyObjectDefine = (key, obj) => { - const code = stringifyObj(obj); parser.hooks.canRename .for(key) .tap("DefinePlugin", ParserHelpers.approve); @@ -162,29 +190,29 @@ class DefinePlugin { .tap("DefinePlugin", expr => new BasicEvaluatedExpression().setTruthy().setRange(expr.range) ); - parser.hooks.evaluateTypeof - .for(key) - .tap("DefinePlugin", ParserHelpers.evaluateToString("object")); - parser.hooks.expression - .for(key) - .tap( - "DefinePlugin", - /__webpack_require__/.test(code) - ? ParserHelpers.toConstantDependencyWithWebpackRequire( - parser, - code - ) - : ParserHelpers.toConstantDependency(parser, code) - ); - parser.hooks.typeof - .for(key) - .tap( - "DefinePlugin", - ParserHelpers.toConstantDependency( + parser.hooks.evaluateTypeof.for(key).tap("DefinePlugin", expr => { + return ParserHelpers.evaluateToString("object")(expr); + }); + parser.hooks.expression.for(key).tap("DefinePlugin", expr => { + const strCode = stringifyObj(obj, parser); + + if (/__webpack_require__/.test(strCode)) { + return ParserHelpers.toConstantDependencyWithWebpackRequire( parser, - JSON.stringify("object") - ) - ); + strCode + )(expr); + } else { + return ParserHelpers.toConstantDependency(parser, strCode)( + expr + ); + } + }); + parser.hooks.typeof.for(key).tap("DefinePlugin", expr => { + return ParserHelpers.toConstantDependency( + parser, + JSON.stringify("object") + )(expr); + }); }; walkDefinitions(definitions, ""); diff --git a/lib/DependenciesBlockVariable.js b/lib/DependenciesBlockVariable.js index 4e32d96cde4..a1a24d184d7 100644 --- a/lib/DependenciesBlockVariable.js +++ b/lib/DependenciesBlockVariable.js @@ -63,11 +63,9 @@ class DependenciesBlockVariable { hasDependencies(filter) { if (filter) { - if (this.dependencies.some(filter)) return true; - } else { - if (this.dependencies.length > 0) return true; + return this.dependencies.some(filter); } - return false; + return this.dependencies.length > 0; } } diff --git a/lib/FlagDependencyUsagePlugin.js b/lib/FlagDependencyUsagePlugin.js index e464c1cd6ac..85c22160892 100644 --- a/lib/FlagDependencyUsagePlugin.js +++ b/lib/FlagDependencyUsagePlugin.js @@ -4,6 +4,11 @@ */ "use strict"; +/** @typedef {import("./Module")} Module */ +/** @typedef {import("./DependenciesBlock")} DependenciesBlock */ + +/** @typedef {false | true | string[]} UsedExports */ + const addToSet = (a, b) => { for (const item of b) { if (!a.includes(item)) a.push(item); @@ -54,37 +59,36 @@ class FlagDependencyUsagePlugin { return; } - queue.push([module, module.usedExports]); + queue.push([module, module, module.usedExports]); }; - const processDependenciesBlock = (depBlock, usedExports) => { + const processDependenciesBlock = (module, depBlock, usedExports) => { for (const dep of depBlock.dependencies) { - processDependency(dep); + processDependency(module, dep); } for (const variable of depBlock.variables) { for (const dep of variable.dependencies) { - processDependency(dep); + processDependency(module, dep); } } for (const block of depBlock.blocks) { - queue.push([block, usedExports]); + queue.push([module, block, usedExports]); } }; - const processDependency = dep => { - // TODO remove dep.getReference existance check in webpack 5 - const reference = dep.getReference && dep.getReference(); + const processDependency = (module, dep) => { + const reference = compilation.getDependencyReference(module, dep); if (!reference) return; - const module = reference.module; + const referenceModule = reference.module; const importedNames = reference.importedNames; - const oldUsed = module.used; - const oldUsedExports = module.usedExports; + const oldUsed = referenceModule.used; + const oldUsedExports = referenceModule.usedExports; if ( !oldUsed || (importedNames && (!oldUsedExports || !isSubset(oldUsedExports, importedNames))) ) { - processModule(module, importedNames); + processModule(referenceModule, importedNames); } }; @@ -92,6 +96,7 @@ class FlagDependencyUsagePlugin { module.used = false; } + /** @type {[Module, DependenciesBlock, UsedExports][]} */ const queue = []; for (const preparedEntrypoint of compilation._preparedEntrypoints) { if (preparedEntrypoint.module) { @@ -101,7 +106,7 @@ class FlagDependencyUsagePlugin { while (queue.length) { const queueItem = queue.pop(); - processDependenciesBlock(queueItem[0], queueItem[1]); + processDependenciesBlock(queueItem[0], queueItem[1], queueItem[2]); } } ); diff --git a/lib/RuntimeTemplate.js b/lib/RuntimeTemplate.js index 92292d389c1..5f06ed4af8a 100644 --- a/lib/RuntimeTemplate.js +++ b/lib/RuntimeTemplate.js @@ -265,6 +265,10 @@ module.exports = class RuntimeTemplate { if (exportName) { const used = module.isUsed(exportName); + if (!used) { + const comment = Template.toNormalComment(`unused export ${exportName}`); + return `${comment} undefined`; + } const comment = used !== exportName ? Template.toNormalComment(exportName) + " " : ""; const access = `${importVar}[${comment}${JSON.stringify(used)}]`; diff --git a/lib/WebpackOptionsApply.js b/lib/WebpackOptionsApply.js index d583787ac13..f22ca8c3074 100644 --- a/lib/WebpackOptionsApply.js +++ b/lib/WebpackOptionsApply.js @@ -56,6 +56,7 @@ const RuntimeChunkPlugin = require("./optimize/RuntimeChunkPlugin"); const NoEmitOnErrorsPlugin = require("./NoEmitOnErrorsPlugin"); const NamedModulesPlugin = require("./NamedModulesPlugin"); const NamedChunksPlugin = require("./NamedChunksPlugin"); +const HashedModuleIdsPlugin = require("./HashedModuleIdsPlugin"); const DefinePlugin = require("./DefinePlugin"); const SizeLimitsPlugin = require("./performance/SizeLimitsPlugin"); const WasmFinalizeExportsPlugin = require("./wasm/WasmFinalizeExportsPlugin"); @@ -354,6 +355,9 @@ class WebpackOptionsApply extends OptionsApply { if (options.optimization.namedModules) { new NamedModulesPlugin().apply(compiler); } + if (options.optimization.hashedModuleIds) { + new HashedModuleIdsPlugin().apply(compiler); + } if (options.optimization.namedChunks) { new NamedChunksPlugin().apply(compiler); } diff --git a/lib/WebpackOptionsDefaulter.js b/lib/WebpackOptionsDefaulter.js index e15d58d7905..f0d3f8922e0 100644 --- a/lib/WebpackOptionsDefaulter.js +++ b/lib/WebpackOptionsDefaulter.js @@ -263,6 +263,7 @@ class WebpackOptionsDefaulter extends OptionsDefaulter { "make", options => options.mode === "development" ); + this.set("optimization.hashedModuleIds", false); this.set( "optimization.namedChunks", "make", diff --git a/lib/dependencies/DependencyReference.js b/lib/dependencies/DependencyReference.js index 0499eedc4ee..a8f6afae069 100644 --- a/lib/dependencies/DependencyReference.js +++ b/lib/dependencies/DependencyReference.js @@ -7,6 +7,9 @@ /** @typedef {import("../Module")} Module */ class DependencyReference { + // TODO webpack 5: module must be dynamic, you must pass a function returning a module + // This is needed to remove the hack in ConcatenatedModule + // The problem is that the `module` in Dependency could be replaced i. e. because of Scope Hoisting /** * * @param {Module} module the referenced module @@ -15,6 +18,7 @@ class DependencyReference { * @param {number} order the order information or NaN if don't care */ constructor(module, importedNames, weak = false, order = NaN) { + // TODO webpack 5: make it a getter this.module = module; // true: full object // false: only sideeffects/no export diff --git a/lib/dependencies/HarmonyExportImportedSpecifierDependency.js b/lib/dependencies/HarmonyExportImportedSpecifierDependency.js index adf0be81421..360c785b113 100644 --- a/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +++ b/lib/dependencies/HarmonyExportImportedSpecifierDependency.js @@ -602,10 +602,10 @@ HarmonyExportImportedSpecifierDependency.Template = class HarmonyExportImportedS getReexportStatement(module, key, name, valueKey) { const exportsName = module.exportsArgument; - const returnValue = this.getReturnValue(valueKey); + const returnValue = this.getReturnValue(name, valueKey); return `__webpack_require__.d(${exportsName}, ${JSON.stringify( key - )}, function() { return ${name}${returnValue}; });\n`; + )}, function() { return ${returnValue}; });\n`; } getReexportFakeNamespaceObjectStatement(module, key, name) { @@ -616,20 +616,29 @@ HarmonyExportImportedSpecifierDependency.Template = class HarmonyExportImportedS } getConditionalReexportStatement(module, key, name, valueKey) { + if (valueKey === false) { + return "/* unused export */\n"; + } const exportsName = module.exportsArgument; - const returnValue = this.getReturnValue(valueKey); + const returnValue = this.getReturnValue(name, valueKey); return `if(__webpack_require__.o(${name}, ${JSON.stringify( valueKey )})) __webpack_require__.d(${exportsName}, ${JSON.stringify( key - )}, function() { return ${name}${returnValue}; });\n`; + )}, function() { return ${returnValue}; });\n`; } - getReturnValue(valueKey) { + getReturnValue(name, valueKey) { if (valueKey === null) { - return "_default.a"; + return `${name}_default.a`; + } + if (valueKey === "") { + return name; + } + if (valueKey === false) { + return "/* unused export */ undefined"; } - return valueKey && "[" + JSON.stringify(valueKey) + "]"; + return `${name}[${JSON.stringify(valueKey)}]`; } }; diff --git a/lib/optimize/ConcatenatedModule.js b/lib/optimize/ConcatenatedModule.js index 2e67f9d014d..05f06b501b7 100644 --- a/lib/optimize/ConcatenatedModule.js +++ b/lib/optimize/ConcatenatedModule.js @@ -20,6 +20,13 @@ const HarmonyCompatibilityDependency = require("../dependencies/HarmonyCompatibi const createHash = require("../util/createHash"); /** @typedef {import("../Dependency")} Dependency */ +/** @typedef {import("../Compilation")} Compilation */ + +/** + * @typedef {Object} ConcatenationEntry + * @property {"concatenated" | "external"} type + * @property {Module} module + */ const ensureNsObjSource = ( info, @@ -125,6 +132,8 @@ const getFinalName = ( requestShortener, strictHarmonyModule ); + } else if (!info.module.isUsed(exportName)) { + return "/* unused export */ undefined"; } const name = info.internalNames.get(directExport); if (!name) { @@ -275,7 +284,7 @@ const getPathInAst = (ast, node) => { }; class ConcatenatedModule extends Module { - constructor(rootModule, modules) { + constructor(rootModule, modules, concatenationList) { super("javascript/esm", null); super.setChunks(rootModule._chunks); @@ -320,10 +329,9 @@ class ConcatenatedModule extends Module { this.warnings = []; this.errors = []; - this._orderedConcatenationList = this._createOrderedConcatenationList( - rootModule, - modulesSet - ); + this._orderedConcatenationList = + concatenationList || + ConcatenatedModule.createConcatenationList(rootModule, modulesSet, null); for (const info of this._orderedConcatenationList) { if (info.type === "concatenated") { const m = info.module; @@ -410,7 +418,13 @@ class ConcatenatedModule extends Module { }, 0); } - _createOrderedConcatenationList(rootModule, modulesSet) { + /** + * @param {Module} rootModule the root of the concatenation + * @param {Set} modulesSet a set of modules which should be concatenated + * @param {Compilation} compilation the compilation context + * @returns {ConcatenationEntry[]} concatenation list + */ + static createConcatenationList(rootModule, modulesSet, compilation) { const list = []; const set = new Set(); @@ -424,15 +438,16 @@ class ConcatenatedModule extends Module { const references = module.dependencies .filter(dep => dep instanceof HarmonyImportDependency) .map(dep => { - const ref = dep.getReference(); + const ref = compilation.getDependencyReference(module, dep); if (ref) map.set(ref, dep); return ref; }) .filter(ref => ref); DependencyReference.sort(references); + // TODO webpack 5: remove this hack, see also DependencyReference return references.map(ref => { const dep = map.get(ref); - return () => dep.getReference().module; + return () => compilation.getDependencyReference(module, dep).module; }); }; diff --git a/lib/optimize/ModuleConcatenationPlugin.js b/lib/optimize/ModuleConcatenationPlugin.js index 24710016f95..15b7d796a39 100644 --- a/lib/optimize/ModuleConcatenationPlugin.js +++ b/lib/optimize/ModuleConcatenationPlugin.js @@ -213,8 +213,9 @@ class ModuleConcatenationPlugin { const failureCache = new Map(); // try to add all imports - for (const imp of this.getImports(currentRoot)) { - const problem = this.tryToAdd( + for (const imp of this._getImports(compilation, currentRoot)) { + const problem = this._tryToAdd( + compilation, currentConfiguration, imp, possibleInners, @@ -245,9 +246,15 @@ class ModuleConcatenationPlugin { for (const concatConfiguration of concatConfigurations) { if (usedModules.has(concatConfiguration.rootModule)) continue; const modules = concatConfiguration.getModules(); + const rootModule = concatConfiguration.rootModule; const newModule = new ConcatenatedModule( - concatConfiguration.rootModule, - modules + rootModule, + Array.from(modules), + ConcatenatedModule.createConcatenationList( + rootModule, + modules, + compilation + ) ); for (const warning of concatConfiguration.getWarningsSorted()) { newModule.optimizationBailout.push(requestShortener => { @@ -320,15 +327,16 @@ class ModuleConcatenationPlugin { ); } - getImports(module) { + _getImports(compilation, module) { return new Set( module.dependencies // Get reference info only for harmony Dependencies - .map( - dep => - dep instanceof HarmonyImportDependency ? dep.getReference() : null - ) + .map(dep => { + if (!(dep instanceof HarmonyImportDependency)) return null; + if (!compilation) return dep.getReference(); + return compilation.getDependencyReference(module, dep); + }) // Reference is valid and has a module // Dependencies are simple enough to concat them @@ -345,7 +353,7 @@ class ModuleConcatenationPlugin { ); } - tryToAdd(config, module, possibleModules, failureCache) { + _tryToAdd(compilation, config, module, possibleModules, failureCache) { const cacheEntry = failureCache.get(module); if (cacheEntry) { return cacheEntry; @@ -383,7 +391,8 @@ class ModuleConcatenationPlugin { ) continue; - const problem = this.tryToAdd( + const problem = this._tryToAdd( + compilation, testConfig, reason.module, possibleModules, @@ -399,8 +408,14 @@ class ModuleConcatenationPlugin { config.set(testConfig); // Eagerly try to add imports too if possible - for (const imp of this.getImports(module)) { - const problem = this.tryToAdd(config, imp, possibleModules, failureCache); + for (const imp of this._getImports(compilation, module)) { + const problem = this._tryToAdd( + compilation, + config, + imp, + possibleModules, + failureCache + ); if (problem) { config.addWarning(imp, problem); } @@ -451,7 +466,7 @@ class ConcatConfiguration { } getModules() { - return this.modules.asArray(); + return this.modules.asSet(); } clone() { diff --git a/lib/wasm/WasmFinalizeExportsPlugin.js b/lib/wasm/WasmFinalizeExportsPlugin.js index 5187dd9f124..09584222bb3 100644 --- a/lib/wasm/WasmFinalizeExportsPlugin.js +++ b/lib/wasm/WasmFinalizeExportsPlugin.js @@ -25,7 +25,10 @@ class WasmFinalizeExportsPlugin { for (const reason of module.reasons) { // 2. is referenced by a non-WebAssembly module if (reason.module.type.startsWith("webassembly") === false) { - const ref = reason.dependency.getReference(); + const ref = compilation.getDependencyReference( + reason.module, + reason.dependency + ); const importedNames = ref.importedNames; diff --git a/lib/web/JsonpMainTemplatePlugin.js b/lib/web/JsonpMainTemplatePlugin.js index e843a270a44..018d2147b09 100644 --- a/lib/web/JsonpMainTemplatePlugin.js +++ b/lib/web/JsonpMainTemplatePlugin.js @@ -154,15 +154,21 @@ class JsonpMainTemplatePlugin { : "", "script.charset = 'utf-8';", `script.timeout = ${chunkLoadTimeout / 1000};`, - crossOriginLoading - ? `script.crossOrigin = ${JSON.stringify(crossOriginLoading)};` - : "", `if (${mainTemplate.requireFn}.nc) {`, Template.indent( `script.setAttribute("nonce", ${mainTemplate.requireFn}.nc);` ), "}", "script.src = jsonpScriptSrc(chunkId);", + crossOriginLoading + ? Template.asString([ + "if (script.src.indexOf(window.location.origin + '/') !== 0) {", + Template.indent( + `script.crossOrigin = ${JSON.stringify(crossOriginLoading)};` + ), + "}" + ]) + : "", "onScriptComplete = function (event) {", Template.indent([ "// avoid mem leaks in IE.", @@ -208,9 +214,6 @@ class JsonpMainTemplatePlugin { ? `link.type = ${JSON.stringify(jsonpScriptType)};` : "", "link.charset = 'utf-8';", - crossOriginLoading - ? `link.crossOrigin = ${JSON.stringify(crossOriginLoading)};` - : "", `if (${mainTemplate.requireFn}.nc) {`, Template.indent( `link.setAttribute("nonce", ${mainTemplate.requireFn}.nc);` @@ -218,7 +221,16 @@ class JsonpMainTemplatePlugin { "}", 'link.rel = "preload";', 'link.as = "script";', - "link.href = jsonpScriptSrc(chunkId);" + "link.href = jsonpScriptSrc(chunkId);", + crossOriginLoading + ? Template.asString([ + "if (link.href.indexOf(window.location.origin + '/') !== 0) {", + Template.indent( + `link.crossOrigin = ${JSON.stringify(crossOriginLoading)};` + ), + "}" + ]) + : "" ]); } ); diff --git a/lib/webpack.js b/lib/webpack.js index facfebf6c08..f5a8af68f95 100644 --- a/lib/webpack.js +++ b/lib/webpack.js @@ -123,6 +123,9 @@ exportPlugins(exports, { UmdMainTemplatePlugin: () => require("./UmdMainTemplatePlugin"), WatchIgnorePlugin: () => require("./WatchIgnorePlugin") }); +exportPlugins((exports.dependencies = {}), { + DependencyReference: () => require("./dependencies/DependencyReference") +}); exportPlugins((exports.optimize = {}), { AggressiveMergingPlugin: () => require("./optimize/AggressiveMergingPlugin"), AggressiveSplittingPlugin: () => diff --git a/package.json b/package.json index 125b88d2a3f..3f40c05b6f6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "webpack", - "version": "4.12.2", + "version": "4.14.0", "author": "Tobias Koppers @sokra", "description": "Packs CommonJs/AMD modules for the browser. Allows to split your codebase into multiple bundles, which can be loaded on demand. Support loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.", "license": "MIT", @@ -15,7 +15,7 @@ "ajv": "^6.1.0", "ajv-keywords": "^3.1.0", "chrome-trace-event": "^1.0.0", - "enhanced-resolve": "^4.0.0", + "enhanced-resolve": "^4.1.0", "eslint-scope": "^3.7.1", "json-parse-better-errors": "^1.0.2", "loader-runner": "^2.3.0", @@ -105,6 +105,7 @@ "scripts": { "setup": "node ./setup/setup.js", "test": "node --max-old-space-size=4096 --trace-deprecation node_modules/jest-cli/bin/jest", + "test:update-snapshots": "yarn jest -u", "test:integration": "node --max-old-space-size=4096 --trace-deprecation node_modules/jest-cli/bin/jest --testMatch \"/test/*.test.js\"", "test:basic": "node --max-old-space-size=4096 --trace-deprecation node_modules/jest-cli/bin/jest --testMatch \"/test/{TestCasesNormal,StatsTestCases,ConfigTestCases}.test.js\"", "test:unit": "node --max-old-space-size=4096 --trace-deprecation node_modules/jest-cli/bin/jest --testMatch \"/test/*.unittest.js\"", diff --git a/schemas/WebpackOptions.json b/schemas/WebpackOptions.json index ed3dfa894c9..2fe65fb16b3 100644 --- a/schemas/WebpackOptions.json +++ b/schemas/WebpackOptions.json @@ -1554,6 +1554,10 @@ "description": "Use readable module identifiers for better debugging", "type": "boolean" }, + "hashedModuleIds": { + "description": "Use hashed module id instead module identifiers for better long term caching", + "type": "boolean" + }, "namedChunks": { "description": "Use readable chunk identifiers for better debugging", "type": "boolean" diff --git a/test/ConfigTestCases.test.js b/test/ConfigTestCases.test.js index 4f3808341bd..4923509f2dd 100644 --- a/test/ConfigTestCases.test.js +++ b/test/ConfigTestCases.test.js @@ -178,7 +178,14 @@ describe("ConfigTestCases", () => { expect: expect, setTimeout: setTimeout, clearTimeout: clearTimeout, - document: new FakeDocument() + document: new FakeDocument(), + location: { + href: "https://test.cases/path/index.html", + origin: "https://test.cases", + toString() { + return "https://test.cases/path/index.html"; + } + } }; function _require(currentDirectory, module) { diff --git a/test/TestCases.template.js b/test/TestCases.template.js index 57dab3269db..89e63c7cadb 100644 --- a/test/TestCases.template.js +++ b/test/TestCases.template.js @@ -29,6 +29,7 @@ const DEFAULT_OPTIMIZATIONS = { noEmitOnErrors: false, concatenateModules: false, namedModules: false, + hashedModuleIds: false, minimizer: [uglifyJsForTesting] }; diff --git a/test/__snapshots__/StatsTestCases.test.js.snap b/test/__snapshots__/StatsTestCases.test.js.snap index 379c50ce9be..3808b4ea6a8 100644 --- a/test/__snapshots__/StatsTestCases.test.js.snap +++ b/test/__snapshots__/StatsTestCases.test.js.snap @@ -693,7 +693,7 @@ exports[`StatsTestCases should print correct stats for concat-and-sideeffects 1` `; exports[`StatsTestCases should print correct stats for define-plugin 1`] = ` -"Hash: cfe08d4450db77f81610f4228fcb997ec81e2aa6 +"Hash: cfe08d4450db77f81610f4228fcb997ec81e2aa6bb43e7d151657ea2b793 Child Hash: cfe08d4450db77f81610 Time: Xms @@ -709,6 +709,14 @@ Child Asset Size Chunks Chunk Names main.js 3.6 KiB 0 [emitted] main Entrypoint main = main.js + [0] ./index.js 24 bytes {0} [built] +Child + Hash: bb43e7d151657ea2b793 + Time: Xms + Built at: Thu Jan 01 1970 00:00:00 GMT + Asset Size Chunks Chunk Names + main.js 3.6 KiB 0 [emitted] main + Entrypoint main = main.js [0] ./index.js 24 bytes {0} [built]" `; @@ -1830,7 +1838,7 @@ exports[`StatsTestCases should print correct stats for preload 1`] = ` normal.js 130 bytes 1 [emitted] normal preloaded2.js 127 bytes 2 [emitted] preloaded2 preloaded3.js 130 bytes 3 [emitted] preloaded3 - main.js 9.87 KiB 4 [emitted] main + main.js 9.86 KiB 4 [emitted] main inner.js 136 bytes 5 [emitted] inner inner2.js 201 bytes 6 [emitted] inner2 Entrypoint main = main.js (preload: preloaded2.js preloaded.js preloaded3.js) diff --git a/test/configCases/crossorigin/set-crossorigin/empty.js b/test/configCases/crossorigin/set-crossorigin/empty.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/test/configCases/crossorigin/set-crossorigin/index.js b/test/configCases/crossorigin/set-crossorigin/index.js new file mode 100644 index 00000000000..6330978d157 --- /dev/null +++ b/test/configCases/crossorigin/set-crossorigin/index.js @@ -0,0 +1,67 @@ +it("should load script without crossorigin attribute (default)", function() { + const promise = import("./empty?a" /* webpackChunkName: "crossorigin-default" */); + + var script = document.head._children.pop(); + __non_webpack_require__("./crossorigin-default.web.js"); + expect(script.src).toBe("https://test.cases/path/crossorigin-default.web.js"); + expect(script.crossOrigin).toBe(undefined); + + return promise; +}); + +it("should load script without crossorigin attribute (relative)", function() { + var originalValue = __webpack_public_path__; + __webpack_public_path__ = "../"; + const promise = import("./empty?b" /* webpackChunkName: "crossorigin-relative" */); + __webpack_public_path__ = originalValue; + + var script = document.head._children.pop(); + __non_webpack_require__("./crossorigin-relative.web.js"); + expect(script.src).toBe("https://test.cases/crossorigin-relative.web.js"); + expect(script.crossOrigin).toBe(undefined); + + return promise; +}); + +it("should load script without crossorigin attribute (server relative)", function() { + var originalValue = __webpack_public_path__; + __webpack_public_path__ = "/server/"; + const promise = import("./empty?c" /* webpackChunkName: "crossorigin-server-relative" */); + __webpack_public_path__ = originalValue; + + var script = document.head._children.pop(); + __non_webpack_require__("./crossorigin-server-relative.web.js"); + expect(script.src).toBe("https://test.cases/server/crossorigin-server-relative.web.js"); + expect(script.crossOrigin).toBe(undefined); + + return promise; +}); + +it("should load script without crossorigin attribute (same origin)", function() { + var originalValue = __webpack_public_path__; + __webpack_public_path__ = "https://test.cases/"; + const promise = import("./empty?d" /* webpackChunkName: "crossorigin-same-origin" */); + __webpack_public_path__ = originalValue; + + var script = document.head._children.pop(); + __non_webpack_require__("./crossorigin-same-origin.web.js"); + expect(script.src).toBe("https://test.cases/crossorigin-same-origin.web.js"); + expect(script.crossOrigin).toBe(undefined); + + return promise; +}); + +it("should load script with crossorigin attribute anonymous (different origin)", function() { + var originalValue = __webpack_public_path__; + __webpack_public_path__ = "https://example.com/"; + const promise = import("./empty?e" /* webpackChunkName: "crossorigin-different-origin" */); + __webpack_public_path__ = originalValue; + + + var script = document.head._children.pop(); + __non_webpack_require__("./crossorigin-different-origin.web.js"); + expect(script.src).toBe("https://example.com/crossorigin-different-origin.web.js"); + expect(script.crossOrigin).toBe("anonymous"); + + return promise; +}); diff --git a/test/configCases/crossorigin/set-crossorigin/webpack.config.js b/test/configCases/crossorigin/set-crossorigin/webpack.config.js new file mode 100644 index 00000000000..68eeb96a523 --- /dev/null +++ b/test/configCases/crossorigin/set-crossorigin/webpack.config.js @@ -0,0 +1,13 @@ +module.exports = { + target: "web", + output: { + chunkFilename: "[name].web.js", + crossOriginLoading: "anonymous" + }, + performance: { + hints: false + }, + optimization: { + minimize: false + } +}; diff --git a/test/configCases/deep-scope-analysis/remove-export-scope-hoisting/index.js b/test/configCases/deep-scope-analysis/remove-export-scope-hoisting/index.js new file mode 100644 index 00000000000..be12281baf0 --- /dev/null +++ b/test/configCases/deep-scope-analysis/remove-export-scope-hoisting/index.js @@ -0,0 +1,9 @@ +import { test, unused } from "./module"; + +it("should run the test", () => { + expect(test()).toEqual({ + used: "used", + unused: undefined + }); + expect(unused).toEqual(undefined); +}); diff --git a/test/configCases/deep-scope-analysis/remove-export-scope-hoisting/module.js b/test/configCases/deep-scope-analysis/remove-export-scope-hoisting/module.js new file mode 100644 index 00000000000..8d9ec8267b9 --- /dev/null +++ b/test/configCases/deep-scope-analysis/remove-export-scope-hoisting/module.js @@ -0,0 +1,10 @@ +import { used, unused } from "./reference"; + +export function test() { + return { + used, + unused + }; +} + +export { unused } diff --git a/test/configCases/deep-scope-analysis/remove-export-scope-hoisting/reference.js b/test/configCases/deep-scope-analysis/remove-export-scope-hoisting/reference.js new file mode 100644 index 00000000000..725c814ac8f --- /dev/null +++ b/test/configCases/deep-scope-analysis/remove-export-scope-hoisting/reference.js @@ -0,0 +1,3 @@ +export var used = "used"; + +export var unused = "unused"; diff --git a/test/configCases/deep-scope-analysis/remove-export-scope-hoisting/webpack.config.js b/test/configCases/deep-scope-analysis/remove-export-scope-hoisting/webpack.config.js new file mode 100644 index 00000000000..3c8519e2a44 --- /dev/null +++ b/test/configCases/deep-scope-analysis/remove-export-scope-hoisting/webpack.config.js @@ -0,0 +1,34 @@ +const DependencyReference = require("../../../../").dependencies + .DependencyReference; +module.exports = { + optimization: { + usedExports: true, + concatenateModules: true + }, + plugins: [ + function() { + this.hooks.compilation.tap("Test", compilation => { + compilation.hooks.dependencyReference.tap( + "Test", + (ref, dep, module) => { + if ( + module.identifier().endsWith("module.js") && + ref.module && + ref.module.identifier().endsWith("reference.js") && + Array.isArray(ref.importedNames) && + ref.importedNames.includes("unused") + ) { + return new DependencyReference( + ref.module, + ref.importedNames.filter(item => item !== "unused"), + ref.weak, + ref.order + ); + } + return ref; + } + ); + }); + } + ] +}; diff --git a/test/configCases/deep-scope-analysis/remove-export/index.js b/test/configCases/deep-scope-analysis/remove-export/index.js new file mode 100644 index 00000000000..be12281baf0 --- /dev/null +++ b/test/configCases/deep-scope-analysis/remove-export/index.js @@ -0,0 +1,9 @@ +import { test, unused } from "./module"; + +it("should run the test", () => { + expect(test()).toEqual({ + used: "used", + unused: undefined + }); + expect(unused).toEqual(undefined); +}); diff --git a/test/configCases/deep-scope-analysis/remove-export/module.js b/test/configCases/deep-scope-analysis/remove-export/module.js new file mode 100644 index 00000000000..8d9ec8267b9 --- /dev/null +++ b/test/configCases/deep-scope-analysis/remove-export/module.js @@ -0,0 +1,10 @@ +import { used, unused } from "./reference"; + +export function test() { + return { + used, + unused + }; +} + +export { unused } diff --git a/test/configCases/deep-scope-analysis/remove-export/reference.js b/test/configCases/deep-scope-analysis/remove-export/reference.js new file mode 100644 index 00000000000..725c814ac8f --- /dev/null +++ b/test/configCases/deep-scope-analysis/remove-export/reference.js @@ -0,0 +1,3 @@ +export var used = "used"; + +export var unused = "unused"; diff --git a/test/configCases/deep-scope-analysis/remove-export/webpack.config.js b/test/configCases/deep-scope-analysis/remove-export/webpack.config.js new file mode 100644 index 00000000000..1b15ba720d7 --- /dev/null +++ b/test/configCases/deep-scope-analysis/remove-export/webpack.config.js @@ -0,0 +1,34 @@ +const DependencyReference = require("../../../../").dependencies + .DependencyReference; +module.exports = { + optimization: { + usedExports: true, + concatenateModules: false + }, + plugins: [ + function() { + this.hooks.compilation.tap("Test", compilation => { + compilation.hooks.dependencyReference.tap( + "Test", + (ref, dep, module) => { + if ( + module.identifier().endsWith("module.js") && + ref.module && + ref.module.identifier().endsWith("reference.js") && + Array.isArray(ref.importedNames) && + ref.importedNames.includes("unused") + ) { + return new DependencyReference( + ref.module, + ref.importedNames.filter(item => item !== "unused"), + ref.weak, + ref.order + ); + } + return ref; + } + ); + }); + } + ] +}; diff --git a/test/configCases/optimization/hashed-module-ids/files/file1.js b/test/configCases/optimization/hashed-module-ids/files/file1.js new file mode 100644 index 00000000000..3cec1b77aad --- /dev/null +++ b/test/configCases/optimization/hashed-module-ids/files/file1.js @@ -0,0 +1 @@ +module.exports = module.id; diff --git a/test/configCases/optimization/hashed-module-ids/files/file2.js b/test/configCases/optimization/hashed-module-ids/files/file2.js new file mode 100644 index 00000000000..3cec1b77aad --- /dev/null +++ b/test/configCases/optimization/hashed-module-ids/files/file2.js @@ -0,0 +1 @@ +module.exports = module.id; diff --git a/test/configCases/optimization/hashed-module-ids/files/file3.js b/test/configCases/optimization/hashed-module-ids/files/file3.js new file mode 100644 index 00000000000..3cec1b77aad --- /dev/null +++ b/test/configCases/optimization/hashed-module-ids/files/file3.js @@ -0,0 +1 @@ +module.exports = module.id; diff --git a/test/configCases/optimization/hashed-module-ids/files/file4.js b/test/configCases/optimization/hashed-module-ids/files/file4.js new file mode 100644 index 00000000000..3cec1b77aad --- /dev/null +++ b/test/configCases/optimization/hashed-module-ids/files/file4.js @@ -0,0 +1 @@ +module.exports = module.id; diff --git a/test/configCases/optimization/hashed-module-ids/files/file5.js b/test/configCases/optimization/hashed-module-ids/files/file5.js new file mode 100644 index 00000000000..3cec1b77aad --- /dev/null +++ b/test/configCases/optimization/hashed-module-ids/files/file5.js @@ -0,0 +1 @@ +module.exports = module.id; diff --git a/test/configCases/optimization/hashed-module-ids/index.js b/test/configCases/optimization/hashed-module-ids/index.js new file mode 100644 index 00000000000..93aeb5474b8 --- /dev/null +++ b/test/configCases/optimization/hashed-module-ids/index.js @@ -0,0 +1,7 @@ +it("should have named modules ids", function() { + for (var i = 1; i <= 5; i++) { + var moduleId = require("./files/file" + i + ".js"); + + expect(moduleId).toMatch(/^[/=a-zA-Z0-9]{4,5}$/); + } +}); diff --git a/test/configCases/optimization/hashed-module-ids/webpack.config.js b/test/configCases/optimization/hashed-module-ids/webpack.config.js new file mode 100644 index 00000000000..19d544d1dbe --- /dev/null +++ b/test/configCases/optimization/hashed-module-ids/webpack.config.js @@ -0,0 +1,5 @@ +module.exports = { + optimization: { + hashedModuleIds: true + } +}; diff --git a/test/configCases/optimization/named-modules/files/file1.js b/test/configCases/optimization/named-modules/files/file1.js new file mode 100644 index 00000000000..3cec1b77aad --- /dev/null +++ b/test/configCases/optimization/named-modules/files/file1.js @@ -0,0 +1 @@ +module.exports = module.id; diff --git a/test/configCases/optimization/named-modules/files/file2.js b/test/configCases/optimization/named-modules/files/file2.js new file mode 100644 index 00000000000..3cec1b77aad --- /dev/null +++ b/test/configCases/optimization/named-modules/files/file2.js @@ -0,0 +1 @@ +module.exports = module.id; diff --git a/test/configCases/optimization/named-modules/files/file3.js b/test/configCases/optimization/named-modules/files/file3.js new file mode 100644 index 00000000000..3cec1b77aad --- /dev/null +++ b/test/configCases/optimization/named-modules/files/file3.js @@ -0,0 +1 @@ +module.exports = module.id; diff --git a/test/configCases/optimization/named-modules/files/file4.js b/test/configCases/optimization/named-modules/files/file4.js new file mode 100644 index 00000000000..3cec1b77aad --- /dev/null +++ b/test/configCases/optimization/named-modules/files/file4.js @@ -0,0 +1 @@ +module.exports = module.id; diff --git a/test/configCases/optimization/named-modules/files/file5.js b/test/configCases/optimization/named-modules/files/file5.js new file mode 100644 index 00000000000..3cec1b77aad --- /dev/null +++ b/test/configCases/optimization/named-modules/files/file5.js @@ -0,0 +1 @@ +module.exports = module.id; diff --git a/test/configCases/optimization/named-modules/index.js b/test/configCases/optimization/named-modules/index.js new file mode 100644 index 00000000000..082bc1bda00 --- /dev/null +++ b/test/configCases/optimization/named-modules/index.js @@ -0,0 +1,10 @@ +var path = require("path"); + +it("should have named modules ids", function() { + for (var i = 1; i <= 5; i++) { + var expectedModuleId = "file" + i + ".js"; + var moduleId = require("./files/file" + i + ".js"); + + expect(path.basename(moduleId)).toBe(expectedModuleId); + } +}); diff --git a/test/configCases/optimization/named-modules/webpack.config.js b/test/configCases/optimization/named-modules/webpack.config.js new file mode 100644 index 00000000000..10572c1da34 --- /dev/null +++ b/test/configCases/optimization/named-modules/webpack.config.js @@ -0,0 +1,5 @@ +module.exports = { + optimization: { + namedModules: true + } +}; diff --git a/test/configCases/split-chunks/runtime-chunk/a.js b/test/configCases/split-chunks/runtime-chunk/a.js index e135684891b..fcae9162325 100644 --- a/test/configCases/split-chunks/runtime-chunk/a.js +++ b/test/configCases/split-chunks/runtime-chunk/a.js @@ -2,7 +2,7 @@ it("should be able to load the split chunk on demand", () => { const promise = import(/* webpackChunkName: "shared" */ "./shared"); const script = document.head._children[0]; - expect(script.src).toBe("dep~b~shared.js"); + expect(script.src).toBe("https://test.cases/path/dep~b~shared.js"); __non_webpack_require__("./dep~b~shared.js"); diff --git a/test/configCases/wasm/identical/index.js b/test/configCases/wasm/identical/index.js new file mode 100644 index 00000000000..178e4d2b979 --- /dev/null +++ b/test/configCases/wasm/identical/index.js @@ -0,0 +1,13 @@ +it("should allow reference the same wasm multiple times", function() { + return import("./module").then(function(module) { + const result = module.run(); + expect(result).toEqual(84); + }); +}); + +it("should allow reference the same wasm multiple times (other chunk)", function() { + return import("./module?2").then(function(module) { + const result = module.run(); + expect(result).toEqual(84); + }); +}); diff --git a/test/configCases/wasm/identical/module.js b/test/configCases/wasm/identical/module.js new file mode 100644 index 00000000000..14eb8ae2f07 --- /dev/null +++ b/test/configCases/wasm/identical/module.js @@ -0,0 +1,6 @@ +import { getNumber } from "./wasm.wat?1"; +import { getNumber as getNumber2 } from "./wasm.wat?2"; + +export function run() { + return getNumber() + getNumber2(); +}; diff --git a/test/configCases/wasm/identical/test.filter.js b/test/configCases/wasm/identical/test.filter.js new file mode 100644 index 00000000000..23177349638 --- /dev/null +++ b/test/configCases/wasm/identical/test.filter.js @@ -0,0 +1,5 @@ +var supportsWebAssembly = require("../../../helpers/supportsWebAssembly"); + +module.exports = function(config) { + return supportsWebAssembly(); +}; diff --git a/test/configCases/wasm/identical/wasm.wat b/test/configCases/wasm/identical/wasm.wat new file mode 100644 index 00000000000..3a135271020 --- /dev/null +++ b/test/configCases/wasm/identical/wasm.wat @@ -0,0 +1,10 @@ +(module + (type $t0 (func (param i32 i32) (result i32))) + (type $t1 (func (result i32))) + (func $add (export "add") (type $t0) (param $p0 i32) (param $p1 i32) (result i32) + (i32.add + (get_local $p0) + (get_local $p1))) + (func $getNumber (export "getNumber") (type $t1) (result i32) + (i32.const 42))) + diff --git a/test/configCases/wasm/identical/webpack.config.js b/test/configCases/wasm/identical/webpack.config.js new file mode 100644 index 00000000000..2081ec1207c --- /dev/null +++ b/test/configCases/wasm/identical/webpack.config.js @@ -0,0 +1,35 @@ +const { CachedSource } = require("webpack-sources"); + +/** @typedef {import("../../../lib/Compilation")} Compilation */ + +module.exports = { + module: { + rules: [ + { + test: /\.wat$/, + loader: "wast-loader", + type: "webassembly/experimental" + } + ] + }, + plugins: [ + function() { + this.hooks.compilation.tap( + "Test", + /** + * @param {Compilation} compilation Compilation + * @returns {void} + */ + compilation => { + compilation.moduleTemplates.webassembly.hooks.package.tap( + "Test", + source => { + // this is important to make each returned value a new instance + return new CachedSource(source); + } + ); + } + ); + } + ] +}; diff --git a/test/configCases/web/prefetch-preload/index.js b/test/configCases/web/prefetch-preload/index.js index 2d393f55a6b..dec98a7ccf4 100644 --- a/test/configCases/web/prefetch-preload/index.js +++ b/test/configCases/web/prefetch-preload/index.js @@ -10,11 +10,11 @@ beforeEach(() => { afterEach(() => { __webpack_nonce__ = oldNonce; __webpack_public_path__ = oldPublicPath; -}) +}); it("should prefetch and preload child chunks on chunk load", () => { __webpack_nonce__ = "nonce"; - __webpack_public_path__ = "/public/path/"; + __webpack_public_path__ = "https://example.com/public/path/"; let link, script; diff --git a/test/helpers/FakeDocument.js b/test/helpers/FakeDocument.js index 0c9d80de06f..680c5157640 100644 --- a/test/helpers/FakeDocument.js +++ b/test/helpers/FakeDocument.js @@ -20,6 +20,8 @@ class FakeElement { this._type = type; this._children = []; this._attributes = Object.create(null); + this._src = undefined; + this._href = undefined; } appendChild(node) { @@ -33,4 +35,40 @@ class FakeElement { getAttribute(name) { return this._attributes[name]; } + + _toRealUrl(value) { + if (/^\//.test(value)) { + return `https://test.cases${value}`; + } else if (/^\.\.\//.test(value)) { + return `https://test.cases${value.substr(2)}`; + } else if (/^\.\//.test(value)) { + return `https://test.cases/path${value.substr(1)}`; + } else if (/^\w+:\/\//.test(value)) { + return value; + } else if (/^\/\//.test(value)) { + return `https:${value}`; + } else { + return `https://test.cases/path/${value}`; + } + } + + set src(value) { + if (this._type === "script") { + this._src = this._toRealUrl(value); + } + } + + get src() { + return this._src; + } + + set href(value) { + if (this._type === "link") { + this._href = this._toRealUrl(value); + } + } + + get href() { + return this._href; + } } diff --git a/test/statsCases/define-plugin/123.txt b/test/statsCases/define-plugin/123.txt new file mode 100644 index 00000000000..190a18037c6 --- /dev/null +++ b/test/statsCases/define-plugin/123.txt @@ -0,0 +1 @@ +123 diff --git a/test/statsCases/define-plugin/321.txt b/test/statsCases/define-plugin/321.txt new file mode 100644 index 00000000000..3ae0b938fa8 --- /dev/null +++ b/test/statsCases/define-plugin/321.txt @@ -0,0 +1 @@ +321 diff --git a/test/statsCases/define-plugin/webpack.config.js b/test/statsCases/define-plugin/webpack.config.js index 999dc282d1d..32d87e4f89a 100644 --- a/test/statsCases/define-plugin/webpack.config.js +++ b/test/statsCases/define-plugin/webpack.config.js @@ -1,4 +1,11 @@ var webpack = require("../../../"); +var fs = require("fs"); +var join = require("path").join; + +function read(path) { + return JSON.stringify(fs.readFileSync(join(__dirname, path), "utf8")); +} + module.exports = [ { mode: "production", @@ -18,5 +25,22 @@ module.exports = [ VALUE: "321" }) ] + }, + + { + mode: "production", + entry: "./index", + plugins: [ + new webpack.DefinePlugin({ + VALUE: webpack.DefinePlugin.runtimeValue(() => read("123.txt"), [ + "./123.txt" + ]) + }), + new webpack.DefinePlugin({ + VALUE: webpack.DefinePlugin.runtimeValue(() => read("321.txt"), [ + "./321.txt" + ]) + }) + ] } ]; diff --git a/test/watchCases/plugins/define-plugin/0/index.js b/test/watchCases/plugins/define-plugin/0/index.js new file mode 100644 index 00000000000..182e0f038f8 --- /dev/null +++ b/test/watchCases/plugins/define-plugin/0/index.js @@ -0,0 +1,17 @@ +it("should be able to use dynamic defines in watch mode", function() { + const module = require("./module"); + expect(module).toEqual({ + default: WATCH_STEP, + type: "string", + [Symbol.toStringTag]: "Module" + }); +}); + +it("should not update a define when dependencies list is missing", function() { + const module2 = require("./module2"); + expect(module2).toEqual({ + default: "0", + type: "string", + [Symbol.toStringTag]: "Module" + }); +}); diff --git a/test/watchCases/plugins/define-plugin/0/module.js b/test/watchCases/plugins/define-plugin/0/module.js new file mode 100644 index 00000000000..272cd4deaa4 --- /dev/null +++ b/test/watchCases/plugins/define-plugin/0/module.js @@ -0,0 +1,2 @@ +export default TEST_VALUE; +export const type = typeof TEST_VALUE; diff --git a/test/watchCases/plugins/define-plugin/0/module2.js b/test/watchCases/plugins/define-plugin/0/module2.js new file mode 100644 index 00000000000..4bac8477803 --- /dev/null +++ b/test/watchCases/plugins/define-plugin/0/module2.js @@ -0,0 +1,2 @@ +export default TEST_VALUE2; +export const type = typeof TEST_VALUE2; diff --git a/test/watchCases/plugins/define-plugin/0/value.txt b/test/watchCases/plugins/define-plugin/0/value.txt new file mode 100644 index 00000000000..573541ac970 --- /dev/null +++ b/test/watchCases/plugins/define-plugin/0/value.txt @@ -0,0 +1 @@ +0 diff --git a/test/watchCases/plugins/define-plugin/1/value.txt b/test/watchCases/plugins/define-plugin/1/value.txt new file mode 100644 index 00000000000..d00491fd7e5 --- /dev/null +++ b/test/watchCases/plugins/define-plugin/1/value.txt @@ -0,0 +1 @@ +1 diff --git a/test/watchCases/plugins/define-plugin/webpack.config.js b/test/watchCases/plugins/define-plugin/webpack.config.js new file mode 100644 index 00000000000..113eb10e5b3 --- /dev/null +++ b/test/watchCases/plugins/define-plugin/webpack.config.js @@ -0,0 +1,22 @@ +const path = require("path"); +const fs = require("fs"); +const webpack = require("../../../../"); +const valueFile = path.resolve( + __dirname, + "../../../js/watch-src/plugins/define-plugin/value.txt" +); +module.exports = { + plugins: [ + new webpack.DefinePlugin({ + TEST_VALUE: webpack.DefinePlugin.runtimeValue( + () => { + return JSON.stringify(fs.readFileSync(valueFile, "utf-8").trim()); + }, + [valueFile] + ), + TEST_VALUE2: webpack.DefinePlugin.runtimeValue(() => { + return JSON.stringify(fs.readFileSync(valueFile, "utf-8").trim()); + }, []) + }) + ] +}; diff --git a/yarn.lock b/yarn.lock index 459841c8436..128195fd789 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1766,9 +1766,9 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0: dependencies: once "^1.4.0" -enhanced-resolve@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.0.0.tgz#e34a6eaa790f62fccd71d93959f56b2b432db10a" +enhanced-resolve@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f" dependencies: graceful-fs "^4.1.2" memory-fs "^0.4.0"