From c30f16f4391e197390315a3a6d367d067a03837a Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Wed, 12 Jun 2013 16:16:06 +0200 Subject: [PATCH] added experimental deduplication support #47 --- bin/config-optimist.js | 2 + bin/convert-argv.js | 5 + lib/FunctionModuleTemplate.js | 2 +- lib/JsonpMainTemplate.js | 5 +- lib/MainTemplate.js | 18 +- lib/Module.js | 4 - lib/NormalModule.js | 82 +++++++- lib/Stats.js | 3 + lib/WebpackOptionsApply.js | 4 + .../ModuleDependencyTemplateAsId.js | 4 + .../ModuleDependencyTemplateAsRequireId.js | 4 + .../TemplateArgumentDependency.js | 27 +++ lib/node/NodeMainTemplate.js | 5 +- lib/optimize/DedupePlugin.js | 190 ++++++++++++++++++ lib/webworker/WebWorkerMainTemplate.js | 5 +- package.json | 2 +- test/browsertest/build.js | 4 +- test/browsertest/lib/dedupe1/dupdep.js | 1 + test/browsertest/lib/dedupe1/index.js | 1 + test/browsertest/lib/dedupe2/dupdep.js | 1 + test/browsertest/lib/dedupe2/index.js | 1 + test/browsertest/lib/index.web.js | 9 + 22 files changed, 361 insertions(+), 18 deletions(-) create mode 100644 lib/dependencies/TemplateArgumentDependency.js create mode 100644 lib/optimize/DedupePlugin.js create mode 100644 test/browsertest/lib/dedupe1/dupdep.js create mode 100644 test/browsertest/lib/dedupe1/index.js create mode 100644 test/browsertest/lib/dedupe2/dupdep.js create mode 100644 test/browsertest/lib/dedupe2/index.js diff --git a/bin/config-optimist.js b/bin/config-optimist.js index 417dfde60..acbd164ab 100644 --- a/bin/config-optimist.js +++ b/bin/config-optimist.js @@ -61,6 +61,8 @@ module.exports = function(optimist) { .boolean("optimize-occurence-order").describe("optimize-occurence-order") + .boolean("optimize-dedupe").describe("optimize-dedupe") + .string("prefetch").describe("prefetch") .string("provide").describe("provide") diff --git a/bin/convert-argv.js b/bin/convert-argv.js index a1c5fcf65..b20532550 100644 --- a/bin/convert-argv.js +++ b/bin/convert-argv.js @@ -267,6 +267,11 @@ module.exports = function(optimist, argv, convertOptions) { options.optimize.occurenceOrder = true; }); + ifBooleanArg("optimize-dedupe", function() { + ensureObject(options, "optimize"); + options.optimize.dedupe = true; + }); + ifArg("prefetch", function(request) { ensureArray(options, "prefetch"); options.prefetch.push(request); diff --git a/lib/FunctionModuleTemplate.js b/lib/FunctionModuleTemplate.js index b8d36c611..e0254013b 100644 --- a/lib/FunctionModuleTemplate.js +++ b/lib/FunctionModuleTemplate.js @@ -19,7 +19,7 @@ FunctionModuleTemplate.prototype.render = function(module, dependencyTemplates) source.add(" !*** " + req.replace(/\*\//g, "*_/") + " ***!\n"); source.add(" \\****" + req.replace(/./g, "*") + "****/\n"); } - source.add("/***/ function(module, exports, require) {\n\n"); + source.add("/***/ function(" + ["module", "exports", "require"].concat(module.arguments || []).join(", ") + ") {\n\n"); source.add(new PrefixSource("\t", module.source(dependencyTemplates, this.outputOptions, this.requestShortener))); source.add("\n\n/***/ }"); return source; diff --git a/lib/JsonpMainTemplate.js b/lib/JsonpMainTemplate.js index 70864033c..feecf26ae 100644 --- a/lib/JsonpMainTemplate.js +++ b/lib/JsonpMainTemplate.js @@ -84,8 +84,9 @@ JsonpMainTemplate.prototype.renderInit = function(hash, chunk) { "installedChunks[chunkId] = 0;" ]), "}", - "for(moduleId in moreModules)", - this.indent("modules[moduleId] = moreModules[moduleId];"), + "for(moduleId in moreModules) {", + this.indent(this.renderAddModule(hash, chunk, "moduleId", "moreModules[moduleId]")), + "}", "while(callbacks.length)", this.indent("callbacks.shift().call(null, require);"), ]), diff --git a/lib/MainTemplate.js b/lib/MainTemplate.js index 685b4b7b0..2d31a2c2f 100644 --- a/lib/MainTemplate.js +++ b/lib/MainTemplate.js @@ -33,7 +33,16 @@ MainTemplate.prototype.render = function(hash, chunk, moduleTemplate, dependency source.add(new PrefixSource("/******/ \t", new OriginalSource(this.asString(buf), "webpackBootstrap " + hash))); source.add("\n/******/ })\n"); source.add("/************************************************************************/\n"); - source.add("/******/ ({\n"); + source.add("/******/ ("); + source.add(this.renderModules(hash, chunk, moduleTemplate, dependencyTemplates)); + source.add(")"); + chunk.rendered = true; + return source; +}; + +MainTemplate.prototype.renderModules = function renderModules(hash, chunk, moduleTemplate, dependencyTemplates) { + var source = new ConcatSource(); + source.add("{\n"); source.add(this.asString(this.renderInitModules(hash, chunk, moduleTemplate, dependencyTemplates))); source.add("\n"); chunk.modules.forEach(function(module, idx) { @@ -41,8 +50,7 @@ MainTemplate.prototype.render = function(hash, chunk, moduleTemplate, dependency source.add("\n/***/ " + module.id + ":\n"); source.add(moduleTemplate.render(module, dependencyTemplates)); }); - source.add("\n/******/ })"); - chunk.rendered = true; + source.add("\n/******/ }"); return source; }; @@ -138,6 +146,10 @@ MainTemplate.prototype.renderInitModules = function(hash, chunk, moduleTemplate, ]; }; +MainTemplate.prototype.renderAddModule = function(hash, chunk, varModuleId, varModule) { + return ["modules[" + varModuleId + "] = " + varModule + ";"] +} + MainTemplate.prototype.updateHash = function(hash) { hash.update("maintemplate"); hash.update("1"); diff --git a/lib/Module.js b/lib/Module.js index 975b0d8c2..41e1f3379 100644 --- a/lib/Module.js +++ b/lib/Module.js @@ -23,10 +23,6 @@ module.exports = Module; Module.prototype = Object.create(DependenciesBlock.prototype); -Module.prototype.separable = function(callback) { - callback(false); -}; - Module.prototype.disconnect = function() { this.reasons.length = 0; this.lastId = this.id; diff --git a/lib/NormalModule.js b/lib/NormalModule.js index 896eb018e..85afc2e55 100644 --- a/lib/NormalModule.js +++ b/lib/NormalModule.js @@ -9,6 +9,7 @@ var OriginalSource = require("webpack-core/lib/OriginalSource"); var RawSource = require("webpack-core/lib/RawSource"); var ReplaceSource = require("webpack-core/lib/ReplaceSource"); var ModuleParseError = require("./ModuleParseError"); +var TemplateArgumentDependency = require("./dependencies/TemplateArgumentDependency"); var path = require("path"); function NormalModule(request, userRequest, rawRequest, loaders, resource, parser) { @@ -82,7 +83,7 @@ NormalModule.prototype.source = function(dependencyTemplates, outputOptions, req function doDep(dep) { var template = dependencyTemplates.get(dep.Class); if(!template) throw new Error("No template for dependency: " + dep.Class.name); - template.apply(dep, source, outputOptions, requestShortener); + template.apply(dep, source, outputOptions, requestShortener, dependencyTemplates); } function doVariable(vars, variable) { var name = variable.name; @@ -151,3 +152,82 @@ NormalModule.prototype.updateHash = function(hash) { hash.update("null"); Module.prototype.updateHash.call(this, hash); }; + +NormalModule.prototype.getSourceHash = function() { + if(!this._source) return ""; + var hash = new (require("crypto").Hash)("md5"); + hash.update(this._source.source()); + return hash.digest("hex"); +}; + +NormalModule.prototype.getAllModuleDependencies = function() { + var list = []; + function doDep(dep) { + if(dep.module && list.indexOf(dep.module) < 0) + list.push(dep.module); + } + function doVariable(variable) { + variable.dependencies.forEach(doDep); + } + function doBlock(block) { + block.variables.forEach(doVariable); + block.dependencies.forEach(doDep); + block.blocks.forEach(doBlock); + } + doBlock(this); + return list; +}; + +NormalModule.prototype.createTemplate = function(keepModules) { + var template = new NormalModule("", "", "", [], "", null); + template._source = this._source; + template.built = this.built; + template.templateModules = keepModules; + var args = template.arguments = []; + function doDeps(deps) { + return deps.map(function(dep) { + if(keepModules.indexOf(dep.module) < 0) { + var argName = "__webpack_module_template_argument_" + args.length + "__"; + args.push(argName); + return new TemplateArgumentDependency(argName, dep); + } else { + return dep; + } + }); + } + function doVariable(variable, newVariable) { + variable.dependencies.forEach(doDep); + } + function doBlock(block, newBlock) { + block.variables.forEach(function(variable) { + var newDependencies = doDeps(variable.dependencies); + newBlock.addVariable(variable.name, variable.expression, newDependencies); + }); + newBlock.dependencies = doDeps(block.dependencies); + block.blocks.forEach(function(childBlock) { + var newChildBlock = new AsyncDependenciesBlock(childBlock.name); + newBlock.addBlock(newChildBlock); + doBlock(childBlock, newChildBlock); + }); + } + doBlock(this, template); + return template; +}; + +NormalModule.prototype.getTemplateArguments = function(keepModules) { + var list = []; + function doDep(dep) { + if(dep.module && keepModules.indexOf(dep.module) < 0) + list.push(dep.module); + } + function doVariable(variable) { + variable.dependencies.forEach(doDep); + } + function doBlock(block) { + block.variables.forEach(doVariable); + block.dependencies.forEach(doDep); + block.blocks.forEach(doBlock); + } + doBlock(this); + return list; +}; diff --git a/lib/Stats.js b/lib/Stats.js index 5bedfe1da..17e84a5d8 100644 --- a/lib/Stats.js +++ b/lib/Stats.js @@ -135,6 +135,7 @@ Stats.prototype.toJson = function toJson(options, forToString) { userRequest: reason.dependency.userRequest } var dep = reason.dependency; + if(dep.templateModules) obj.templateModules = dep.templateModules.map(function(module) { return module.id; }); if(dep.loc) obj.loc = dep.loc.start.line + ":" + dep.loc.start.column + "-" + (dep.loc.start.line != dep.loc.end.line ? dep.loc.end.line + ":" : "") + dep.loc.end.column return obj; @@ -436,6 +437,7 @@ Stats.jsonToString = function jsonToString(obj, useColors) { normal(reason.type); normal(" "); cyan(reason.userRequest); + if(reason.templateModules) cyan(reason.templateModules.join(" ")); normal(" ["); normal(reason.moduleId); normal("] "); @@ -469,6 +471,7 @@ Stats.jsonToString = function jsonToString(obj, useColors) { normal(reason.type); normal(" "); cyan(reason.userRequest); + if(reason.templateModules) cyan(reason.templateModules.join(" ")); normal(" ["); normal(reason.moduleId); normal("] "); diff --git a/lib/WebpackOptionsApply.js b/lib/WebpackOptionsApply.js index c6009ea2f..03be63d3b 100644 --- a/lib/WebpackOptionsApply.js +++ b/lib/WebpackOptionsApply.js @@ -42,6 +42,7 @@ var RemoveParentModulesPlugin = require("./optimize/RemoveParentModulesPlugin"); var RemoveEmptyChunksPlugin = require("./optimize/RemoveEmptyChunksPlugin"); var MergeDuplicateChunksPlugin = require("./optimize/MergeDuplicateChunksPlugin"); var FlagIncludedChunksPlugin = require("./optimize/FlagIncludedChunksPlugin"); +var DedupePlugin = require("./optimize/DedupePlugin"); var ModulesInDirectoriesPlugin = require("enhanced-resolve/lib/ModulesInDirectoriesPlugin"); var ModulesInRootPlugin = require("enhanced-resolve/lib/ModulesInRootPlugin"); @@ -156,6 +157,9 @@ WebpackOptionsApply.prototype.process = function(options, compiler) { else if(options.optimize.minimize) compiler.apply(new UglifyJsPlugin(options.optimize.minimize)); + if(options.optimize.dedupe === true) + compiler.apply(new DedupePlugin()); + if(options.cache === undefined ? options.watch : options.cache) compiler.apply(new CachePlugin(typeof options.cache == "object" ? options.cache : null)); diff --git a/lib/dependencies/ModuleDependencyTemplateAsId.js b/lib/dependencies/ModuleDependencyTemplateAsId.js index 7c2ef427d..4abf66c6f 100644 --- a/lib/dependencies/ModuleDependencyTemplateAsId.js +++ b/lib/dependencies/ModuleDependencyTemplateAsId.js @@ -14,3 +14,7 @@ ModuleDependencyTemplateAsId.prototype.apply = function(dep, source, outputOptio var content = "(function webpackMissingModule() { throw new Error(" + JSON.stringify("Cannot find module \"" + dep.request + "\"") + "); }())"; source.replace(dep.range[0], dep.range[1]-1, content); }; + +ModuleDependencyTemplateAsId.prototype.applyAsTemplateArgument = function(name, dep, source, outputOptions, requestShortener) { + source.replace(dep.range[0], dep.range[1]-1, name); +}; diff --git a/lib/dependencies/ModuleDependencyTemplateAsRequireId.js b/lib/dependencies/ModuleDependencyTemplateAsRequireId.js index 980ee07fe..6a47fa5a8 100644 --- a/lib/dependencies/ModuleDependencyTemplateAsRequireId.js +++ b/lib/dependencies/ModuleDependencyTemplateAsRequireId.js @@ -14,3 +14,7 @@ ModuleDependencyTemplateAsRequireId.prototype.apply = function(dep, source, outp var content = "(function webpackMissingModule() { throw new Error(" + JSON.stringify("Cannot find module \"" + dep.request + "\"") + "); }())"; source.replace(dep.range[0], dep.range[1]-1, content); }; + +ModuleDependencyTemplateAsRequireId.prototype.applyAsTemplateArgument = function(name, dep, source, outputOptions, requestShortener) { + source.replace(dep.range[0], dep.range[1]-1, "(require(" + name + "))"); +}; diff --git a/lib/dependencies/TemplateArgumentDependency.js b/lib/dependencies/TemplateArgumentDependency.js new file mode 100644 index 000000000..684c625e0 --- /dev/null +++ b/lib/dependencies/TemplateArgumentDependency.js @@ -0,0 +1,27 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ +function TemplateArgumentDependency(name, dep) { + this.name = name; + this.Class = TemplateArgumentDependency; + this.dep = dep; +} +module.exports = TemplateArgumentDependency; + +TemplateArgumentDependency.prototype.type = "template argument"; + +TemplateArgumentDependency.prototype.updateHash = function(hash) { + hash.update(this.name); +}; + + +TemplateArgumentDependency.Template = function TemplateArgumentDependencyTemplate() {}; + +TemplateArgumentDependency.Template.prototype.apply = function(dep, source, outputOptions, requestShortener, dependencyTemplates) { + var d = dep.dep; + var template = dependencyTemplates.get(d.Class); + if(!template) throw new Error("No template for dependency: " + d.Class.name); + if(!template.applyAsTemplateArgument) throw new Error("Template cannot be applied as TemplateArgument: " + d.Class.name); + return template.applyAsTemplateArgument(dep.name, d, source, outputOptions, requestShortener, dependencyTemplates); +}; diff --git a/lib/node/NodeMainTemplate.js b/lib/node/NodeMainTemplate.js index 3828f4ac1..44407754b 100644 --- a/lib/node/NodeMainTemplate.js +++ b/lib/node/NodeMainTemplate.js @@ -39,8 +39,9 @@ NodeMainTemplate.prototype.renderRequireEnsure = function(hash, chunk) { .replace(Template.REGEXP_NAME, "")) .replace(Template.REGEXP_ID, "\" + chunkId + \"") + ");", "var moreModules = chunk.modules, chunkIds = chunk.ids;", - "for(var moduleId in moreModules)", - this.indent("modules[moduleId] = moreModules[moduleId];"), + "for(var moduleId in moreModules) {", + this.indent(this.renderAddModule(hash, chunk, "moduleId", "moreModules[moduleId]")), + "}", "for(var i = 0; i < chunkIds.length; i++)", this.indent("installedChunks[chunkIds[i]] = 1;"), ]), diff --git a/lib/optimize/DedupePlugin.js b/lib/optimize/DedupePlugin.js new file mode 100644 index 000000000..86e37d1a8 --- /dev/null +++ b/lib/optimize/DedupePlugin.js @@ -0,0 +1,190 @@ +/* + 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) { + (function x(dups, roots, visited, chunk) { + var currentDups = []; + var currentRoots = []; + chunk.modules.forEach(function(module) { + if(module.duplicates) { + var idx = currentDups.indexOf(module.duplicates); + if(idx >= 0) { + module.rootDuplicates = currentRoots[idx]; + module.rootDuplicates.push(module); + module.rootDuplicates.commonModules = mergeCommonModules(module.rootDuplicates.commonModules, module.getAllModuleDependencies()); + } else { + idx = dups.indexOf(module.duplicates); + if(idx < 0) { + module.rootDuplicates = [module]; + module.rootDuplicates.commonModules = module.getAllModuleDependencies(); + module.rootDuplicates.initialCcommonModulesLength = module.rootDuplicates.commonModules.length; + dups = dups.concat([module.duplicates]); + roots = roots.concat([module.rootDuplicates]); + currentDups = currentDups.concat([module.duplicates]); + currentRoots = currentRoots.concat([module.rootDuplicates]); + } else { + module.rootDuplicates = roots[idx]; + module.rootDuplicates.commonModules = mergeCommonModules(module.rootDuplicates.commonModules, module.getAllModuleDependencies()); + } + } + } + }); + 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.initialCcommonModulesLength; + if(initialLength !== commonModules.length) { + var template = roots[0].createTemplate(commonModules); + roots.template = template; + chunk.addModule(template); + template.addChunk(chunk); + compilation.modules.push(template); + } + }); + }([], [], [], chunk)); + }); + }); + function mergeCommonModules(commonModules, newModules) { + return commonModules.filter(function(module) { + return newModules.indexOf(module) >= 0; + }); + } + }); + compiler.moduleTemplate = new DedupModuleTemplateDecorator(compiler.moduleTemplate); + compiler.mainTemplate.renderAddModule = function(hash, chunk, varModuleId, varModule) { + return [ + "var _m = " + varModule + ";", + "switch(typeof _m) {", + "case \"number\":", + this.indent([ + "modules[" + varModuleId + "] = modules[_m];", + "break;" + ]), + "case \"object\":", + this.indent([ + "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("modules[" + varModuleId + "] = _m;"), + "}" + ] + }; + var oldRenderModules = compiler.mainTemplate.renderModules; + compiler.mainTemplate.renderModules = function renderModules(hash, chunk, moduleTemplate, dependencyTemplates) { + var source = new ConcatSource(); + source.add("(function(modules) {\n"); + source.add(this.indent([ + "for(var i in modules) {", + this.indent([ + "switch(typeof modules[i]) {", + "case \"number\":", + this.indent([ + "modules[i] = modules[modules[i]];", + "break;" + ]), + "case \"object\":", + this.indent([ + "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) { + if(!module.rootDuplicates) return this.template.render(module, dependencyTemplates); + if(module.rootDuplicates.template) { + module.rootDuplicates.template.addReason(module, { + type: "template", + request: module.request, + templateModules: module.rootDuplicates.template.templateModules + }); + var array = [module.rootDuplicates.template.id].concat(module.getTemplateArguments(module.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 { + module.rootDuplicates.sort(function(a, b) { + return a.id - b.id; + }); + if(module === module.rootDuplicates[0]) return this.template.render(module, dependencyTemplates); + var source = new ConcatSource("" + module.rootDuplicates[0].id); + return source; + } +}; + +DedupModuleTemplateDecorator.prototype.updateHash = function(hash) { + hash.update("DedupModuleTemplateDecorator"); + this.template.updateHash(hash); +}; \ No newline at end of file diff --git a/lib/webworker/WebWorkerMainTemplate.js b/lib/webworker/WebWorkerMainTemplate.js index f1da1046a..ff04456a1 100644 --- a/lib/webworker/WebWorkerMainTemplate.js +++ b/lib/webworker/WebWorkerMainTemplate.js @@ -50,8 +50,9 @@ WebWorkerMainTemplate.prototype.renderInit = function(hash, chunk) { buf.push( "this[" + JSON.stringify(chunkCallbackName) + "] = function webpackChunkCallback(chunkIds, moreModules) {", this.indent([ - "for(var moduleId in moreModules)", - this.indent("modules[moduleId] = moreModules[moduleId];"), + "for(var moduleId in moreModules) {", + this.indent(this.renderAddModule(hash, chunk, "moduleId", "moreModules[moduleId]")), + "}", "while(chunkIds.length)", this.indent("installedChunks[chunkIds.pop()] = 1;") ]), diff --git a/package.json b/package.json index a21efbc4b..74e2bb074 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "webpack", - "version": "0.10.0-beta19", + "version": "0.10.0-beta20", "author": "Tobias Koppers @sokra", "description": "Packs CommonJs/AMD/Labeled 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.", "dependencies": { diff --git a/test/browsertest/build.js b/test/browsertest/build.js index 13eeaae6f..730552d6a 100644 --- a/test/browsertest/build.js +++ b/test/browsertest/build.js @@ -34,9 +34,9 @@ var library1 = cp.spawn("node", join(["../../bin/webpack.js", "--output-pathinfo bindOutput(library1); library1.on("exit", function(code) { if(code === 0) { - // node ../../bin/webpack --output-pathinfo --colors --resolve-alias vm=vm-browserify --output-public-path js/ --module-bind json --module-bind css=style!css --module-bind less=style!css!less --module-bind coffee --module-bind jade --prefetch ./less/stylesheet.less ./lib/index js/web.js + // node ../../bin/webpack --output-pathinfo --colors --resolve-alias vm=vm-browserify --output-public-path js/ --module-bind json --module-bind css=style!css --module-bind less=style!css!less --module-bind coffee --module-bind jade --prefetch ./less/stylesheet.less --optimize-dedupe ./lib/index js/web.js var main = cp.spawn("node", join(["../../bin/webpack.js", "--output-pathinfo", "--colors", "--resolve-alias", "vm=vm-browserify", "--workers", - "--output-public-path", "js/", "--module-bind", "json", "--module-bind", "css=style!css", "--module-bind", "less=style/url!file?postfix=.css&string!less", "--module-bind", "coffee", "--module-bind", "jade", "--prefetch", "./less/stylesheet.less", "./lib/index", "js/web.js", "--progress"], extraArgs)); + "--output-public-path", "js/", "--module-bind", "json", "--module-bind", "css=style!css", "--module-bind", "less=style/url!file?postfix=.css&string!less", "--module-bind", "coffee", "--module-bind", "jade", "--prefetch", "./less/stylesheet.less", "--optimize-dedupe", "./lib/index", "js/web.js", "--progress"], extraArgs)); bindOutput(main); } }); diff --git a/test/browsertest/lib/dedupe1/dupdep.js b/test/browsertest/lib/dedupe1/dupdep.js new file mode 100644 index 000000000..51e20e063 --- /dev/null +++ b/test/browsertest/lib/dedupe1/dupdep.js @@ -0,0 +1 @@ +module.exports = "edupe1"; \ No newline at end of file diff --git a/test/browsertest/lib/dedupe1/index.js b/test/browsertest/lib/dedupe1/index.js new file mode 100644 index 000000000..7399d12ac --- /dev/null +++ b/test/browsertest/lib/dedupe1/index.js @@ -0,0 +1 @@ +module.exports = require("../d") + require("./dupdep"); \ No newline at end of file diff --git a/test/browsertest/lib/dedupe2/dupdep.js b/test/browsertest/lib/dedupe2/dupdep.js new file mode 100644 index 000000000..1178b058b --- /dev/null +++ b/test/browsertest/lib/dedupe2/dupdep.js @@ -0,0 +1 @@ +module.exports = "edupe2"; \ No newline at end of file diff --git a/test/browsertest/lib/dedupe2/index.js b/test/browsertest/lib/dedupe2/index.js new file mode 100644 index 000000000..7399d12ac --- /dev/null +++ b/test/browsertest/lib/dedupe2/index.js @@ -0,0 +1 @@ +module.exports = require("../d") + require("./dupdep"); \ No newline at end of file diff --git a/test/browsertest/lib/index.web.js b/test/browsertest/lib/index.web.js index 152703c3d..80b68bbd3 100644 --- a/test/browsertest/lib/index.web.js +++ b/test/browsertest/lib/index.web.js @@ -550,4 +550,13 @@ describe("main", function() { }); }); + describe("deduplication", function() { + it("should load a duplicate module with different dependencies correctly", function() { + var dedupe1 = require("./dedupe1"); + var dedupe2 = require("./dedupe2"); + dedupe1.should.be.eql("dedupe1"); + dedupe2.should.be.eql("dedupe2"); + }); + }); + });