Merge pull request #7821 from webpack/refactor/use_runtime

Use RuntimeTemplate to generate error code
This commit is contained in:
Tobias Koppers 2018-08-02 15:12:28 +02:00 committed by GitHub
commit a2bddc9195
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 172 additions and 469 deletions

View File

@ -10,7 +10,6 @@ const { OriginalSource, RawSource } = require("webpack-sources");
const Module = require("./Module");
const DelegatedExportsDependency = require("./dependencies/DelegatedExportsDependency");
const DelegatedSourceDependency = require("./dependencies/DelegatedSourceDependency");
const WebpackMissingModule = require("./dependencies/WebpackMissingModule");
/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("./Compilation")} Compilation */
@ -102,7 +101,9 @@ class DelegatedModule extends Module {
let str;
if (!sourceModule) {
str = WebpackMissingModule.moduleCode(this.sourceRequest);
str = runtimeTemplate.throwMissingModuleErrorBlock({
request: this.sourceRequest
});
} else {
str = `module.exports = (${runtimeTemplate.moduleExports({
module: sourceModule,

View File

@ -77,18 +77,19 @@ class DynamicEntryPlugin {
}
);
}
/**
* @param {string|string[]} entry entry value or array of entry paths
* @param {string} name name of entry
* @returns {SingleEntryDependency|MultiEntryDependency} returns dep
*/
static createDependency(entry, name) {
if (Array.isArray(entry)) {
return MultiEntryPlugin.createDependency(entry, name);
} else {
return SingleEntryPlugin.createDependency(entry, name);
}
}
}
module.exports = DynamicEntryPlugin;
/**
* @param {string|string[]} entry entry value or array of entry paths
* @param {string} name name of entry
* @returns {SingleEntryDependency|MultiEntryDependency} returns dep
*/
DynamicEntryPlugin.createDependency = (entry, name) => {
if (Array.isArray(entry)) {
return MultiEntryPlugin.createDependency(entry, name);
} else {
return SingleEntryPlugin.createDependency(entry, name);
}
};

View File

@ -8,7 +8,6 @@
const { OriginalSource, RawSource } = require("webpack-sources");
const Module = require("./Module");
const Template = require("./Template");
const WebpackMissingModule = require("./dependencies/WebpackMissingModule");
/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("./Chunk")} Chunk */
@ -18,17 +17,103 @@ const WebpackMissingModule = require("./dependencies/WebpackMissingModule");
/** @typedef {import("./RuntimeTemplate")} RuntimeTemplate */
/** @typedef {import("./util/createHash").Hash} Hash */
/**
* @param {string|string[]} variableName the variable name or path
* @param {string} type the module system
* @returns {string} the generated source
*/
const getSourceForGlobalVariableExternal = (variableName, type) => {
if (!Array.isArray(variableName)) {
// make it an array as the look up works the same basically
variableName = [variableName];
}
// needed for e.g. window["some"]["thing"]
const objectLookup = variableName.map(r => `[${JSON.stringify(r)}]`).join("");
return `(function() { module.exports = ${type}${objectLookup}; }());`;
};
/**
* @param {string|string[]} moduleAndSpecifiers the module request
* @returns {string} the generated source
*/
const getSourceForCommonJsExternal = moduleAndSpecifiers => {
if (!Array.isArray(moduleAndSpecifiers)) {
return `module.exports = require(${JSON.stringify(moduleAndSpecifiers)});`;
}
const moduleName = moduleAndSpecifiers[0];
const objectLookup = moduleAndSpecifiers
.slice(1)
.map(r => `[${JSON.stringify(r)}]`)
.join("");
return `module.exports = require(${moduleName})${objectLookup};`;
};
/**
* @param {string} variableName the variable name to check
* @param {string} request the request path
* @param {RuntimeTemplate} runtimeTemplate the runtime template
* @returns {string} the generated source
*/
const checkExternalVariable = (variableName, request, runtimeTemplate) => {
return `if(typeof ${variableName} === 'undefined') { ${runtimeTemplate.throwMissingModuleErrorBlock(
{ request }
)} }\n`;
};
/**
* @param {string|number} id the module id
* @param {boolean} optional true, if the module is optional
* @param {string} request the request path
* @param {RuntimeTemplate} runtimeTemplate the runtime template
* @returns {string} the generated source
*/
const getSourceForAmdOrUmdExternal = (
id,
optional,
request,
runtimeTemplate
) => {
const externalVariable = `__WEBPACK_EXTERNAL_MODULE_${Template.toIdentifier(
`${id}`
)}__`;
const missingModuleError = optional
? checkExternalVariable(externalVariable, request, runtimeTemplate)
: "";
return `${missingModuleError}module.exports = ${externalVariable};`;
};
/**
* @param {boolean} optional true, if the module is optional
* @param {string} request the request path
* @param {RuntimeTemplate} runtimeTemplate the runtime template
* @returns {string} the generated source
*/
const getSourceForDefaultCase = (optional, request, runtimeTemplate) => {
const missingModuleError = optional
? checkExternalVariable(request, request, runtimeTemplate)
: "";
return `${missingModuleError}module.exports = ${request};`;
};
class ExternalModule extends Module {
constructor(request, type, userRequest) {
super("javascript/dynamic", null);
// Info from Factory
/** @type {string} */
this.request = request;
/** @type {string} */
this.externalType = type;
/** @type {string} */
this.userRequest = userRequest;
/** @type {boolean} */
this.external = true;
}
/**
* @returns {string} the external module identifier
*/
libIdent() {
return this.userRequest;
}
@ -80,58 +165,7 @@ class ExternalModule extends Module {
callback();
}
getSourceForGlobalVariableExternal(variableName, type) {
if (!Array.isArray(variableName)) {
// make it an array as the look up works the same basically
variableName = [variableName];
}
// needed for e.g. window["some"]["thing"]
const objectLookup = variableName
.map(r => `[${JSON.stringify(r)}]`)
.join("");
return `(function() { module.exports = ${type}${objectLookup}; }());`;
}
getSourceForCommonJsExternal(moduleAndSpecifiers) {
if (!Array.isArray(moduleAndSpecifiers)) {
return `module.exports = require(${JSON.stringify(
moduleAndSpecifiers
)});`;
}
const moduleName = moduleAndSpecifiers[0];
const objectLookup = moduleAndSpecifiers
.slice(1)
.map(r => `[${JSON.stringify(r)}]`)
.join("");
return `module.exports = require(${moduleName})${objectLookup};`;
}
checkExternalVariable(variableToCheck, request) {
return `if(typeof ${variableToCheck} === 'undefined') {${WebpackMissingModule.moduleCode(
request
)}}\n`;
}
getSourceForAmdOrUmdExternal(id, optional, request) {
const externalVariable = `__WEBPACK_EXTERNAL_MODULE_${Template.toIdentifier(
`${id}`
)}__`;
const missingModuleError = optional
? this.checkExternalVariable(externalVariable, request)
: "";
return `${missingModuleError}module.exports = ${externalVariable};`;
}
getSourceForDefaultCase(optional, request) {
const missingModuleError = optional
? this.checkExternalVariable(request, request)
: "";
return `${missingModuleError}module.exports = ${request};`;
}
getSourceString(runtime) {
getSourceString(runtimeTemplate) {
const request =
typeof this.request === "object"
? this.request[this.externalType]
@ -140,39 +174,29 @@ class ExternalModule extends Module {
case "this":
case "window":
case "self":
return this.getSourceForGlobalVariableExternal(
request,
this.externalType
);
return getSourceForGlobalVariableExternal(request, this.externalType);
case "global":
return this.getSourceForGlobalVariableExternal(
runtime.outputOptions.globalObject,
return getSourceForGlobalVariableExternal(
runtimeTemplate.outputOptions.globalObject,
this.externalType
);
case "commonjs":
case "commonjs2":
return this.getSourceForCommonJsExternal(request);
return getSourceForCommonJsExternal(request);
case "amd":
case "umd":
case "umd2":
return this.getSourceForAmdOrUmdExternal(
return getSourceForAmdOrUmdExternal(
this.id,
this.optional,
request
request,
runtimeTemplate
);
default:
return this.getSourceForDefaultCase(this.optional, request);
return getSourceForDefaultCase(this.optional, request, runtimeTemplate);
}
}
getSource(sourceString) {
if (this.useSourceMap) {
return new OriginalSource(sourceString, this.identifier());
}
return new RawSource(sourceString);
}
/**
* @param {DependencyTemplates} dependencyTemplates the dependency templates
* @param {RuntimeTemplate} runtimeTemplate the runtime template
@ -180,7 +204,12 @@ class ExternalModule extends Module {
* @returns {Source} generated source
*/
source(dependencyTemplates, runtimeTemplate, type) {
return this.getSource(this.getSourceString(runtimeTemplate));
const sourceString = this.getSourceString(runtimeTemplate);
if (this.useSourceMap) {
return new OriginalSource(sourceString, this.identifier());
} else {
return new RawSource(sourceString);
}
}
/**

View File

@ -32,11 +32,9 @@ class MultiEntryPlugin {
compiler.hooks.compilation.tap(
"MultiEntryPlugin",
(compilation, { normalModuleFactory }) => {
const multiModuleFactory = new MultiModuleFactory();
compilation.dependencyFactories.set(
MultiEntryDependency,
multiModuleFactory
new MultiModuleFactory()
);
compilation.dependencyFactories.set(
SingleEntryDependency,

View File

@ -7,7 +7,6 @@
const { RawSource } = require("webpack-sources");
const Module = require("./Module");
const Template = require("./Template");
/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("./Compilation")} Compilation */
@ -100,17 +99,18 @@ class MultiModule extends Module {
if (idx === this.dependencies.length - 1) {
str.push("module.exports = ");
}
str.push("__webpack_require__(");
if (runtimeTemplate.outputOptions.pathinfo) {
str.push(Template.toComment(dep.request));
}
str.push(`${JSON.stringify(dep.module.id)}`);
str.push(")");
} else {
const content = require("./dependencies/WebpackMissingModule").module(
dep.request
str.push(
runtimeTemplate.moduleRaw({
module: dep.module,
request: dep.request
})
);
} else {
str.push(
runtimeTemplate.missingModule({
request: dep.request
})
);
str.push(content);
}
str.push(";\n");
idx++;

View File

@ -9,10 +9,16 @@ const Template = require("./Template");
/** @typedef {import("./AsyncDependenciesBlock")} AsyncDependenciesBlock */
/** @typedef {import("./Module")} Module */
/** @typedef {import("./RequestShortener")} RequestShortener */
module.exports = class RuntimeTemplate {
/**
* @param {TODO} outputOptions the compilation output options
* @param {RequestShortener} requestShortener the request shortener
*/
constructor(outputOptions, requestShortener) {
this.outputOptions = outputOptions || {};
/** @type {RequestShortener} */
this.requestShortener = requestShortener;
}
@ -47,21 +53,52 @@ module.exports = class RuntimeTemplate {
}
}
throwMissingModuleErrorFunction({ request }) {
/**
* @param {object} options generation options
* @param {string=} options.request request string used originally
* @returns {string} generated error block
*/
throwMissingModuleErrorBlock({ request }) {
const err = `Cannot find module '${request}'`;
return `function webpackMissingModule() { var e = new Error(${JSON.stringify(
return `var e = new Error(${JSON.stringify(
err
)}); e.code = 'MODULE_NOT_FOUND'; throw e; }`;
)}); e.code = 'MODULE_NOT_FOUND'; throw e;`;
}
/**
* @param {object} options generation options
* @param {string=} options.request request string used originally
* @returns {string} generated error function
*/
throwMissingModuleErrorFunction({ request }) {
return `function webpackMissingModule() { ${this.throwMissingModuleErrorBlock(
{ request }
)} }`;
}
/**
* @param {object} options generation options
* @param {string=} options.request request string used originally
* @returns {string} generated error IIFE
*/
missingModule({ request }) {
return `!(${this.throwMissingModuleErrorFunction({ request })}())`;
}
/**
* @param {object} options generation options
* @param {string=} options.request request string used originally
* @returns {string} generated error statement
*/
missingModuleStatement({ request }) {
return `${this.missingModule({ request })};\n`;
}
/**
* @param {object} options generation options
* @param {string=} options.request request string used originally
* @returns {string} generated error code
*/
missingModulePromise({ request }) {
return `Promise.resolve().then(${this.throwMissingModuleErrorFunction({
request
@ -69,7 +106,6 @@ module.exports = class RuntimeTemplate {
}
/**
*
* @param {Object} options options object
* @param {Module} options.module the module
* @param {string} options.request the request that should be printed as comment

View File

@ -6,7 +6,6 @@
"use strict";
const NullDependency = require("./NullDependency");
const webpackMissingModule = require("./WebpackMissingModule").module;
/** @typedef {import("webpack-sources").ReplaceSource} ReplaceSource */
/** @typedef {import("../Dependency")} Dependency */
@ -34,7 +33,9 @@ UnsupportedDependency.Template = class UnsupportedDependencyTemplate extends Nul
source.replace(
dep.range[0],
dep.range[1],
webpackMissingModule(dep.request)
runtimeTemplate.missingModule({
request: dep.request
})
);
}
};

View File

@ -1,22 +0,0 @@
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const toErrorCode = err =>
`var e = new Error(${JSON.stringify(err)}); e.code = 'MODULE_NOT_FOUND';`;
exports.module = request =>
`!(function webpackMissingModule() { ${exports.moduleCode(request)} }())`;
exports.promise = request => {
const errorCode = toErrorCode(`Cannot find module '${request}'`);
return `Promise.reject(function webpackMissingModule() { ${errorCode} return e; }())`;
};
exports.moduleCode = request => {
const errorCode = toErrorCode(`Cannot find module '${request}'`);
return `${errorCode} throw e;`;
};

View File

@ -1,308 +0,0 @@
/* globals describe, it, beforeEach */
"use strict";
const ExternalModule = require("../lib/ExternalModule");
const OriginalSource = require("webpack-sources").OriginalSource;
const RawSource = require("webpack-sources").RawSource;
describe("ExternalModule", () => {
let externalModule;
let request;
let type;
beforeEach(() => {
request = "some/request";
type = "some-type";
externalModule = new ExternalModule(request, type, `${type} ${request}`);
});
describe("#identifier", () => {
it("returns an identifier for this module", () => {
const expected = `external "${request}"`;
expect(externalModule.identifier()).toBe(expected);
});
});
describe("#readableIdentifier", () => {
it("returns an identifier for this module", () => {
const expected = `external "${request}"`;
expect(externalModule.identifier()).toBe(expected);
});
});
describe("#needRebuild", () => {
it("always returns false", () => {
expect(externalModule.needRebuild()).toBe(false);
});
});
describe("#size", () => {
it("always returns 42", () => {
expect(externalModule.size()).toBe(42);
});
});
describe("#source", () => {
it("calls getSource with the result of getSourceString", () => {
// set up
const expectedString = "something expected stringy";
const expectedSource = "something expected source";
externalModule.getSource = jest.fn(() => expectedSource);
externalModule.getSourceString = jest.fn(() => expectedString);
// invoke
const result = externalModule.source();
// check
expect(externalModule.getSource.mock.calls.length).toBe(1);
expect(externalModule.getSourceString.mock.calls.length).toBe(1);
expect(externalModule.getSource.mock.calls[0][0]).toBe(expectedString);
expect(result).toEqual(expectedSource);
});
});
describe("#getSource", () => {
describe("given it should use source maps", () => {
beforeEach(() => {
externalModule.useSourceMap = true;
});
it("returns an instance of OriginalSource", () => {
// set up
const someSourceString = "some source string";
// invoke
const result = externalModule.getSource(someSourceString);
// check
expect(result).toBeInstanceOf(OriginalSource);
});
});
describe("given it does not use source maps", () => {
beforeEach(() => {
externalModule.useSourceMap = false;
});
it("returns an instance of RawSource", () => {
// set up
const someSourceString = "some source string";
// invoke
const result = externalModule.getSource(someSourceString);
// check
expect(result).toBeInstanceOf(RawSource);
});
});
});
describe("#getSourceForGlobalVariableExternal", () => {
describe("given an array as variable name in the global namespace", () => {
it("use the array as lookup in the global object", () => {
// set up
const type = "window";
const varName = ["foo", "bar"];
const expected =
'(function() { module.exports = window["foo"]["bar"]; }());';
// invoke
const result = externalModule.getSourceForGlobalVariableExternal(
varName,
type
);
// check
expect(result).toEqual(expected);
});
});
describe("given an single variable name", () => {
it("look it up in the global namespace", () => {
// set up
const type = "window";
const varName = "foo";
const expected = '(function() { module.exports = window["foo"]; }());';
// invoke
const result = externalModule.getSourceForGlobalVariableExternal(
varName,
type
);
// check
expect(result).toEqual(expected);
});
});
});
describe("#getSourceForCommonJsExternal", () => {
describe("given an array as names in the global namespace", () => {
it("use the first to require a module and the rest as lookup on the required module", () => {
// set up
const varName = ["module", "look", "up"];
const expected = 'module.exports = require(module)["look"]["up"];';
// invoke
const result = externalModule.getSourceForCommonJsExternal(
varName,
type
);
// check
expect(result).toEqual(expected);
});
});
describe("given an single variable name", () => {
it("require a module with that name", () => {
// set up
const type = "window";
const varName = "foo";
const expected = 'module.exports = require("foo");';
// invoke
const result = externalModule.getSourceForCommonJsExternal(
varName,
type
);
// check
expect(result).toEqual(expected);
});
});
});
describe("#checkExternalVariable", () => {
it("creates a check that fails if a variable does not exist", () => {
// set up
const variableToCheck = "foo";
const request = "bar";
const expected = `if(typeof foo === 'undefined') {var e = new Error("Cannot find module 'bar'"); e.code = 'MODULE_NOT_FOUND'; throw e;}
`;
// invoke
const result = externalModule.checkExternalVariable(
variableToCheck,
request
);
// check
expect(result).toEqual(expected);
});
});
describe("#getSourceForAmdOrUmdExternal", () => {
it("looks up a global variable as specified by the id", () => {
// set up
const id = "someId";
const optional = false;
const expected = "module.exports = __WEBPACK_EXTERNAL_MODULE_someId__;";
// invoke
const result = externalModule.getSourceForAmdOrUmdExternal(
id,
optional,
request
);
// check
expect(result).toEqual(expected);
});
describe("given an optional check is set", function() {
it("ads a check for the existence of the variable before looking it up", () => {
// set up
const id = "someId";
const optional = true;
const expected = `if(typeof __WEBPACK_EXTERNAL_MODULE_someId__ === 'undefined') {var e = new Error("Cannot find module 'some/request'"); e.code = 'MODULE_NOT_FOUND'; throw e;}
module.exports = __WEBPACK_EXTERNAL_MODULE_someId__;`;
// invoke
const result = externalModule.getSourceForAmdOrUmdExternal(
id,
optional,
request
);
// check
expect(result).toEqual(expected);
});
});
});
describe("#getSourceForDefaultCase", () => {
it("returns the given request as lookup", () => {
// set up
const optional = false;
const expected = "module.exports = some/request;";
// invoke
const result = externalModule.getSourceForDefaultCase(optional, request);
// check
expect(result).toEqual(expected);
});
describe("given an optional check is requested", function() {
it("checks for the existence of the request setting it", () => {
// set up
const optional = true;
const expected = `if(typeof some/request === 'undefined') {var e = new Error("Cannot find module 'some/request'"); e.code = 'MODULE_NOT_FOUND'; throw e;}
module.exports = some/request;`;
// invoke
const result = externalModule.getSourceForDefaultCase(
optional,
request
);
// check
expect(result).toEqual(expected);
});
});
});
describe("#updateHash", () => {
let hashedText;
let hash;
beforeEach(() => {
hashedText = "";
hash = {
update: text => {
hashedText += text;
}
};
externalModule.id = 12345678;
externalModule.updateHash(hash);
});
it("updates hash with request", () => {
expect(hashedText).toMatch("some/request");
});
it("updates hash with type", () => {
expect(hashedText).toMatch("some-type");
});
it("updates hash with module id", () => {
expect(hashedText).toMatch("12345678");
});
});
describe("#updateHash without optional", () => {
let hashedText;
let hash;
beforeEach(() => {
hashedText = "";
hash = {
update: text => {
hashedText += text;
}
};
// Note no set of `externalModule.optional`, which crashed externals in 3.7.0
externalModule.id = 12345678;
externalModule.updateHash(hash);
});
it("updates hash with request", () => {
expect(hashedText).toMatch("some/request");
});
it("updates hash with type", () => {
expect(hashedText).toMatch("some-type");
});
it("updates hash with optional flag", () => {
expect(hashedText).toMatch("false");
});
it("updates hash with module id", () => {
expect(hashedText).toMatch("12345678");
});
});
});

View File

@ -1,33 +0,0 @@
/* globals describe, it */
"use strict";
const WebpackMissingModule = require("../lib/dependencies/WebpackMissingModule");
describe("WebpackMissingModule", () => {
describe("#moduleCode", () => {
it("returns an error message based on given error message", () => {
const errorMessage = WebpackMissingModule.moduleCode("mock message");
expect(errorMessage).toBe(
"var e = new Error(\"Cannot find module 'mock message'\"); e.code = 'MODULE_NOT_FOUND'; throw e;"
);
});
});
describe("#promise", () => {
it("returns an error message based on given error message", () => {
const errorMessage = WebpackMissingModule.promise("mock message");
expect(errorMessage).toBe(
"Promise.reject(function webpackMissingModule() { var e = new Error(\"Cannot find module 'mock message'\"); e.code = 'MODULE_NOT_FOUND'; return e; }())"
);
});
});
describe("#module", () => {
it("returns an error message based on given error message", () => {
const errorMessage = WebpackMissingModule.module("mock message");
expect(errorMessage).toBe(
"!(function webpackMissingModule() { var e = new Error(\"Cannot find module 'mock message'\"); e.code = 'MODULE_NOT_FOUND'; throw e; }())"
);
});
});
});