Last active
May 19, 2019 10:57
-
-
Save cacheflow/2106eebb303a1f57a639ac619892729e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict"; | |
/*globals describe it */ | |
const path = require("path"); | |
const webpack = require("../lib/webpack"); | |
const base = path.join(__dirname, "fixtures", "errors"); | |
describe("Errors", () => { | |
jest.setTimeout(20000); | |
function customOutputFilesystem(c) { | |
const files = {}; | |
c.outputFileSystem = { | |
join: path.join.bind(path), | |
mkdirp(path, callback) { | |
callback(); | |
}, | |
writeFile(name, content, callback) { | |
files[name] = content.toString("utf-8"); | |
callback(); | |
} | |
}; | |
return files; | |
} | |
function getErrors(options, callback) { | |
options.context = base; | |
const c = webpack(options); | |
customOutputFilesystem(c); | |
c.run((err, stats) => { | |
if (err) throw err; | |
expect(typeof stats).toBe("object"); | |
stats = stats.toJson({ | |
errorDetails: false | |
}); | |
expect(typeof stats).toBe("object"); | |
expect(stats).toHaveProperty("errors"); | |
expect(stats).toHaveProperty("warnings"); | |
expect(Array.isArray(stats.errors)).toBe(true); | |
expect(Array.isArray(stats.warnings)).toBe(true); | |
callback(stats.errors, stats.warnings); | |
}); | |
} | |
function getErrorsPromise(options, callback) { | |
return new Promise((resolve, reject) => { | |
getErrors(options, (errors, warnings) => { | |
callback(errors, warnings); | |
resolve(); | |
}); | |
}); | |
} | |
it("should throw an error if file doesn't exist", done => { | |
getErrors( | |
{ | |
mode: "development", | |
entry: "./missingFile" | |
}, | |
(errors, warnings) => { | |
expect(errors).toHaveLength(2); | |
expect(warnings).toHaveLength(0); | |
errors.sort(); | |
let lines = errors[0].split("\n"); | |
expect(lines[0]).toMatch(/missingFile.js/); | |
expect(lines[1]).toMatch(/^Module not found/); | |
expect(lines[1]).toMatch(/\.\/dir\/missing2/); | |
expect(lines[2]).toMatch(/missingFile.js 12:9/); | |
lines = errors[1].split("\n"); | |
expect(lines[0]).toMatch(/missingFile.js/); | |
expect(lines[1]).toMatch(/^Module not found/); | |
expect(lines[1]).toMatch(/\.\/missing/); | |
expect(lines[2]).toMatch(/missingFile.js 4:0/); | |
done(); | |
} | |
); | |
}); | |
it("should report require.extensions as unsupported", done => { | |
getErrors( | |
{ | |
mode: "development", | |
entry: "./require.extensions" | |
}, | |
(errors, warnings) => { | |
expect(errors).toHaveLength(0); | |
expect(warnings).toHaveLength(1); | |
const lines = warnings[0].split("\n"); | |
expect(lines[0]).toMatch(/require.extensions\.js/); | |
expect(lines[1]).toMatch( | |
/require.extensions is not supported by webpack/ | |
); | |
done(); | |
} | |
); | |
}); | |
it("should report require.main.require as unsupported", done => { | |
getErrors( | |
{ | |
mode: "development", | |
entry: "./require.main.require" | |
}, | |
(errors, warnings) => { | |
expect(errors).toHaveLength(0); | |
expect(warnings).toHaveLength(1); | |
const lines = warnings[0].split("\n"); | |
expect(lines[0]).toMatch(/require.main.require\.js/); | |
expect(lines[1]).toMatch( | |
/require.main.require is not supported by webpack/ | |
); | |
done(); | |
} | |
); | |
}); | |
it("should report module.parent.require as unsupported", done => { | |
getErrors( | |
{ | |
mode: "development", | |
entry: "./module.parent.require" | |
}, | |
(errors, warnings) => { | |
expect(errors).toHaveLength(0); | |
expect(warnings).toHaveLength(1); | |
const lines = warnings[0].split("\n"); | |
expect(lines[0]).toMatch(/module.parent.require\.js/); | |
expect(lines[1]).toMatch( | |
/module.parent.require is not supported by webpack/ | |
); | |
done(); | |
} | |
); | |
}); | |
it("should warn about case-sensitive module names", done => { | |
getErrors( | |
{ | |
mode: "development", | |
entry: "./case-sensitive" | |
}, | |
(errors, warnings) => { | |
if (errors.length === 0) { | |
expect(warnings).toHaveLength(1); | |
const lines = warnings[0].split("\n"); | |
expect(lines[4]).toMatch(/FILE\.js/); | |
expect(lines[5]).toMatch(/Used by/); | |
expect(lines[6]).toMatch(/case-sensitive/); | |
expect(lines[7]).toMatch(/file\.js/); | |
expect(lines[8]).toMatch(/Used by/); | |
expect(lines[9]).toMatch(/case-sensitive/); | |
} else { | |
expect(errors).toHaveLength(1); | |
expect(warnings).toHaveLength(0); | |
} | |
done(); | |
} | |
); | |
}); | |
it("should warn when not using mode", done => { | |
getErrors( | |
{ | |
entry: "./entry-point" | |
}, | |
(errors, warnings) => { | |
expect(errors).toHaveLength(0); | |
expect(warnings).toHaveLength(1); | |
let lines = warnings[0].split("\n"); | |
expect(lines[0]).toMatch(/configuration/); | |
expect(lines[1]).toMatch(/mode/); | |
expect(lines[1]).toMatch(/development/); | |
expect(lines[1]).toMatch(/production/); | |
done(); | |
} | |
); | |
}); | |
it("should not warn if the NoEmitOnErrorsPlugin is used over the NoErrorsPlugin", done => { | |
getErrors( | |
{ | |
mode: "production", | |
entry: "./no-errors-deprecate" | |
}, | |
(errors, warnings) => { | |
expect(errors).toHaveLength(0); | |
expect(warnings).toHaveLength(0); | |
done(); | |
} | |
); | |
}); | |
it("should not not emit if NoEmitOnErrorsPlugin is used and there is an error", done => { | |
getErrors( | |
{ | |
mode: "production", | |
entry: "./missingFile" | |
}, | |
(errors, warnings) => { | |
expect(errors).toHaveLength(2); | |
expect(warnings).toHaveLength(0); | |
errors.sort(); | |
let lines = errors[0].split("\n"); | |
expect(lines[0]).toMatch(/missingFile.js/); | |
expect(lines[1]).toMatch(/^Module not found/); | |
expect(lines[1]).toMatch(/\.\/dir\/missing2/); | |
expect(lines[2]).toMatch(/missingFile.js 12:9/); | |
lines = errors[1].split("\n"); | |
expect(lines[0]).toMatch(/missingFile.js/); | |
expect(lines[1]).toMatch(/^Module not found/); | |
expect(lines[1]).toMatch(/\.\/missing/); | |
expect(lines[2]).toMatch(/missingFile.js 4:0/); | |
done(); | |
} | |
); | |
}); | |
it("should throw an error when trying to use [chunkhash] when it's invalid", done => { | |
getErrors( | |
{ | |
mode: "development", | |
entry: { | |
a: "./entry-point", | |
b: "./entry-point", | |
c: "./entry-point" | |
}, | |
output: { | |
filename: "[chunkhash].js" | |
}, | |
plugins: [new webpack.HotModuleReplacementPlugin()] | |
}, | |
(errors, warnings) => { | |
expect(errors).toHaveLength(3); | |
expect(warnings).toHaveLength(0); | |
errors.forEach(error => { | |
const lines = error.split("\n"); | |
expect(lines[0]).toMatch(/chunk (a|b|c)/); | |
expect(lines[2]).toMatch(/\[chunkhash\].js/); | |
expect(lines[2]).toMatch(/use \[hash\] instead/); | |
}); | |
done(); | |
} | |
); | |
}); | |
it("should show loader name when emit/throw errors or warnings from loaders", () => { | |
return Promise.all([ | |
getErrorsPromise( | |
{ | |
mode: "development", | |
entry: "./entry-point-error-loader-required.js" | |
}, | |
(errors, warnings) => { | |
expect(warnings).toHaveLength(1); | |
expect(warnings[0].split("\n")[1]).toMatch( | |
/^Module Warning \(from .\/emit-error-loader.js\):$/ | |
); | |
expect(errors).toHaveLength(1); | |
expect(errors[0].split("\n")[1]).toMatch( | |
/^Module Error \(from .\/emit-error-loader.js\):$/ | |
); | |
} | |
), | |
getErrorsPromise( | |
{ | |
mode: "development", | |
entry: path.resolve(base, "./emit-error-loader") + "!./entry-point.js" | |
}, | |
(errors, warnings) => { | |
expect(warnings).toHaveLength(1); | |
expect(warnings[0].split("\n")[1]).toMatch( | |
/^Module Warning \(from .\/emit-error-loader.js\):$/ | |
); | |
expect(errors).toHaveLength(1); | |
expect(errors[0].split("\n")[1]).toMatch( | |
/^Module Error \(from .\/emit-error-loader.js\):$/ | |
); | |
} | |
), | |
getErrorsPromise( | |
{ | |
mode: "development", | |
entry: "./not-a-json.js", | |
module: { | |
rules: [ | |
{ | |
test: /not-a-json\.js$/, | |
use: [ | |
"json-loader", | |
{ | |
loader: path.resolve(base, "./emit-error-loader") | |
} | |
] | |
} | |
] | |
} | |
}, | |
(errors, warnings) => { | |
expect(warnings).toHaveLength(1); | |
expect(warnings[0].split("\n")[1]).toMatch( | |
/^Module Warning \(from .\/emit-error-loader.js\):$/ | |
); | |
expect(errors).toHaveLength(2); | |
expect(errors[0].split("\n")[1]).toMatch( | |
/^Module Error \(from .\/emit-error-loader.js\):$/ | |
); | |
expect(errors[1].split("\n")[1]).toMatch( | |
/^Module build failed \(from \(webpack\)\/node_modules\/json-loader\/index.js\):$/ | |
); | |
} | |
), | |
getErrorsPromise( | |
{ | |
mode: "development", | |
entry: "./entry-point.js", | |
module: { | |
rules: [ | |
{ | |
test: /entry-point\.js$/, | |
use: path.resolve(base, "./async-error-loader") | |
} | |
] | |
} | |
}, | |
(errors, warnings) => { | |
expect(errors).toHaveLength(1); | |
expect(errors[0].split("\n")[1]).toMatch( | |
/^Module build failed \(from .\/async-error-loader.js\):$/ | |
); | |
} | |
), | |
getErrorsPromise( | |
{ | |
mode: "development", | |
entry: "./entry-point.js", | |
module: { | |
rules: [ | |
{ | |
test: /entry-point\.js$/, | |
use: path.resolve(base, "./throw-error-loader") | |
} | |
] | |
} | |
}, | |
(errors, warnings) => { | |
expect(errors).toHaveLength(1); | |
expect(errors[0].split("\n")[1]).toMatch( | |
/^Module build failed \(from .\/throw-error-loader.js\):$/ | |
); | |
} | |
), | |
getErrorsPromise( | |
{ | |
mode: "development", | |
entry: "./entry-point.js", | |
module: { | |
rules: [ | |
{ | |
test: /entry-point\.js$/, | |
use: path.resolve(base, "./irregular-error-loader") | |
} | |
] | |
} | |
}, | |
(errors, warnings) => { | |
expect(warnings).toHaveLength(2); | |
expect(warnings[0].split("\n")[1]).toMatch( | |
/^Module Warning \(from .\/irregular-error-loader.js\):$/ | |
); | |
expect(warnings[1].split("\n")[1]).toMatch( | |
/^Module Warning \(from .\/irregular-error-loader.js\):$/ | |
); | |
expect(errors).toHaveLength(3); | |
expect(errors[0].split("\n")[1]).toMatch( | |
/^Module Error \(from .\/irregular-error-loader.js\):$/ | |
); | |
expect(errors[1].split("\n")[1]).toMatch( | |
/^Module Error \(from .\/irregular-error-loader.js\):$/ | |
); | |
expect(errors[2].split("\n")[1]).toMatch( | |
/^Module build failed \(from .\/irregular-error-loader.js\):$/ | |
); | |
} | |
) | |
]); | |
}); | |
it("should throw a build error if no source be returned after run loaders", done => { | |
getErrors( | |
{ | |
mode: "development", | |
entry: path.resolve(base, "./no-return-loader") + "!./entry-point.js" | |
}, | |
(errors, warnings) => { | |
expect(errors).toHaveLength(1); | |
const messages = errors[0].split("\n"); | |
expect(messages[1]).toMatch( | |
/^Module build failed: Error: Final loader \(.+\) didn't return a Buffer or String/ | |
); | |
done(); | |
} | |
); | |
}); | |
it("should show loader used if it is present when module parsing fails", done => { | |
const folder = path.join(__dirname, "/fixtures"); | |
getErrors( | |
{ | |
mode: "development", | |
entry: path.resolve(folder, "./abc.html"), | |
module: { | |
rules: [ | |
{ | |
test: /\.json$/, | |
use: [{ loader: "json-loader" }] | |
} | |
] | |
} | |
}, | |
(errors, warnings) => { | |
const messages = errors[0].split("\n"); | |
const loaderUsedMessage = messages.filter(message => | |
message.includes("Tried processing with loaders: json-loader.") | |
)[0]; | |
expect(loaderUsedMessage).toMatch( | |
"Tried processing with loaders: json-loader." | |
); | |
done(); | |
} | |
); | |
}); | |
it("should show all loaders used if they are in config when module parsing fails", done => { | |
const folder = path.join(__dirname, "/fixtures"); | |
const loaders = [ | |
{ | |
test: /\.json$/, | |
use: [{ loader: "json-loader" }] | |
}, | |
{ | |
test: /\.coffee$/, | |
use: [{ loader: "coffee-loader" }] | |
}, | |
{ | |
test: /\.css$/, | |
use: [{ loader: "style-loader" }] | |
} | |
]; | |
const configA = { | |
mode: "development", | |
entry: path.resolve(folder, "./abc.html"), | |
module: { | |
rules: loaders | |
} | |
}; | |
const configB = { | |
mode: "development", | |
entry: path.resolve(folder, "./abc.html"), | |
module: { | |
rules: loaders.slice(1) | |
} | |
}; | |
const firstMessages = getErrors(configA, (errors, warnings) => { | |
return errors; | |
}); | |
const secondMessages = getErrors(configB, (errors, warnings) => { | |
return errors; | |
}); | |
console.log(firstMessages()); | |
console.log(secondMessages()); | |
done(); | |
}); | |
it("should show all loaders used if use is a string", done => { | |
const folder = path.join(__dirname, "/fixtures"); | |
getErrors( | |
{ | |
mode: "development", | |
entry: path.resolve(folder, "./abc.html"), | |
module: { | |
rules: [ | |
{ test: /\.json$/, use: "json-loader" }, | |
{ test: /\.coffee$/, use: "coffee-loader" } | |
] | |
} | |
}, | |
(errors, warnings) => { | |
const messages = errors[0].split("\n"); | |
const loaderUsedMessage = messages.filter(message => | |
message.includes( | |
"Tried processing with loaders: json-loader and coffee-loader." | |
) | |
)[0]; | |
expect(loaderUsedMessage).toMatch( | |
"Tried processing with loaders: json-loader and coffee-loader." | |
); | |
done(); | |
} | |
); | |
}); | |
it("should show 'No Loaders present to process this file.' if loaders are not included in config when module parsing fails", done => { | |
const folder = path.join(__dirname, "/fixtures"); | |
getErrors( | |
{ | |
mode: "development", | |
entry: path.resolve(folder, "./abc.html"), | |
module: {} | |
}, | |
(errors, warnings) => { | |
const messages = errors[0].split("\n"); | |
const loaderUsedMessage = messages.filter(message => | |
message.includes("No Loaders present to process this file.") | |
)[0]; | |
expect(loaderUsedMessage).toMatch( | |
"No Loaders present to process this file." | |
); | |
done(); | |
} | |
); | |
}); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict"; | |
/** | |
* Returns a formatted loader error either as a string or separated by an "and" or commas. | |
* @param {array|string} data | |
* @returns {array|string} | |
*/ | |
const formatLoaderError = data => { | |
if (!data.length) return []; | |
if (typeof data === "string") { | |
return `${data}`; | |
} | |
if (Array.isArray(data)) { | |
const len = data.length; | |
if (len === 1) { | |
return `${data[0] || ""}`; | |
} | |
if (len === 2) { | |
return data.join(" and "); | |
} | |
return `${data | |
.map((d, i) => { | |
return i !== data.length - 1 ? `${d}` : `and ${d}`; | |
}) | |
.join(", ")}.`; | |
} | |
return ""; | |
}; | |
module.exports = formatLoaderError; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
MIT License http://www.opensource.org/licenses/mit-license.php | |
Author Tobias Koppers @sokra | |
*/ | |
"use strict"; | |
const WebpackError = require("./WebpackError"); | |
/** @typedef {import("./Module")} Module */ | |
class ModuleParseError extends WebpackError { | |
/** | |
* @param {Module} module the errored module | |
* @param {string} source source code | |
* @param {Error&any} err the parse error | |
* @param {any} loaders the loaders used | |
*/ | |
constructor(module, source, err, loaders) { | |
let message = "Module parse failed: " + err.message; | |
let loc = undefined; | |
message += "\nYou may need an appropriate loader to handle this file type."; | |
if (loaders.length >= 1) { | |
message += `\nTried processing with loaders: ${loaders}.`; | |
} else { | |
message += "\nNo Loaders present to process this file."; | |
} | |
if ( | |
err.loc && | |
typeof err.loc === "object" && | |
typeof err.loc.line === "number" | |
) { | |
var lineNumber = err.loc.line; | |
if (/[\0\u0001\u0002\u0003\u0004\u0005\u0006\u0007]/.test(source)) { | |
// binary file | |
message += "\n(Source code omitted for this binary file)"; | |
} else { | |
const sourceLines = source.split("\n"); | |
const start = Math.max(0, lineNumber - 3); | |
const linesBefore = sourceLines.slice(start, lineNumber - 1); | |
const theLine = sourceLines[lineNumber - 1]; | |
const linesAfter = sourceLines.slice(lineNumber, lineNumber + 2); | |
message += | |
linesBefore.map(l => `\n| ${l}`).join("") + | |
`\n> ${theLine}` + | |
linesAfter.map(l => `\n| ${l}`).join(""); | |
} | |
loc = err.loc; | |
} else { | |
message += "\n" + err.stack; | |
} | |
super(message); | |
this.name = "ModuleParseError"; | |
this.module = module; | |
this.loc = loc; | |
this.error = err; | |
Error.captureStackTrace(this, this.constructor); | |
} | |
} | |
module.exports = ModuleParseError; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
MIT License http://www.opensource.org/licenses/mit-license.php | |
Author Tobias Koppers @sokra | |
*/ | |
"use strict"; | |
const NativeModule = require("module"); | |
const { | |
CachedSource, | |
LineToLineMappedSource, | |
OriginalSource, | |
RawSource, | |
SourceMapSource | |
} = require("webpack-sources"); | |
const { getContext, runLoaders } = require("loader-runner"); | |
const WebpackError = require("./WebpackError"); | |
const Module = require("./Module"); | |
const ModuleParseError = require("./ModuleParseError"); | |
const ModuleBuildError = require("./ModuleBuildError"); | |
const ModuleError = require("./ModuleError"); | |
const ModuleWarning = require("./ModuleWarning"); | |
const createHash = require("./util/createHash"); | |
const formatLoaderError = require("../lib/formatLoaderError"); | |
const contextify = require("./util/identifier").contextify; | |
/** @typedef {import("./util/createHash").Hash} Hash */ | |
const asString = buf => { | |
if (Buffer.isBuffer(buf)) { | |
return buf.toString("utf-8"); | |
} | |
return buf; | |
}; | |
const asBuffer = str => { | |
if (!Buffer.isBuffer(str)) { | |
return Buffer.from(str, "utf-8"); | |
} | |
return str; | |
}; | |
class NonErrorEmittedError extends WebpackError { | |
constructor(error) { | |
super(); | |
this.name = "NonErrorEmittedError"; | |
this.message = "(Emitted value instead of an instance of Error) " + error; | |
Error.captureStackTrace(this, this.constructor); | |
} | |
} | |
/** | |
* @typedef {Object} CachedSourceEntry | |
* @property {TODO} source the generated source | |
* @property {string} hash the hash value | |
*/ | |
class NormalModule extends Module { | |
constructor({ | |
type, | |
request, | |
userRequest, | |
rawRequest, | |
loaders, | |
resource, | |
matchResource, | |
parser, | |
generator, | |
resolveOptions | |
}) { | |
super(type, getContext(resource)); | |
// Info from Factory | |
this.request = request; | |
this.userRequest = userRequest; | |
this.rawRequest = rawRequest; | |
this.binary = type.startsWith("webassembly"); | |
this.parser = parser; | |
this.generator = generator; | |
this.resource = resource; | |
this.matchResource = matchResource; | |
this.loaders = loaders; | |
if (resolveOptions !== undefined) this.resolveOptions = resolveOptions; | |
// Info from Build | |
this.error = null; | |
this._source = null; | |
this._buildHash = ""; | |
this.buildTimestamp = undefined; | |
/** @private @type {Map<string, CachedSourceEntry>} */ | |
this._cachedSources = new Map(); | |
// Options for the NormalModule set by plugins | |
// TODO refactor this -> options object filled from Factory | |
this.useSourceMap = false; | |
this.lineToLine = false; | |
// Cache | |
this._lastSuccessfulBuildMeta = {}; | |
} | |
identifier() { | |
return this.request; | |
} | |
readableIdentifier(requestShortener) { | |
return requestShortener.shorten(this.userRequest); | |
} | |
libIdent(options) { | |
return contextify(options.context, this.userRequest); | |
} | |
nameForCondition() { | |
const resource = this.matchResource || this.resource; | |
const idx = resource.indexOf("?"); | |
if (idx >= 0) return resource.substr(0, idx); | |
return resource; | |
} | |
updateCacheModule(module) { | |
this.type = module.type; | |
this.request = module.request; | |
this.userRequest = module.userRequest; | |
this.rawRequest = module.rawRequest; | |
this.parser = module.parser; | |
this.generator = module.generator; | |
this.resource = module.resource; | |
this.matchResource = module.matchResource; | |
this.loaders = module.loaders; | |
this.resolveOptions = module.resolveOptions; | |
} | |
createSourceForAsset(name, content, sourceMap) { | |
if (!sourceMap) { | |
return new RawSource(content); | |
} | |
if (typeof sourceMap === "string") { | |
return new OriginalSource(content, sourceMap); | |
} | |
return new SourceMapSource(content, name, sourceMap); | |
} | |
createLoaderContext(resolver, options, compilation, fs) { | |
const requestShortener = compilation.runtimeTemplate.requestShortener; | |
const loaderContext = { | |
version: 2, | |
emitWarning: warning => { | |
if (!(warning instanceof Error)) { | |
warning = new NonErrorEmittedError(warning); | |
} | |
const currentLoader = this.getCurrentLoader(loaderContext); | |
this.warnings.push( | |
new ModuleWarning(this, warning, { | |
from: requestShortener.shorten(currentLoader.loader) | |
}) | |
); | |
}, | |
emitError: error => { | |
if (!(error instanceof Error)) { | |
error = new NonErrorEmittedError(error); | |
} | |
const currentLoader = this.getCurrentLoader(loaderContext); | |
this.errors.push( | |
new ModuleError(this, error, { | |
from: requestShortener.shorten(currentLoader.loader) | |
}) | |
); | |
}, | |
// TODO remove in webpack 5 | |
exec: (code, filename) => { | |
// @ts-ignore Argument of type 'this' is not assignable to parameter of type 'Module'. | |
const module = new NativeModule(filename, this); | |
// @ts-ignore _nodeModulePaths is deprecated and undocumented Node.js API | |
module.paths = NativeModule._nodeModulePaths(this.context); | |
module.filename = filename; | |
module._compile(code, filename); | |
return module.exports; | |
}, | |
resolve(context, request, callback) { | |
resolver.resolve({}, context, request, {}, callback); | |
}, | |
getResolve(options) { | |
const child = options ? resolver.withOptions(options) : resolver; | |
return (context, request, callback) => { | |
if (callback) { | |
child.resolve({}, context, request, {}, callback); | |
} else { | |
return new Promise((resolve, reject) => { | |
child.resolve({}, context, request, {}, (err, result) => { | |
if (err) reject(err); | |
else resolve(result); | |
}); | |
}); | |
} | |
}; | |
}, | |
emitFile: (name, content, sourceMap) => { | |
if (!this.buildInfo.assets) { | |
this.buildInfo.assets = Object.create(null); | |
} | |
this.buildInfo.assets[name] = this.createSourceForAsset( | |
name, | |
content, | |
sourceMap | |
); | |
}, | |
rootContext: options.context, | |
webpack: true, | |
sourceMap: !!this.useSourceMap, | |
_module: this, | |
_compilation: compilation, | |
_compiler: compilation.compiler, | |
fs: fs | |
}; | |
compilation.hooks.normalModuleLoader.call(loaderContext, this); | |
if (options.loader) { | |
Object.assign(loaderContext, options.loader); | |
} | |
return loaderContext; | |
} | |
getCurrentLoader(loaderContext, index = loaderContext.loaderIndex) { | |
if ( | |
this.loaders && | |
this.loaders.length && | |
index < this.loaders.length && | |
index >= 0 && | |
this.loaders[index] | |
) { | |
return this.loaders[index]; | |
} | |
return null; | |
} | |
createSource(source, resourceBuffer, sourceMap) { | |
// if there is no identifier return raw source | |
if (!this.identifier) { | |
return new RawSource(source); | |
} | |
// from here on we assume we have an identifier | |
const identifier = this.identifier(); | |
if (this.lineToLine && resourceBuffer) { | |
return new LineToLineMappedSource( | |
source, | |
identifier, | |
asString(resourceBuffer) | |
); | |
} | |
if (this.useSourceMap && sourceMap) { | |
return new SourceMapSource(source, identifier, sourceMap); | |
} | |
if (Buffer.isBuffer(source)) { | |
// @ts-ignore | |
// TODO We need to fix @types/webpack-sources to allow RawSource to take a Buffer | string | |
return new RawSource(source); | |
} | |
return new OriginalSource(source, identifier); | |
} | |
doBuild(options, compilation, resolver, fs, callback) { | |
const loaderContext = this.createLoaderContext( | |
resolver, | |
options, | |
compilation, | |
fs | |
); | |
runLoaders( | |
{ | |
resource: this.resource, | |
loaders: this.loaders, | |
context: loaderContext, | |
readResource: fs.readFile.bind(fs) | |
}, | |
(err, result) => { | |
if (result) { | |
this.buildInfo.cacheable = result.cacheable; | |
this.buildInfo.fileDependencies = new Set(result.fileDependencies); | |
this.buildInfo.contextDependencies = new Set( | |
result.contextDependencies | |
); | |
} | |
if (err) { | |
if (!(err instanceof Error)) { | |
err = new NonErrorEmittedError(err); | |
} | |
const currentLoader = this.getCurrentLoader(loaderContext); | |
const error = new ModuleBuildError(this, err, { | |
from: | |
currentLoader && | |
compilation.runtimeTemplate.requestShortener.shorten( | |
currentLoader.loader | |
) | |
}); | |
return callback(error); | |
} | |
const resourceBuffer = result.resourceBuffer; | |
const source = result.result[0]; | |
const sourceMap = result.result.length >= 1 ? result.result[1] : null; | |
const extraInfo = result.result.length >= 2 ? result.result[2] : null; | |
if (!Buffer.isBuffer(source) && typeof source !== "string") { | |
const currentLoader = this.getCurrentLoader(loaderContext, 0); | |
const err = new Error( | |
`Final loader (${ | |
currentLoader | |
? compilation.runtimeTemplate.requestShortener.shorten( | |
currentLoader.loader | |
) | |
: "unknown" | |
}) didn't return a Buffer or String` | |
); | |
const error = new ModuleBuildError(this, err); | |
return callback(error); | |
} | |
this._source = this.createSource( | |
this.binary ? asBuffer(source) : asString(source), | |
resourceBuffer, | |
sourceMap | |
); | |
this._ast = | |
typeof extraInfo === "object" && | |
extraInfo !== null && | |
extraInfo.webpackAST !== undefined | |
? extraInfo.webpackAST | |
: null; | |
return callback(); | |
} | |
); | |
} | |
markModuleAsErrored(error) { | |
// Restore build meta from successful build to keep importing state | |
this.buildMeta = Object.assign({}, this._lastSuccessfulBuildMeta); | |
this.error = error; | |
this.errors.push(this.error); | |
this._source = new RawSource( | |
"throw new Error(" + JSON.stringify(this.error.message) + ");" | |
); | |
this._ast = null; | |
} | |
applyNoParseRule(rule, content) { | |
// must start with "rule" if rule is a string | |
if (typeof rule === "string") { | |
return content.indexOf(rule) === 0; | |
} | |
if (typeof rule === "function") { | |
return rule(content); | |
} | |
// we assume rule is a regexp | |
return rule.test(content); | |
} | |
// check if module should not be parsed | |
// returns "true" if the module should !not! be parsed | |
// returns "false" if the module !must! be parsed | |
shouldPreventParsing(noParseRule, request) { | |
// if no noParseRule exists, return false | |
// the module !must! be parsed. | |
if (!noParseRule) { | |
return false; | |
} | |
// we only have one rule to check | |
if (!Array.isArray(noParseRule)) { | |
// returns "true" if the module is !not! to be parsed | |
return this.applyNoParseRule(noParseRule, request); | |
} | |
for (let i = 0; i < noParseRule.length; i++) { | |
const rule = noParseRule[i]; | |
// early exit on first truthy match | |
// this module is !not! to be parsed | |
if (this.applyNoParseRule(rule, request)) { | |
return true; | |
} | |
} | |
// no match found, so this module !should! be parsed | |
return false; | |
} | |
_initBuildHash(compilation) { | |
const hash = createHash(compilation.outputOptions.hashFunction); | |
if (this._source) { | |
hash.update("source"); | |
this._source.updateHash(hash); | |
} | |
hash.update("meta"); | |
hash.update(JSON.stringify(this.buildMeta)); | |
this._buildHash = hash.digest("hex"); | |
} | |
build(options, compilation, resolver, fs, callback) { | |
this.buildTimestamp = Date.now(); | |
this.built = true; | |
this._source = null; | |
this._ast = null; | |
this._buildHash = ""; | |
this.error = null; | |
this.errors.length = 0; | |
this.warnings.length = 0; | |
this.buildMeta = {}; | |
this.buildInfo = { | |
cacheable: false, | |
fileDependencies: new Set(), | |
contextDependencies: new Set() | |
}; | |
return this.doBuild(options, compilation, resolver, fs, err => { | |
this._cachedSources.clear(); | |
// if we have an error mark module as failed and exit | |
if (err) { | |
this.markModuleAsErrored(err); | |
this._initBuildHash(compilation); | |
return callback(); | |
} | |
// check if this module should !not! be parsed. | |
// if so, exit here; | |
const noParseRule = options.module && options.module.noParse; | |
if (this.shouldPreventParsing(noParseRule, this.request)) { | |
this._initBuildHash(compilation); | |
return callback(); | |
} | |
const getLoaders = options => { | |
const module = | |
options && options.constructor === Object ? options.module || {} : {}; | |
const rules = module.rules || []; | |
if (!rules.length) { | |
return []; | |
} | |
const arr = []; | |
const getLoader = rule => rule.loader || rule.use || ""; | |
rules.forEach(rule => { | |
if (Array.isArray(rule.use)) { | |
rule.use.forEach(r => { | |
arr.push(getLoader(r)); | |
}); | |
} else { | |
arr.push(getLoader(rule)); | |
} | |
}); | |
return arr; | |
}; | |
const handleParseError = e => { | |
const source = this._source.source(); | |
const loaders = getLoaders(options); | |
const formattedLoaderError = formatLoaderError(loaders); | |
const error = new ModuleParseError( | |
this, | |
source, | |
e, | |
formattedLoaderError | |
); | |
this.markModuleAsErrored(error); | |
this._initBuildHash(compilation); | |
return callback(); | |
}; | |
const handleParseResult = result => { | |
this._lastSuccessfulBuildMeta = this.buildMeta; | |
this._initBuildHash(compilation); | |
return callback(); | |
}; | |
try { | |
const result = this.parser.parse( | |
this._ast || this._source.source(), | |
{ | |
current: this, | |
module: this, | |
compilation: compilation, | |
options: options | |
}, | |
(err, result) => { | |
if (err) { | |
handleParseError(err); | |
} else { | |
handleParseResult(result); | |
} | |
} | |
); | |
if (result !== undefined) { | |
// parse is sync | |
handleParseResult(result); | |
} | |
} catch (e) { | |
handleParseError(e); | |
} | |
}); | |
} | |
getHashDigest(dependencyTemplates) { | |
// TODO webpack 5 refactor | |
let dtHash = dependencyTemplates.get("hash"); | |
return `${this.hash}-${dtHash}`; | |
} | |
source(dependencyTemplates, runtimeTemplate, type = "javascript") { | |
const hashDigest = this.getHashDigest(dependencyTemplates); | |
const cacheEntry = this._cachedSources.get(type); | |
if (cacheEntry !== undefined && cacheEntry.hash === hashDigest) { | |
// We can reuse the cached source | |
return cacheEntry.source; | |
} | |
const source = this.generator.generate( | |
this, | |
dependencyTemplates, | |
runtimeTemplate, | |
type | |
); | |
const cachedSource = new CachedSource(source); | |
this._cachedSources.set(type, { | |
source: cachedSource, | |
hash: hashDigest | |
}); | |
return cachedSource; | |
} | |
originalSource() { | |
return this._source; | |
} | |
needRebuild(fileTimestamps, contextTimestamps) { | |
// always try to rebuild in case of an error | |
if (this.error) return true; | |
// always rebuild when module is not cacheable | |
if (!this.buildInfo.cacheable) return true; | |
// Check timestamps of all dependencies | |
// Missing timestamp -> need rebuild | |
// Timestamp bigger than buildTimestamp -> need rebuild | |
for (const file of this.buildInfo.fileDependencies) { | |
const timestamp = fileTimestamps.get(file); | |
if (!timestamp) return true; | |
if (timestamp >= this.buildTimestamp) return true; | |
} | |
for (const file of this.buildInfo.contextDependencies) { | |
const timestamp = contextTimestamps.get(file); | |
if (!timestamp) return true; | |
if (timestamp >= this.buildTimestamp) return true; | |
} | |
// elsewise -> no rebuild needed | |
return false; | |
} | |
size() { | |
return this._source ? this._source.size() : -1; | |
} | |
/** | |
* @param {Hash} hash the hash used to track dependencies | |
* @returns {void} | |
*/ | |
updateHash(hash) { | |
hash.update(this._buildHash); | |
super.updateHash(hash); | |
} | |
} | |
module.exports = NormalModule; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment