有些文件一开始用不到,我们希望用到的时候再去加载,那么应该怎么做呢?其实 webpack 已经提供好了方法,我们可以通过import()语法,也可以通过require.ensure,当我们采用这种方法加载模块时候,就会发起一个 JSONP 请求,动态去加载模块,但是,有几个问题,比如,如何保证文件只加载一次,如何判断文件加载完成。
现在我们改一下主入口文件,将原来的add.js修改为通过 import 语法异步加载模块
异步文件加载
1 2 3 4
| import('./add').then(data => { console.log(data) })
|
这个时候我们再来执行命令来查看输出文件,可以看到除了原来的 bundle 之外,还多出了一个src_add_js.bundle.js,而原来的bundle.js也多了不少的内容,由于代码过长,这里就不贴上来了,从入口开始分析异步加载
1 2 3 4
| dist | |__bundle.js |__src_add_js.bundle.js
|
入口函数同样为__webpack_require__("./src/main.js"),在这里和之前的有些不同
,可以看到,import语法已经被转换为了__webpack_require__.e
1 2 3 4 5 6 7
| __webpack_require__.e("src_add_js")
.then(__webpack_require__.bind(__webpack_require__,"./src/add.js"))
.then(data => { console.log(data) })
|
可以看到,下面这里主要是通过installedChunks记录了当前安装模块的一些状态信息,undefined 为模块未加载,null表示预加载,Promise正在加载,0加载完成,如果已经存在installedChunkData中,表示正在加载,将Promise push到promises数组中,否则创建一个Promise,同时添加到installedChunks记录下来,并且通过__webpack_require__.u和__webpack_require__.p返回了完整的文件路径,将路径,chunkId,都传递给了__webpack_require__.l方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| (() => { <!--定义一个静态属性f,初始值为空对象,方便挂载其他方法--> __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; }, [])); }; })();
<!--定义j方法--> // object to store loaded and loading chunks // undefined = chunk not loaded, null = chunk preloaded/prefetched // Promise = chunk loading, 0 = chunk loaded // installedChunks记录已经安装的异步模块,undefined 为模块未加载,null表示预加载,Promise正在加载,0加载完成 var installedChunks = { "main": 0 }; __webpack_require__.f.j = (chunkId, promises) => { // 如果当前chunk存在,直接返回chunk的状态,否则返回undefined 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) { // installedChunks = { // main:0, // src_add_js:[resolve,reject,promise] // } // // installedChunkData = [resolve,reject,promise] // // var promise = new Promise((resolve, reject) => { installedChunkData = installedChunks[chunkId] = [resolve, reject]; }); promises.push(installedChunkData[2] = promise);
// start chunk loading // 拼接路径 file:///G:/%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AAwebpack/dist/src_add_js.bundle.js // __webpack_require__.p 返回当前/dist/目录,__webpack_require__.u拼接返回src_add_js.bundle.js var url = __webpack_require__.p + __webpack_require__.u(chunkId); 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; } } };
|
看下__webpack_require__.l的实现,通过inProgress判断是否已经在加载,再判断当前的script标签中是否已经存在当前加载的模块,不存在的话则会创建一个script标签,url为绝对路径,插入到document.head中,当插入到页面中时候,会执行到对应chunk的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| // url:file:///G:/%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AAwebpack/dist/src_add_js.bundle.js // done:loadingEnded 函数 // key: chunk-src_add_js // chunkId: src_add_js var inProgress = {}; var dataWebpackPrefix = "webpack-demo:"; __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; } } } // 创建script标签 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]; // 加载完成script标签事件 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); };
|
创建好script标签,会执行src_add_js.bundle.js的代码。
这里的self === window,可以看到执行了webpackChunkwebpack_demo中的push方法,那么,这个方法在哪里定义的呢?
1 2 3 4 5 6 7 8 9 10 11 12 13
| (self["webpackChunkwebpack_demo"] = self["webpackChunkwebpack_demo"] || []).push([["src_add_js"],{ "./src/add.js": ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { "use strict"; eval("..."); }), "./src/c.js": ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
"use strict"; eval("..."); }) }]);
|
在最下方,在window上挂载了webpackChunkwebpack_demo赋值给了chunkLoadingGlobal并且将数组中的push方法修改为webpackJsonpCallback,也就是当执行push方法,会触发上面的webpackJsonpCallback函数,第一个参数是push函数,data解构出来chunkIds是所有的异步模块数组,moreModules是异步模块中引用的模块, 遍历chunkIds数组,找到installedChunks对象中保存的依赖的resolve函数,并且将installedChunks中的chunkId设置为 0,表示加载完成,通过遍历moreModules,加入到__webpack_modules__中,之后遍历执行resolves中,也就是所有installedChunks中的resolve函数,表示加载完成,加载完成之后,会执行加载完成事件,将script标签删除
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| 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, resolves = []; for(;i < chunkIds.length; i++) { chunkId = chunkIds[i]; if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { resolves.push(installedChunks[chunkId][0]); } installedChunks[chunkId] = 0; } for(moduleId in moreModules) { if(__webpack_require__.o(moreModules, moduleId)) { __webpack_require__.m[moduleId] = moreModules[moduleId]; } } if(runtime) runtime(__webpack_require__); if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); while(resolves.length) { resolves.shift()(); }
} // 修改push方法 var chunkLoadingGlobal = self["webpackChunkwebpack_demo"] = self["webpackChunkwebpack_demo"] || []; chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, // webpackJsonpCallback函数第一个参数是push函数 chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
|
本文标题:webpack输出文件分析(二)
文章作者:Niuhk
发布时间:2021-03-21
最后更新:2022-01-23
原始链接:https://www.niuhk.cn/2021/03/21/webpack输出文件分析(二)/
版权声明:转载请注明出处!