From 4c3be660decdbb4254cceade2ee7c7111d4778ee Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Thu, 6 Sep 2018 16:38:21 +0300 Subject: [PATCH 01/10] refactor: add --env.hmr options to templates --- templates/webpack.angular.js | 5 +++++ templates/webpack.javascript.js | 6 ++++++ templates/webpack.typescript.js | 6 ++++++ 3 files changed, 17 insertions(+) diff --git a/templates/webpack.angular.js b/templates/webpack.angular.js index 8fe26743..aedf95b1 100644 --- a/templates/webpack.angular.js +++ b/templates/webpack.angular.js @@ -42,6 +42,7 @@ module.exports = env => { uglify, // --env.uglify report, // --env.report sourceMap, // --env.sourceMap + hmr, // --env.hmr } = env; const appFullPath = resolve(projectRoot, appPath); @@ -265,5 +266,9 @@ module.exports = env => { })); } + if (hmr) { + config.plugins.push(new HotModuleReplacementPlugin()); + } + return config; }; diff --git a/templates/webpack.javascript.js b/templates/webpack.javascript.js index 2795ee65..3566ee2b 100644 --- a/templates/webpack.javascript.js +++ b/templates/webpack.javascript.js @@ -40,6 +40,7 @@ module.exports = env => { uglify, // --env.uglify report, // --env.report sourceMap, // --env.sourceMap + hmr, // --env.hmr } = env; const appFullPath = resolve(projectRoot, appPath); @@ -227,5 +228,10 @@ module.exports = env => { })); } + if (hmr) { + config.plugins.push(new HotModuleReplacementPlugin()); + } + + return config; }; diff --git a/templates/webpack.typescript.js b/templates/webpack.typescript.js index 5a7cbf9a..d7383fa0 100644 --- a/templates/webpack.typescript.js +++ b/templates/webpack.typescript.js @@ -40,6 +40,7 @@ module.exports = env => { uglify, // --env.uglify report, // --env.report sourceMap, // --env.sourceMap + hmr, // --env.hmr } = env; const appFullPath = resolve(projectRoot, appPath); @@ -237,5 +238,10 @@ module.exports = env => { })); } + if (hmr) { + config.plugins.push(new HotModuleReplacementPlugin()); + } + + return config; }; From fa6158d33fe53848169da0371555b8d9897fac40 Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Fri, 7 Sep 2018 09:15:28 +0300 Subject: [PATCH 02/10] refactor: pass hmr option from hooks to templates --- lib/before-prepareJS.js | 3 ++- lib/before-watch.js | 37 +++++++++++++++++++------------------ 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/lib/before-prepareJS.js b/lib/before-prepareJS.js index 62fffbcf..a1b9a6dd 100644 --- a/lib/before-prepareJS.js +++ b/lib/before-prepareJS.js @@ -1,7 +1,8 @@ const { runWebpackCompiler } = require("./compiler"); -module.exports = function ($logger, $liveSyncService, hookArgs) { +module.exports = function ($logger, $liveSyncService, $options, hookArgs) { const env = hookArgs.config.env || {}; + env.hmr = !!$options.hmr; const platform = hookArgs.config.platform; const appFilesUpdaterOptions = hookArgs.config.appFilesUpdaterOptions; const config = { diff --git a/lib/before-watch.js b/lib/before-watch.js index f3d01e87..e732a65c 100644 --- a/lib/before-watch.js +++ b/lib/before-watch.js @@ -1,22 +1,23 @@ const { runWebpackCompiler } = require("./compiler"); -module.exports = function ($logger, $liveSyncService, hookArgs) { - if (hookArgs.config) { - const appFilesUpdaterOptions = hookArgs.config.appFilesUpdaterOptions; - if (appFilesUpdaterOptions.bundle) { - const platforms = hookArgs.config.platforms; - return Promise.all(platforms.map(platform => { - const env = hookArgs.config.env || {}; - const config = { - env, - platform, - bundle: appFilesUpdaterOptions.bundle, - release: appFilesUpdaterOptions.release, - watch: true - }; +module.exports = function ($logger, $liveSyncService, $options, hookArgs) { + if (hookArgs.config) { + const appFilesUpdaterOptions = hookArgs.config.appFilesUpdaterOptions; + if (appFilesUpdaterOptions.bundle) { + const platforms = hookArgs.config.platforms; + return Promise.all(platforms.map(platform => { + const env = hookArgs.config.env || {}; + env.hmr = !!$options.hmr; + const config = { + env, + platform, + bundle: appFilesUpdaterOptions.bundle, + release: appFilesUpdaterOptions.release, + watch: true + }; - return runWebpackCompiler(config, hookArgs.projectData, $logger, $liveSyncService, hookArgs); - })); - } - } + return runWebpackCompiler(config, hookArgs.projectData, $logger, $liveSyncService, hookArgs); + })); + } + } } From 9896d7603d0f089094353061f22161715f66b8b2 Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Fri, 7 Sep 2018 09:16:34 +0300 Subject: [PATCH 03/10] refactor: accept hot modules from xml loader --- xml-namespace-loader.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xml-namespace-loader.js b/xml-namespace-loader.js index 9e97a941..85e09fdd 100644 --- a/xml-namespace-loader.js +++ b/xml-namespace-loader.js @@ -82,7 +82,8 @@ module.exports = function (source) { .replace(/\u2028/g, '\\u2028') .replace(/\u2029/g, '\\u2029'); - const wrapped = `${moduleRegisters}\nmodule.exports = ${json}`; + const hmr = `module.hot && module.hot.accept();`; + const wrapped = `${moduleRegisters}\nmodule.exports = ${json};${hmr}`; this.callback(null, wrapped); } From 46dfb2bcb91e339ba8d0e7fc500882bbd5085b62 Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Fri, 7 Sep 2018 09:18:55 +0300 Subject: [PATCH 04/10] fix-next: properly instantiate HMR plugin --- templates/webpack.angular.js | 2 +- templates/webpack.javascript.js | 2 +- templates/webpack.typescript.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/webpack.angular.js b/templates/webpack.angular.js index aedf95b1..cf7e9dc5 100644 --- a/templates/webpack.angular.js +++ b/templates/webpack.angular.js @@ -267,7 +267,7 @@ module.exports = env => { } if (hmr) { - config.plugins.push(new HotModuleReplacementPlugin()); + config.plugins.push(new webpack.HotModuleReplacementPlugin()); } return config; diff --git a/templates/webpack.javascript.js b/templates/webpack.javascript.js index 3566ee2b..cc98d58a 100644 --- a/templates/webpack.javascript.js +++ b/templates/webpack.javascript.js @@ -229,7 +229,7 @@ module.exports = env => { } if (hmr) { - config.plugins.push(new HotModuleReplacementPlugin()); + config.plugins.push(new webpack.HotModuleReplacementPlugin()); } diff --git a/templates/webpack.typescript.js b/templates/webpack.typescript.js index d7383fa0..26890cde 100644 --- a/templates/webpack.typescript.js +++ b/templates/webpack.typescript.js @@ -239,7 +239,7 @@ module.exports = env => { } if (hmr) { - config.plugins.push(new HotModuleReplacementPlugin()); + config.plugins.push(new webpack.HotModuleReplacementPlugin()); } From e6b96bb7fd39038445b994294dfdc6b82f3679da Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Fri, 7 Sep 2018 17:27:44 +0300 Subject: [PATCH 05/10] fix: rewrite errored hot update chunk to prevent the HMR process from failing --- plugins/WatchStateLoggerPlugin.ts | 55 +++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/plugins/WatchStateLoggerPlugin.ts b/plugins/WatchStateLoggerPlugin.ts index 690b24bf..2634f74f 100644 --- a/plugins/WatchStateLoggerPlugin.ts +++ b/plugins/WatchStateLoggerPlugin.ts @@ -1,4 +1,5 @@ import { join } from "path"; +import { writeFileSync } from "fs"; export enum messages { compilationComplete = "Webpack compilation complete.", @@ -24,6 +25,7 @@ export class WatchStateLoggerPlugin { }); compiler.hooks.afterEmit.tapAsync("WatchStateLoggerPlugin", function (compilation, callback) { callback(); + if (plugin.isRunningWatching) { console.log(messages.startWatching); } else { @@ -32,12 +34,61 @@ export class WatchStateLoggerPlugin { const emittedFiles = Object .keys(compilation.assets) - .filter(assetKey => compilation.assets[assetKey].emitted) + .filter(assetKey => compilation.assets[assetKey].emitted); + + if (compilation.errors.length > 0) { + WatchStateLoggerPlugin.rewriteHotUpdateChunk(compiler, emittedFiles); + } + + // provide fake paths to the {N} CLI - relative to the 'app' folder + // in order to trigger the livesync process + const emittedFilesFakePaths = emittedFiles .map(file => join(compiler.context, file)); process.send && process.send(messages.compilationComplete, error => null); // Send emitted files so they can be LiveSynced if need be - process.send && process.send({ emittedFiles }, error => null); + process.send && process.send({ emittedFiles: emittedFilesFakePaths }, error => null); }); } + + /** + * Rewrite an errored chunk to make the hot module replace successful. + * @param compiler the webpack compiler + * @param emittedFiles the emitted files from the current compilation + */ + private static rewriteHotUpdateChunk(compiler, emittedFiles: string[]) { + const chunk = this.findHotUpdateChunk(emittedFiles); + if (!chunk) { + return; + } + + const { name } = this.parseHotUpdateChunkName(chunk); + if (!name) { + return; + } + + const absolutePath = join(compiler.outputPath, chunk); + + const newContent = `webpackHotUpdate('${name}', {});`; + writeFileSync(absolutePath, newContent); + } + + private static findHotUpdateChunk(emittedFiles: string[]) { + return emittedFiles.find(file => file.endsWith("hot-update.js")); + } + + /** + * Parse the filename of the hot update chunk. + * @param name bundle.deccb264c01d6d42416c.hot-update.js + * @returns { name: string, hash: string } { name: 'bundle', hash: 'deccb264c01d6d42416c' } + */ + private static parseHotUpdateChunkName(name) { + const matcher = /^(.+)\.(.+)\.hot-update/gm; + const matches = matcher.exec(name); + + return { + name: matches[1] || "", + hash: matches[2] || "", + }; + } } From 7432bb90cac16799ab0f4a0302c7ef1069db061d Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Fri, 7 Sep 2018 17:28:29 +0300 Subject: [PATCH 06/10] refactor(bundle-config-loader): register HMR logic in entry file (app.js) --- bundle-config-loader.js | 11 ++++ hot.js | 140 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+) create mode 100644 hot.js diff --git a/bundle-config-loader.js b/bundle-config-loader.js index cf3d70fb..343e25b7 100644 --- a/bundle-config-loader.js +++ b/bundle-config-loader.js @@ -8,7 +8,18 @@ module.exports = function (source) { `; if (!angular && registerModules) { + const hmr = ` + if (module.hot) { + global.__hmrLivesyncBackup = global.__onLiveSync; + global.__onLiveSync = function () { + console.log("LiveSyncing..."); + require("nativescript-dev-webpack/hot")("", {}); + }; + } + `; + source = ` + ${hmr} const context = require.context("~/", true, ${registerModules}); global.registerWebpackModules(context); ${source} diff --git a/hot.js b/hot.js new file mode 100644 index 00000000..90c00665 --- /dev/null +++ b/hot.js @@ -0,0 +1,140 @@ +const log = console; +const refresh = 'Please refresh the page.'; +const hotOptions = { + ignoreUnaccepted: true, + ignoreDeclined: true, + ignoreErrored: true, + onUnaccepted(data) { + const chain = [].concat(data.chain); + const last = chain[chain.length - 1]; + + if (last === 0) { + chain.pop(); + } + + log.warn(`Ignored an update to unaccepted module ${chain.join(' ➭ ')}`); + }, + onDeclined(data) { + log.warn(`Ignored an update to declined module ${data.chain.join(' ➭ ')}`); + }, + onErrored(data) { + log.warn( + `Ignored an error while updating module ${data.moduleId} <${data.type}>` + ); + log.warn(data.error); + }, +}; + +let lastHash; + +function upToDate() { + return lastHash.indexOf(__webpack_hash__) >= 0; +} + +function result(modules, appliedModules) { + const unaccepted = modules.filter( + (moduleId) => appliedModules && appliedModules.indexOf(moduleId) < 0 + ); + + if (unaccepted.length > 0) { + let message = 'The following modules could not be updated:'; + + for (const moduleId of unaccepted) { + message += `\n ⦻ ${moduleId}`; + } + log.warn(message); + } + + if (!(appliedModules || []).length) { + console.info('No Modules Updated.'); + } else { + const message = ['The following modules were updated:']; + + for (const moduleId of appliedModules) { + message.push(` ↻ ${moduleId}`); + } + + console.info(message.join('\n')); + + const numberIds = appliedModules.every( + (moduleId) => typeof moduleId === 'number' + ); + if (numberIds) { + console.info( + 'Please consider using the NamedModulesPlugin for module names.' + ); + } + } +} + +function check(options) { + module.hot + .check() + .then((modules) => { + if (!modules) { + log.warn( + `Cannot find update. The server may have been restarted. ${refresh}` + ); + return null; + } + + return module.hot + .apply(hotOptions) + .then((appliedModules) => { + if (!upToDate()) { + log.warn("Hashes don't match. Ignoring second update..."); + // check(options); + } + + result(modules, appliedModules); + + if (upToDate()) { + console.info('App is up to date.'); + } + }) + .catch((err) => { + const status = module.hot.status(); + if (['abort', 'fail'].indexOf(status) >= 0) { + log.warn(`Cannot apply update. ${refresh}`); + log.warn(err.stack || err.message); + if (options.reload) { + window.location.reload(); + } + } else { + log.warn(`Update failed: ${err.stack}` || err.message); + } + }); + }) + .catch((err) => { + const status = module.hot.status(); + if (['abort', 'fail'].indexOf(status) >= 0) { + log.warn(`Cannot check for update. ${refresh}`); + log.warn(err.stack || err.message); + } else { + log.warn(`Update check failed: ${err.stack}` || err.message); + } + }); +} + +if (module.hot) { + console.info('Hot Module Replacement Enabled. Waiting for signal.'); +} else { + console.error('Hot Module Replacement is disabled.'); +} + +module.exports = function update(currentHash, options) { + lastHash = currentHash; + if (!upToDate()) { + const status = module.hot.status(); + + if (status === 'idle') { + console.info('Checking for updates to the bundle.'); + check(options); + } else if (['abort', 'fail'].indexOf(status) >= 0) { + log.warn( + `Cannot apply update. A previous update ${status}ed. ${refresh}` + ); + } + } +}; + From e78a68815fa96b71e0084a335b2f261254e2f917 Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Fri, 7 Sep 2018 17:29:59 +0300 Subject: [PATCH 07/10] refactor: insert HMR logic in all app pages --- page-hot-loader.js | 14 ++++++++++++++ templates/webpack.javascript.js | 7 ++++++- templates/webpack.typescript.js | 5 +++++ 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 page-hot-loader.js diff --git a/page-hot-loader.js b/page-hot-loader.js new file mode 100644 index 00000000..d14b93e0 --- /dev/null +++ b/page-hot-loader.js @@ -0,0 +1,14 @@ +module.exports = function (source) { + const hmr = ` + if (module.hot) { + module.hot.accept(); + module.hot.dispose(() => { + setTimeout(() => { + global.__hmrLivesyncBackup(); + }); + }) + } + `; + + return `${source};${hmr}` +}; diff --git a/templates/webpack.javascript.js b/templates/webpack.javascript.js index cc98d58a..20619d41 100644 --- a/templates/webpack.javascript.js +++ b/templates/webpack.javascript.js @@ -164,7 +164,12 @@ module.exports = env => { { loader: "css-loader", options: { minimize: false, url: false } }, "sass-loader" ] - } + }, + + { + test: /-page\.js$/, + use: "nativescript-dev-webpack/page-hot-loader" + }, ] }, plugins: [ diff --git a/templates/webpack.typescript.js b/templates/webpack.typescript.js index 26890cde..2f4f681a 100644 --- a/templates/webpack.typescript.js +++ b/templates/webpack.typescript.js @@ -175,6 +175,11 @@ module.exports = env => { options: { configFileName: "tsconfig.tns.json" }, } }, + + { + test: /-page\.ts$/, + use: "nativescript-dev-webpack/page-hot-loader" + }, ] }, plugins: [ From b254d61cb3b0ecc5cb03f853620d93502afeba7a Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Fri, 7 Sep 2018 17:43:46 +0300 Subject: [PATCH 08/10] refactor: extract hmr accept/dispose logic in a helper --- hot-loader-helper.js | 11 +++++++++++ page-hot-loader.js | 15 +++------------ xml-namespace-loader.js | 4 ++-- 3 files changed, 16 insertions(+), 14 deletions(-) create mode 100644 hot-loader-helper.js diff --git a/hot-loader-helper.js b/hot-loader-helper.js new file mode 100644 index 00000000..cc5dcd76 --- /dev/null +++ b/hot-loader-helper.js @@ -0,0 +1,11 @@ +module.exports.reload = ` + if (module.hot) { + module.hot.accept(); + module.hot.dispose(() => { + setTimeout(() => { + global.__hmrLivesyncBackup(); + }); + }) + } +`; + diff --git a/page-hot-loader.js b/page-hot-loader.js index d14b93e0..3b4f19c2 100644 --- a/page-hot-loader.js +++ b/page-hot-loader.js @@ -1,14 +1,5 @@ -module.exports = function (source) { - const hmr = ` - if (module.hot) { - module.hot.accept(); - module.hot.dispose(() => { - setTimeout(() => { - global.__hmrLivesyncBackup(); - }); - }) - } - `; +const { reload } = require("./hot-loader-helper"); - return `${source};${hmr}` +module.exports = function (source) { + return `${source};${reload}`; }; diff --git a/xml-namespace-loader.js b/xml-namespace-loader.js index 85e09fdd..adfd1574 100644 --- a/xml-namespace-loader.js +++ b/xml-namespace-loader.js @@ -1,5 +1,6 @@ const { parse, relative, join, basename, extname } = require("path"); const { convertSlashesInPath } = require("./projectHelpers"); +const { reload } = require("./hot-loader-helper"); module.exports = function (source) { this.value = source; @@ -82,8 +83,7 @@ module.exports = function (source) { .replace(/\u2028/g, '\\u2028') .replace(/\u2029/g, '\\u2029'); - const hmr = `module.hot && module.hot.accept();`; - const wrapped = `${moduleRegisters}\nmodule.exports = ${json};${hmr}`; + const wrapped = `${moduleRegisters}\nmodule.exports = ${json};${reload}`; this.callback(null, wrapped); } From 61042626534e0761bd7d6d9a275cf7d8cc3441d2 Mon Sep 17 00:00:00 2001 From: Vasil Trifonov Date: Fri, 7 Sep 2018 18:43:58 +0300 Subject: [PATCH 09/10] Added hot loaders for markup and style --- markup-hot-loader.js | 5 +++++ style-hot-loader.js | 5 +++++ templates/webpack.javascript.js | 20 +++++++++++++++----- templates/webpack.typescript.js | 20 +++++++++++++++----- xml-namespace-loader.js | 3 +-- 5 files changed, 41 insertions(+), 12 deletions(-) create mode 100644 markup-hot-loader.js create mode 100644 style-hot-loader.js diff --git a/markup-hot-loader.js b/markup-hot-loader.js new file mode 100644 index 00000000..3b4f19c2 --- /dev/null +++ b/markup-hot-loader.js @@ -0,0 +1,5 @@ +const { reload } = require("./hot-loader-helper"); + +module.exports = function (source) { + return `${source};${reload}`; +}; diff --git a/style-hot-loader.js b/style-hot-loader.js new file mode 100644 index 00000000..3b4f19c2 --- /dev/null +++ b/style-hot-loader.js @@ -0,0 +1,5 @@ +const { reload } = require("./hot-loader-helper"); + +module.exports = function (source) { + return `${source};${reload}`; +}; diff --git a/templates/webpack.javascript.js b/templates/webpack.javascript.js index 20619d41..da5748b1 100644 --- a/templates/webpack.javascript.js +++ b/templates/webpack.javascript.js @@ -151,6 +151,21 @@ module.exports = env => { ].filter(loader => !!loader) }, + { + test: /-page\.js$/, + use: "nativescript-dev-webpack/page-hot-loader" + }, + + { + test: /\.(css|scss)$/, + use: "nativescript-dev-webpack/style-hot-loader" + }, + + { + test: /\.(html|xml)$/, + use: "nativescript-dev-webpack/markup-hot-loader" + }, + { test: /\.(html|xml)$/, use: "nativescript-dev-webpack/xml-namespace-loader"}, { @@ -165,11 +180,6 @@ module.exports = env => { "sass-loader" ] }, - - { - test: /-page\.js$/, - use: "nativescript-dev-webpack/page-hot-loader" - }, ] }, plugins: [ diff --git a/templates/webpack.typescript.js b/templates/webpack.typescript.js index 2f4f681a..e3673cde 100644 --- a/templates/webpack.typescript.js +++ b/templates/webpack.typescript.js @@ -153,6 +153,21 @@ module.exports = env => { ].filter(loader => !!loader) }, + { + test: /-page\.ts$/, + use: "nativescript-dev-webpack/page-hot-loader" + }, + + { + test: /\.(css|scss)$/, + use: "nativescript-dev-webpack/style-hot-loader" + }, + + { + test: /\.(html|xml)$/, + use: "nativescript-dev-webpack/markup-hot-loader" + }, + { test: /\.(html|xml)$/, use: "nativescript-dev-webpack/xml-namespace-loader"}, { @@ -175,11 +190,6 @@ module.exports = env => { options: { configFileName: "tsconfig.tns.json" }, } }, - - { - test: /-page\.ts$/, - use: "nativescript-dev-webpack/page-hot-loader" - }, ] }, plugins: [ diff --git a/xml-namespace-loader.js b/xml-namespace-loader.js index adfd1574..2cb83c3a 100644 --- a/xml-namespace-loader.js +++ b/xml-namespace-loader.js @@ -1,6 +1,5 @@ const { parse, relative, join, basename, extname } = require("path"); const { convertSlashesInPath } = require("./projectHelpers"); -const { reload } = require("./hot-loader-helper"); module.exports = function (source) { this.value = source; @@ -83,7 +82,7 @@ module.exports = function (source) { .replace(/\u2028/g, '\\u2028') .replace(/\u2029/g, '\\u2029'); - const wrapped = `${moduleRegisters}\nmodule.exports = ${json};${reload}`; + const wrapped = `${moduleRegisters}\nmodule.exports = ${json};`; this.callback(null, wrapped); } From 4ddb2c658526a79bc303b4dccf325d1496a4df72 Mon Sep 17 00:00:00 2001 From: Vasil Trifonov Date: Fri, 7 Sep 2018 20:10:50 +0300 Subject: [PATCH 10/10] Update the chunk rewrite to remove only the modules with errors --- plugins/WatchStateLoggerPlugin.ts | 40 +++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/plugins/WatchStateLoggerPlugin.ts b/plugins/WatchStateLoggerPlugin.ts index 2634f74f..a3fb0813 100644 --- a/plugins/WatchStateLoggerPlugin.ts +++ b/plugins/WatchStateLoggerPlugin.ts @@ -1,5 +1,5 @@ import { join } from "path"; -import { writeFileSync } from "fs"; +import { writeFileSync, readFileSync } from "fs"; export enum messages { compilationComplete = "Webpack compilation complete.", @@ -37,7 +37,7 @@ export class WatchStateLoggerPlugin { .filter(assetKey => compilation.assets[assetKey].emitted); if (compilation.errors.length > 0) { - WatchStateLoggerPlugin.rewriteHotUpdateChunk(compiler, emittedFiles); + WatchStateLoggerPlugin.rewriteHotUpdateChunk(compiler, compilation, emittedFiles); } // provide fake paths to the {N} CLI - relative to the 'app' folder @@ -56,7 +56,7 @@ export class WatchStateLoggerPlugin { * @param compiler the webpack compiler * @param emittedFiles the emitted files from the current compilation */ - private static rewriteHotUpdateChunk(compiler, emittedFiles: string[]) { + private static rewriteHotUpdateChunk(compiler, compilation, emittedFiles: string[]) { const chunk = this.findHotUpdateChunk(emittedFiles); if (!chunk) { return; @@ -69,7 +69,7 @@ export class WatchStateLoggerPlugin { const absolutePath = join(compiler.outputPath, chunk); - const newContent = `webpackHotUpdate('${name}', {});`; + const newContent = this.getWebpackHotUpdateReplacementContent(compilation.errors, absolutePath, name); writeFileSync(absolutePath, newContent); } @@ -77,6 +77,38 @@ export class WatchStateLoggerPlugin { return emittedFiles.find(file => file.endsWith("hot-update.js")); } + /** + * Gets only the modules object after 'webpackHotUpdate("bundle",' in the chunk + */ + private static getModulesObjectFromChunk(chunkPath) { + let content = readFileSync(chunkPath, "utf8") + const startIndex = content.indexOf(",") + 1; + let endIndex = content.length - 1; + if(content.endsWith(';')) { + endIndex--; + } + return content.substring(startIndex, endIndex); + } + + /** + * Gets the webpackHotUpdate call with updated modules not to include the ones with errors + */ + private static getWebpackHotUpdateReplacementContent(compilationErrors, filePath, moduleName) { + const errorModuleIds = compilationErrors.filter(x => x.module).map(x => x.module.id); + if (!errorModuleIds || errorModuleIds.length == 0) { + // could not determine error modiles so discard everything + return `webpackHotUpdate('${moduleName}', {});`; + } + const updatedModules = this.getModulesObjectFromChunk(filePath); + + // we need to filter the modules with a function in the file as it is a relaxed JSON not valid to be parsed and manipulated + return `const filter = function(updatedModules, modules) { + modules.forEach(moduleId => delete updatedModules[moduleId]); + return updatedModules; + } + webpackHotUpdate('${moduleName}', filter(${updatedModules}, ${JSON.stringify(errorModuleIds)}));`; + } + /** * Parse the filename of the hot update chunk. * @param name bundle.deccb264c01d6d42416c.hot-update.js