From b088448d3ed1baf6d435b46f80727ae340109c9a Mon Sep 17 00:00:00 2001 From: Kristian Dimitrov Date: Tue, 2 Oct 2018 16:14:23 +0300 Subject: [PATCH 1/2] feat: send data for fallback if hmr apply fails --- hot.js | 39 ++++++++++++++------------- lib/compiler.js | 13 ++++++--- lib/utils.js | 44 ++++++++++++++++++++++++++++++- plugins/WatchStateLoggerPlugin.ts | 40 ++-------------------------- 4 files changed, 75 insertions(+), 61 deletions(-) diff --git a/hot.js b/hot.js index ec976196..2d6f5f50 100644 --- a/hot.js +++ b/hot.js @@ -6,9 +6,9 @@ const log = { }; const refresh = 'Application needs to be restarted in order to apply the changes.'; const hotOptions = { - ignoreUnaccepted: true, - ignoreDeclined: true, - ignoreErrored: true, + ignoreUnaccepted: false, + ignoreDeclined: false, + ignoreErrored: false, onUnaccepted(data) { const chain = [].concat(data.chain); const last = chain[chain.length - 1]; @@ -30,10 +30,11 @@ const hotOptions = { }, }; -let lastHash; +let nextHash; +let currentHash; function upToDate() { - return lastHash.indexOf(__webpack_hash__) >= 0; + return nextHash.indexOf(__webpack_hash__) >= 0; } function result(modules, appliedModules) { @@ -90,13 +91,13 @@ function check(options) { result(modules, appliedModules); if (upToDate()) { - log.info('App is up to date.'); + log.info(`Successfully applied update with hmr hash ${currentHash}. 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(`Cannot apply update with hmr hash ${currentHash}.`); log.warn(err.stack || err.message); if (options.reload) { window.location.reload(); @@ -123,13 +124,13 @@ if (module.hot) { log.error('Hot Module Replacement is disabled.'); } -function update(currentHash, options) { - lastHash = currentHash; +function update(latestHash, options) { + nextHash = latestHash; if (!upToDate()) { const status = module.hot.status(); if (status === 'idle') { - log.info('Checking for updates to the bundle.'); + log.info(`Checking for updates to the bundle with hmr hash ${currentHash}.`); check(options); } else if (['abort', 'fail'].indexOf(status) >= 0) { log.warn( @@ -139,24 +140,24 @@ function update(currentHash, options) { } }; -function getCurrentHash(currentHash, getFileContent) { - const file = getFileContent(`${currentHash}.hot-update.json`); +function getNextHash(hash, getFileContent) { + const file = getFileContent(`${hash}.hot-update.json`); return file.readText().then(hotUpdateContent => { if(hotUpdateContent) { const manifest = JSON.parse(hotUpdateContent); const newHash = manifest.h; - return getCurrentHash(newHash, getFileContent); + return getNextHash(newHash, getFileContent); } else { - return Promise.resolve(currentHash); + return Promise.resolve(hash); } }).catch(error => Promise.reject(error)); } module.exports = function checkState(initialHash, getFileContent) { - getCurrentHash(initialHash, getFileContent).then(currentHash => { - if(currentHash != initialHash) { - update(currentHash, {}); + currentHash = initialHash; + getNextHash(initialHash, getFileContent).then(nextHash => { + if(nextHash != initialHash) { + update(nextHash, {}); } }) -} - +} \ No newline at end of file diff --git a/lib/compiler.js b/lib/compiler.js index 84145428..7d79e628 100644 --- a/lib/compiler.js +++ b/lib/compiler.js @@ -5,7 +5,7 @@ const { existsSync } = require("fs"); const readline = require("readline"); const { messages } = require("../plugins/WatchStateLoggerPlugin"); -const { buildEnvData, debuggingEnabled } = require("./utils"); +const { buildEnvData, debuggingEnabled, getUpdatedEmittedFiles } = require("./utils"); let hasBeenInvoked = false; @@ -80,11 +80,18 @@ exports.runWebpackCompiler = function runWebpackCompiler(config, $projectData, $ return; } + const result = getUpdatedEmittedFiles(message.emittedFiles); + + if (hookArgs.hmrData && hookArgs.hmrData.fallbackFiles) { + hookArgs.hmrData.fallbackFiles[platform] = result.fallbackFiles; + hookArgs.hmrData.hash = result.hash || ""; + } + if (hookArgs.filesToSyncMap && hookArgs.startSyncFilesTimeout) { - hookArgs.filesToSyncMap[platform] = message.emittedFiles; + hookArgs.filesToSyncMap[platform] = result.emittedFiles; hookArgs.startSyncFilesTimeout(platform); } else if (hookArgs.filesToSync && hookArgs.startSyncFilesTimeout) { - hookArgs.filesToSync.push(...message.emittedFiles); + hookArgs.filesToSync.push(...result.emittedFiles); hookArgs.startSyncFilesTimeout(platform); } } diff --git a/lib/utils.js b/lib/utils.js index c5f5b6b7..4df213df 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -29,6 +29,46 @@ function buildEnvData($projectData, platform, env) { return envData; } +/** + * Checks if there's a file in the following pattern 5e0326f3bb50f9f26cf0.hot-update.json + * if yes this is a HMR update and remove all bundle files as we don't need them to be synced, + * but only the update chunks + */ +function getUpdatedEmittedFiles(emittedFiles) { + let fallbackFiles = []; + let hotHash; + if (emittedFiles.some(x => x.endsWith('.hot-update.json'))) { + let result = emittedFiles.slice(); + const hotUpdateScripts = emittedFiles.filter(x => x.endsWith('.hot-update.js')); + hotUpdateScripts.forEach(hotUpdateScript => { + const { name, hash } = parseHotUpdateChunkName(hotUpdateScript); + hotHash = hash; + // remove bundle/vendor.js files if there's a bundle.XXX.hot-update.js or vendor.XXX.hot-update.js + result = result.filter(file => file !== `${name}.js`); + }); + //if applying of hot update fails, we must fallback to the full files + fallbackFiles = emittedFiles.filter(file => result.indexOf(file) === -1); + return { emittedFiles: result, fallbackFiles, hash: hotHash }; + } + else { + return { emittedFiles, fallbackFiles }; + } +} + +/** + * Parse the filename of the hot update chunk. + * @param name bundle.deccb264c01d6d42416c.hot-update.js + * @returns { name: string, hash: string } { name: 'bundle', hash: 'deccb264c01d6d42416c' } + */ +function parseHotUpdateChunkName(name) { + const matcher = /^(.+)\.(.+)\.hot-update/gm; + const matches = matcher.exec(name); + return { + name: matches[1] || "", + hash: matches[2] || "", + }; +} + function shouldSnapshot(config) { const platformSupportsSnapshot = isAndroid(config.platform); const osSupportsSnapshot = os.type() !== "Windows_NT"; @@ -39,5 +79,7 @@ function shouldSnapshot(config) { module.exports = { buildEnvData, debuggingEnabled, - shouldSnapshot + shouldSnapshot, + getUpdatedEmittedFiles, + parseHotUpdateChunkName }; diff --git a/plugins/WatchStateLoggerPlugin.ts b/plugins/WatchStateLoggerPlugin.ts index 51a65bb6..c2c5bdde 100644 --- a/plugins/WatchStateLoggerPlugin.ts +++ b/plugins/WatchStateLoggerPlugin.ts @@ -1,5 +1,6 @@ import { join } from "path"; import { writeFileSync, readFileSync } from "fs"; +const utils = require("../lib/utils"); export enum messages { compilationComplete = "Webpack compilation complete.", @@ -40,8 +41,6 @@ export class WatchStateLoggerPlugin { WatchStateLoggerPlugin.rewriteHotUpdateChunk(compiler, compilation, emittedFiles); } - emittedFiles = WatchStateLoggerPlugin.getUpdatedEmittedFiles(emittedFiles); - // provide fake paths to the {N} CLI - relative to the 'app' folder // in order to trigger the livesync process const emittedFilesFakePaths = emittedFiles @@ -64,7 +63,7 @@ export class WatchStateLoggerPlugin { return; } - const { name } = this.parseHotUpdateChunkName(chunk); + const { name } = utils.parseHotUpdateChunkName(chunk); if (!name) { return; } @@ -92,26 +91,6 @@ export class WatchStateLoggerPlugin { return content.substring(startIndex, endIndex); } - /** - * Checks if there's a file in the following pattern 5e0326f3bb50f9f26cf0.hot-update.json - * if yes this is a HMR update and remove all bundle files as we don't need them to be synced, - * but only the update chunks - */ - static getUpdatedEmittedFiles(emittedFiles) { - if(emittedFiles.some(x => x.endsWith('.hot-update.json'))) { - let result = emittedFiles.slice(); - const hotUpdateScripts = emittedFiles.filter(x => x.endsWith('.hot-update.js')); - hotUpdateScripts.forEach(hotUpdateScript => { - const { name } = this.parseHotUpdateChunkName(hotUpdateScript); - // remove bundle/vendor.js files if there's a bundle.XXX.hot-update.js or vendor.XXX.hot-update.js - result = result.filter(file => file !== `${name}.js`); - }); - return result; - } else { - return emittedFiles; - } - } - /** * Gets the webpackHotUpdate call with updated modules not to include the ones with errors */ @@ -130,19 +109,4 @@ export class WatchStateLoggerPlugin { } webpackHotUpdate('${moduleName}', filter(${updatedModules}, ${JSON.stringify(errorModuleIds)}));`; } - - /** - * 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 230949804e33520f952a4431b08c68e44fb8be20 Mon Sep 17 00:00:00 2001 From: Kristian Dimitrov Date: Mon, 8 Oct 2018 19:12:49 +0300 Subject: [PATCH 2/2] chore: fix comments --- hot.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hot.js b/hot.js index 2d6f5f50..db8fe549 100644 --- a/hot.js +++ b/hot.js @@ -91,17 +91,16 @@ function check(options) { result(modules, appliedModules); if (upToDate()) { + //Do not modify message - CLI depends on this exact content to determine hmr operation status. log.info(`Successfully applied update with hmr hash ${currentHash}. App is up to date.`); } }) .catch((err) => { const status = module.hot.status(); if (['abort', 'fail'].indexOf(status) >= 0) { + //Do not modify message - CLI depends on this exact content to determine hmr operation status. log.warn(`Cannot apply update with hmr hash ${currentHash}.`); log.warn(err.stack || err.message); - if (options.reload) { - window.location.reload(); - } } else { log.warn(`Update failed: ${err.stack || err.message}`); } @@ -130,6 +129,7 @@ function update(latestHash, options) { const status = module.hot.status(); if (status === 'idle') { + //Do not modify message - CLI depends on this exact content to determine hmr operation status. log.info(`Checking for updates to the bundle with hmr hash ${currentHash}.`); check(options); } else if (['abort', 'fail'].indexOf(status) >= 0) {