From 971bbb61f02f1f6e71e69fba61b92fe75354f343 Mon Sep 17 00:00:00 2001 From: Kyle Truong Date: Sat, 17 Jun 2017 14:57:32 -0400 Subject: [PATCH 1/5] 4099 ES6 Refactor lib/HotModuleReplacementPlugin --- lib/HotModuleReplacementPlugin.js | 445 +++++++++++++++--------------- 1 file changed, 217 insertions(+), 228 deletions(-) diff --git a/lib/HotModuleReplacementPlugin.js b/lib/HotModuleReplacementPlugin.js index 8cab661c765..64c5f9cc3a3 100644 --- a/lib/HotModuleReplacementPlugin.js +++ b/lib/HotModuleReplacementPlugin.js @@ -3,260 +3,249 @@ Author Tobias Koppers @sokra */ "use strict"; -var Template = require("./Template"); -var ModuleHotAcceptDependency = require("./dependencies/ModuleHotAcceptDependency"); -var ModuleHotDeclineDependency = require("./dependencies/ModuleHotDeclineDependency"); -var RawSource = require("webpack-sources").RawSource; -var ConstDependency = require("./dependencies/ConstDependency"); -var NullFactory = require("./NullFactory"); +const Template = require("./Template"); +const ModuleHotAcceptDependency = require("./dependencies/ModuleHotAcceptDependency"); +const ModuleHotDeclineDependency = require("./dependencies/ModuleHotDeclineDependency"); +const RawSource = require("webpack-sources").RawSource; +const ConstDependency = require("./dependencies/ConstDependency"); +const NullFactory = require("./NullFactory"); const ParserHelpers = require("./ParserHelpers"); -function HotModuleReplacementPlugin(options) { - options = options || {}; - this.multiStep = options.multiStep; - this.fullBuildTimeout = options.fullBuildTimeout || 200; - this.requestTimeout = options.requestTimeout || 10000; -} -module.exports = HotModuleReplacementPlugin; - -HotModuleReplacementPlugin.prototype.apply = function(compiler) { - var multiStep = this.multiStep; - var fullBuildTimeout = this.fullBuildTimeout; - var requestTimeout = this.requestTimeout; - 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; - - 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"); +module.exports = class HotModuleReplacementPlugin { + constructor(options) { + this.options = options || {}; + this.multiStep = this.options.multiStep; + this.fullBuildTimeout = this.options.fullBuildTimeout || 200; + this.requestTimeout = this.options.requestTimeout || 10000; + } + + apply(compiler) { + const multiStep = this.multiStep; + const fullBuildTimeout = this.fullBuildTimeout; + const requestTimeout = this.requestTimeout; + const hotUpdateChunkFilename = compiler.options.output.hotUpdateChunkFilename; + const hotUpdateMainFilename = compiler.options.output.hotUpdateMainFilename; + compiler.plugin("compilation", (compilation, params) => { + const hotUpdateChunkTemplate = compilation.hotUpdateChunkTemplate; + if(!hotUpdateChunkTemplate) return; + + const normalModuleFactory = params.normalModuleFactory; + + 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(module => { + const identifier = module.identifier(); + const hash = require("crypto").createHash("md5"); + module.updateHash(hash); + records.moduleHashs[identifier] = hash.digest("hex"); + }); + records.chunkHashs = {}; + this.chunks.forEach(chunk => { + records.chunkHashs[chunk.id] = chunk.hash; + }); + records.chunkModuleIds = {}; + this.chunks.forEach(chunk => { + records.chunkModuleIds[chunk.id] = chunk.mapModules(m => m.id); + }); + }); + let initialPass = false; + let recompilation = false; + compilation.plugin("after-hash", function() { + let records = this.records; + if(!records) { + initialPass = true; + return; + } + if(!records.hash) + initialPass = true; + const preHash = records.preHash || "x"; + const prepreHash = records.prepreHash || "x"; + if(preHash === this.hash) { + recompilation = true; + this.modifyHash(prepreHash); + return; + } + records.prepreHash = records.hash || "x"; + records.preHash = this.hash; + this.modifyHash(records.prepreHash); }); - records.chunkHashs = {}; - this.chunks.forEach(function(chunk) { - records.chunkHashs[chunk.id] = chunk.hash; + compilation.plugin("should-generate-chunk-assets", () => { + if(multiStep && !recompilation && !initialPass) + return false; }); - records.chunkModuleIds = {}; - this.chunks.forEach(function(chunk) { - records.chunkModuleIds[chunk.id] = chunk.mapModules(function(m) { - return m.id; - }); + compilation.plugin("need-additional-pass", () => { + if(multiStep && !recompilation && !initialPass) + return true; }); - }); - var initialPass = false; - var recompilation = false; - compilation.plugin("after-hash", function() { - var records = this.records; - if(!records) { - initialPass = true; - return; - } - if(!records.hash) - initialPass = true; - var preHash = records.preHash || "x"; - var prepreHash = records.prepreHash || "x"; - if(preHash === this.hash) { - recompilation = true; - this.modifyHash(prepreHash); - return; - } - records.prepreHash = records.hash || "x"; - records.preHash = this.hash; - this.modifyHash(records.prepreHash); - }); - compilation.plugin("should-generate-chunk-assets", function() { - if(multiStep && !recompilation && !initialPass) - return false; - }); - compilation.plugin("need-additional-pass", function() { - if(multiStep && !recompilation && !initialPass) - return true; - }); - compiler.plugin("additional-pass", function(callback) { - if(multiStep) - return setTimeout(callback, fullBuildTimeout); - return callback(); - }); - compilation.plugin("additional-chunk-assets", function() { - var records = this.records; - if(records.hash === this.hash) return; - if(!records.moduleHashs || !records.chunkHashs || !records.chunkModuleIds) return; - 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; + compiler.plugin("additional-pass", callback => { + if(multiStep) + return setTimeout(callback, fullBuildTimeout); + return callback(); }); - var hotUpdateMainContent = { - h: this.hash, - c: {} - }; - Object.keys(records.chunkHashs).forEach(function(chunkId) { - chunkId = isNaN(+chunkId) ? chunkId : +chunkId; - var currentChunk = this.chunks.find(chunk => chunk.id === chunkId); - if(currentChunk) { - var newModules = currentChunk.getModules().filter(function(module) { - return module.hotUpdate; - }); - var allModules = {}; - currentChunk.forEachModule(function(module) { - allModules[module.id] = true; - }); - var removedModules = records.chunkModuleIds[chunkId].filter(function(id) { - return !allModules[id]; - }); - if(newModules.length > 0 || removedModules.length > 0) { - var source = hotUpdateChunkTemplate.render(chunkId, newModules, removedModules, this.hash, this.moduleTemplate, this.dependencyTemplates); - var filename = this.getPath(hotUpdateChunkFilename, { - hash: records.hash, - chunk: currentChunk + compilation.plugin("additional-chunk-assets", function() { + const records = this.records; + if(records.hash === this.hash) return; + if(!records.moduleHashs || !records.chunkHashs || !records.chunkModuleIds) return; + this.modules.forEach(module => { + const identifier = module.identifier(); + let hash = require("crypto").createHash("md5"); + module.updateHash(hash); + hash = hash.digest("hex"); + module.hotUpdate = records.moduleHashs[identifier] !== hash; + }); + const hotUpdateMainContent = { + h: this.hash, + c: {}, + }; + Object.keys(records.chunkHashs).forEach(function(chunkId) { + chunkId = isNaN(+chunkId) ? chunkId : +chunkId; + const currentChunk = this.chunks.find(chunk => chunk.id === chunkId); + if(currentChunk) { + const newModules = currentChunk.getModules().filter(module => module.hotUpdate); + const allModules = {}; + currentChunk.forEachModule(module => { + allModules[module.id] = true; }); - this.additionalChunkAssets.push(filename); - this.assets[filename] = source; - hotUpdateMainContent.c[chunkId] = true; - currentChunk.files.push(filename); - this.applyPlugins("chunk-asset", currentChunk, filename); + const removedModules = records.chunkModuleIds[chunkId].filter(id => !allModules[id]); + if(newModules.length > 0 || removedModules.length > 0) { + const source = hotUpdateChunkTemplate.render(chunkId, newModules, removedModules, this.hash, this.moduleTemplate, this.dependencyTemplates); + const filename = this.getPath(hotUpdateChunkFilename, { + hash: records.hash, + chunk: currentChunk + }); + this.additionalChunkAssets.push(filename); + this.assets[filename] = source; + hotUpdateMainContent.c[chunkId] = true; + currentChunk.files.push(filename); + this.applyPlugins("chunk-asset", currentChunk, filename); + } + } else { + hotUpdateMainContent.c[chunkId] = false; } - } else { - hotUpdateMainContent.c[chunkId] = false; - } - }, this); - var source = new RawSource(JSON.stringify(hotUpdateMainContent)); - var filename = this.getPath(hotUpdateMainFilename, { - hash: records.hash + }, this); + const source = new RawSource(JSON.stringify(hotUpdateMainContent)); + const filename = this.getPath(hotUpdateMainFilename, { + hash: records.hash + }); + this.assets[filename] = source; }); - 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("hash", hash => { + hash.update("HotMainTemplateDecorator"); + }); - compilation.mainTemplate.plugin("require-extensions", function(source) { - var buf = [source]; - buf.push(""); - buf.push("// __webpack_hash__"); - buf.push(this.requireFn + ".h = function() { return hotCurrentHash; };"); - return this.asString(buf); - }); + compilation.mainTemplate.plugin("module-require", (_, chunk, hash, varModuleId) => { + return `hotCreateRequire(${varModuleId})`; + }); - 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(/\$requestTimeout\$/g, requestTimeout) - .replace(/\/\*foreachInstalledChunks\*\//g, chunk.chunks.length > 0 ? "for(var chunkId in installedChunks)" : "var chunkId = " + JSON.stringify(chunk.id) + ";") - ]); - }); + compilation.mainTemplate.plugin("require-extensions", function(source) { + const buf = [source]; + buf.push(""); + buf.push("// __webpack_hash__"); + buf.push(`${this.requireFn}.h = () => hotCurrentHash`); + return this.asString(buf); + }); - compilation.mainTemplate.plugin("global-hash", function() { - return true; - }); + 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(/\$requestTimeout\$/g, requestTimeout) + .replace(/\/\*foreachInstalledChunks\*\//g, chunk.chunks.length > 0 ? "for(const chunkId in installedChunks)" : `const chunkId = ${JSON.stringify(chunk.id)};`) + ]); + }); - compilation.mainTemplate.plugin("current-hash", function(_, length) { - if(isFinite(length)) - return "hotCurrentHash.substr(0, " + length + ")"; - else - return "hotCurrentHash"; - }); + compilation.mainTemplate.plugin("global-hash", () => true); - compilation.mainTemplate.plugin("module-obj", function(source, chunk, hash, varModuleId) { - return this.asString([ - source + ",", - "hot: hotCreateModule(" + varModuleId + "),", - "parents: (hotCurrentParentsTemp = hotCurrentParents, hotCurrentParents = [], hotCurrentParentsTemp),", - "children: []" - ]); - }); + compilation.mainTemplate.plugin("current-hash", (_, length) => { + if(isFinite(length)) + return `hotCurrentHash.substr(0, ${length})`; + else + return "hotCurrentHash"; + }); - params.normalModuleFactory.plugin("parser", function(parser, parserOptions) { - parser.plugin("expression __webpack_hash__", ParserHelpers.toConstantDependency("__webpack_require__.h()")); - parser.plugin("evaluate typeof __webpack_hash__", ParserHelpers.evaluateToString("string")); - parser.plugin("evaluate Identifier module.hot", function(expr) { - return ParserHelpers.evaluateToIdentifier("module.hot", !!this.state.compilation.hotUpdateChunkTemplate)(expr); + compilation.mainTemplate.plugin("module-obj", function(source, chunk, hash, varModuleId) { + return this.asString([ + `${source},`, + `hot: hotCreateModule(${varModuleId}),`, + "parents: (hotCurrentParentsTemp = hotCurrentParents, hotCurrentParents = [], hotCurrentParentsTemp),", + "children: []" + ]); }); - 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 = [], - requests = []; - if(arg.isString()) { - params = [arg]; - } else if(arg.isArray()) { - params = arg.items.filter(function(param) { - return param.isString(); - }); + + params.normalModuleFactory.plugin("parser", (parser, parserOptions) => { + parser.plugin("expression __webpack_hash__", ParserHelpers.toConstantDependency("__webpack_require__.h()")); + parser.plugin("evaluate typeof __webpack_hash__", ParserHelpers.evaluateToString("string")); + parser.plugin("evaluate Identifier module.hot", function(expr) { + return ParserHelpers.evaluateToIdentifier("module.hot", !!this.state.compilation.hotUpdateChunkTemplate)(expr); + }); + parser.plugin("call module.hot.accept", function(expr) { + if(!this.state.compilation.hotUpdateChunkTemplate) return false; + if(expr.arguments.length >= 1) { + const arg = this.evaluateExpression(expr.arguments[0]); + let params = []; + let requests = []; + if(arg.isString()) { + params = [arg]; + } else if(arg.isArray()) { + params = arg.items.filter(param => param.isString()); + } + if(params.length > 0) { + params.forEach((param, idx) => { + const request = param.string; + const dep = new ModuleHotAcceptDependency(request, param.range); + dep.optional = true; + dep.loc = Object.create(expr.loc); + dep.loc.index = idx; + this.state.module.addDependency(dep); + requests.push(request); + }); + if(expr.arguments.length > 1) + this.applyPluginsBailResult("hot accept callback", expr.arguments[1], requests); + else + this.applyPluginsBailResult("hot accept without callback", expr, requests); + } } - if(params.length > 0) { - params.forEach(function(param, idx) { - var request = param.string; - var dep = new ModuleHotAcceptDependency(request, param.range); + }); + parser.plugin("call module.hot.decline", function(expr) { + if(!this.state.compilation.hotUpdateChunkTemplate) return false; + if(expr.arguments.length === 1) { + const arg = this.evaluateExpression(expr.arguments[0]); + let params = []; + if(arg.isString()) { + params = [arg]; + } else if(arg.isArray()) { + params = arg.items.filter(param => param.isString()); + } + params.forEach((param, idx) => { + const dep = new ModuleHotDeclineDependency(param.string, param.range); dep.optional = true; dep.loc = Object.create(expr.loc); dep.loc.index = idx; this.state.module.addDependency(dep); - requests.push(request); - }.bind(this)); - if(expr.arguments.length > 1) - this.applyPluginsBailResult("hot accept callback", expr.arguments[1], requests); - else - this.applyPluginsBailResult("hot accept without callback", expr, requests); - } - } - }); - parser.plugin("call module.hot.decline", 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]; - } else if(arg.isArray()) { - params = arg.items.filter(function(param) { - return param.isString(); }); } - params.forEach(function(param, idx) { - var dep = new ModuleHotDeclineDependency(param.string, param.range); - dep.optional = true; - dep.loc = Object.create(expr.loc); - dep.loc.index = idx; - this.state.module.addDependency(dep); - }.bind(this)); - } + }); + parser.plugin("expression module.hot", ParserHelpers.skipTraversal); }); - parser.plugin("expression module.hot", ParserHelpers.skipTraversal); }); - }); + } }; -var hotInitCode = Template.getFunctionContent(require("./HotModuleReplacement.runtime.js")); +const hotInitCode = Template.getFunctionContent(require("./HotModuleReplacement.runtime.js")); From a6dea39888a322f21bd18338a872ffdb65d63091 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Sun, 18 Jun 2017 17:49:07 +0200 Subject: [PATCH 2/5] change to var in generated code --- lib/HotModuleReplacementPlugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/HotModuleReplacementPlugin.js b/lib/HotModuleReplacementPlugin.js index 64c5f9cc3a3..15850dd2e91 100644 --- a/lib/HotModuleReplacementPlugin.js +++ b/lib/HotModuleReplacementPlugin.js @@ -166,7 +166,7 @@ module.exports = class HotModuleReplacementPlugin { .replace(/\$require\$/g, this.requireFn) .replace(/\$hash\$/g, JSON.stringify(hash)) .replace(/\$requestTimeout\$/g, requestTimeout) - .replace(/\/\*foreachInstalledChunks\*\//g, chunk.chunks.length > 0 ? "for(const chunkId in installedChunks)" : `const chunkId = ${JSON.stringify(chunk.id)};`) + .replace(/\/\*foreachInstalledChunks\*\//g, chunk.chunks.length > 0 ? "for(var chunkId in installedChunks)" : `var chunkId = ${JSON.stringify(chunk.id)};`) ]); }); From c3d8fdffc8d3708f656d3155e31e616be9f82dcd Mon Sep 17 00:00:00 2001 From: Kyle Truong Date: Sat, 1 Jul 2017 12:13:33 -0400 Subject: [PATCH 3/5] - Fix bug while refactoring HotModuleReplacementPlugin as suggested in PR --- lib/HotModuleReplacementPlugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/HotModuleReplacementPlugin.js b/lib/HotModuleReplacementPlugin.js index 15850dd2e91..c0293bce068 100644 --- a/lib/HotModuleReplacementPlugin.js +++ b/lib/HotModuleReplacementPlugin.js @@ -153,7 +153,7 @@ module.exports = class HotModuleReplacementPlugin { const buf = [source]; buf.push(""); buf.push("// __webpack_hash__"); - buf.push(`${this.requireFn}.h = () => hotCurrentHash`); + buf.push(this.requireFn + ".h = function() { return hotCurrentHash; };"); return this.asString(buf); }); From be13bf9c7b196801e2cd585ec1da60a85c0338ee Mon Sep 17 00:00:00 2001 From: Kyle Truong Date: Sat, 1 Jul 2017 12:27:17 -0400 Subject: [PATCH 4/5] - Fix ES6 refactoring lib/HotModuleReplacementPlugin according to codacy coding quality review --- lib/HotModuleReplacementPlugin.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/HotModuleReplacementPlugin.js b/lib/HotModuleReplacementPlugin.js index c0293bce068..b8294a7c9ae 100644 --- a/lib/HotModuleReplacementPlugin.js +++ b/lib/HotModuleReplacementPlugin.js @@ -163,10 +163,10 @@ module.exports = class HotModuleReplacementPlugin { source, "", hotInitCode - .replace(/\$require\$/g, this.requireFn) - .replace(/\$hash\$/g, JSON.stringify(hash)) - .replace(/\$requestTimeout\$/g, requestTimeout) - .replace(/\/\*foreachInstalledChunks\*\//g, chunk.chunks.length > 0 ? "for(var chunkId in installedChunks)" : `var chunkId = ${JSON.stringify(chunk.id)};`) + .replace(/\$require\$/g, this.requireFn) + .replace(/\$hash\$/g, JSON.stringify(hash)) + .replace(/\$requestTimeout\$/g, requestTimeout) + .replace(/\/\*foreachInstalledChunks\*\//g, chunk.chunks.length > 0 ? "for(var chunkId in installedChunks)" : `var chunkId = ${JSON.stringify(chunk.id)};`) ]); }); From 5202091ff833ae49aa7e64f82a18324425586230 Mon Sep 17 00:00:00 2001 From: Kyle Truong Date: Sat, 1 Jul 2017 12:55:45 -0400 Subject: [PATCH 5/5] - Undo previous commit because beautify error --- lib/HotModuleReplacementPlugin.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/HotModuleReplacementPlugin.js b/lib/HotModuleReplacementPlugin.js index b8294a7c9ae..c0293bce068 100644 --- a/lib/HotModuleReplacementPlugin.js +++ b/lib/HotModuleReplacementPlugin.js @@ -163,10 +163,10 @@ module.exports = class HotModuleReplacementPlugin { source, "", hotInitCode - .replace(/\$require\$/g, this.requireFn) - .replace(/\$hash\$/g, JSON.stringify(hash)) - .replace(/\$requestTimeout\$/g, requestTimeout) - .replace(/\/\*foreachInstalledChunks\*\//g, chunk.chunks.length > 0 ? "for(var chunkId in installedChunks)" : `var chunkId = ${JSON.stringify(chunk.id)};`) + .replace(/\$require\$/g, this.requireFn) + .replace(/\$hash\$/g, JSON.stringify(hash)) + .replace(/\$requestTimeout\$/g, requestTimeout) + .replace(/\/\*foreachInstalledChunks\*\//g, chunk.chunks.length > 0 ? "for(var chunkId in installedChunks)" : `var chunkId = ${JSON.stringify(chunk.id)};`) ]); });