227 lines
8.1 KiB
JavaScript
227 lines
8.1 KiB
JavaScript
/*
|
|
MIT License http://www.opensource.org/licenses/mit-license.php
|
|
Author Tobias Koppers @sokra
|
|
*/
|
|
var ConcatSource = require("webpack-core/lib/ConcatSource");
|
|
var TemplateArgumentDependency = require("../dependencies/TemplateArgumentDependency");
|
|
|
|
function DedupePlugin() {
|
|
}
|
|
module.exports = DedupePlugin;
|
|
|
|
DedupePlugin.prototype.apply = function(compiler) {
|
|
compiler.plugin("compilation", function(compilation) {
|
|
|
|
compilation.dependencyTemplates.set(TemplateArgumentDependency, new TemplateArgumentDependency.Template());
|
|
|
|
compilation.plugin("after-optimize-modules", function(modules) {
|
|
var modulesByHash = {};
|
|
var allDups = [];
|
|
modules.forEach(function(module, idx) {
|
|
if(!module.getSourceHash || !module.getAllModuleDependencies || !module.createTemplate || !module.getTemplateArguments) return;
|
|
var hash = module.getSourceHash();
|
|
var dupModule = modulesByHash[hash];
|
|
if(dupModule) {
|
|
if(dupModule.duplicates) {
|
|
dupModule.duplicates.push(module);
|
|
module.duplicates = dupModule.duplicates;
|
|
} else {
|
|
allDups.push(module.duplicates = dupModule.duplicates = [dupModule, module]);
|
|
}
|
|
} else {
|
|
modulesByHash[hash] = module;
|
|
}
|
|
});
|
|
});
|
|
compilation.plugin("after-optimize-chunks", function(chunks) {
|
|
var entryChunks = chunks.filter(function(c) { return c.entry; });
|
|
entryChunks.forEach(function(chunk) { // for each entry chunk
|
|
var hasDeduplicatedModules = false;
|
|
(function x(dups, roots, visited, chunk) {
|
|
var currentDups = [];
|
|
var currentRoots = [];
|
|
chunk.modules.forEach(function(module) {
|
|
if(module.duplicates) {
|
|
if(!module.rootDuplicatesChunks)
|
|
module.rootDuplicatesChunks = module.chunks.slice();
|
|
var chunkIndex = module.rootDuplicatesChunks.indexOf(chunk);
|
|
if(!module.rootDuplicates) module.rootDuplicates = [];
|
|
var idx = currentDups.indexOf(module.duplicates);
|
|
if(idx >= 0) {
|
|
module.rootDuplicates[chunkIndex] = currentRoots[idx];
|
|
module.rootDuplicates[chunkIndex].push(module);
|
|
module.rootDuplicates[chunkIndex].commonModules =
|
|
mergeCommonModules(module.rootDuplicates[chunkIndex].commonModules, module.getAllModuleDependencies());
|
|
hasDeduplicatedModules = true;
|
|
} else {
|
|
idx = dups.indexOf(module.duplicates);
|
|
if(idx < 0) {
|
|
module.rootDuplicates[chunkIndex] = [module];
|
|
module.rootDuplicates[chunkIndex].commonModules = module.getAllModuleDependencies();
|
|
module.rootDuplicates[chunkIndex].initialCommonModulesLength = module.rootDuplicates[chunkIndex].commonModules.length;
|
|
dups = dups.concat([module.duplicates]);
|
|
roots = roots.concat([module.rootDuplicates[chunkIndex]]);
|
|
currentDups = currentDups.concat([module.duplicates]);
|
|
currentRoots = currentRoots.concat([module.rootDuplicates[chunkIndex]]);
|
|
} else {
|
|
module.rootDuplicates[chunkIndex] = roots[idx];
|
|
module.rootDuplicates[chunkIndex].commonModules =
|
|
mergeCommonModules(module.rootDuplicates[chunkIndex].commonModules, module.getAllModuleDependencies());
|
|
hasDeduplicatedModules = true;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
chunk.chunks.forEach(function(chunk) {
|
|
if(visited.indexOf(chunk) < 0)
|
|
x(dups, roots, visited.concat(chunk), chunk);
|
|
})
|
|
|
|
currentRoots.forEach(function(roots) {
|
|
var commonModules = roots.commonModules;
|
|
var initialLength = roots.initialCommonModulesLength;
|
|
if(initialLength !== commonModules.length) {
|
|
var template = roots[0].createTemplate(commonModules);
|
|
roots.template = template;
|
|
chunk.addModule(template);
|
|
template.addChunk(chunk);
|
|
compilation.modules.push(template);
|
|
hasDeduplicatedModules = true;
|
|
}
|
|
});
|
|
}([], [], [], chunk));
|
|
if(hasDeduplicatedModules)
|
|
chunk._DedupePlugin_hasDeduplicatedModules = true;
|
|
});
|
|
});
|
|
function mergeCommonModules(commonModules, newModules) {
|
|
return commonModules.filter(function(module) {
|
|
return newModules.indexOf(module) >= 0;
|
|
});
|
|
}
|
|
});
|
|
compiler.moduleTemplate = new DedupModuleTemplateDecorator(compiler.moduleTemplate);
|
|
compiler.mainTemplate = Object.create(compiler.mainTemplate);
|
|
var oldRenderAddModule = compiler.mainTemplate.renderAddModule;
|
|
compiler.mainTemplate.renderAddModule = function(hash, chunk, varModuleId, varModule) {
|
|
if(!chunk._DedupePlugin_hasDeduplicatedModules) {
|
|
return oldRenderAddModule.call(this, hash, chunk, varModuleId, varModule);
|
|
}
|
|
return [
|
|
"var _m = " + varModule + ";",
|
|
"",
|
|
"// Check if module is deduplicated",
|
|
"switch(typeof _m) {",
|
|
"case \"number\":",
|
|
this.indent([
|
|
"// Module is a copy of another module",
|
|
"modules[" + varModuleId + "] = modules[_m];",
|
|
"break;"
|
|
]),
|
|
"case \"object\":",
|
|
this.indent([
|
|
"// Module can be created from a template",
|
|
"modules[" + varModuleId + "] = (function(_m) {",
|
|
this.indent([
|
|
"var args = _m.slice(1), fn = modules[_m[0]];",
|
|
"return function (a,b,c) {",
|
|
this.indent([
|
|
"fn.apply(null, [a,b,c].concat(args));"
|
|
]),
|
|
"};"
|
|
]),
|
|
"}(_m));",
|
|
"break;"
|
|
]),
|
|
"default:",
|
|
this.indent([
|
|
"// Normal module",
|
|
"modules[" + varModuleId + "] = _m;"
|
|
]),
|
|
"}"
|
|
]
|
|
};
|
|
var oldRenderModules = compiler.mainTemplate.renderModules;
|
|
compiler.mainTemplate.renderModules = function renderModules(hash, chunk, moduleTemplate, dependencyTemplates) {
|
|
if(!chunk._DedupePlugin_hasDeduplicatedModules) {
|
|
return oldRenderModules.call(this, hash, chunk, moduleTemplate, dependencyTemplates);
|
|
}
|
|
var source = new ConcatSource();
|
|
source.add("(function(modules) {\n");
|
|
source.add(this.indent([
|
|
"// Check all modules for deduplicated modules",
|
|
"for(var i in modules) {",
|
|
this.indent([
|
|
"switch(typeof modules[i]) {",
|
|
"case \"number\":",
|
|
this.indent([
|
|
"// Module is a copy of another module",
|
|
"modules[i] = modules[modules[i]];",
|
|
"break;"
|
|
]),
|
|
"case \"object\":",
|
|
this.indent([
|
|
"// Module can be created from a template",
|
|
"modules[i] = (function(_m) {",
|
|
this.indent([
|
|
"var args = _m.slice(1), fn = modules[_m[0]];",
|
|
"return function (a,b,c) {",
|
|
this.indent([
|
|
"fn.apply(null, [a,b,c].concat(args));"
|
|
]),
|
|
"};"
|
|
]),
|
|
"}(modules[i]));"
|
|
]),
|
|
"}"
|
|
]),
|
|
"}",
|
|
"return modules;"
|
|
]));
|
|
source.add("\n}(");
|
|
source.add(oldRenderModules.call(this, hash, chunk, moduleTemplate, dependencyTemplates));
|
|
source.add("))");
|
|
return source;
|
|
};
|
|
};
|
|
|
|
function DedupModuleTemplateDecorator(template) {
|
|
this.template = template;
|
|
}
|
|
|
|
DedupModuleTemplateDecorator.prototype.render = function(module, dependencyTemplates, chunk) {
|
|
if(!module.rootDuplicatesChunks || !chunk) return this.template.render(module, dependencyTemplates, chunk);
|
|
var chunkIndex = module.rootDuplicatesChunks.indexOf(chunk);
|
|
if(!module.rootDuplicates || !module.rootDuplicates[chunkIndex]) return this.template.render(module, dependencyTemplates, chunk);
|
|
var rootDuplicates = module.rootDuplicates[chunkIndex];
|
|
if(rootDuplicates.template) {
|
|
rootDuplicates.template.addReason(module, {
|
|
type: "template",
|
|
request: module.request,
|
|
templateModules: rootDuplicates.template.templateModules
|
|
});
|
|
rootDuplicates.template.reasons.sort(function(a, b) {
|
|
if(a.request == b.request) return 0;
|
|
return a.request < b.request ? -1 : 1;
|
|
});
|
|
var array = [rootDuplicates.template.id].concat(module.getTemplateArguments(rootDuplicates.template.templateModules).map(function(module) {
|
|
if(typeof module.id !== "number")
|
|
return "(function webpackMissingModule() { throw new Error(" + JSON.stringify("Cannot find module") + "); }())"
|
|
return module.id;
|
|
}));
|
|
var source = new ConcatSource("[" + array.join(", ") + "]");
|
|
return source;
|
|
} else {
|
|
rootDuplicates.sort(function(a, b) {
|
|
return a.id - b.id;
|
|
});
|
|
if(module === rootDuplicates[0]) return this.template.render(module, dependencyTemplates, chunk);
|
|
var source = new ConcatSource("" + rootDuplicates[0].id);
|
|
return source;
|
|
}
|
|
};
|
|
|
|
DedupModuleTemplateDecorator.prototype.updateHash = function(hash) {
|
|
hash.update("DedupModuleTemplateDecorator");
|
|
this.template.updateHash(hash);
|
|
}; |