webpack/lib/NormalModule.js

364 lines
10 KiB
JavaScript
Raw Normal View History

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");
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;
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;
}
2017-02-11 11:16:18 +08:00
identifier() {
return this.request;
}
2017-02-11 11:16:18 +08:00
readableIdentifier(requestShortener) {
return requestShortener.shorten(this.userRequest);
}
2017-02-11 11:16:18 +08:00
libIdent(options) {
return contextify(options, this.userRequest);
}
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
createLoaderContext(resolver, options, compilation, fs) {
2017-02-11 11:16:18 +08:00
const loaderContext = {
version: 2,
emitWarning: (warning) => {
this.warnings.push(new ModuleWarning(this, warning));
2017-02-11 11:16:18 +08:00
},
emitError: (error) => {
this.errors.push(new ModuleError(this, error));
2017-02-11 11:16:18 +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);
},
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 11:16:18 +08:00
compilation.applyPlugins("normal-module-loader", loaderContext, this);
if(options.loader)
Object.assign(loaderContext, options.loader);
return loaderContext;
}
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);
}
doBuild(options, compilation, resolver, fs, callback) {
this.cacheable = false;
const module = this;
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)
}, 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));
2014-02-04 19:21:01 +08:00
}
2017-02-11 11:16:18 +08:00
const resourceBuffer = result.resourceBuffer;
let source = result.result[0];
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") {
return callback(module.error = new ModuleBuildError(module, new Error("Final loader didn't return a Buffer or String")));
}
module._source = this.createSouce(asString(source), resourceBuffer, sourceMap);
2017-02-11 11:16:18 +08:00
return callback();
});
}
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
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 = {};
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;
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();
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 11:16:18 +08:00
needRebuild(fileTimestamps, contextTimestamps) {
let timestamp = 0;
this.fileDependencies.forEach(function(file) {
const ts = fileTimestamps[file];
if(!ts) timestamp = Infinity;
if(ts > timestamp) timestamp = ts;
});
this.contextDependencies.forEach(function(context) {
const ts = contextTimestamps[context];
if(!ts) timestamp = Infinity;
if(ts > timestamp) timestamp = ts;
});
return timestamp >= this.buildTimestamp;
}
2013-01-31 01:49:25 +08:00
2017-02-11 11:16:18 +08:00
size() {
return this._source ? this._source.size() : -1;
}
updateHashWithSource(hash) {
if(!this._source) {
2017-02-11 11:16:18 +08:00
hash.update("null");
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));
}
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;