ContextModule.js 6.69 KB
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/
var path = require("path");
var Module = require("./Module");
var OriginalSource = require("webpack-sources").OriginalSource;
var RawSource = require("webpack-sources").RawSource;
var AsyncDependenciesBlock = require("./AsyncDependenciesBlock");

function ContextModule(resolveDependencies, context, recursive, regExp, addon, async) {
	Module.call(this);
	this.resolveDependencies = resolveDependencies;
	this.context = context;
	this.recursive = recursive;
	this.regExp = regExp;
	this.addon = addon;
	this.async = !!async;
	this.cacheable = true;
	this.contextDependencies = [context];
	this.built = false;
}
module.exports = ContextModule;

ContextModule.prototype = Object.create(Module.prototype);
ContextModule.prototype.constructor = ContextModule;

ContextModule.prototype.identifier = function() {
	var identifier = "";
	identifier += this.context + " ";
	if(this.async)
		identifier += "async ";
	if(!this.recursive)
		identifier += "nonrecursive ";
	if(this.addon)
		identifier += this.addon;
	if(this.regExp)
		identifier += this.regExp;
	return identifier.replace(/ $/, "");
};

function prettyRegExp(str) {
	return str.substring(1, str.length - 1);
}

ContextModule.prototype.readableIdentifier = function(requestShortener) {
	var identifier = "";
	identifier += requestShortener.shorten(this.context) + " ";
	if(this.async)
		identifier += "async ";
	if(!this.recursive)
		identifier += "nonrecursive ";
	if(this.addon)
		identifier += requestShortener.shorten(this.addon);
	if(this.regExp)
		identifier += prettyRegExp(this.regExp + "");
	return identifier.replace(/ $/, "");
};

function contextify(options, request) {
	return request.split("!").map(function(r) {
		var rp = path.relative(options.context, r);
		if(path.sep === "\\")
			rp = rp.replace(/\\/g, "/");
		if(rp.indexOf("../") !== 0)
			rp = "./" + rp;
		return rp;
	}).join("!");
}

ContextModule.prototype.libIdent = function(options) {
	var identifier = contextify(options, this.context) + " ";
	if(this.async)
		identifier += "async ";
	if(this.recursive)
		identifier += "recursive ";
	if(this.addon)
		identifier += contextify(options, this.addon);
	if(this.regExp)
		identifier += prettyRegExp(this.regExp + "");
	return identifier.replace(/ $/, "");
};

ContextModule.prototype.needRebuild = function(fileTimestamps, contextTimestamps) {
	var ts = contextTimestamps[this.context];
	if(!ts) return true;
	return ts >= this.builtTime;
};

ContextModule.prototype.unbuild = function unbuild() {
	this.built = false;
	Module.prototype.unbuild.call(this);
};

ContextModule.prototype.build = function(options, compilation, resolver, fs, callback) {
	this.built = true;
	this.builtTime = new Date().getTime();
	var addon = this.addon;
	this.resolveDependencies(fs, this.context, this.recursive, this.regExp, function(err, dependencies) {
		if(err) return callback(err);
		if(dependencies) {
			dependencies.forEach(function(dep) {
				dep.loc = dep.userRequest;
				dep.request = addon + dep.request;
			});
		}
		if(this.async) {
			if(dependencies) {
				dependencies.forEach(function(dep) {
					var block = new AsyncDependenciesBlock(null, dep.module, dep.loc);
					block.addDependency(dep);
					this.addBlock(block);
				}, this);
			}
		} else {
			this.dependencies = dependencies;
		}
		callback();
	}.bind(this));
};

ContextModule.prototype.source = function() {
	var str;
	var map = {};
	if(this.dependencies && this.dependencies.length > 0) {
		this.dependencies.slice().sort(function(a, b) {
			if(a.userRequest === b.userRequest) return 0;
			return a.userRequest < b.userRequest ? -1 : 1;
		}).forEach(function(dep) {
			if(dep.module)
				map[dep.userRequest] = dep.module.id;
		});
		str = [
			"var map = ", JSON.stringify(map, null, "\t"), ";\n",
			"function webpackContext(req) {\n",
			"\treturn __webpack_require__(webpackContextResolve(req));\n",
			"};\n",
			"function webpackContextResolve(req) {\n",
			"\tvar id = map[req];\n",
			"\tif(!(id + 1)) // check for number\n",
			"\t\tthrow new Error(\"Cannot find module '\" + req + \"'.\");\n",
			"\treturn id;\n",
			"};\n",
			"webpackContext.keys = function webpackContextKeys() {\n",
			"\treturn Object.keys(map);\n",
			"};\n",
			"webpackContext.resolve = webpackContextResolve;\n",
			"module.exports = webpackContext;\n",
			"webpackContext.id = " + JSON.stringify(this.id) + ";\n"
		];
	} else if(this.blocks && this.blocks.length > 0) {
		var items = this.blocks.map(function(block) {
			return {
				dependency: block.dependencies[0],
				block: block,
				userRequest: block.dependencies[0].userRequest
			};
		}).filter(function(item) {
			return item.dependency.module;
		});
		var hasMultipleChunks = false;
		items.sort(function(a, b) {
			if(a.userRequest === b.userRequest) return 0;
			return a.userRequest < b.userRequest ? -1 : 1;
		}).forEach(function(item) {
			if(item.dependency.module) {
				var chunks = item.block.chunks || [];
				if(chunks.length !== 1)
					hasMultipleChunks = true;
				map[item.userRequest] = [item.dependency.module.id].concat(chunks.map(function(chunk) {
					return chunk.id;
				}));
			}
		});
		str = [
			"var map = ", JSON.stringify(map, null, "\t"), ";\n",
			"function webpackAsyncContext(req) {\n",
			"\tvar ids = map[req];",
			"\tif(!ids)\n",
			"\t\treturn Promise.reject(new Error(\"Cannot find module '\" + req + \"'.\"));\n",
			"\treturn ",
			hasMultipleChunks ?
			"Promise.all(ids.slice(1).map(__webpack_require__.e))" :
			"__webpack_require__.e(ids[1])",
			".then(function() {\n",
			"\t\treturn __webpack_require__(ids[0]);\n",
			"\t});\n",
			"};\n",
			"webpackAsyncContext.keys = function webpackAsyncContextKeys() {\n",
			"\treturn Object.keys(map);\n",
			"};\n",
			"module.exports = webpackAsyncContext;\n",
			"webpackAsyncContext.id = " + JSON.stringify(this.id) + ";\n"
		];
	} else {
		str = [
			"function webpackEmptyContext(req) {\n",
			"\tthrow new Error(\"Cannot find module '\" + req + \"'.\");\n",
			"}\n",
			"webpackEmptyContext.keys = function() { return []; };\n",
			"webpackEmptyContext.resolve = webpackEmptyContext;\n",
			"module.exports = webpackEmptyContext;\n",
			"webpackEmptyContext.id = " + JSON.stringify(this.id) + ";\n"
		];
	}
	if(this.useSourceMap) {
		return new OriginalSource(str.join(""), this.identifier());
	} else {
		return new RawSource(str.join(""));
	}
};

ContextModule.prototype.size = function() {
	return this.dependencies.map(function(dep) {
		return dep.userRequest.length + 5;
	}).reduce(function(a, b) {
		return a + b;
	}, 160);
};