webpack
Version:
Packs CommonJs/AMD modules for the browser. Allows to split your codebase into multiple bundles, which can be loaded on demand. Support loaders to preprocess files, i.e. json, jade, coffee, css, less, ... and your custom stuff.
645 lines (592 loc) • 20.7 kB
JavaScript
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
var Template = require("./Template");
var BasicEvaluatedExpression = require("./BasicEvaluatedExpression");
var ModuleHotAcceptDependency = require("./dependencies/ModuleHotAcceptDependency");
var ModuleHotDeclineDependency = require("./dependencies/ModuleHotDeclineDependency");
var RawSource = require("webpack-core/lib/RawSource");
var ConstDependency = require("./dependencies/ConstDependency");
var NullFactory = require("./NullFactory");
function HotModuleReplacementPlugin() {
}
module.exports = HotModuleReplacementPlugin;
HotModuleReplacementPlugin.prototype.apply = function(compiler) {
var hotUpdateChunkFilename = compiler.options.output.hotUpdateChunkFilename;
var hotUpdateMainFilename = compiler.options.output.hotUpdateMainFilename;
compiler.plugin("compilation", function(compilation, params) {
var hotUpdateChunkTemplate = compilation.hotUpdateChunkTemplate;
if(!hotUpdateChunkTemplate) return;
var normalModuleFactory = params.normalModuleFactory;
var contextModuleFactory = params.contextModuleFactory;
compilation.dependencyFactories.set(ConstDependency, new NullFactory());
compilation.dependencyTemplates.set(ConstDependency, new ConstDependency.Template());
compilation.dependencyFactories.set(ModuleHotAcceptDependency, normalModuleFactory);
compilation.dependencyTemplates.set(ModuleHotAcceptDependency, new ModuleHotAcceptDependency.Template());
compilation.dependencyFactories.set(ModuleHotDeclineDependency, normalModuleFactory);
compilation.dependencyTemplates.set(ModuleHotDeclineDependency, new ModuleHotDeclineDependency.Template());
compilation.plugin("record", function(compilation, records) {
if(records.hash === this.hash) return;
records.hash = compilation.hash;
records.moduleHashs = {};
this.modules.forEach(function(module) {
var identifier = module.identifier();
var hash = require("crypto").createHash("md5");
module.updateHash(hash);
records.moduleHashs[identifier] = hash.digest("hex");
});
records.chunkHashs = {};
this.chunks.forEach(function(chunk) {
records.chunkHashs[chunk.id] = chunk.hash;
});
records.chunkModuleIds = {};
this.chunks.forEach(function(chunk) {
records.chunkModuleIds[chunk.id] = chunk.modules.map(function(m) { return m.id; });
});
});
compilation.plugin("after-hash", function() {
var records = this.records;
if(!records) return;
var lastHash = records.hash || "x";
var preHash = records.preHash || "x";
var prepreHash = records.prepreHash || "x";
if(preHash === this.hash) {
this.modifyHash(prepreHash);
return;
}
records.prepreHash = records.hash || "x";
records.preHash = this.hash;
this.modifyHash(records.prepreHash);
});
compilation.plugin("additional-chunk-assets", function() {
var records = this.records;
if(records.hash === this.hash) return;
if(!records.moduleHashs || !records.chunkHashs || !records.chunkModuleIds) return;
var moduleHashs = {};
this.modules.forEach(function(module) {
var identifier = module.identifier();
var hash = require("crypto").createHash("md5");
module.updateHash(hash);
hash = hash.digest("hex");
module.hotUpdate = records.moduleHashs[identifier] !== hash;
});
var hotUpdateMainContent = {
h: this.hash,
c: []
};
Object.keys(records.chunkHashs).forEach(function(chunkId) {
chunkId = +chunkId;
var currentChunk = this.chunks.filter(function(chunk) {
return chunk.id === chunkId;
})[0];
if(currentChunk) {
var newModules = currentChunk.modules.filter(function(module) {
return module.hotUpdate;
});
if(newModules.length > 0) {
var source = hotUpdateChunkTemplate.render(chunkId, newModules, this.hash, this.moduleTemplate, this.dependencyTemplates);
var filename = this.getPath(hotUpdateChunkFilename, {
hash: records.hash,
chunk: currentChunk
});
this.additionalChunkAssets.push(filename);
this.assets[filename] = source;
hotUpdateMainContent.c.push(chunkId);
currentChunk.files.push(filename);
this.applyPlugins("chunk-asset", currentChunk, filename);
}
}
}, this);
var source = new RawSource(JSON.stringify(hotUpdateMainContent));
var filename = this.getPath(hotUpdateMainFilename, {
hash: records.hash
});
this.assets[filename] = source;
});
compilation.mainTemplate.plugin("hash", function(hash) {
hash.update("HotMainTemplateDecorator");
});
compilation.mainTemplate.plugin("module-require", function(_, chunk, hash, varModuleId) {
return "hotCreateRequire(" + varModuleId + ")";
});
compilation.mainTemplate.plugin("require-extensions", function(source, chunk, hash) {
var buf = [source];
buf.push("");
buf.push("// __webpack_hash__");
buf.push(this.requireFn + ".h = function() { return hotCurrentHash; };");
return this.asString(buf);
});
compilation.mainTemplate.plugin("bootstrap", function(source, chunk, hash) {
source = this.applyPluginsWaterfall("hot-bootstrap", source, chunk, hash);
return this.asString([
source,
"",
hotInitCode
.replace(/\$require\$/g, this.requireFn)
.replace(/\$hash\$/g, JSON.stringify(hash))
.replace(/\/\*foreachInstalledChunks\*\//g, chunk.chunks.length > 0 ? "for(var chunkId in installedChunks)" : "var chunkId = " + chunk.id + ";")
]);
});
compilation.mainTemplate.plugin("global-hash", function() {
return true;
});
compilation.mainTemplate.plugin("current-hash", function() {
return "hotCurrentHash";
});
compilation.mainTemplate.plugin("module-obj", function(source, chunk, hash, varModuleId) {
return this.asString([
source + ",",
"hot: hotCreateModule(" + varModuleId + "),",
"parents: hotCurrentParents,",
"children: []"
]);
});
});
compiler.parser.plugin("expression __webpack_hash__", function(expr) {
var dep = new ConstDependency("__webpack_require__.h()", expr.range);
dep.loc = expr.loc;
this.state.current.addDependency(dep);
return true;
});
compiler.parser.plugin("evaluate typeof __webpack_hash__", function(expr) {
return new BasicEvaluatedExpression().setString("string").setRange(expr.range);
});
compiler.parser.plugin("evaluate Identifier module.hot", function(expr) {
return new BasicEvaluatedExpression()
.setBoolean(!!this.state.compilation.hotUpdateChunkTemplate)
.setRange(expr.range);
});
compiler.parser.plugin("call module.hot.accept", function(expr) {
if(!this.state.compilation.hotUpdateChunkTemplate) return false;
if(expr.arguments.length > 1) {
var arg = this.evaluateExpression(expr.arguments[0]);
var params = [];
if(arg.isString()) {
params = [arg];
}
if(arg.isArray()){
params = arg.items.filter(function(param) {
return param.isString();
});
}
params.forEach(function(param){
var dep = new ModuleHotAcceptDependency(param.string, param.range);
dep.optional = true;
this.state.module.addDependency(dep);
}.bind(this));
}
});
compiler.parser.plugin("call module.hot.decline", function(expr) {
if(!this.state.compilation.hotUpdateChunkTemplate) return false;
if(expr.arguments.length > 1) {
var param = this.evaluateExpression(expr.arguments[0]);
if(param.isString()) {
var dep = new ModuleHotDeclineDependency(param.string, param.range);
dep.optional = true;
this.state.module.addDependency(dep);
}
}
});
compiler.parser.plugin("expression module.hot", function() {
return true;
});
};
var hotInitCode = Template.getFunctionContent(function() {
var hotApplyOnUpdate = true;
var hotCurrentHash = $hash$;
var hotCurrentModuleData = {};
var hotCurrentParents = [];
function hotCreateRequire(moduleId) {
var me = installedModules[moduleId];
if(!me) return $require$;
var fn = function(request) {
if(me.hot.active) {
if(installedModules[request]) {
if(installedModules[request].parents.indexOf(moduleId) < 0)
installedModules[request].parents.push(moduleId);
if(me.children.indexOf(request) < 0)
me.children.push(request);
} else hotCurrentParents = [moduleId];
} else {
console.warn("[HMR] unexpected require(" + request + ") from disposed module " + moduleId);
hotCurrentParents = [];
}
return $require$(request);
};
for(var name in $require$) {
if(Object.prototype.hasOwnProperty.call($require$, name)) {
fn[name] = $require$[name];
}
}
fn.e = function(chunkId, callback) {
if(hotStatus === "ready")
hotSetStatus("prepare");
hotChunksLoading++;
$require$.e(chunkId, function() {
try {
callback.call(null, fn);
} finally {
finishChunkLoading();
}
function finishChunkLoading() {
hotChunksLoading--;
if(hotStatus === "prepare") {
if(!hotWaitingFilesMap[chunkId]) {
hotEnsureUpdateChunk(chunkId);
}
if(hotChunksLoading === 0 && hotWaitingFiles === 0) {
hotUpdateDownloaded();
}
}
}
});
}
return fn;
}
function hotCreateModule(moduleId) {
var hot = {
// private stuff
_acceptedDependencies: {},
_declinedDependencies: {},
_selfAccepted: false,
_selfDeclined: false,
_disposeHandlers: [],
// Module API
active: true,
accept: function(dep, callback) {
if(typeof dep === "undefined")
hot._selfAccepted = true;
else if(typeof dep === "function")
hot._selfAccepted = dep;
else if(typeof dep === "number")
hot._acceptedDependencies[dep] = callback;
else for(var i = 0; i < dep.length; i++)
hot._acceptedDependencies[dep[i]] = callback;
},
decline: function(dep) {
if(typeof dep === "undefined")
hot._selfDeclined = true;
else if(typeof dep === "number")
hot._declinedDependencies[dep] = true;
else for(var i = 0; i < dep.length; i++)
hot._declinedDependencies[dep[i]] = true;
},
dispose: function(callback) {
hot._disposeHandlers.push(callback);
},
addDisposeHandler: function(callback) {
hot._disposeHandlers.push(callback);
},
removeDisposeHandler: function(callback) {
var idx = hot._disposeHandlers.indexOf(callback);
if(idx >= 0) hot._disposeHandlers.splice(idx, 1);
},
// Management API
check: hotCheck,
apply: hotApply,
status: function(l) {
if(!l) return hotStatus;
hotStatusHandlers.push(l);
},
addStatusHandler: function(l) {
hotStatusHandlers.push(l);
},
removeStatusHandler: function(l) {
var idx = hotStatusHandlers.indexOf(l);
if(idx >= 0) hotStatusHandlers.splice(idx, 1);
},
//inherit from previous dispose call
data: hotCurrentModuleData[moduleId]
};
return hot;
}
var hotStatusHandlers = [];
var hotStatus = "idle";
function hotSetStatus(newStatus) {
var oldStatus = hotStatus;
hotStatus = newStatus;
for(var i = 0; i < hotStatusHandlers.length; i++)
hotStatusHandlers[i].call(null, newStatus);
}
// while downloading
var hotWaitingFiles = 0;
var hotChunksLoading = 0;
var hotWaitingFilesMap = {};
var hotRequestedFilesMap = {};
var hotAvailibleFilesMap = {};
var hotCallback;
// The update info
var hotUpdate, hotUpdateNewHash;
function hotCheck(apply, callback) {
if(hotStatus !== "idle") throw new Error("check() is only allowed in idle status");
if(typeof apply === "function") {
hotApplyOnUpdate = false;
callback = apply;
} else {
hotApplyOnUpdate = apply;
callback = callback || function(err) { if(err) throw err };
}
hotSetStatus("check");
hotDownloadManifest(function(err, update) {
if(err) return callback(err);
if(!update) {
hotSetStatus("idle");
callback(null, null);
return;
}
hotRequestedFilesMap = {};
hotAvailibleFilesMap = {};
hotWaitingFilesMap = {};
for(var i = 0; i < update.c.length; i++)
hotAvailibleFilesMap[update.c[i]] = true;
hotUpdateNewHash = update.h;
hotSetStatus("prepare");
hotCallback = callback;
hotUpdate = {};
/*foreachInstalledChunks*/ {
hotEnsureUpdateChunk(chunkId);
}
if(hotChunksLoading === 0 && hotWaitingFiles === 0) {
hotUpdateDownloaded();
}
});
}
function hotAddUpdateChunk(chunkId, moreModules) {
if(!hotAvailibleFilesMap[chunkId] || !hotRequestedFilesMap[chunkId])
return;
hotRequestedFilesMap[chunkId] = false;
for(var moduleId in moreModules) {
if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
hotUpdate[moduleId] = moreModules[moduleId];
}
}
if(--hotWaitingFiles === 0 && hotChunksLoading === 0) {
hotUpdateDownloaded();
}
}
function hotEnsureUpdateChunk(chunkId) {
if(!hotAvailibleFilesMap[chunkId]) {
hotWaitingFilesMap[chunkId] = true;
} else {
hotRequestedFilesMap[chunkId] = true;
hotWaitingFiles++;
hotDownloadUpdateChunk(chunkId);
}
}
function hotUpdateDownloaded() {
hotSetStatus("ready");
var callback = hotCallback;
hotCallback = null;
if(!callback) return;
if(hotApplyOnUpdate) {
hotApply(hotApplyOnUpdate, callback);
} else {
var outdatedModules = [];
for(var id in hotUpdate) {
if(Object.prototype.hasOwnProperty.call(hotUpdate, id)) {
outdatedModules.push(+id);
}
}
callback(null, outdatedModules);
}
}
function hotApply(options, callback) {
if(hotStatus !== "ready") throw new Error("apply() is only allowed in ready status");
if(typeof options === "function") {
callback = options;
options = {};
} else if(options && typeof options === "object") {
callback = callback || function(err) { if(err) throw err };
} else {
options = {};
callback = callback || function(err) { if(err) throw err };
}
function getAffectedStuff(module) {
var outdatedModules = [module];
var outdatedDependencies = [];
var queue = outdatedModules.slice();
while(queue.length > 0) {
var moduleId = queue.pop();
var module = installedModules[moduleId];
if(!module || module.hot._selfAccepted)
continue;
if(module.hot._selfDeclined) {
return new Error("Aborted because of self decline: " + moduleId);
}
if(moduleId === 0) {
return;
}
for(var i = 0; i < module.parents.length; i++) {
var parentId = module.parents[i];
var parent = installedModules[parentId];
if(parent.hot._declinedDependencies[moduleId]) {
return new Error("Aborted because of declined dependency: " + moduleId + " in " + parentId);
}
if(outdatedModules.indexOf(parentId) >= 0) continue;
if(parent.hot._acceptedDependencies[moduleId]) {
if(!outdatedDependencies[parentId])
outdatedDependencies[parentId] = [];
addAllToSet(outdatedDependencies[parentId], [moduleId]);
continue;
}
delete outdatedDependencies[parentId];
outdatedModules.push(parentId);
queue.push(parentId);
}
}
return [outdatedModules, outdatedDependencies];
}
function addAllToSet(a, b) {
for(var i = 0; i < b.length; i++) {
var item = b[i];
if(a.indexOf(item) < 0)
a.push(item);
}
}
// at begin all updates modules are outdated
// the "outdated" status can propagate to parents if they don't accept the children
var outdatedDependencies = {};
var outdatedModules = [];
var appliedUpdate = {};
for(var id in hotUpdate) {
if(Object.prototype.hasOwnProperty.call(hotUpdate, id)) {
var moduleId = +id;
var result = getAffectedStuff(moduleId);
if(!result) {
if(options.ignoreUnaccepted)
continue;
hotSetStatus("abort");
return callback(new Error("Aborted because " + moduleId + " is not accepted"));
}
if(result instanceof Error) {
hotSetStatus("abort");
return callback(result);
}
appliedUpdate[moduleId] = hotUpdate[moduleId];
addAllToSet(outdatedModules, result[0]);
for(var moduleId in result[1]) {
if(Object.prototype.hasOwnProperty.call(result[1], moduleId)) {
if(!outdatedDependencies[moduleId])
outdatedDependencies[moduleId] = [];
addAllToSet(outdatedDependencies[moduleId], result[1][moduleId]);
}
}
}
}
// Store self accepted outdated modules to require them later by the module system
var outdatedSelfAcceptedModules = [];
for(var i = 0; i < outdatedModules.length; i++) {
var moduleId = outdatedModules[i];
if(installedModules[moduleId] && installedModules[moduleId].hot._selfAccepted)
outdatedSelfAcceptedModules.push({
module: moduleId,
errorHandler: installedModules[moduleId].hot._selfAccepted
});
}
// Now in "dispose" phase
hotSetStatus("dispose");
var queue = outdatedModules.slice();
while(queue.length > 0) {
var moduleId = queue.pop();
var module = installedModules[moduleId];
if(!module) continue;
var data = {};
// Call dispose handlers
var disposeHandlers = module.hot._disposeHandlers;
for(var j = 0; j < disposeHandlers.length; j++) {
var cb = disposeHandlers[j]
cb(data);
}
hotCurrentModuleData[moduleId] = data;
// disable module (this disables requires from this module)
module.hot.active = false;
// remove module from cache
delete installedModules[moduleId];
// remove "parents" references from all children
for(var j = 0; j < module.children.length; j++) {
var child = installedModules[module.children[j]];
if(!child) continue;
var idx = child.parents.indexOf(moduleId);
if(idx >= 0) {
child.parents.splice(idx, 1);
if(child.parents.length === 0 && child.hot && child.hot._disposeHandlers && child.hot._disposeHandlers.length > 0) {
// Child has dispose handlers and no more references, dispose it too
queue.push(child.id);
}
}
}
}
// remove outdated dependency from module children
for(var moduleId in outdatedDependencies) {
if(Object.prototype.hasOwnProperty.call(outdatedDependencies, moduleId)) {
var module = installedModules[moduleId];
var moduleOutdatedDependencies = outdatedDependencies[moduleId];
for(var j = 0; j < moduleOutdatedDependencies.length; j++) {
var dependency = moduleOutdatedDependencies[j];
var idx = module.children.indexOf(dependency);
if(idx >= 0) module.children.splice(idx, 1);
}
}
}
// Not in "apply" phase
hotSetStatus("apply");
hotCurrentHash = hotUpdateNewHash;
// insert new code
for(var moduleId in appliedUpdate) {
if(Object.prototype.hasOwnProperty.call(appliedUpdate, moduleId)) {
modules[moduleId] = appliedUpdate[moduleId];
}
}
// call accept handlers
var error = null;
for(var moduleId in outdatedDependencies) {
if(Object.prototype.hasOwnProperty.call(outdatedDependencies, moduleId)) {
var module = installedModules[moduleId];
var moduleOutdatedDependencies = outdatedDependencies[moduleId];
var callbacks = [];
for(var i = 0; i < moduleOutdatedDependencies.length; i++) {
var dependency = moduleOutdatedDependencies[i];
var cb = module.hot._acceptedDependencies[dependency];
if(callbacks.indexOf(cb) >= 0) continue;
callbacks.push(cb);
}
for(var i = 0; i < callbacks.length; i++) {
var cb = callbacks[i];
try {
cb(outdatedDependencies);
} catch(err) {
if(!error)
error = err;
}
}
}
}
// Load self accepted modules
for(var i = 0; i < outdatedSelfAcceptedModules.length; i++) {
var item = outdatedSelfAcceptedModules[i];
var moduleId = item.module;
hotCurrentParents = [moduleId];
try {
$require$(moduleId);
} catch(err) {
if(typeof item.errorHandler === "function") {
try {
item.errorHandler(err);
} catch(err) {
if(!error)
error = err;
}
} else if(!error)
error = err;
}
}
// handle errors in accept handlers and self accepted module load
if(error) {
hotSetStatus("fail");
return callback(error);
}
hotSetStatus("idle");
callback(null, outdatedModules);
}
});