mirror of https://github.com/webpack/webpack.git
Merge pull request #4259 from timse/refactor-normalmodule-to-es6
Refactor NormalModule to es6
This commit is contained in:
commit
27deabcefe
|
|
@ -2,22 +2,27 @@
|
|||
MIT License http://www.opensource.org/licenses/mit-license.php
|
||||
Author Tobias Koppers @sokra
|
||||
*/
|
||||
var path = require("path");
|
||||
var Module = require("./Module");
|
||||
var SourceMapSource = require("webpack-sources").SourceMapSource;
|
||||
var OriginalSource = require("webpack-sources").OriginalSource;
|
||||
var RawSource = require("webpack-sources").RawSource;
|
||||
var ReplaceSource = require("webpack-sources").ReplaceSource;
|
||||
var CachedSource = require("webpack-sources").CachedSource;
|
||||
var LineToLineMappedSource = require("webpack-sources").LineToLineMappedSource;
|
||||
var ModuleParseError = require("./ModuleParseError");
|
||||
"use strict";
|
||||
|
||||
var ModuleBuildError = require("./ModuleBuildError");
|
||||
var ModuleError = require("./ModuleError");
|
||||
var ModuleWarning = require("./ModuleWarning");
|
||||
const path = require("path");
|
||||
const NativeModule = require("module");
|
||||
const crypto = require("crypto");
|
||||
|
||||
var runLoaders = require("loader-runner").runLoaders;
|
||||
var getContext = require("loader-runner").getContext;
|
||||
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 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)) {
|
||||
|
|
@ -26,41 +31,9 @@ function asString(buf) {
|
|||
return buf;
|
||||
}
|
||||
|
||||
function NormalModule(request, userRequest, rawRequest, loaders, resource, parser) {
|
||||
Module.call(this);
|
||||
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;
|
||||
}
|
||||
module.exports = NormalModule;
|
||||
|
||||
NormalModule.prototype = Object.create(Module.prototype);
|
||||
NormalModule.prototype.constructor = NormalModule;
|
||||
|
||||
NormalModule.prototype.identifier = function() {
|
||||
return this.request;
|
||||
};
|
||||
|
||||
NormalModule.prototype.readableIdentifier = function(requestShortener) {
|
||||
return requestShortener.shorten(this.userRequest);
|
||||
};
|
||||
|
||||
function contextify(options, request) {
|
||||
function contextify(context, request) {
|
||||
return request.split("!").map(function(r) {
|
||||
var rp = path.relative(options.context, r);
|
||||
let rp = path.relative(context, r);
|
||||
if(path.sep === "\\")
|
||||
rp = rp.replace(/\\/g, "/");
|
||||
if(rp.indexOf("../") !== 0)
|
||||
|
|
@ -69,260 +42,463 @@ function contextify(options, request) {
|
|||
}).join("!");
|
||||
}
|
||||
|
||||
NormalModule.prototype.libIdent = function(options) {
|
||||
return contextify(options, this.userRequest);
|
||||
};
|
||||
class NormalModule extends Module {
|
||||
|
||||
NormalModule.prototype.nameForCondition = function() {
|
||||
var idx = this.resource.indexOf("?");
|
||||
if(idx >= 0) return this.resource.substr(0, idx);
|
||||
return this.resource;
|
||||
};
|
||||
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);
|
||||
}
|
||||
|
||||
NormalModule.prototype.doBuild = function doBuild(options, compilation, resolver, fs, callback) {
|
||||
this.cacheable = false;
|
||||
var module = this;
|
||||
var loaderContext = {
|
||||
version: 2,
|
||||
emitWarning: function(warning) {
|
||||
module.warnings.push(new ModuleWarning(module, warning));
|
||||
},
|
||||
emitError: function(error) {
|
||||
module.errors.push(new ModuleError(module, error));
|
||||
},
|
||||
exec: function(code, filename) {
|
||||
var Module = require("module");
|
||||
var m = new Module(filename, module);
|
||||
m.paths = Module._nodeModulePaths(module.context);
|
||||
m.filename = filename;
|
||||
m._compile(code, filename);
|
||||
return m.exports;
|
||||
},
|
||||
resolve: function(context, request, callback) {
|
||||
resolver.resolve({}, context, request, callback);
|
||||
},
|
||||
resolveSync: function(context, request) {
|
||||
return resolver.resolveSync({}, context, request);
|
||||
},
|
||||
options: options
|
||||
};
|
||||
loaderContext.webpack = true;
|
||||
loaderContext.sourceMap = !!this.useSourceMap;
|
||||
loaderContext.emitFile = function(name, content, sourceMap) {
|
||||
if(typeof sourceMap === "string") {
|
||||
this.assets[name] = new OriginalSource(content, sourceMap);
|
||||
} else if(sourceMap) {
|
||||
this.assets[name] = new SourceMapSource(content, name, sourceMap);
|
||||
} else {
|
||||
this.assets[name] = new RawSource(content);
|
||||
}
|
||||
}.bind(this);
|
||||
loaderContext._module = this;
|
||||
loaderContext._compilation = compilation;
|
||||
loaderContext._compiler = compilation.compiler;
|
||||
loaderContext.fs = fs;
|
||||
compilation.applyPlugins("normal-module-loader", loaderContext, this);
|
||||
if(options.loader)
|
||||
for(var key in options.loader)
|
||||
loaderContext[key] = options.loader[key];
|
||||
|
||||
runLoaders({
|
||||
resource: this.resource,
|
||||
loaders: this.loaders,
|
||||
context: loaderContext,
|
||||
readResource: fs.readFile.bind(fs)
|
||||
}, function(err, result) {
|
||||
if(result) {
|
||||
module.cacheable = result.cacheable;
|
||||
module.fileDependencies = result.fileDependencies;
|
||||
module.contextDependencies = result.contextDependencies;
|
||||
}
|
||||
if(err) {
|
||||
return callback(module.error = new ModuleBuildError(module, err));
|
||||
return new OriginalSource(content, sourceMap);
|
||||
}
|
||||
|
||||
var resourceBuffer = result.resourceBuffer;
|
||||
var source = result.result[0];
|
||||
var sourceMap = result.result[1];
|
||||
return new SourceMapSource(content, name, sourceMap);
|
||||
}
|
||||
|
||||
if(!Buffer.isBuffer(source) && typeof source !== "string") {
|
||||
return callback(module.error = new ModuleBuildError(module, new Error("Final loader didn't return a Buffer or String")));
|
||||
createLoaderContext(resolver, options, compilation, fs) {
|
||||
const loaderContext = {
|
||||
version: 2,
|
||||
emitWarning: (warning) => {
|
||||
this.warnings.push(new ModuleWarning(this, warning));
|
||||
},
|
||||
emitError: (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);
|
||||
}
|
||||
source = asString(source);
|
||||
if(module.identifier && module.lineToLine && resourceBuffer) {
|
||||
module._source = new LineToLineMappedSource(source, module.identifier(),
|
||||
asString(resourceBuffer));
|
||||
} else if(module.identifier && module.useSourceMap && sourceMap) {
|
||||
module._source = new SourceMapSource(source, module.identifier(), sourceMap);
|
||||
} else if(module.identifier) {
|
||||
module._source = new OriginalSource(source, module.identifier());
|
||||
} else {
|
||||
module._source = 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));
|
||||
}
|
||||
return callback();
|
||||
});
|
||||
};
|
||||
|
||||
NormalModule.prototype.disconnect = function disconnect() {
|
||||
this.built = false;
|
||||
Module.prototype.disconnect.call(this);
|
||||
};
|
||||
if(this.useSourceMap && sourceMap) {
|
||||
return new SourceMapSource(source, identifier, sourceMap);
|
||||
}
|
||||
|
||||
NormalModule.prototype.build = function build(options, compilation, resolver, fs, callback) {
|
||||
var _this = this;
|
||||
_this.buildTimestamp = new Date().getTime();
|
||||
_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, function(err) {
|
||||
_this.dependencies.length = 0;
|
||||
_this.variables.length = 0;
|
||||
_this.blocks.length = 0;
|
||||
_this._cachedSource = null;
|
||||
if(err) return setError(err);
|
||||
if(options.module && options.module.noParse) {
|
||||
var testRegExp = function testRegExp(regExp) {
|
||||
return typeof regExp === "string" ?
|
||||
_this.request.indexOf(regExp) === 0 :
|
||||
regExp.test(_this.request);
|
||||
};
|
||||
if(Array.isArray(options.module.noParse)) {
|
||||
if(options.module.noParse.some(testRegExp, _this))
|
||||
return callback();
|
||||
} else if(testRegExp.call(_this, options.module.noParse)) {
|
||||
return callback();
|
||||
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;
|
||||
}
|
||||
}
|
||||
try {
|
||||
_this.parser.parse(_this._source.source(), {
|
||||
current: _this,
|
||||
module: _this,
|
||||
compilation: compilation,
|
||||
options: options
|
||||
});
|
||||
} catch(e) {
|
||||
var source = _this._source.source();
|
||||
return setError(_this.error = new ModuleParseError(_this, source, e));
|
||||
}
|
||||
return callback();
|
||||
});
|
||||
|
||||
function setError(err) {
|
||||
_this.meta = null;
|
||||
if(_this.error) {
|
||||
_this.errors.push(_this.error);
|
||||
_this._source = new RawSource("throw new Error(" + JSON.stringify(_this.error.message) + ");");
|
||||
} else {
|
||||
_this._source = new RawSource("throw new Error('Module build failed');");
|
||||
}
|
||||
callback();
|
||||
}
|
||||
};
|
||||
if(err) {
|
||||
const error = new ModuleBuildError(this, err);
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
NormalModule.prototype.source = function(dependencyTemplates, outputOptions, requestShortener) {
|
||||
var hash = require("crypto").createHash("md5");
|
||||
this.updateHash(hash);
|
||||
hash = hash.digest("hex");
|
||||
if(this._cachedSource && this._cachedSource.hash === hash) {
|
||||
return this._cachedSource.source;
|
||||
}
|
||||
var _source = this._source;
|
||||
if(!_source) return new RawSource("throw new Error('No source available');");
|
||||
var source = new ReplaceSource(_source);
|
||||
this._cachedSource = {
|
||||
source: source,
|
||||
hash: hash
|
||||
};
|
||||
var topLevelBlock = this;
|
||||
const resourceBuffer = result.resourceBuffer;
|
||||
const source = result.result[0];
|
||||
const sourceMap = result.result[1];
|
||||
|
||||
function doDep(dep) {
|
||||
var template = dependencyTemplates.get(dep.constructor);
|
||||
if(!template) throw new Error("No template for dependency: " + dep.constructor.name);
|
||||
template.apply(dep, source, outputOptions, requestShortener, dependencyTemplates);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
function doVariable(availableVars, vars, variable) {
|
||||
var name = variable.name;
|
||||
var expr = variable.expressionSource(dependencyTemplates, outputOptions, requestShortener);
|
||||
|
||||
function isEqual(v) {
|
||||
return v.name === name && v.expression.source() === expr.source();
|
||||
}
|
||||
if(availableVars.some(isEqual)) return;
|
||||
vars.push({
|
||||
name: name,
|
||||
expression: expr
|
||||
this._source = this.createSource(asString(source), resourceBuffer, sourceMap);
|
||||
return callback();
|
||||
});
|
||||
}
|
||||
|
||||
function doBlock(availableVars, block) {
|
||||
block.dependencies.forEach(doDep);
|
||||
var vars = [];
|
||||
if(block.variables.length > 0) {
|
||||
block.variables.forEach(doVariable.bind(null, availableVars, vars));
|
||||
var varNames = [];
|
||||
var varExpressions = [];
|
||||
var varStartCode = "";
|
||||
var varEndCode = "";
|
||||
|
||||
var emitFunction = function emitFunction() {
|
||||
if(varNames.length === 0) return;
|
||||
varStartCode += "/* WEBPACK VAR INJECTION */(function(" + varNames.join(", ") + ") {";
|
||||
// exports === this in the topLevelBlock, but exports do compress better...
|
||||
varEndCode = (topLevelBlock === block ? "}.call(" + (topLevelBlock.exportsArgument || "exports") + ", " : "}.call(this, ") +
|
||||
varExpressions.map(function(e) {
|
||||
return e.source();
|
||||
}).join(", ") + "))" + varEndCode;
|
||||
|
||||
varNames.length = 0;
|
||||
varExpressions.length = 0;
|
||||
};
|
||||
vars.forEach(function(v) {
|
||||
if(varNames.indexOf(v.name) >= 0) emitFunction();
|
||||
varNames.push(v.name);
|
||||
varExpressions.push(v.expression);
|
||||
});
|
||||
emitFunction();
|
||||
var start = block.range ? block.range[0] : -10;
|
||||
var end = block.range ? block.range[1] : (_source.size() + 1);
|
||||
if(varStartCode) source.insert(start + 0.5, varStartCode);
|
||||
if(varEndCode) source.insert(end + 0.5, "\n/* WEBPACK VAR INJECTION */" + varEndCode);
|
||||
}
|
||||
block.blocks.forEach(doBlock.bind(null, availableVars.concat(vars)));
|
||||
disconnect() {
|
||||
this.built = false;
|
||||
super.disconnect();
|
||||
}
|
||||
doBlock([], this);
|
||||
return new CachedSource(source);
|
||||
};
|
||||
|
||||
NormalModule.prototype.needRebuild = function needRebuild(fileTimestamps, contextTimestamps) {
|
||||
var timestamp = 0;
|
||||
this.fileDependencies.forEach(function(file) {
|
||||
var ts = fileTimestamps[file];
|
||||
if(!ts) timestamp = Infinity;
|
||||
if(ts > timestamp) timestamp = ts;
|
||||
});
|
||||
this.contextDependencies.forEach(function(context) {
|
||||
var ts = contextTimestamps[context];
|
||||
if(!ts) timestamp = Infinity;
|
||||
if(ts > timestamp) timestamp = ts;
|
||||
});
|
||||
return timestamp >= this.buildTimestamp;
|
||||
};
|
||||
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) + ");");
|
||||
}
|
||||
|
||||
NormalModule.prototype.size = function() {
|
||||
return this._source ? this._source.size() : -1;
|
||||
};
|
||||
applyNoParseRule(rule, content) {
|
||||
// must start with "rule" if rule is a string
|
||||
if(typeof rule === "string") {
|
||||
return content.indexOf(rule) === 0;
|
||||
}
|
||||
// we assume rule is a regexp
|
||||
return rule.test(content);
|
||||
}
|
||||
|
||||
NormalModule.prototype.updateHash = function(hash) {
|
||||
if(this._source) {
|
||||
// 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 = new Date().getTime();
|
||||
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() {
|
||||
const hash = crypto.createHash("md5");
|
||||
this.updateHash(hash);
|
||||
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.map((variable) => this.sourceVariables(
|
||||
variable, availableVars, dependencyTemplates, outputOptions, requestShortener))
|
||||
.filter(Boolean);
|
||||
|
||||
/**
|
||||
* 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) => variableChunk.map(variable => variable.name))
|
||||
.map(names => this.variableInjectionFunctionWrapperStartCode(names));
|
||||
|
||||
// and all the ends
|
||||
const functionWrapperEnds = injectionVariableChunks.map((variableChunk) => variableChunk.map(variable => variable.expression))
|
||||
.map(expressions => this.variableInjectionFunctionWrapperEndCode(expressions, 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();
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
} else
|
||||
hash.update("null");
|
||||
hash.update("meta");
|
||||
hash.update(JSON.stringify(this.meta));
|
||||
Module.prototype.updateHash.call(this, 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;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,407 @@
|
|||
/* globals describe, it, beforeEach, afterEach */
|
||||
"use strict";
|
||||
require("should");
|
||||
const sinon = require("sinon");
|
||||
const NormalModule = require("../lib/NormalModule");
|
||||
const path = require("path");
|
||||
const SourceMapSource = require("webpack-sources").SourceMapSource;
|
||||
const OriginalSource = require("webpack-sources").OriginalSource;
|
||||
const RawSource = require("webpack-sources").RawSource;
|
||||
|
||||
describe("NormalModule", function() {
|
||||
let normalModule;
|
||||
let request;
|
||||
let userRequest;
|
||||
let rawRequest;
|
||||
let loaders;
|
||||
let resource;
|
||||
let parser;
|
||||
beforeEach(function() {
|
||||
request = "some/request";
|
||||
userRequest = "some/userRequest";
|
||||
rawRequest = "some/rawRequest";
|
||||
loaders = [];
|
||||
resource = "some/resource";
|
||||
parser = {
|
||||
parse() {}
|
||||
};
|
||||
normalModule = new NormalModule(
|
||||
request,
|
||||
userRequest,
|
||||
rawRequest,
|
||||
loaders,
|
||||
resource,
|
||||
parser
|
||||
);
|
||||
});
|
||||
describe("#identifier", function() {
|
||||
it("returns an identifier for this module", function() {
|
||||
normalModule.identifier().should.eql(request);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#readableIdentifier", function() {
|
||||
it("calls the given requestShortener with the user request", function() {
|
||||
const spy = sinon.spy();
|
||||
normalModule.readableIdentifier({
|
||||
shorten: spy
|
||||
});
|
||||
spy.callCount.should.eql(1);
|
||||
spy.args[0][0].should.eql(userRequest);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#libIdent", function() {
|
||||
it("contextifies the userRequest of the module", function() {
|
||||
normalModule.libIdent({
|
||||
context: "some/context"
|
||||
}).should.eql("../userRequest");
|
||||
});
|
||||
describe("given a userRequest containing loaders", function() {
|
||||
beforeEach(function() {
|
||||
userRequest = "some/userRequest!some/other/userRequest!some/thing/is/off/here";
|
||||
normalModule = new NormalModule(
|
||||
request,
|
||||
userRequest,
|
||||
rawRequest,
|
||||
loaders,
|
||||
resource,
|
||||
parser
|
||||
);
|
||||
});
|
||||
it("contextifies every path in the userRequest", function() {
|
||||
normalModule.libIdent({
|
||||
context: "some/context"
|
||||
}).should.eql("../userRequest!../other/userRequest!../thing/is/off/here");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#nameForCondition", function() {
|
||||
it("return the resource", function() {
|
||||
normalModule.nameForCondition().should.eql(resource);
|
||||
});
|
||||
describe("given a resource containing a ?-sign", function() {
|
||||
const baseResource = "some/resource";
|
||||
beforeEach(function() {
|
||||
resource = baseResource + "?some=query";
|
||||
normalModule = new NormalModule(
|
||||
request,
|
||||
userRequest,
|
||||
rawRequest,
|
||||
loaders,
|
||||
resource,
|
||||
parser
|
||||
);
|
||||
});
|
||||
it("return only the part before the ?-sign", function() {
|
||||
normalModule.nameForCondition().should.eql(baseResource);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#createSourceForAsset", function() {
|
||||
let name;
|
||||
let content;
|
||||
let sourceMap;
|
||||
beforeEach(function() {
|
||||
name = "some name";
|
||||
content = "some content";
|
||||
sourceMap = "some sourcemap";
|
||||
});
|
||||
describe("given no sourcemap", function() {
|
||||
it("returns a RawSource", function() {
|
||||
normalModule.createSourceForAsset(name, content).should.be.instanceOf(RawSource);
|
||||
});
|
||||
});
|
||||
describe("given a string as the sourcemap", function() {
|
||||
it("returns a OriginalSource", function() {
|
||||
normalModule.createSourceForAsset(name, content, sourceMap).should.be.instanceOf(OriginalSource);
|
||||
});
|
||||
});
|
||||
describe("given a some other kind of sourcemap", function() {
|
||||
beforeEach(function() {
|
||||
sourceMap = () => {};
|
||||
});
|
||||
it("returns a SourceMapSource", function() {
|
||||
normalModule.createSourceForAsset(name, content, sourceMap).should.be.instanceOf(SourceMapSource);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#source", function() {
|
||||
describe("without the module having any source", function() {
|
||||
beforeEach(function() {
|
||||
normalModule._source = null;
|
||||
});
|
||||
it("returns a Source containing an Error", function() {
|
||||
normalModule.source().should.be.instanceOf(RawSource);
|
||||
normalModule.source().source().should.eql("throw new Error('No source available');");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#updateHashWithSource", function() {
|
||||
let hashSpy;
|
||||
let hash;
|
||||
beforeEach(function() {
|
||||
hashSpy = sinon.spy();
|
||||
hash = {
|
||||
update: hashSpy
|
||||
};
|
||||
});
|
||||
describe("without the module having any source", function() {
|
||||
beforeEach(function() {
|
||||
normalModule._source = null;
|
||||
});
|
||||
it("calls hash function with \"null\"", function() {
|
||||
normalModule.updateHashWithSource(hash);
|
||||
hashSpy.callCount.should.eql(1);
|
||||
hashSpy.args[0][0].should.eql("null");
|
||||
});
|
||||
});
|
||||
describe("without the module having source", function() {
|
||||
let expectedSource = "some source";
|
||||
beforeEach(function() {
|
||||
normalModule._source = new RawSource(expectedSource);
|
||||
});
|
||||
it("calls hash function with \"source\" and then the actual source of the module", function() {
|
||||
normalModule.updateHashWithSource(hash);
|
||||
hashSpy.callCount.should.eql(2);
|
||||
hashSpy.args[0][0].should.eql("source");
|
||||
hashSpy.args[1][0].should.eql(expectedSource);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("#needRebuild", function() {
|
||||
let fileTimestamps;
|
||||
let contextTimestamps;
|
||||
let fileDependencies;
|
||||
let contextDependencies;
|
||||
let fileA;
|
||||
let fileB;
|
||||
|
||||
function setDeps(
|
||||
fileDependencies,
|
||||
contextDependencies) {
|
||||
normalModule.fileDependencies = fileDependencies;
|
||||
normalModule.contextDependencies = contextDependencies;
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
fileA = "fileA";
|
||||
fileB = "fileB";
|
||||
fileDependencies = [fileA, fileB];
|
||||
contextDependencies = [fileA, fileB];
|
||||
fileTimestamps = {
|
||||
[fileA]: 1,
|
||||
[fileB]: 1,
|
||||
};
|
||||
contextTimestamps = {
|
||||
[fileA]: 1,
|
||||
[fileB]: 1,
|
||||
};
|
||||
normalModule.buildTimestamp = 2;
|
||||
setDeps(fileDependencies, contextDependencies);
|
||||
});
|
||||
describe("given all timestamps are older than the buildTimestamp", function() {
|
||||
it("returns false", function() {
|
||||
normalModule.needRebuild(fileTimestamps, contextTimestamps).should.eql(false);
|
||||
});
|
||||
});
|
||||
describe("given a file timestamp is newer than the buildTimestamp", function() {
|
||||
beforeEach(function() {
|
||||
fileTimestamps[fileA] = 3;
|
||||
});
|
||||
it("returns true", function() {
|
||||
normalModule.needRebuild(fileTimestamps, contextTimestamps).should.eql(true);
|
||||
});
|
||||
});
|
||||
describe("given a no file timestamp exists", function() {
|
||||
beforeEach(function() {
|
||||
fileTimestamps = {};
|
||||
});
|
||||
it("returns true", function() {
|
||||
normalModule.needRebuild(fileTimestamps, contextTimestamps).should.eql(true);
|
||||
});
|
||||
});
|
||||
describe("given a context timestamp is newer than the buildTimestamp", function() {
|
||||
beforeEach(function() {
|
||||
contextTimestamps[fileA] = 3;
|
||||
});
|
||||
it("returns true", function() {
|
||||
normalModule.needRebuild(fileTimestamps, contextTimestamps).should.eql(true);
|
||||
});
|
||||
});
|
||||
describe("given a no context timestamp exists", function() {
|
||||
beforeEach(function() {
|
||||
contextTimestamps = {};
|
||||
});
|
||||
it("returns true", function() {
|
||||
normalModule.needRebuild(fileTimestamps, contextTimestamps).should.eql(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("#splitVariablesInUniqueNamedChunks", function() {
|
||||
let variables;
|
||||
beforeEach(function() {
|
||||
variables = [{
|
||||
name: "foo"
|
||||
}, {
|
||||
name: "bar"
|
||||
}, {
|
||||
name: "baz"
|
||||
}, {
|
||||
name: "some"
|
||||
}, {
|
||||
name: "more"
|
||||
}];
|
||||
});
|
||||
describe("given an empty array of vars", function() {
|
||||
it("returns an empty array", function() {
|
||||
normalModule.splitVariablesInUniqueNamedChunks([]).should.eql([
|
||||
[]
|
||||
]);
|
||||
});
|
||||
});
|
||||
describe("given an array of distrinct variables", function() {
|
||||
it("returns an array containing an array containing the variables", function() {
|
||||
normalModule.splitVariablesInUniqueNamedChunks(variables).should.eql([variables]);
|
||||
});
|
||||
});
|
||||
describe("given an array with duplicate variables", function() {
|
||||
it("returns several arrays each containing only distinct variable names", function() {
|
||||
normalModule.splitVariablesInUniqueNamedChunks(variables.concat(variables)).should.eql([variables, variables]);
|
||||
});
|
||||
describe("and a duplicate as the last variable", function() {
|
||||
it("returns correctly split distinct arrays", function() {
|
||||
normalModule.splitVariablesInUniqueNamedChunks(variables.concat(variables).concat(variables[0])).should.eql([variables, variables, [variables[0]]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#applyNoParseRule", function() {
|
||||
let rule;
|
||||
let content;
|
||||
describe("given a string as rule", function() {
|
||||
beforeEach(function() {
|
||||
rule = "some-rule";
|
||||
});
|
||||
describe("and the content starting with the string specified in rule", function() {
|
||||
beforeEach(function() {
|
||||
content = rule + "some-content";
|
||||
});
|
||||
it("returns true", function() {
|
||||
normalModule.shouldPreventParsing(rule, content).should.eql(true);
|
||||
});
|
||||
});
|
||||
describe("and the content does not start with the string specified in rule", function() {
|
||||
beforeEach(function() {
|
||||
content = "some-content";
|
||||
});
|
||||
it("returns false", function() {
|
||||
normalModule.shouldPreventParsing(rule, content).should.eql(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("given a regex as rule", function() {
|
||||
beforeEach(function() {
|
||||
rule = /some-rule/;
|
||||
});
|
||||
describe("and the content matches the rule", function() {
|
||||
beforeEach(function() {
|
||||
content = rule + "some-content";
|
||||
});
|
||||
it("returns true", function() {
|
||||
normalModule.shouldPreventParsing(rule, content).should.eql(true);
|
||||
});
|
||||
});
|
||||
describe("and the content does not match the rule", function() {
|
||||
beforeEach(function() {
|
||||
content = "some-content";
|
||||
});
|
||||
it("returns false", function() {
|
||||
normalModule.shouldPreventParsing(rule, content).should.eql(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#shouldPreventParsing", function() {
|
||||
let applyNoParseRuleSpy;
|
||||
beforeEach(function() {
|
||||
applyNoParseRuleSpy = sinon.stub();
|
||||
normalModule.applyNoParseRule = applyNoParseRuleSpy;
|
||||
});
|
||||
describe("given no noParseRule", function() {
|
||||
it("returns false", function() {
|
||||
normalModule.shouldPreventParsing().should.eql(false);
|
||||
applyNoParseRuleSpy.callCount.should.eql(0);
|
||||
});
|
||||
});
|
||||
describe("given a noParseRule", function() {
|
||||
let returnValOfSpy;
|
||||
beforeEach(function() {
|
||||
returnValOfSpy = true;
|
||||
applyNoParseRuleSpy.returns(returnValOfSpy);
|
||||
});
|
||||
describe("that is a string", function() {
|
||||
it("calls and returns whatever applyNoParseRule returns", function() {
|
||||
normalModule.shouldPreventParsing("some rule").should.eql(returnValOfSpy);
|
||||
applyNoParseRuleSpy.callCount.should.eql(1);
|
||||
});
|
||||
});
|
||||
describe("that is a regex", function() {
|
||||
it("calls and returns whatever applyNoParseRule returns", function() {
|
||||
normalModule.shouldPreventParsing("some rule").should.eql(returnValOfSpy);
|
||||
applyNoParseRuleSpy.callCount.should.eql(1);
|
||||
});
|
||||
});
|
||||
describe("that is an array", function() {
|
||||
describe("of strings and or regexs", function() {
|
||||
let someRules;
|
||||
beforeEach(function() {
|
||||
someRules = [
|
||||
"some rule",
|
||||
/some rule1/,
|
||||
"some rule2",
|
||||
];
|
||||
});
|
||||
describe("and none of them match", function() {
|
||||
beforeEach(function() {
|
||||
returnValOfSpy = false;
|
||||
applyNoParseRuleSpy.returns(returnValOfSpy);
|
||||
});
|
||||
it("returns false", function() {
|
||||
normalModule.shouldPreventParsing(someRules).should.eql(returnValOfSpy);
|
||||
applyNoParseRuleSpy.callCount.should.eql(3);
|
||||
});
|
||||
});
|
||||
describe("and the first of them matches", function() {
|
||||
beforeEach(function() {
|
||||
returnValOfSpy = true;
|
||||
applyNoParseRuleSpy.returns(returnValOfSpy);
|
||||
});
|
||||
it("returns true", function() {
|
||||
normalModule.shouldPreventParsing(someRules).should.eql(returnValOfSpy);
|
||||
applyNoParseRuleSpy.callCount.should.eql(1);
|
||||
});
|
||||
});
|
||||
describe("and the last of them matches", function() {
|
||||
beforeEach(function() {
|
||||
returnValOfSpy = true;
|
||||
applyNoParseRuleSpy.onCall(0).returns(false);
|
||||
applyNoParseRuleSpy.onCall(1).returns(false);
|
||||
applyNoParseRuleSpy.onCall(2).returns(true);
|
||||
});
|
||||
it("returns true", function() {
|
||||
normalModule.shouldPreventParsing(someRules).should.eql(returnValOfSpy);
|
||||
applyNoParseRuleSpy.callCount.should.eql(3);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue