2013-01-31 01:49:25 +08:00
|
|
|
/*
|
|
|
|
MIT License http://www.opensource.org/licenses/mit-license.php
|
|
|
|
Author Tobias Koppers @sokra
|
|
|
|
*/
|
2017-02-11 11:16:18 +08:00
|
|
|
"use strict";
|
2013-01-31 01:49:25 +08:00
|
|
|
|
2017-02-11 11:16:18 +08:00
|
|
|
const path = require("path");
|
|
|
|
const NativeModule = require("module");
|
|
|
|
const Module = require("./Module");
|
|
|
|
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 ModuleParseError = require("./ModuleParseError");
|
2016-01-04 04:42:56 +08:00
|
|
|
|
2017-02-11 11:16:18 +08:00
|
|
|
const ModuleBuildError = require("./ModuleBuildError");
|
|
|
|
const ModuleError = require("./ModuleError");
|
|
|
|
const ModuleWarning = require("./ModuleWarning");
|
|
|
|
|
|
|
|
const runLoaders = require("loader-runner").runLoaders;
|
|
|
|
const getContext = require("loader-runner").getContext;
|
2016-01-04 04:42:56 +08:00
|
|
|
|
|
|
|
function asString(buf) {
|
|
|
|
if(Buffer.isBuffer(buf)) {
|
|
|
|
return buf.toString("utf-8");
|
|
|
|
}
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
|
2015-05-17 00:27:59 +08:00
|
|
|
function contextify(options, request) {
|
|
|
|
return request.split("!").map(function(r) {
|
2017-02-11 11:16:18 +08:00
|
|
|
let rp = path.relative(options.context, r);
|
2015-05-17 00:27:59 +08:00
|
|
|
if(path.sep === "\\")
|
|
|
|
rp = rp.replace(/\\/g, "/");
|
|
|
|
if(rp.indexOf("../") !== 0)
|
|
|
|
rp = "./" + rp;
|
|
|
|
return rp;
|
|
|
|
}).join("!");
|
|
|
|
}
|
|
|
|
|
2017-02-11 11:16:18 +08:00
|
|
|
class NormalModule extends Module {
|
2015-05-17 00:27:59 +08:00
|
|
|
|
2017-02-11 11:16:18 +08:00
|
|
|
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;
|
|
|
|
}
|
2016-09-14 18:04:42 +08:00
|
|
|
|
2017-02-11 11:16:18 +08:00
|
|
|
identifier() {
|
|
|
|
return this.request;
|
|
|
|
}
|
2016-01-04 04:42:56 +08:00
|
|
|
|
2017-02-11 11:16:18 +08:00
|
|
|
readableIdentifier(requestShortener) {
|
|
|
|
return requestShortener.shorten(this.userRequest);
|
|
|
|
}
|
2016-01-04 04:42:56 +08:00
|
|
|
|
2017-02-11 11:16:18 +08:00
|
|
|
libIdent(options) {
|
|
|
|
return contextify(options, this.userRequest);
|
|
|
|
}
|
2016-01-04 04:42:56 +08:00
|
|
|
|
2017-02-11 11:16:18 +08:00
|
|
|
nameForCondition() {
|
|
|
|
const idx = this.resource.indexOf("?");
|
|
|
|
if(idx >= 0) return this.resource.substr(0, idx);
|
|
|
|
return this.resource;
|
|
|
|
}
|
2013-01-31 01:49:25 +08:00
|
|
|
|
2017-02-11 12:32:48 +08:00
|
|
|
createLoaderContext(resolver, options, compilation, fs) {
|
2017-02-11 11:16:18 +08:00
|
|
|
const loaderContext = {
|
|
|
|
version: 2,
|
|
|
|
emitWarning: (warning) => {
|
2017-02-11 12:32:48 +08:00
|
|
|
this.warnings.push(new ModuleWarning(this, warning));
|
2017-02-11 11:16:18 +08:00
|
|
|
},
|
2017-02-11 12:32:48 +08:00
|
|
|
emitError: (error) => {
|
|
|
|
this.errors.push(new ModuleError(this, error));
|
2017-02-11 11:16:18 +08:00
|
|
|
},
|
2017-02-11 12:32:48 +08:00
|
|
|
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;
|
2017-02-11 11:16:18 +08:00
|
|
|
},
|
|
|
|
resolve: function(context, request, callback) {
|
|
|
|
resolver.resolve({}, context, request, callback);
|
|
|
|
},
|
|
|
|
resolveSync: function(context, request) {
|
|
|
|
return resolver.resolveSync({}, context, request);
|
|
|
|
},
|
2017-02-11 12:32:48 +08:00
|
|
|
emitFile: (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);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
options: options,
|
|
|
|
webpack: true,
|
|
|
|
sourceMap: !!this.useSourceMap,
|
|
|
|
_module: this,
|
|
|
|
_compilation: compilation,
|
|
|
|
_compiler: compilation.compiler,
|
|
|
|
fs: fs,
|
2017-02-11 11:16:18 +08:00
|
|
|
};
|
2017-02-11 12:32:48 +08:00
|
|
|
|
2017-02-11 11:16:18 +08:00
|
|
|
compilation.applyPlugins("normal-module-loader", loaderContext, this);
|
|
|
|
if(options.loader)
|
2017-02-11 12:32:48 +08:00
|
|
|
Object.assign(loaderContext, options.loader);
|
|
|
|
|
|
|
|
return loaderContext;
|
|
|
|
}
|
|
|
|
|
2017-02-11 12:57:52 +08:00
|
|
|
createSouce(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);
|
|
|
|
}
|
|
|
|
|
2017-02-11 12:32:48 +08:00
|
|
|
doBuild(options, compilation, resolver, fs, callback) {
|
|
|
|
this.cacheable = false;
|
|
|
|
const loaderContext = this.createLoaderContext(resolver, options, compilation, fs);
|
2013-01-31 01:49:25 +08:00
|
|
|
|
2017-02-11 11:16:18 +08:00
|
|
|
runLoaders({
|
|
|
|
resource: this.resource,
|
|
|
|
loaders: this.loaders,
|
|
|
|
context: loaderContext,
|
|
|
|
readResource: fs.readFile.bind(fs)
|
2017-02-11 12:59:08 +08:00
|
|
|
}, (err, result) => {
|
2017-02-11 11:16:18 +08:00
|
|
|
if(result) {
|
2017-02-11 12:59:08 +08:00
|
|
|
this.cacheable = result.cacheable;
|
|
|
|
this.fileDependencies = result.fileDependencies;
|
|
|
|
this.contextDependencies = result.contextDependencies;
|
2017-02-11 11:16:18 +08:00
|
|
|
}
|
2017-02-11 13:10:07 +08:00
|
|
|
|
2017-02-11 11:16:18 +08:00
|
|
|
if(err) {
|
2017-02-11 12:59:08 +08:00
|
|
|
return callback(this.error = new ModuleBuildError(this, err));
|
2014-02-04 19:21:01 +08:00
|
|
|
}
|
2016-07-03 19:13:01 +08:00
|
|
|
|
2017-02-11 11:16:18 +08:00
|
|
|
const resourceBuffer = result.resourceBuffer;
|
2017-02-11 12:59:35 +08:00
|
|
|
const source = result.result[0];
|
2017-02-11 11:16:18 +08:00
|
|
|
const sourceMap = result.result[1];
|
2013-01-31 01:49:25 +08:00
|
|
|
|
2017-02-11 11:16:18 +08:00
|
|
|
if(!Buffer.isBuffer(source) && typeof source !== "string") {
|
2017-02-11 12:59:08 +08:00
|
|
|
this.error = new ModuleBuildError(this, new Error("Final loader didn't return a Buffer or String"));
|
|
|
|
return callback(this.error);
|
2017-02-11 11:16:18 +08:00
|
|
|
}
|
2017-02-11 12:57:52 +08:00
|
|
|
|
2017-02-11 12:59:08 +08:00
|
|
|
this._source = this.createSouce(asString(source), resourceBuffer, sourceMap);
|
2017-02-11 11:16:18 +08:00
|
|
|
return callback();
|
|
|
|
});
|
2014-05-17 06:31:52 +08:00
|
|
|
}
|
2015-07-13 06:20:09 +08:00
|
|
|
|
2017-02-11 11:16:18 +08:00
|
|
|
disconnect() {
|
|
|
|
this.built = false;
|
|
|
|
super.disconnect();
|
2013-01-31 01:49:25 +08:00
|
|
|
}
|
2015-07-13 06:20:09 +08:00
|
|
|
|
2017-02-11 11:41:26 +08:00
|
|
|
markModuleAsErrored() {
|
|
|
|
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');");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-11 11:16:18 +08:00
|
|
|
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 = {};
|
2016-06-21 03:46:27 +08:00
|
|
|
|
2017-02-11 11:16:18 +08:00
|
|
|
return this.doBuild(options, compilation, resolver, fs, (err) => {
|
|
|
|
this.dependencies.length = 0;
|
|
|
|
this.variables.length = 0;
|
|
|
|
this.blocks.length = 0;
|
|
|
|
this._cachedSource = null;
|
2017-02-11 11:41:26 +08:00
|
|
|
if(err) {
|
|
|
|
this.markModuleAsErrored();
|
|
|
|
return callback();
|
|
|
|
}
|
2017-02-11 11:16:18 +08:00
|
|
|
if(options.module && options.module.noParse) {
|
|
|
|
const 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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
this.parser.parse(this._source.source(), {
|
|
|
|
current: this,
|
|
|
|
module: this,
|
|
|
|
compilation: compilation,
|
|
|
|
options: options
|
|
|
|
});
|
|
|
|
} catch(e) {
|
|
|
|
const source = this._source.source();
|
2017-02-11 11:41:26 +08:00
|
|
|
this.error = new ModuleParseError(this, source, e);
|
|
|
|
this.markModuleAsErrored();
|
|
|
|
return callback();
|
2017-02-11 11:16:18 +08:00
|
|
|
}
|
|
|
|
return callback();
|
2015-07-13 06:20:09 +08:00
|
|
|
});
|
2013-01-31 01:49:25 +08:00
|
|
|
}
|
2015-07-13 06:20:09 +08:00
|
|
|
|
2017-02-11 11:16:18 +08:00
|
|
|
source(dependencyTemplates, outputOptions, requestShortener) {
|
|
|
|
let hash = require("crypto").createHash("md5");
|
|
|
|
this.updateHash(hash);
|
|
|
|
hash = hash.digest("hex");
|
|
|
|
if(this._cachedSource && this._cachedSource.hash === hash) {
|
|
|
|
return this._cachedSource.source;
|
|
|
|
}
|
|
|
|
const _source = this._source;
|
|
|
|
if(!_source) return new RawSource("throw new Error('No source available');");
|
|
|
|
const source = new ReplaceSource(_source);
|
|
|
|
this._cachedSource = {
|
|
|
|
source: source,
|
|
|
|
hash: hash
|
|
|
|
};
|
|
|
|
const topLevelBlock = this;
|
|
|
|
|
|
|
|
function doDep(dep) {
|
|
|
|
const template = dependencyTemplates.get(dep.constructor);
|
|
|
|
if(!template) throw new Error("No template for dependency: " + dep.constructor.name);
|
|
|
|
template.apply(dep, source, outputOptions, requestShortener, dependencyTemplates);
|
|
|
|
}
|
2015-07-13 06:20:09 +08:00
|
|
|
|
2017-02-11 11:16:18 +08:00
|
|
|
function doVariable(availableVars, vars, variable) {
|
|
|
|
const name = variable.name;
|
|
|
|
const expr = variable.expressionSource(dependencyTemplates, outputOptions, requestShortener);
|
2013-02-26 19:36:34 +08:00
|
|
|
|
2017-02-11 11:16:18 +08:00
|
|
|
function isEqual(v) {
|
|
|
|
return v.name === name && v.expression.source() === expr.source();
|
|
|
|
}
|
|
|
|
if(availableVars.some(isEqual)) return;
|
|
|
|
vars.push({
|
|
|
|
name: name,
|
|
|
|
expression: expr
|
2013-01-31 01:49:25 +08:00
|
|
|
});
|
|
|
|
}
|
2017-02-11 11:16:18 +08:00
|
|
|
|
|
|
|
function doBlock(availableVars, block) {
|
|
|
|
block.dependencies.forEach(doDep);
|
|
|
|
const vars = [];
|
|
|
|
if(block.variables.length > 0) {
|
|
|
|
block.variables.forEach(doVariable.bind(null, availableVars, vars));
|
|
|
|
const varNames = [];
|
|
|
|
const varExpressions = [];
|
|
|
|
let varStartCode = "";
|
|
|
|
let varEndCode = "";
|
|
|
|
|
|
|
|
const 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();
|
|
|
|
const start = block.range ? block.range[0] : -10;
|
|
|
|
const 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)));
|
|
|
|
}
|
|
|
|
doBlock([], this);
|
|
|
|
return new CachedSource(source);
|
2013-01-31 01:49:25 +08:00
|
|
|
}
|
|
|
|
|
2017-02-11 13:10:07 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-02-11 11:16:18 +08:00
|
|
|
needRebuild(fileTimestamps, contextTimestamps) {
|
2017-02-11 13:10:07 +08:00
|
|
|
const highestFileDepTimestamp = this.getHighestTimestamp(
|
|
|
|
this.fileDependencies, fileTimestamps);
|
|
|
|
// if the hightest is Infinity, we need a needRebuild
|
|
|
|
// exit early here.
|
|
|
|
if(highestFileDepTimestamp === Infinity) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const highestContextDepTimestamp = this.getHighestTimestamp(
|
|
|
|
this.contextDependencies, contextTimestamps);
|
|
|
|
|
|
|
|
// Again if the hightest is Infinity, we need a needRebuild
|
|
|
|
// 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;
|
2017-02-11 11:16:18 +08:00
|
|
|
}
|
2013-01-31 01:49:25 +08:00
|
|
|
|
2017-02-11 11:16:18 +08:00
|
|
|
size() {
|
|
|
|
return this._source ? this._source.size() : -1;
|
|
|
|
}
|
|
|
|
|
2017-02-11 11:17:54 +08:00
|
|
|
updateHashWithSource(hash) {
|
|
|
|
if(!this._source) {
|
2017-02-11 11:16:18 +08:00
|
|
|
hash.update("null");
|
2017-02-11 11:17:54 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
hash.update("source");
|
|
|
|
this._source.updateHash(hash);
|
|
|
|
}
|
|
|
|
|
|
|
|
updateHashWithMeta(hash) {
|
2017-02-11 11:16:18 +08:00
|
|
|
hash.update("meta");
|
|
|
|
hash.update(JSON.stringify(this.meta));
|
2017-02-11 11:17:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
updateHash(hash) {
|
|
|
|
this.updateHashWithSource(hash);
|
|
|
|
this.updateHashWithMeta(hash);
|
2017-02-11 11:16:18 +08:00
|
|
|
super.updateHash(hash);
|
|
|
|
}
|
2013-01-31 01:49:25 +08:00
|
|
|
|
2017-02-11 11:16:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = NormalModule;
|