苍梧网站建设asp网站后台制作
苍梧网站建设,asp网站后台制作,百度知道怎么赚钱,网络规划设计师 第2版 ed2k前言
在平时的开发中#xff0c;我们经常使用 import()实现代码分割和懒加载。在低版本的浏览器中并不支持动态 import()#xff0c;那 webpack 是如何实现 import() polyfill 的#xff1f;
原理分析
我们先来看看下面的 demo
function component() {const btn docume…前言
在平时的开发中我们经常使用 import()实现代码分割和懒加载。在低版本的浏览器中并不支持动态 import()那 webpack 是如何实现 import() polyfill 的
原理分析
我们先来看看下面的 demo
function component() {const btn document.createElement(button);btn.onclick () {import(./a.js).then((res) {console.log(动态加载a.js.., res);});};btn.innerHTML Button;return btn;
}document.body.appendChild(component()); 点击按钮动态加载 a.js脚本查看浏览器网络请求可以发现a.js请求返回的内容如下 简单看实际上返回的就是下面这个东西
(self[webpackChunkwebpack_demo] self[webpackChunkwebpack_demo] || []).push([[src_a_js],{./src/a.js: () {},},
]);
从上面可以看出 3 点信息 1.webpackChunkwebpack_demo 是挂到全局 window 对象上的属性 2.webpackChunkwebpack_demo 是个数组 3.webpackChunkwebpack_demo 有个 push 方法用于添加动态的模块。当a.js脚本请求成功后这个方法会自动执行。
再来看看 main.js 返回的内容 仔细观察动态 import 经过 webpack 编译后变成了下面的一坨东西
__webpack_require__.e(src_a_js).then(__webpack_require__.bind(__webpack_require__, ./src/a.js)).then((res) {console.log(动态加载a.js.., res);});
上面代码中__webpack_require__ 用于执行模块比如上面我们通过webpackChunkwebpack_demo.push添加的模块里面的./src/a.js函数就是在__webpack_require__里面执行的。
__webpack_require__.e函数就是用来动态加载远程脚本。因此从上面的代码中我们可以看出 首先 webpack 将动态 import 编译成 __webpack_require__.e 函数 __webpack_require__.e函数加载远程的脚本加载完成后调用 __webpack_require__ 函数 __webpack_require__函数负责调用远程脚本返回来的模块获取脚本里面导出的对象并返回
源码分析及实现
如何动态加载远程模块
在开始之前我们先来看下如何使用 script 标签加载远程模块
var inProgress {};
// url: http://localhost:8080/src_a_js.main.js
// done: 加载完成的回调
const loadScript (url, done) {if (inProgress[url]) {inProgress[url].push(done);return;}const script document.createElement(script);script.charset utf-8;script.src url;inProgress[url] [done];var onScriptComplete (prev, event) {var doneFns inProgress[url];delete inProgress[url];script.parentNode script.parentNode.removeChild(script);doneFns doneFns.forEach((fn) fn(event));if (prev) return prev(event);};script.onload onScriptComplete.bind(null, script.onload);document.head.appendChild(script);
};
loadScript(url, done) 函数比较简单就是通过创建 script 标签加载远程脚本加载完成后执行 done 回调。inProgress用于避免多次创建 script 标签。比如我们多次调用loadScript(http://localhost:8080/src_a_js.main.js, done)时应该只创建一次 script 标签不需要每次都创建。这也是为什么我们调用多次 import(a.js)浏览器 network 请求只看到家在一次脚本的原因
实际上这就是 webpack 用于加载远程模块的极简版本。
__webpack_require__.e 函数的实现 首先我们使用installedChunks对象保存动态加载的模块。key 是 chunkId
// 存储已经加载和正在加载的chunks此对象存储的是动态import的chunk对象的key是chunkId值为
// 以下几种
// undefined: chunk not loaded
// null: chunk preloaded/prefetched
// [resolve, reject, Promise]: chunk loading
// 0: chunk loaded
var installedChunks {main: 0,
};
由于 import() 返回的是一个 promise然后import()经过 webpack 编译后就是一个__webpack_require__.e函数因此可以得出__webpack_require__.e返回的也是一个 promise如下所示
const scriptUrl document.currentScript.src.replace(/#.*$/, ).replace(/\?.*$/, ).replace(/\/[^\/]$/, /);__webpack_require__.e (chunkId) {return Promise.resolve(ensureChunk(chunkId, promises));
};const ensureChunk (chunkId) {var installedChunkData installedChunks[chunkId];if (installedChunkData 0) return;let promise;// 1.如果多次调用了__webpack_require__.e函数即多次调用import(a.js)加载相同的模块只要第一次的加载还没完成就直接使用第一次的Promiseif (installedChunkData) {promise installedChunkData[2];} else {promise new Promise((resolve, reject) {// 2.注意此时的resolvereject还没执行installedChunkData installedChunks[chunkId] [resolve, reject];});installedChunkData[2] promise; //3. 此时的installedChunkData 为[resolve, reject, promise]var url scriptUrl chunkId;var error new Error();// 4.在script标签加载完成或者加载失败后执行loadingEnded方法var loadingEnded (event) {if (Object.prototype.hasOwnProperty.call(installedChunks, chunkId)) {installedChunkData installedChunks[chunkId];if (installedChunkData ! 0) installedChunks[chunkId] undefined;if (installedChunkData) {console.log(加载失败.....);installedChunkData[1](error); // 5.执行上面的reject那resolve在哪里执行呢}}};loadScript(url, loadingEnded, chunk- chunkId, chunkId);}return promise;
};
__webpack_require__.e的主要逻辑在ensureChunk方法中注意该方法里面的第 1 到第 5 个注释。这个方法创建一个 promise并调用loadScript方法加载动态模块。需要特别主要的是返回的 promise 的 resolve 方法并不是在 script 标签加载完成后改变。如果脚本加载错误或者超时会在 loadingEnded 方法里调用 promise 的 reject 方法。实际上promise 的 resolve 方法是在脚本请求完成后在 self[webpackChunkwebpack_demo].push()执行的时候调用的
如何执行远程模块
远程模块是通过self[webpackChunkwebpack_demo].push()函数执行的
前面我们提到a.js请求返回的内容是一个self[webpackChunkwebpack_demo].push()函数。当请求完成会自动执行这个函数。实际上这就是一个 jsonp 的回调方式。该方法的实现如下
var webpackJsonpCallback (data) {var [chunkIds, moreModules] data;var moduleId,chunkId,i 0;for (moduleId in moreModules) {// 1.__webpack_require__.m存储的是所有的模块包括静态模块和动态模块__webpack_require__.m[moduleId] moreModules[moduleId];}for (; i chunkIds.length; i) {chunkId chunkIds[i];if (installedChunks[chunkId]) {// 2.调用ensureChunk方法生成的promise的resolve回调installedChunks[chunkId][0]();}// 3.将该模块标记为0表示已经加载过installedChunks[chunkId] 0;}
};self[webpackChunkwebpack_demo] [];
self[webpackChunkwebpack_demo].push webpackJsonpCallback.bind(null);
所有通过import()加载的模块经过 webpack 编译后都会被 self[webpackChunkwebpack_demo].push()包裹。
总结
在 webpack 构建编译阶段import()会被编译成类似__webpack_require__.e(src_a_js).then(__webpack_require__.bind(__webpack_require__, ./src/a.js))的调用方式
__webpack_require__.e(src_a_js).then(__webpack_require__.bind(__webpack_require__, ./src/a.js)).then((res) {console.log(动态加载a.js.., res);});
__webpack_require__.e()方法会创建一个 script 标签用于请求脚本方法执行完返回一个 promise此时的 promise 状态还没改变。
script 标签被添加到 document.head 后触发浏览器网络请求。请求成功后动态的脚本会自动执行此时self[webpackChunkwebpack_demo].push()方法执行将动态的模块添加到__webpack_require__.m属性中。同时调用 promise 的 resolve 方法改变状态模块加载完成。
脚本执行完成后最后执行 script 标签的 onload 回调。onload 回调主要是用于处理脚本加载失败或者超时的场景并调用 promise 的 reject 回调表示脚本加载失败
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/89617.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!