lepu-test-platform-web/node_modules/webpack/lib/NormalModule.js

557 lines
16 KiB
JavaScript

/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
"use strict";
const path = require("path");
const NativeModule = require("module");
const crypto = require("crypto");
const SourceMapSource = require("webpack-sources").SourceMapSource;
const OriginalSource = require("webpack-sources").OriginalSource;
const RawSource = require("webpack-sources").RawSource;
const ReplaceSource = require("webpack-sources").ReplaceSource;
const CachedSource = require("webpack-sources").CachedSource;
const LineToLineMappedSource = require("webpack-sources").LineToLineMappedSource;
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 runLoaders = require("loader-runner").runLoaders;
const getContext = require("loader-runner").getContext;
function asString(buf) {
if(Buffer.isBuffer(buf)) {
return buf.toString("utf-8");
}
return buf;
}
function contextify(context, request) {
return request.split("!").map(function(r) {
const splitPath = r.split("?");
splitPath[0] = path.relative(context, splitPath[0]);
if(path.sep === "\\")
splitPath[0] = splitPath[0].replace(/\\/g, "/");
if(splitPath[0].indexOf("../") !== 0)
splitPath[0] = "./" + splitPath[0];
return splitPath.join("?");
}).join("!");
}
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);
}
}
const dependencyTemplatesHashMap = new WeakMap();
class NormalModule extends Module {
constructor(request, userRequest, rawRequest, loaders, resource, parser) {
super();
this.request = request;
this.userRequest = userRequest;
this.rawRequest = rawRequest;
this.parser = parser;
this.resource = resource;
this.context = getContext(resource);
this.loaders = loaders;
this.fileDependencies = [];
this.contextDependencies = [];
this.warnings = [];
this.errors = [];
this.error = null;
this._source = null;
this.assets = {};
this.built = false;
this._cachedSource = null;
}
identifier() {
return this.request;
}
readableIdentifier(requestShortener) {
return requestShortener.shorten(this.userRequest);
}
libIdent(options) {
return contextify(options.context, this.userRequest);
}
nameForCondition() {
const idx = this.resource.indexOf("?");
if(idx >= 0) return this.resource.substr(0, idx);
return this.resource;
}
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 loaderContext = {
version: 2,
emitWarning: (warning) => {
if(!(warning instanceof Error))
warning = new NonErrorEmittedError(warning);
this.warnings.push(new ModuleWarning(this, warning));
},
emitError: (error) => {
if(!(error instanceof Error))
error = new NonErrorEmittedError(error);
this.errors.push(new ModuleError(this, error));
},
exec: (code, filename) => {
const module = new NativeModule(filename, this);
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);
},
resolveSync(context, request) {
return resolver.resolveSync({}, context, request);
},
emitFile: (name, content, sourceMap) => {
this.assets[name] = this.createSourceForAsset(name, content, sourceMap);
},
options: options,
webpack: true,
sourceMap: !!this.useSourceMap,
_module: this,
_compilation: compilation,
_compiler: compilation.compiler,
fs: fs,
};
compilation.applyPlugins("normal-module-loader", loaderContext, this);
if(options.loader)
Object.assign(loaderContext, options.loader);
return loaderContext;
}
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);
}
return new OriginalSource(source, identifier);
}
doBuild(options, compilation, resolver, fs, callback) {
this.cacheable = false;
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.cacheable = result.cacheable;
this.fileDependencies = result.fileDependencies;
this.contextDependencies = result.contextDependencies;
}
if(err) {
const error = new ModuleBuildError(this, err);
return callback(error);
}
const resourceBuffer = result.resourceBuffer;
const source = result.result[0];
const sourceMap = result.result[1];
if(!Buffer.isBuffer(source) && typeof source !== "string") {
const error = new ModuleBuildError(this, new Error("Final loader didn't return a Buffer or String"));
return callback(error);
}
this._source = this.createSource(asString(source), resourceBuffer, sourceMap);
return callback();
});
}
disconnect() {
this.built = false;
super.disconnect();
}
markModuleAsErrored(error) {
this.meta = null;
this.error = error;
this.errors.push(this.error);
this._source = new RawSource("throw new Error(" + JSON.stringify(this.error.message) + ");");
}
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;
}
build(options, compilation, resolver, fs, callback) {
this.buildTimestamp = Date.now();
this.built = true;
this._source = null;
this.error = null;
this.errors.length = 0;
this.warnings.length = 0;
this.meta = {};
return this.doBuild(options, compilation, resolver, fs, (err) => {
this.dependencies.length = 0;
this.variables.length = 0;
this.blocks.length = 0;
this._cachedSource = null;
// if we have an error mark module as failed and exit
if(err) {
this.markModuleAsErrored(err);
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)) {
return callback();
}
try {
this.parser.parse(this._source.source(), {
current: this,
module: this,
compilation: compilation,
options: options
});
} catch(e) {
const source = this._source.source();
const error = new ModuleParseError(this, source, e);
this.markModuleAsErrored(error);
return callback();
}
return callback();
});
}
getHashDigest(dependencyTemplates) {
let dtHash = dependencyTemplatesHashMap.get("hash");
const hash = crypto.createHash("md5");
this.updateHash(hash);
hash.update(`${dtHash}`);
return hash.digest("hex");
}
sourceDependency(dependency, dependencyTemplates, source, outputOptions, requestShortener) {
const template = dependencyTemplates.get(dependency.constructor);
if(!template) throw new Error("No template for dependency: " + dependency.constructor.name);
template.apply(dependency, source, outputOptions, requestShortener, dependencyTemplates);
}
sourceVariables(variable, availableVars, dependencyTemplates, outputOptions, requestShortener) {
const name = variable.name;
const expr = variable.expressionSource(dependencyTemplates, outputOptions, requestShortener);
if(availableVars.some(v => v.name === name && v.expression.source() === expr.source())) {
return;
}
return {
name: name,
expression: expr
};
}
/*
* creates the start part of a IIFE around the module to inject a variable name
* (function(...){ <- this part
* }.call(...))
*/
variableInjectionFunctionWrapperStartCode(varNames) {
const args = varNames.join(", ");
return `/* WEBPACK VAR INJECTION */(function(${args}) {`;
}
contextArgument(block) {
if(this === block) {
return this.exportsArgument || "exports";
}
return "this";
}
/*
* creates the end part of a IIFE around the module to inject a variable name
* (function(...){
* }.call(...)) <- this part
*/
variableInjectionFunctionWrapperEndCode(varExpressions, block) {
const firstParam = this.contextArgument(block);
const furtherParams = varExpressions.map(e => e.source()).join(", ");
return `}.call(${firstParam}, ${furtherParams}))`;
}
splitVariablesInUniqueNamedChunks(vars) {
const startState = [
[]
];
return vars.reduce((chunks, variable) => {
const current = chunks[chunks.length - 1];
// check if variable with same name exists already
// if so create a new chunk of variables.
const variableNameAlreadyExists = current.some(v => v.name === variable.name);
if(variableNameAlreadyExists) {
// start new chunk with current variable
chunks.push([variable]);
} else {
// else add it to current chunk
current.push(variable);
}
return chunks;
}, startState);
}
sourceBlock(block, availableVars, dependencyTemplates, source, outputOptions, requestShortener) {
block.dependencies.forEach((dependency) => this.sourceDependency(
dependency, dependencyTemplates, source, outputOptions, requestShortener));
/**
* Get the variables of all blocks that we need to inject.
* These will contain the variable name and its expression.
* The name will be added as a paramter in a IIFE the expression as its value.
*/
const vars = block.variables.reduce((result, value) => {
const variable = this.sourceVariables(
value, availableVars, dependencyTemplates, outputOptions, requestShortener);
if(variable) {
result.push(variable);
}
return result;
}, []);
/**
* if we actually have variables
* this is important as how #splitVariablesInUniqueNamedChunks works
* it will always return an array in an array which would lead to a IIFE wrapper around
* a module if we do this with an empty vars array.
*/
if(vars.length > 0) {
/**
* Split all variables up into chunks of unique names.
* e.g. imagine you have the following variable names that need to be injected:
* [foo, bar, baz, foo, some, more]
* we can not inject "foo" twice, therefore we just make two IIFEs like so:
* (function(foo, bar, baz){
* (function(foo, some, more){
* ...
* }(...));
* }(...));
*
* "splitVariablesInUniqueNamedChunks" splits the variables shown above up to this:
* [[foo, bar, baz], [foo, some, more]]
*/
const injectionVariableChunks = this.splitVariablesInUniqueNamedChunks(vars);
// create all the beginnings of IIFEs
const functionWrapperStarts = injectionVariableChunks.map((variableChunk) => {
return this.variableInjectionFunctionWrapperStartCode(
variableChunk.map(variable => variable.name)
);
});
// and all the ends
const functionWrapperEnds = injectionVariableChunks.map((variableChunk) => {
return this.variableInjectionFunctionWrapperEndCode(
variableChunk.map(variable => variable.expression), block
);
});
// join them to one big string
const varStartCode = functionWrapperStarts.join("");
// reverse the ends first before joining them, as the last added must be the inner most
const varEndCode = functionWrapperEnds.reverse().join("");
// if we have anything, add it to the source
if(varStartCode && varEndCode) {
const start = block.range ? block.range[0] : -10;
const end = block.range ? block.range[1] : (this._source.size() + 1);
source.insert(start + 0.5, varStartCode);
source.insert(end + 0.5, "\n/* WEBPACK VAR INJECTION */" + varEndCode);
}
}
block.blocks.forEach((block) =>
this.sourceBlock(
block,
availableVars.concat(vars),
dependencyTemplates,
source,
outputOptions,
requestShortener
)
);
}
source(dependencyTemplates, outputOptions, requestShortener) {
const hashDigest = this.getHashDigest(dependencyTemplates);
if(this._cachedSource && this._cachedSource.hash === hashDigest) {
return this._cachedSource.source;
}
if(!this._source) {
return new RawSource("throw new Error('No source available');");
}
const source = new ReplaceSource(this._source);
this._cachedSource = {
source: source,
hash: hashDigest
};
this.sourceBlock(this, [], dependencyTemplates, source, outputOptions, requestShortener);
return new CachedSource(source);
}
originalSource() {
return this._source;
}
getHighestTimestamp(keys, timestampsByKey) {
let highestTimestamp = 0;
for(let i = 0; i < keys.length; i++) {
const key = keys[i];
const timestamp = timestampsByKey[key];
// if there is no timestamp yet, early return with Infinity
if(!timestamp) return Infinity;
highestTimestamp = Math.max(highestTimestamp, timestamp);
}
return highestTimestamp;
}
needRebuild(fileTimestamps, contextTimestamps) {
const highestFileDepTimestamp = this.getHighestTimestamp(
this.fileDependencies, fileTimestamps);
// if the hightest is Infinity, we need a rebuild
// exit early here.
if(highestFileDepTimestamp === Infinity) {
return true;
}
const highestContextDepTimestamp = this.getHighestTimestamp(
this.contextDependencies, contextTimestamps);
// Again if the hightest is Infinity, we need a rebuild
// exit early here.
if(highestContextDepTimestamp === Infinity) {
return true;
}
// else take the highest of file and context timestamps and compare
// to last build timestamp
return Math.max(highestContextDepTimestamp, highestFileDepTimestamp) >= this.buildTimestamp;
}
size() {
return this._source ? this._source.size() : -1;
}
updateHashWithSource(hash) {
if(!this._source) {
hash.update("null");
return;
}
hash.update("source");
this._source.updateHash(hash);
}
updateHashWithMeta(hash) {
hash.update("meta");
hash.update(JSON.stringify(this.meta));
}
updateHash(hash) {
this.updateHashWithSource(hash);
this.updateHashWithMeta(hash);
super.updateHash(hash);
}
}
module.exports = NormalModule;