Merge branch 'master' into feature/type-compiler-compilation-save

This commit is contained in:
Tobias Koppers 2018-06-29 19:02:35 +02:00 committed by GitHub
commit 6e68f96d16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
65 changed files with 700 additions and 111 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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<Module>} */
succeedModule: new SyncHook(["module"]),
/** @type {SyncHook<Module[]>} */
/** @type {SyncWaterfallHook<DependencyReference, Dependency, Module>} */
dependencyReference: new SyncWaterfallHook([
"dependencyReference",
"dependency",
"module"
]),
/** @type {SyncHook<Module[]>} */
finishModules: new SyncHook(["modules"]),
/** @type {SyncHook<Module>} */
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<string, {hash: string, source: Source, chunk: Chunk}>} */
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(

View File

@ -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, "");

View File

@ -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;
}
}

View File

@ -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]);
}
}
);

View File

@ -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)}]`;

View File

@ -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);
}

View File

@ -263,6 +263,7 @@ class WebpackOptionsDefaulter extends OptionsDefaulter {
"make",
options => options.mode === "development"
);
this.set("optimization.hashedModuleIds", false);
this.set(
"optimization.namedChunks",
"make",

View File

@ -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

View File

@ -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)}]`;
}
};

View File

@ -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<Module>} 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;
});
};

View File

@ -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() {

View File

@ -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;

View File

@ -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)};`
),
"}"
])
: ""
]);
}
);

View File

@ -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: () =>

View File

@ -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 \"<rootDir>/test/*.test.js\"",
"test:basic": "node --max-old-space-size=4096 --trace-deprecation node_modules/jest-cli/bin/jest --testMatch \"<rootDir>/test/{TestCasesNormal,StatsTestCases,ConfigTestCases}.test.js\"",
"test:unit": "node --max-old-space-size=4096 --trace-deprecation node_modules/jest-cli/bin/jest --testMatch \"<rootDir>/test/*.unittest.js\"",

View File

@ -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"

View File

@ -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) {

View File

@ -29,6 +29,7 @@ const DEFAULT_OPTIMIZATIONS = {
noEmitOnErrors: false,
concatenateModules: false,
namedModules: false,
hashedModuleIds: false,
minimizer: [uglifyJsForTesting]
};

View File

@ -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)

View File

@ -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;
});

View File

@ -0,0 +1,13 @@
module.exports = {
target: "web",
output: {
chunkFilename: "[name].web.js",
crossOriginLoading: "anonymous"
},
performance: {
hints: false
},
optimization: {
minimize: false
}
};

View File

@ -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);
});

View File

@ -0,0 +1,10 @@
import { used, unused } from "./reference";
export function test() {
return {
used,
unused
};
}
export { unused }

View File

@ -0,0 +1,3 @@
export var used = "used";
export var unused = "unused";

View File

@ -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;
}
);
});
}
]
};

View File

@ -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);
});

View File

@ -0,0 +1,10 @@
import { used, unused } from "./reference";
export function test() {
return {
used,
unused
};
}
export { unused }

View File

@ -0,0 +1,3 @@
export var used = "used";
export var unused = "unused";

View File

@ -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;
}
);
});
}
]
};

View File

@ -0,0 +1 @@
module.exports = module.id;

View File

@ -0,0 +1 @@
module.exports = module.id;

View File

@ -0,0 +1 @@
module.exports = module.id;

View File

@ -0,0 +1 @@
module.exports = module.id;

View File

@ -0,0 +1 @@
module.exports = module.id;

View File

@ -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}$/);
}
});

View File

@ -0,0 +1,5 @@
module.exports = {
optimization: {
hashedModuleIds: true
}
};

View File

@ -0,0 +1 @@
module.exports = module.id;

View File

@ -0,0 +1 @@
module.exports = module.id;

View File

@ -0,0 +1 @@
module.exports = module.id;

View File

@ -0,0 +1 @@
module.exports = module.id;

View File

@ -0,0 +1 @@
module.exports = module.id;

View File

@ -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);
}
});

View File

@ -0,0 +1,5 @@
module.exports = {
optimization: {
namedModules: true
}
};

View File

@ -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");

View File

@ -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);
});
});

View File

@ -0,0 +1,6 @@
import { getNumber } from "./wasm.wat?1";
import { getNumber as getNumber2 } from "./wasm.wat?2";
export function run() {
return getNumber() + getNumber2();
};

View File

@ -0,0 +1,5 @@
var supportsWebAssembly = require("../../../helpers/supportsWebAssembly");
module.exports = function(config) {
return supportsWebAssembly();
};

View File

@ -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)))

View File

@ -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);
}
);
}
);
}
]
};

View File

@ -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;

View File

@ -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;
}
}

View File

@ -0,0 +1 @@
123

View File

@ -0,0 +1 @@
321

View File

@ -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"
])
})
]
}
];

View File

@ -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"
});
});

View File

@ -0,0 +1,2 @@
export default TEST_VALUE;
export const type = typeof TEST_VALUE;

View File

@ -0,0 +1,2 @@
export default TEST_VALUE2;
export const type = typeof TEST_VALUE2;

View File

@ -0,0 +1 @@
0

View File

@ -0,0 +1 @@
1

View File

@ -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());
}, [])
})
]
};

View File

@ -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"