likes
comments
collection
share

前端如何用webpack做好资源的容灾处理(二)

作者站长头像
站长
· 阅读数 12

咸鱼了1年多,原本计划9月份就写的文章,硬是今天才提起劲写文章。

写这篇文章呢是因为项目升级到webpack5以后我发现之前写的插件无法使用了, 因为webpack修改了一些hooks,并且模版代码也进行了一些变更,导致之前的插件不行了。所以就着重讲一下在webpack5 里如何去做这个插件的开发。

前端如何做容灾的思路,之前已经出过了容灾的文章呢之前已经出过了。没看过的可以去看下之前这篇文章,里面详细介绍了整个设计思路《前端如何用webpack做好资源的容灾处理?(一)》。 所以这篇文章我们就不会再去讲处理的思路, 因为都是一样的。

那webpack5里其实发生了很多变更,像上一篇里用到的compilation.mainTemplate.hooks上的很多勾子都已废弃,提供了另外的方式让你入侵webpack的模版代码,但个人感觉还是没有webpack4的方便。两个插件呢我们还是保证参数都一致,这就不过多介绍了,具体看第一篇文章。

以下具体说一下跟webpack4相比具体差在哪里

同步的CDN资源

js和css异常函数的逻辑还是一模一样照搬过来

    if( this.resources ) {
      const resourcesBackup = resources.map(_url => _url.slice(1))

      coreJsContent += `
        window.__CDN_RELOAD__ = (function () {
          var cdnAssetsList = ${JSON.stringify(resourcesBackup)}
          var cdnReloadTimesMap = {};

          return function (domTarget, cdnIndex) {
            var tagName = domTarget.tagName.toLowerCase()
            var getTimes = cdnReloadTimesMap[cdnIndex] === undefined ? ( cdnReloadTimesMap[cdnIndex] = 0 ) : cdnReloadTimesMap[cdnIndex]
            var useCdnUrl = cdnAssetsList[cdnIndex][getTimes++]
            cdnReloadTimesMap[cdnIndex] = getTimes
            if( !useCdnUrl ) {
              return
            }
            if( tagName === 'script' ) {
              var scriptText = '<scr' + 'ipt type=\"text/javascript\" src=\"' + useCdnUrl + '\" onerror=\"__CDN_RELOAD__(this, ' + cdnIndex + ')\" ></scr' + 'ipt>'
              document.write(scriptText)
            }
            else if( tagName === 'link' ) {
              var newLink = domTarget.cloneNode()
              newLink.href = useCdnUrl
              domTarget.parentNode.insertBefore(newLink, domTarget)
            }
          }
        })();
      `
    }

但是html-webpack-plugin身上的勾子有点区别,之前是compilation.hooks.htmlWebpackPluginAlterAssetTags.tap, 现在改叫hooks.alterAssetTags.tap,而且还得用HtmlWebpackPlugin.getHooks这个方法去取

 compiler.hooks.compilation.tap( pluginName, async ( compilation ) => {
      // 先检测是否存在 html-webpack-plugin 否则无法注入js,css
      const HtmlWebpackPlugin = require('html-webpack-plugin');
      if (HtmlWebpackPlugin.getHooks) {
        const hooks = HtmlWebpackPlugin.getHooks(compilation);
        const htmlPlugins = compilation.options.plugins.filter(plugin => plugin instanceof HtmlWebpackPlugin);
        if (htmlPlugins.length === 0) {
          const message = "Error running html-webpack-tags-plugin, are you sure you have html-webpack-plugin before it in your webpack config's plugins?";
          throw new Error(message);
        }
        // html webpack plugin的资源提交勾子  按资源归类
        hooks.alterAssetTags.tap(pluginName, (pluginArgs) => {
          const cdnScripts = []
          const cdnStyles = []
          if( resources) {
            resources.forEach((_url, cdnIndex) => {
              if( /\.js$/.test(_url[0]) ) {
                cdnScripts.push({
                  tagName: 'script',
                  closeTag: true,
                  attributes: {
                    type: 'text/javascript',
                    src: _url[0],
                    onerror: `__CDN_RELOAD__(this, ${cdnIndex})`
                  }
                })
              }

              if( /\.css$/.test(_url[0]) ) {
                cdnStyles.push({
                  tagName: "link",
                  selfClosingTag: false,
                  voidTag: true,
                  attributes: {
                    href: _url[0],
                    rel: "stylesheet",
                    onerror: `__CDN_RELOAD__(this, ${cdnIndex})`
                  }
                })
              }
            })
            // 将cdn资源加入队列最前端
            pluginArgs.assetTags.scripts.unshift(...cdnScripts)
            pluginArgs.assetTags.styles.unshift(...cdnStyles)
          }
        });
      }
    })

中间的逻辑的话是一样的,区分出js和css的资源,给它们挂上onerror的方法, 然后写回到html-webpack-plugin 里, 最后由它生成对应的script和link标签。

异步的chunk资源

变化最大的就是这个资源了的处理了,上面也说了compilation.mainTemplate.hooks的勾子很多都没了, 我只能寻找webpack5内的新的替代。在webpack5里呢给了这个勾子compilation.mainTemplate.hooks.localVars 可以将我们的代码注入到webpack的模版里, 具体插入在哪里呢我们可以试一下。其实这里有个小技巧,正常情况下webpack会对产出的代码进行编译混淆压缩,但这样不方便我们开发插件调试, 我们可以暂时把webpack的压缩关闭。方便我们去阅读产出的代码。

前端如何用webpack做好资源的容灾处理(二)

ok,我们关闭压缩以后试一下这段代码。我们随便返回了一个可执行的console.log, 为了让其比较显眼还加了几个🔥

    compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
      const { mainTemplate, runtimeTemplate } = compilation;
      // 其return 返回的字符串会被注入webpack模版
      mainTemplate.hooks.localVars.tap(
        pluginName,
        (source, chunk) => {
          return 'console.log("🔥🔥mainTemplate.hooks.localVars🔥🔥");'
        }
      );
    })

执行一下webpack,看看效果,如图确实出现在了生成的webpack代码内

前端如何用webpack做好资源的容灾处理(二)

当然我们也可以看一下完整的webpack代码:

/******/ (() => { // webpackBootstrap
/******/ 	var __webpack_modules__ = ({});
/************************************************************************/
/******/ 	// The module cache
/******/ 	var __webpack_module_cache__ = {};
/******/ 	
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/ 		// Check if module is in cache
/******/ 		var cachedModule = __webpack_module_cache__[moduleId];
/******/ 		if (cachedModule !== undefined) {
/******/ 			return cachedModule.exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = __webpack_module_cache__[moduleId] = {
/******/ 			// no module.id needed
/******/ 			// no module.loaded needed
/******/ 			exports: {}
/******/ 		};
/******/ 	
/******/ 		// Execute the module function
/******/ 		__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ 	
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/ 	
/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = __webpack_modules__;
/******/ 	
/************************************************************************/
/******/ 	/* webpack/runtime/create fake namespace object */
/******/ 	(() => {
/******/ 		var getProto = Object.getPrototypeOf ? (obj) => (Object.getPrototypeOf(obj)) : (obj) => (obj.__proto__);
/******/ 		var leafPrototypes;
/******/ 		// create a fake namespace object
/******/ 		// mode & 1: value is a module id, require it
/******/ 		// mode & 2: merge all properties of value into the ns
/******/ 		// mode & 4: return value when already ns object
/******/ 		// mode & 16: return value when it's Promise-like
/******/ 		// mode & 8|1: behave like require
/******/ 		__webpack_require__.t = function(value, mode) {
/******/ 			if(mode & 1) value = this(value);
/******/ 			if(mode & 8) return value;
/******/ 			if(typeof value === 'object' && value) {
/******/ 				if((mode & 4) && value.__esModule) return value;
/******/ 				if((mode & 16) && typeof value.then === 'function') return value;
/******/ 			}
/******/ 			var ns = Object.create(null);
/******/ 			__webpack_require__.r(ns);
/******/ 			var def = {};
/******/ 			leafPrototypes = leafPrototypes || [null, getProto({}), getProto([]), getProto(getProto)];
/******/ 			for(var current = mode & 2 && value; typeof current == 'object' && !~leafPrototypes.indexOf(current); current = getProto(current)) {
/******/ 				Object.getOwnPropertyNames(current).forEach((key) => (def[key] = () => (value[key])));
/******/ 			}
/******/ 			def['default'] = () => (value);
/******/ 			__webpack_require__.d(ns, def);
/******/ 			return ns;
/******/ 		};
/******/ 	})();
/******/ 	
/******/ 	/* webpack/runtime/define property getters */
/******/ 	(() => {
/******/ 		// define getter functions for harmony exports
/******/ 		__webpack_require__.d = (exports, definition) => {
/******/ 			for(var key in definition) {
/******/ 				if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ 					Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ 				}
/******/ 			}
/******/ 		};
/******/ 	})();
/******/ 	
/******/ 	/* webpack/runtime/ensure chunk */
/******/ 	(() => {
/******/ 		__webpack_require__.f = {};
/******/ 		// This file contains only the entry chunk.
/******/ 		// The chunk loading function for additional chunks
/******/ 		__webpack_require__.e = (chunkId) => {
/******/ 			return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => {
/******/ 				__webpack_require__.f[key](chunkId, promises);
/******/ 				return promises;
/******/ 			}, []));
/******/ 		};
/******/ 	})();
/******/ 	
/******/ 	/* webpack/runtime/get javascript chunk filename */
/******/ 	(() => {
/******/ 		// This function allow to reference async chunks
/******/ 		__webpack_require__.u = (chunkId) => {
/******/ 			// return url for filenames based on template
/******/ 			return "" + chunkId + ".js";
/******/ 		};
/******/ 	})();
/******/ 	
/******/ 	/* webpack/runtime/get mini-css chunk filename */
/******/ 	(() => {
/******/ 		// This function allow to reference async chunks
/******/ 		__webpack_require__.miniCssF = (chunkId) => {
/******/ 			// return url for filenames based on template
/******/ 			return "" + chunkId + ".css";
/******/ 		};
/******/ 	})();
/******/ 	
/******/ 	/* webpack/runtime/global */
/******/ 	(() => {
/******/ 		__webpack_require__.g = (function() {
/******/ 			if (typeof globalThis === 'object') return globalThis;
/******/ 			try {
/******/ 				return this || new Function('return this')();
/******/ 			} catch (e) {
/******/ 				if (typeof window === 'object') return window;
/******/ 			}
/******/ 		})();
/******/ 	})();
/******/ 	
/******/ 	/* webpack/runtime/hasOwnProperty shorthand */
/******/ 	(() => {
/******/ 		__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ 	})();
/******/ 	
/******/ 	/* webpack/runtime/load script */
/******/ 	(() => {
/******/ 		var inProgress = {};
/******/ 		var dataWebpackPrefix = "@alltuu/webpack5-assets-load-error-demotion-webpack-plugin:";
/******/ 		// loadScript function to load a script via script tag
/******/ 		__webpack_require__.l = (url, done, key, chunkId) => {
/******/ 			if(inProgress[url]) { inProgress[url].push(done); return; }
/******/ 			var script, needAttach;
/******/ 			if(key !== undefined) {
/******/ 				var scripts = document.getElementsByTagName("script");
/******/ 				for(var i = 0; i < scripts.length; i++) {
/******/ 					var s = scripts[i];
/******/ 					if(s.getAttribute("src") == url || s.getAttribute("data-webpack") == dataWebpackPrefix + key) { script = s; break; }
/******/ 				}
/******/ 			}
/******/ 			if(!script) {
/******/ 				needAttach = true;
/******/ 				script = document.createElement('script');
/******/ 		
/******/ 				script.charset = 'utf-8';
/******/ 				script.timeout = 120;
/******/ 				if (__webpack_require__.nc) {
/******/ 					script.setAttribute("nonce", __webpack_require__.nc);
/******/ 				}
/******/ 				script.setAttribute("data-webpack", dataWebpackPrefix + key);
/******/ 				script.src = url;
/******/ 			}
/******/ 			inProgress[url] = [done];
/******/ 			var onScriptComplete = (prev, event) => {
/******/ 				// avoid mem leaks in IE.
/******/ 				script.onerror = script.onload = null;
/******/ 				clearTimeout(timeout);
/******/ 				var doneFns = inProgress[url];
/******/ 				delete inProgress[url];
/******/ 				script.parentNode && script.parentNode.removeChild(script);
/******/ 				doneFns && doneFns.forEach((fn) => (fn(event)));
/******/ 				if(prev) return prev(event);
/******/ 			}
/******/ 			;
/******/ 			var timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000);
/******/ 			script.onerror = onScriptComplete.bind(null, script.onerror);
/******/ 			script.onload = onScriptComplete.bind(null, script.onload);
/******/ 			needAttach && document.head.appendChild(script);
/******/ 		};
/******/ 	})();
/******/ 	
/******/ 	/* webpack/runtime/make namespace object */
/******/ 	(() => {
/******/ 		// define __esModule on exports
/******/ 		__webpack_require__.r = (exports) => {
/******/ 			if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ 				Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ 			}
/******/ 			Object.defineProperty(exports, '__esModule', { value: true });
/******/ 		};
/******/ 	})();
/******/ 	
/******/ 	/* webpack/runtime/publicPath */
/******/ 	(() => {
/******/ 		__webpack_require__.p = "/";
/******/ 	})();
/******/ 	
/******/ 	/* webpack/runtime/compat */
/******/ 	console.log("🔥🔥mainTemplate.hooks.localVars🔥🔥");
/******/ 	
/******/ 	/* webpack/runtime/css loading */
/******/ 	(() => {
/******/ 		var createStylesheet = (chunkId, fullhref, resolve, reject) => {
/******/ 			var linkTag = document.createElement("link");
/******/ 		
/******/ 			linkTag.rel = "stylesheet";
/******/ 			linkTag.type = "text/css";
/******/ 			var onLinkComplete = (event) => {
/******/ 				// avoid mem leaks.
/******/ 				linkTag.onerror = linkTag.onload = null;
/******/ 				if (event.type === 'load') {
/******/ 					resolve();
/******/ 				} else {
/******/ 					var errorType = event && (event.type === 'load' ? 'missing' : event.type);
/******/ 					var realHref = event && event.target && event.target.href || fullhref;
/******/ 					var err = new Error("Loading CSS chunk " + chunkId + " failed.\n(" + realHref + ")");
/******/ 					err.code = "CSS_CHUNK_LOAD_FAILED";
/******/ 					err.type = errorType;
/******/ 					err.request = realHref;
/******/ 					linkTag.parentNode.removeChild(linkTag)
/******/ 					reject(err);
/******/ 				}
/******/ 			}
/******/ 			linkTag.onerror = linkTag.onload = onLinkComplete;
/******/ 			linkTag.href = fullhref;
/******/ 		
/******/ 			document.head.appendChild(linkTag);
/******/ 			return linkTag;
/******/ 		};
/******/ 		var findStylesheet = (href, fullhref) => {
/******/ 			var existingLinkTags = document.getElementsByTagName("link");
/******/ 			for(var i = 0; i < existingLinkTags.length; i++) {
/******/ 				var tag = existingLinkTags[i];
/******/ 				var dataHref = tag.getAttribute("data-href") || tag.getAttribute("href");
/******/ 				if(tag.rel === "stylesheet" && (dataHref === href || dataHref === fullhref)) return tag;
/******/ 			}
/******/ 			var existingStyleTags = document.getElementsByTagName("style");
/******/ 			for(var i = 0; i < existingStyleTags.length; i++) {
/******/ 				var tag = existingStyleTags[i];
/******/ 				var dataHref = tag.getAttribute("data-href");
/******/ 				if(dataHref === href || dataHref === fullhref) return tag;
/******/ 			}
/******/ 		};
/******/ 		var loadStylesheet = (chunkId) => {
/******/ 			return new Promise((resolve, reject) => {
/******/ 				var href = __webpack_require__.miniCssF(chunkId);
/******/ 				var fullhref = __webpack_require__.p + href;
/******/ 				if(findStylesheet(href, fullhref)) return resolve();
/******/ 				createStylesheet(chunkId, fullhref, resolve, reject);
/******/ 			});
/******/ 		}
/******/ 		// object to store loaded CSS chunks
/******/ 		var installedCssChunks = {
/******/ 			179: 0
/******/ 		};
/******/ 		
/******/ 		__webpack_require__.f.miniCss = (chunkId, promises) => {
/******/ 			var cssChunks = {"813":1};
/******/ 			if(installedCssChunks[chunkId]) promises.push(installedCssChunks[chunkId]);
/******/ 			else if(installedCssChunks[chunkId] !== 0 && cssChunks[chunkId]) {
/******/ 				promises.push(installedCssChunks[chunkId] = loadStylesheet(chunkId).then(() => {
/******/ 					installedCssChunks[chunkId] = 0;
/******/ 				}, (e) => {
/******/ 					delete installedCssChunks[chunkId];
/******/ 					throw e;
/******/ 				}));
/******/ 			}
/******/ 		};
/******/ 		
/******/ 		// no hmr
/******/ 	})();
/******/ 	
/******/ 	/* webpack/runtime/jsonp chunk loading */
/******/ 	(() => {
/******/ 		// no baseURI
/******/ 		
/******/ 		// object to store loaded and loading chunks
/******/ 		// undefined = chunk not loaded, null = chunk preloaded/prefetched
/******/ 		// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded
/******/ 		var installedChunks = {
/******/ 			179: 0
/******/ 		};
/******/ 		
/******/ 		__webpack_require__.f.j = (chunkId, promises) => {
/******/ 				// JSONP chunk loading for javascript
/******/ 				var installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;
/******/ 				if(installedChunkData !== 0) { // 0 means "already installed".
/******/ 		
/******/ 					// a Promise means "currently loading".
/******/ 					if(installedChunkData) {
/******/ 						promises.push(installedChunkData[2]);
/******/ 					} else {
/******/ 						if(true) { // all chunks have JS
/******/ 							// setup Promise in chunk cache
/******/ 							var promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject]));
/******/ 							promises.push(installedChunkData[2] = promise);
/******/ 		
/******/ 							// start chunk loading
/******/ 							var url = __webpack_require__.p + __webpack_require__.u(chunkId);
/******/ 							// create error before stack unwound to get useful stacktrace later
/******/ 							var error = new Error();
/******/ 							var loadingEnded = (event) => {
/******/ 								if(__webpack_require__.o(installedChunks, chunkId)) {
/******/ 									installedChunkData = installedChunks[chunkId];
/******/ 									if(installedChunkData !== 0) installedChunks[chunkId] = undefined;
/******/ 									if(installedChunkData) {
/******/ 										var errorType = event && (event.type === 'load' ? 'missing' : event.type);
/******/ 										var realSrc = event && event.target && event.target.src;
/******/ 										error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';
/******/ 										error.name = 'ChunkLoadError';
/******/ 										error.type = errorType;
/******/ 										error.request = realSrc;
/******/ 										installedChunkData[1](error);
/******/ 									}
/******/ 								}
/******/ 							};
/******/ 							__webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId);
/******/ 						} else installedChunks[chunkId] = 0;
/******/ 					}
/******/ 				}
/******/ 		};
/******/ 		
/******/ 		// no prefetching
/******/ 		
/******/ 		// no preloaded
/******/ 		
/******/ 		// no HMR
/******/ 		
/******/ 		// no HMR manifest
/******/ 		
/******/ 		// no on chunks loaded
/******/ 		
/******/ 		// install a JSONP callback for chunk loading
/******/ 		var webpackJsonpCallback = (parentChunkLoadingFunction, data) => {
/******/ 			var [chunkIds, moreModules, runtime] = data;
/******/ 			// add "moreModules" to the modules object,
/******/ 			// then flag all "chunkIds" as loaded and fire callback
/******/ 			var moduleId, chunkId, i = 0;
/******/ 			if(chunkIds.some((id) => (installedChunks[id] !== 0))) {
/******/ 				for(moduleId in moreModules) {
/******/ 					if(__webpack_require__.o(moreModules, moduleId)) {
/******/ 						__webpack_require__.m[moduleId] = moreModules[moduleId];
/******/ 					}
/******/ 				}
/******/ 				if(runtime) var result = runtime(__webpack_require__);
/******/ 			}
/******/ 			if(parentChunkLoadingFunction) parentChunkLoadingFunction(data);
/******/ 			for(;i < chunkIds.length; i++) {
/******/ 				chunkId = chunkIds[i];
/******/ 				if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {
/******/ 					installedChunks[chunkId][0]();
/******/ 				}
/******/ 				installedChunks[chunkId] = 0;
/******/ 			}
/******/ 		
/******/ 		}
/******/ 		
/******/ 		var chunkLoadingGlobal = self["webpackChunk_alltuu_webpack5_assets_load_error_demotion_webpack_plugin"] = self["webpackChunk_alltuu_webpack5_assets_load_error_demotion_webpack_plugin"] || [];
/******/ 		chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
/******/ 		chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
/******/ 	})();
/******/ 	
/************************************************************************/
var __webpack_exports__ = {};


__webpack_require__.e(/* import() */ 415).then(__webpack_require__.t.bind(__webpack_require__, 415, 23)).then(res => {
  console.log(res)
})


__webpack_require__.e(/* import() */ 813).then(__webpack_require__.bind(__webpack_require__, 813)).then(res => {
  console.log('res', res)
})

/******/ })()
;

我们可以看到生成的代码里面其实是有很多的自执行函数的。其中的__webpack_require__.u负责加载js, __webpack_require__.miniCssF 负责加载css, __webpack_require__.e负责webpack的chunk资源的加载啊 __webpack_require__.p 是配置里填写的publicPath

知道了这些对于写插件来说大致就够了, 接下来就是通过这个mainTemplate.hooks.localVars.tap勾子,我们对上面的几个webpack的原生函数进行包装,让其能够记录加载的重试次数,和动态切换publicPath。

这里要提一句的是,上述的几个函数的函数名,webpack提供了RuntimeGlobals这个对象可以获取到,当然你直接字符串写也是可以的。暂时没看出有什么其他的特别作用,知道的可以说一下。

const { RuntimeGlobals } = require('webpack');
compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
      const { mainTemplate, runtimeTemplate } = compilation;
      mainTemplate.hooks.localVars.tap(
        pluginName,
        (source, chunk) => {
          const script = runtimeTemplate.iife('',
            `
            var getRetryDelay = ${this.chunkRetryDelay};
            // 记录所有资源的重载次数
            var retryMap = {}
            var sleep = function (delay) {
              return new Promise(function(resolve, reject) {
                setTimeout(resolve, delay)
              })
            }
            // 获取当前资源 当前 chunk的重试次数
            var getRetryTimes = function (chunkId) {
              if( retryMap[chunkId] === undefined ) {
                retryMap[chunkId] = 0
              }
              return retryMap[chunkId]
            }

            var webpackU = ${RuntimeGlobals.getChunkScriptFilename}
            var webpackMiniF = __webpack_require__.miniCssF
            // js 资源携带query
            ${RuntimeGlobals.getChunkScriptFilename} = function (chunkId) {
              var chunkIdTimes = getRetryTimes(chunkId)
              if( chunkIdTimes === 0 ) {
                return webpackU(chunkId)
              }else {
                return webpackU(chunkId) + '?reload=' + chunkIdTimes
              }
            }
            // css 资源携带query
            __webpack_require__.miniCssF = function (chunkId) {
              var chunkIdTimes = getRetryTimes(chunkId)
              if( chunkIdTimes === 0 ) {
                return webpackMiniF(chunkId)
              }else {
                return webpackMiniF(chunkId) + '?reload=' + chunkIdTimes
              }
            }


            var originPublicPath = ${RuntimeGlobals.publicPath}
            var chunkPublicpath = ${JSON.stringify(this.chunkPublicpath)}
            var publicPathpathFull = [ originPublicPath ].concat(chunkPublicpath)
            function getPublicPath(times) {
              return publicPathpathFull[ Math.min(publicPathpathFull.length - 1, times) ];
            }

            var oldWebpackE = ${RuntimeGlobals.ensureChunk}
            // 按chunk级别去重试并记录次数即可, 因为 css / js 有一个成功的话,就算第二次成功也不会再请求的,有缓存
            ${RuntimeGlobals.ensureChunk} = function (chunkId) {
              var curRetryTimes = getRetryTimes(chunkId)
              ${RuntimeGlobals.publicPath} = getPublicPath(curRetryTimes)
              var result = oldWebpackE(chunkId)
              // 一定要赋值回去,否则client 端还是会接收到catch
              result = result.catch(function(error) {
                if( curRetryTimes < ${this.maxChunkRetries} ) {
                  retryMap[chunkId]++
                  var delayTime = typeof getRetryDelay === 'function' ? getRetryDelay(curRetryTimes) : getRetryDelay;
                  return sleep(delayTime).then(function () {
                    return ${RuntimeGlobals.ensureChunk}(chunkId)
                  })
                }else {
                  throw error;
                }
              })
              return result
            }
            `
          );
          return script + ';'
        }
      );
    })

具体逻辑就不说了,因为上一篇已经详细说过了, 都是一样的思路。

最终生成的代码(我们注入的部分)

//....

/******/ 	
/******/ 	/* webpack/runtime/compat */
/******/ 	(() => {
/******/ 	
/******/ 		            var getRetryDelay = function(times) {
/******/ 		        return times * 1000
/******/ 		      };
/******/ 		            // 记录所有资源的重载次数
/******/ 		            var retryMap = {}
/******/ 		            var sleep = function (delay) {
/******/ 		              return new Promise(function(resolve, reject) {
/******/ 		                setTimeout(resolve, delay)
/******/ 		              })
/******/ 		            }
/******/ 		            // 获取当前资源 当前 chunk的重试次数
/******/ 		            var getRetryTimes = function (chunkId) {
/******/ 		              if( retryMap[chunkId] === undefined ) {
/******/ 		                retryMap[chunkId] = 0
/******/ 		              }
/******/ 		              return retryMap[chunkId]
/******/ 		            }
/******/ 	
/******/ 		            var webpackU = __webpack_require__.u
/******/ 		            var webpackMiniF = __webpack_require__.miniCssF
/******/ 		            // js 资源携带query
/******/ 		            __webpack_require__.u = function (chunkId) {
/******/ 		              var chunkIdTimes = getRetryTimes(chunkId)
/******/ 		              if( chunkIdTimes === 0 ) {
/******/ 		                return webpackU(chunkId)
/******/ 		              }else {
/******/ 		                return webpackU(chunkId) + '?reload=' + chunkIdTimes
/******/ 		              }
/******/ 		            }
/******/ 		            // css 资源携带query
/******/ 		            __webpack_require__.miniCssF = function (chunkId) {
/******/ 		              var chunkIdTimes = getRetryTimes(chunkId)
/******/ 		              if( chunkIdTimes === 0 ) {
/******/ 		                return webpackMiniF(chunkId)
/******/ 		              }else {
/******/ 		                return webpackMiniF(chunkId) + '?reload=' + chunkIdTimes
/******/ 		              }
/******/ 		            }
/******/ 	
/******/ 	
/******/ 		            var originPublicPath = __webpack_require__.p
/******/ 		            var chunkPublicpath = ["https://www.baidu.com/","https://www.jianshu.com/"]
/******/ 		            var publicPathpathFull = [ originPublicPath ].concat(chunkPublicpath)
/******/ 		            function getPublicPath(times) {
/******/ 		              return publicPathpathFull[ Math.min(publicPathpathFull.length - 1, times) ];
/******/ 		            }
/******/ 	
/******/ 		            var oldWebpackE = __webpack_require__.e
/******/ 		            // 按chunk级别去重试并记录次数即可, 因为 css / js 有一个成功的话,就算第二次成功也不会再请求的,有缓存
/******/ 		            __webpack_require__.e = function (chunkId) {
/******/ 		              var curRetryTimes = getRetryTimes(chunkId)
/******/ 		              __webpack_require__.p = getPublicPath(curRetryTimes)
/******/ 		              var result = oldWebpackE(chunkId)
/******/ 		              // 一定要赋值回去,否则client 端还是会接收到catch
/******/ 		              result = result.catch(function(error) {
/******/ 		                if( curRetryTimes < 3 ) {
/******/ 		                  retryMap[chunkId]++
/******/ 		                  var delayTime = typeof getRetryDelay === 'function' ? getRetryDelay(curRetryTimes) : getRetryDelay;
/******/ 		                  return sleep(delayTime).then(function () {
/******/ 		                    return __webpack_require__.e(chunkId)
/******/ 		                  })
/******/ 		                }else {
/******/ 		                  throw error;
/******/ 		                }
/******/ 		              })
/******/ 		              return result
/******/ 		            }
/******/ 	})();
/******/ 	


//....

ok,两个版本的插件区别大致就在这里, 完整的代码大家可以去看github。

有需要webpack5版本的小伙伴可以直接拿去用

npm:webpack5-assets-reload-webpack-plugin

github:nxl3477/webpack5-assets-reload-webpack-plugin