代码结构



main.js

console.log("这是main页面");
import(/* webpackChunkName: "foo" */"./foo").then(res => {
console.log("动态导入foo")
console.log(res);
console.log(res.sum(1,10))
});

foo.js

export function sum(num1, num2) {
return num1 + num2;
}

webpack.common.js

const resolveApp = require("./paths");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const VueLoaderPlugin = require("vue-loader/lib/plugin");
const TerserPlugin = require("terser-webpack-plugin");
const { merge } = require("webpack-merge"); const prodConfig = require("./webpack.prod");
const devConfig = require("./webpack.dev"); const commonConfig = {
entry: {
// index: {import:"./src/index.js",dependOn:"shared"},
// main: {import:"./src/main.js",dependOn:"shared"},
// shared:['lodash'] main: "./src/main.js", },
output: {
filename: "[name].bundle.js",
path: resolveApp("./build"), chunkFilename: "[name].chunk.js"
},
resolve: {
extensions: [".wasm", ".mjs", ".js", ".json", ".jsx", ".ts", ".vue"],
alias: {
"@": resolveApp("./src"),
pages: resolveApp("./src/pages"),
},
}, optimization:{
// 对代码进行压缩相关的操作
minimizer: [
new TerserPlugin({
extractComments: false,
}),
],
// chunkIds: "natural",
splitChunks:{ // async异步导入
// initial同步导入
// all 异步/同步导入
chunks: "all",
// 最小尺寸: 如果拆分出来一个, 那么拆分出来的这个包的大小最小为minSize
minSize: 200,
// 将大于maxSize的包, 拆分成不小于minSize的包
maxSize: 200,
// minChunks表示引入的包, 至少被导入了几次
minChunks: 1,
cacheGroups:{
vendor: {
test: /[\\/]node_modules[\\/]/,
filename: "[id]_vendors.js",
// name: "vendor-chunks.js",
priority: -10
},
default: {
minChunks: 2,
filename: "common_[id].js",
priority: -50
}
}
}
},
module: {
rules: [
{
test: /\.jsx?$/i,
use: "babel-loader",
},
{
test: /\.vue$/i,
use: "vue-loader",
},
{
test: /\.css/i,
use: ["style-loader", "css-loader"],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: "./index.html",
}),
new VueLoaderPlugin(),
]
}; module.exports = function(env) { const isProduction = env.production;
process.env.NODE_ENV = isProduction ? "production": "development"; const config = isProduction ? prodConfig : devConfig;
const mergeConfig = merge(commonConfig, config); return mergeConfig;
};

其余部分参考代码地址:

https://github.com/JerryXu008/webapck_lazy_load_theory

打包之后代码

执行 npm run build ,然后查看生成的文件

foo.chunk.js

(self["webpackChunkwebpack_devserver"] = self["webpackChunkwebpack_devserver"] || []).push([["foo"], {

 ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

      "use strict";
__webpack_require__.r(__webpack_exports__); __webpack_require__.d(__webpack_exports__, {
"sum": () => (sum)
});
function sum(num1, num2) {
return num1 + num2;
}
}) }]);

main.bundle.js

(() => { // webpackBootstrap
var __webpack_modules__ = ({});
/************************************************************************/
// The module cache
var __webpack_module_cache__ = {}; // The require function
function __webpack_require__(moduleId) {
// Check if module is in cache
if (__webpack_module_cache__[moduleId]) {
return __webpack_module_cache__[moduleId].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](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/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)) {
//把所有的key和value都放到export中(这里是用代理的方式)
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) => { const chunkKeyFunctionArr = Object.keys(__webpack_require__.f);//[j] let promiseArr = chunkKeyFunctionArr.reduce((promises, key) => {
// 传入promises的地址到__webpack_require__.f,方法内部会把新创建的promise加入到promises
__webpack_require__.f[key](chunkId, promises); //循环执行完毕,最终的promises会作为返回值传递给promiseArr
return promises;
}, [])
//Promise.all 的特点是 加入到里面的所有promise全部执行完毕(resolve或reject)之后,才会执行then
return Promise.all(promiseArr); };
})(); /* 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 + ".chunk.js";
};
})(); /* 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 = "webpack_devserver:";
// 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) => {
console.log("脚本加载完毕")
//console.log("看看这里2",window.promise)
// 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);
console.log("开始插入脚本")
needAttach && document.head.appendChild(script);
console.log("插入脚本完毕") };
})(); /* 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 */
(() => {
var scriptUrl;
if (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + "";
var document = __webpack_require__.g.document;
if (!scriptUrl && document) {
if (document.currentScript)
scriptUrl = document.currentScript.src
if (!scriptUrl) {
var scripts = document.getElementsByTagName("script");
if (scripts.length) scriptUrl = scripts[scripts.length - 1].src
}
}
// When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration
// or pass an empty string ("") and set the __webpack_public_path__ variable from your code to use your own logic.
if (!scriptUrl) throw new Error("Automatic publicPath is not supported in this browser");
scriptUrl = scriptUrl.replace(/#.*$/, "").replace(/\?.*$/, "").replace(/\/[^\/]+$/, "/");
__webpack_require__.p = scriptUrl;
})(); /* webpack/runtime/jsonp chunk loading */
(() => {
// no baseURI // object to store loaded and loading chunks
// undefined = chunk not loaded, null = chunk preloaded/prefetched
// Promise = chunk loading, 0 = chunk loaded
var installedChunks = {
"main": 0
}; //chunkId:foo
__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) {
// 为chunks建立一个promise
var promise = new Promise((resolve, reject) => {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
//window.promise = promise //把新建的promise放到 promises数组中
promises.push(installedChunkData[2] = promise); // start chunk loading
//获取chunk模块的url地址,用于动态在html中插入script标签
var url = __webpack_require__.p + __webpack_require__.u(chunkId); var error = new Error(); //url加载加载完毕之后,开始执行的方法
var loadingEnded = (event) => {
//console.log("看看这里4",window.promise)
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);
}
}
};
//动态插入chunk模块的script标签
__webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId); } else installedChunks[chunkId] = 0;
}
}
}; // install a JSONP callback for chunk loading
var webpackJsonpCallback = (parentChunkLoadingFunction, data) => {
console.log("加载脚本push",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]);
}
console.log("resolves:",resolves)
installedChunks[chunkId] = 0;
}
for (moduleId in moreModules) {
if (__webpack_require__.o(moreModules, moduleId)) {
//把新加载的模块存入__webpack_modules__,供其他模块调用
__webpack_require__.m[moduleId] = moreModules[moduleId];
}
}
if (runtime) runtime(__webpack_require__);
//执行以前的push动作,传统的数组push
if (parentChunkLoadingFunction){
parentChunkLoadingFunction(data);
}
while (resolves.length) {
resolves.shift()();//执行resolve,此时会把promise的pending状态变为resolve
} } var chunkLoadingGlobal = self["webpackChunkwebpack_devserver"] = self["webpackChunkwebpack_devserver"] || [];
chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
// 这里很关键,重写了chunkLoadingGlobal的push方法,并把以前的push方法作为新push方法的第一个参数
chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); // no deferred startup
}) (); /************************************************************************/
var __webpack_exports__ = {};
/*!*********************!*\
!*** ./src/main.js ***!
\*********************/
console.log("这是main页面"); __webpack_require__.e("foo") //组装promise.all
//等promise.all内部的所有promise执行resolev或者reject,会执行then
//回调函数为__webpack_require__,并把"./src/foo.js"作为第一个参数,其实就是去调用key为"./src/foo.js"的模块
.then(__webpack_require__.bind(__webpack_require__, "./src/foo.js"))
.then(function (res) {
console.log("动态导入foo");
console.log(res);
console.log(res.sum(1, 10));
});
})()
;
//# sourceMappingURL=main.bundle.js.map

关键代码说明

foo.chunk.js

这个js主要是把打包前的代码封装到一个模块里,并用键值对的方式被push到 全局变量self["webpackChunkwebpack_devserver"]中,

这个全局变量是一个数组,最关键的地方是 他的push会被重写,在这个重写的方法中是实现懒加载的关键,后面会说。

  • main.bundle.js

查看入口代码的位置:

           console.log("这是main页面");

	__webpack_require__.e("foo") //组装promise.all
//等promise.all内部的所有promise执行resolev或者reject,会执行then
//回调函数为__webpack_require__,并把"./src/foo.js"作为第一个参数,其实就是去调用key为"./src/foo.js"的模块
.then(__webpack_require__.bind(__webpack_require__, "./src/foo.js"))
.then(function (res) {
console.log("动态导入foo");
console.log(res);
console.log(res.sum(1, 10));
});

关键地方是__webpack_require__.e("foo"),这个从代码可以看出返回的是一个promise,方法的参数是chunkid,这个chunkid就是为foo.bundle.js 准备的

跳转到__webpack_require__.e,如下:

  • webpack_require.e 方法
(() => {
__webpack_require__.f = {};
__webpack_require__.e = (chunkId) => { const chunkKeyFunctionArr = Object.keys(__webpack_require__.f);//[j] let promiseArr = chunkKeyFunctionArr.reduce((promises, key) => {
// 传入promises的地址到__webpack_require__.f,方法内部会把新创建的promise加入到promises
__webpack_require__.f[key](chunkId, promises);
//循环执行完毕,最终的promises会作为返回值传递给promiseArr
return promises;
}, [])
//Promise.all 的特点是 加入到里面的所有promise全部执行完毕(resolve或reject)之后,才会执行then
return Promise.all(promiseArr); };
})();

这里为了方便观察,微调了打包之后的这个方法的代码,这个方法最后返回一个Primose.all ,这个值也是一个promise,特点是里面的所有promise全部执行完毕(resolve或reject)之后,才会执行then。也就是说 要等到prmiseArr里面的所有promise都为非pending模式之后,才执行。

通过调试,promise的状态改变是在__webpack_require__.f[key](chunkId, promises)方法中,所以要跳转到这个方法(此时的key为j,所以跳转到__webpack_require__.f.j)

  • webpack_require.f.j 方法

这里只列关键点:

//  为chunks建立一个promise
var promise = new Promise((resolve, reject) =>
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
//把新建的promise放到 promises数组中
promises.push(installedChunkData[2] = promise);

这里是把promise加入到promises数组中,此时的promise的状态是pending

__webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId);

url 为 foo.chunk.js 文件在服务器的位置,需要首先下载下来

所以跳转到__webpack_require__.l

  • webpack_require.l 方法
                                 var onScriptComplete = (prev, event) => {
console.log("脚本加载完毕")
//console.log("看看这里2",window.promise)
// 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);
console.log("开始插入脚本")
needAttach && document.head.appendChild(script);
console.log("插入脚本完毕")

关键地方在document.head.appendChild(script),当把脚本插入到html中,就开始下载文件了。

但是有一个地方要注意:



promise 由 pending变为 fullfilling 并不是在onScriptComplete中变的,而是在 html加载 foo.chunk.js 的过程中改变的,关键地方就是foo.chunk.js中的

push方法

  • 查看push方法
                 var chunkLoadingGlobal = self["webpackChunkwebpack_devserver"] = self["webpackChunkwebpack_devserver"] || [];
chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
// 这里很关键,重写了chunkLoadingGlobal的push方法,并把以前的push方法作为新push方法的第一个参数
chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
  • webpackJsonpCallback 方法



    关键点就是箭头的三个指向,主要做的就是把foo模块的键值对加入到 全局对象中,然后执行promise的resolve方法

    等执行完resolve方法,promise.all 的 then方法就可以执行了。

  • prmise.all(xxx).then()



    在then中的回调方法是__webpack_require__,这个方法是所有模块的通用方法,就是 通过传递key值,从全局对象中找出模块,然后执行模块内的代码。

  • webpack_require



    标红处就是执行foo的模块代码,下面跳转到模块代码

  • foo模块代码



    关键地方就是标红处,这个方法的作用是给要导出的module添加key和value

  • webpack_require.d 方法



    此时exports 里面就会有sum方法的键值对了。

    执行完毕之后,会回到 __webpack_require__中,这个方法最后一个就是返回 module.exports

  • 下一个then方法

.then(function (res) {
console.log("动态导入foo");
console.log(res);
console.log(res.sum(1, 10));
});

第一个then执行完毕后,会返回一个新的promise,之后会在执行一个then,这个then里面的回调函数就是上一个then返回的exports,所以

从里面取出sum,执行即可

以上就是webpack懒加载的执行原理和过程

WebPack之懒加载原理的更多相关文章

  1. 解析苹果的官方例子LazyTableImages实现图片懒加载原理

    解析苹果的官方例子LazyTableImages实现图片懒加载原理 首先在官网下载源码: https://developer.apple.com/library/ios/navigation/#sec ...

  2. 「Vue.js」Vue-Router + Webpack 路由懒加载实现

    一.前言 当打包构建应用时,Javascript 包会变得非常大,影响页面加载.如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了.结合 Vue ...

  3. webpack打包懒加载

    lazyload https://webpack.js.org/guides/lazy-loading/ 懒加载 -- 按需加载. Lazy, or "on demand", lo ...

  4. 使用jQuery实现图片懒加载原理

    原文:https://www.liaoxuefeng.com/article/00151045553343934ba3bb4ed684623b1bf00488231d88d000 在网页中,常常需要用 ...

  5. js实现图片懒加载原理

    原理 图片懒加载是前端页面优化的一种方式,在页面中有很多图片的时候,图片加载就需要很多时间,很耗费服务器性能,不仅影响渲染速度还会浪费带宽,为了解决这个问题,提高用户体验,所以就出现了懒加载这种方式来 ...

  6. vue+webpack 实现懒加载的三种方式

    第一种: 引入方式 就是正常的路由引入方式 const router = new Router({ routes: [ { path: '/hyh', component: hyh, name: 'h ...

  7. webpack分片chunk加载原理

    首先,使用create-react-app快速创建一个demo npx create-react-app react-demo # npx命令需要npm5.2+ cd react-demo npm s ...

  8. 深入理解React:懒加载(lazy)实现原理

    目录 代码分割 React的懒加载 import() 原理 React.lazy 原理 Suspense 原理 参考 1.代码分割 (1)为什么要进行代码分割? 现在前端项目基本都采用打包技术,比如 ...

  9. 在webpack中使用Code Splitting--代码分割来实现vue中的懒加载

    当Vue应用程序越来越大,使用Webpack的代码分割来懒加载组件,路由或者Vuex模块, 只有在需要时候才加载代码. 我们可以在Vue应用程序中在三个不同层级应用懒加载和代码分割: 组件,也称为异步 ...

  10. hibernate懒加载(转载)

    http://blog.csdn.net/sanjy523892105/article/details/7071139 懒加载详解 懒加载为Hibernate中比较常用的特性之一,下面我们详细来了解下 ...

随机推荐

  1. linux-添加磁盘-MBR分区-挂载

    [root@localhost ~]# lsblk 查看当前磁盘 [root@localhost ~]# fdisk /dev/sdb 磁盘分区 所有的硬件都在/dev下面存放 欢迎使用 fdisk ...

  2. django.core.exceptions.ImproperlyConfigured: Requested setting INSTALLED_APPS, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.

    import os if __name__ == '__main__': # 下面的autoUI改成你当前的项目名称 os.environ.setdefault("DJANGO_SETTIN ...

  3. Typora+PicGo+GitHub

    图片可以成功上传github,但是picgo相册无法预览,typora里也加载不出 image load failed 方法: 在`C:\Windows\System32\drivers\etc\ho ...

  4. 简单的python线程池实现线程安全demo

    from concurrent.futures import ThreadPoolExecutor import threading import time import sys sys.path.a ...

  5. Net6 托管服务、FluentValidation

    Net6 托管服务.FluentValidation 托管服务 1.场景,代码运行在后台.比如服务器启动的时候在后台预先加载数据到缓存,每天凌晨3点把数据导出到备份数据库,每隔5秒钟在两张表之间同步一 ...

  6. QFramework UI 笔记(后续不断 直到UKitI篇结束)

    1.所有的UIElement  传消息给 UIPanel 时,必须先注册 SendEvent();      RegisterEvent(); 2.注意Unity直接生成控件的名称,命名带(1)之类的 ...

  7. Mac Google浏览器 Chrome

    Mac Google浏览器 Chrome https://590m.com/f/28636472-500465601-d4c369 (访问密码:7410)

  8. 通过网页下载qq音乐在线听歌曲

    1.输入网址 打开 qq音乐网页版 https://y.qq.com/ 2.搜索喜欢的歌曲 3.播放喜欢的歌曲 4.谷歌浏览器检查代码 5.找到 network栏 搜索 m4a 6.找到相关地址 复制 ...

  9. centos-7部署kafka-v2.13.3.0.1集群

    1.部署测试机器规划 ip         kafka 版本   zookeeper 版本 192.168.113.132        v2.13.3.0.1 v3.6.3 192.168.113. ...

  10. 逆向学习物联网-网关ESP8266-03软件编程实现

    1.技术原理及流程 1) MQTT数据通讯原理  2).网关协议运行状态机  3). 主程序流程 2.关键程序代码实现 MDK集成开发环境的搭建,大家可以百度搜索,或者参考感知层的软件设计部分. 1) ...