WebPack之懒加载原理
代码结构

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和valuewebpack_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之懒加载原理的更多相关文章
- 解析苹果的官方例子LazyTableImages实现图片懒加载原理
		
解析苹果的官方例子LazyTableImages实现图片懒加载原理 首先在官网下载源码: https://developer.apple.com/library/ios/navigation/#sec ...
 - 「Vue.js」Vue-Router + Webpack 路由懒加载实现
		
一.前言 当打包构建应用时,Javascript 包会变得非常大,影响页面加载.如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了.结合 Vue ...
 - webpack打包懒加载
		
lazyload https://webpack.js.org/guides/lazy-loading/ 懒加载 -- 按需加载. Lazy, or "on demand", lo ...
 - 使用jQuery实现图片懒加载原理
		
原文:https://www.liaoxuefeng.com/article/00151045553343934ba3bb4ed684623b1bf00488231d88d000 在网页中,常常需要用 ...
 - js实现图片懒加载原理
		
原理 图片懒加载是前端页面优化的一种方式,在页面中有很多图片的时候,图片加载就需要很多时间,很耗费服务器性能,不仅影响渲染速度还会浪费带宽,为了解决这个问题,提高用户体验,所以就出现了懒加载这种方式来 ...
 - vue+webpack 实现懒加载的三种方式
		
第一种: 引入方式 就是正常的路由引入方式 const router = new Router({ routes: [ { path: '/hyh', component: hyh, name: 'h ...
 - webpack分片chunk加载原理
		
首先,使用create-react-app快速创建一个demo npx create-react-app react-demo # npx命令需要npm5.2+ cd react-demo npm s ...
 - 深入理解React:懒加载(lazy)实现原理
		
目录 代码分割 React的懒加载 import() 原理 React.lazy 原理 Suspense 原理 参考 1.代码分割 (1)为什么要进行代码分割? 现在前端项目基本都采用打包技术,比如 ...
 - 在webpack中使用Code Splitting--代码分割来实现vue中的懒加载
		
当Vue应用程序越来越大,使用Webpack的代码分割来懒加载组件,路由或者Vuex模块, 只有在需要时候才加载代码. 我们可以在Vue应用程序中在三个不同层级应用懒加载和代码分割: 组件,也称为异步 ...
 - hibernate懒加载(转载)
		
http://blog.csdn.net/sanjy523892105/article/details/7071139 懒加载详解 懒加载为Hibernate中比较常用的特性之一,下面我们详细来了解下 ...
 
随机推荐
- CSC落榜
			
2021年5月31日21:00点,CSC公布结果,未通过.看到这,我感觉空气瞬间凝固,窒息,那一瞬间我无比平静,我以为我会哭,但是,却泣不成声,脑中第一时间想到得是,如何面对认识得人,全世界感觉都知道 ...
 - conda 备份与还原环境
			
文章目录 1.创建环境2.激活环境3.安装包(1)手动一个一个安装(2)批量安装4.卸载包(1)手动一个一个卸载(2)批量卸载5.查看当前环境中所有已安装的包6.退出当前环境方法1:激活base环境即 ...
 - 【快速学】C/C++编译器
			
编译器 谁维护 平台 版权 Visual C++ Microsoft https://visualstudio.microsoft.com/ Microsoft Windows 有免费版 GCC C ...
 - VScode打开文件夹位置技巧
			
VScode在打开文件夹,弹出对话框的时候,去文件夹(应用)到达该路径,对话框中的路径自动变为当前文件夹(应用)的路径.去文件夹(应用)到达该路径
 - zabbix 监控域名到期时间
			
cat userparameter_http.conf UserParameter=http_discovery,/usr/bin/python /etc/zabbix/scripts/base/ht ...
 - 小米手机MIUI12获取hci.log的方法记录
			
按照之前的方式,开发者选项打开获取蓝牙HCI的log开关,但是在本地一直找不到log. 在网上查了很久资料,终于找到有用的方法了.记录一下. 感谢大佬 https://www.jianshu.com/ ...
 - zookeeper设置开机自启
			
开机自启:(1)编辑zookeeper.service文件 vim /usr/lib/systemd/system/zookeeper.service 加入如下内容复制代码[Unit]Descript ...
 - Linux 看进程的线程数
			
pstree -p 12345|wc -l 看进程的线程数 centos7默认并没有安装pstree,所以会有pstree:command not found 安装一下 yum install psm ...
 - js字符串截取(获取指定字符后面的所有字符内容)
			
function getCaption(obj, text){ let index = obj.lastIndexOf(text) + text.length-1; obj = obj.substri ...
 - linux虚拟机,ifconfig无法获取静态ip地址
			
之前一直显示这种ip地址,如下图(网图),查看了DHCP,是正常启动的,虚拟网络编辑器中设置的也正确.后来发现更改虚拟机的设置后就可以了,如下: 设置方法:VMware-虚拟机-设置-网络适配器,选择 ...