Javascript webpack动态import


Posted in Javascript onApril 19, 2022

前言

在vue中我们经常用到动态导入页面组件,那么它是如何实现的呢,本文将通过简单的案例,快速了解实现原理

例子

// index.js
import('./test').then(fn => {
  console.log(fn.default());
})
// test.js
export default function func() {
  return 1
}

打包后的代码包含两个文件 bundle.js 和 0.js

点击展开bundle.js

/******/ (function(modules) { // webpackBootstrap
/******/ 	// install a JSONP callback for chunk loading
/******/ 	function webpackJsonpCallback(data) {
/******/ 		var chunkIds = data[0];
/******/ 		var moreModules = data[1];
/******/
/******/
/******/ 		// 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(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
/******/ 				resolves.push(installedChunks[chunkId][0]);
/******/ 			}
/******/ 			installedChunks[chunkId] = 0;
/******/ 		}
/******/ 		for(moduleId in moreModules) {
/******/ 			if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
/******/ 				modules[moduleId] = moreModules[moduleId];
/******/ 			}
/******/ 		}
/******/ 		if(parentJsonpFunction) parentJsonpFunction(data);
/******/
/******/ 		while(resolves.length) {
/******/ 			resolves.shift()();
/******/ 		}
/******/
/******/ 	};
/******/
/******/
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
/******/ 	// 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
/******/ 	};
/******/
/******/
/******/
/******/ 	// script path function
/******/ 	function jsonpScriptSrc(chunkId) {
/******/ 		return __webpack_require__.p + "" + chunkId + ".bundle.js"
/******/ 	}
/******/
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId]) {
/******/ 			return installedModules[moduleId].exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// Flag the module as loaded
/******/ 		module.l = true;
/******/
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/
/******/ 	// This file contains only the entry chunk.
/******/ 	// The chunk loading function for additional chunks
/******/ 	__webpack_require__.e = function requireEnsure(chunkId) {
/******/ 		var promises = [];
/******/
/******/
/******/ 		// JSONP chunk loading for javascript
/******/
/******/ 		var installedChunkData = installedChunks[chunkId];
/******/ 		if(installedChunkData !== 0) { // 0 means "already installed".
/******/
/******/ 			// a Promise means "currently loading".
/******/ 			if(installedChunkData) {
/******/ 				promises.push(installedChunkData[2]);
/******/ 			} else {
/******/ 				// setup Promise in chunk cache
/******/ 				var promise = new Promise(function(resolve, reject) {
/******/ 					installedChunkData = installedChunks[chunkId] = [resolve, reject];
/******/ 				});
/******/ 				promises.push(installedChunkData[2] = promise);
/******/
/******/ 				// start chunk loading
/******/ 				var script = document.createElement('script');
/******/ 				var onScriptComplete;
/******/
/******/ 				script.charset = 'utf-8';
/******/ 				script.timeout = 120;
/******/ 				if (__webpack_require__.nc) {
/******/ 					script.setAttribute("nonce", __webpack_require__.nc);
/******/ 				}
/******/ 				script.src = jsonpScriptSrc(chunkId);
/******/
/******/ 				// create error before stack unwound to get useful stacktrace later
/******/ 				var error = new Error();
/******/ 				onScriptComplete = function (event) {
/******/ 					// avoid mem leaks in IE.
/******/ 					script.onerror = script.onload = null;
/******/ 					clearTimeout(timeout);
/******/ 					var chunk = installedChunks[chunkId];
/******/ 					if(chunk !== 0) {
/******/ 						if(chunk) {
/******/ 							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;
/******/ 							chunk[1](error);
/******/ 						}
/******/ 						installedChunks[chunkId] = undefined;
/******/ 					}
/******/ 				};
/******/ 				var timeout = setTimeout(function(){
/******/ 					onScriptComplete({ type: 'timeout', target: script });
/******/ 				}, 120000);
/******/ 				script.onerror = script.onload = onScriptComplete;
/******/ 				document.head.appendChild(script);
/******/ 			}
/******/ 		}
/******/ 		return Promise.all(promises);
/******/ 	};
/******/
/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;
/******/
/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;
/******/
/******/ 	// define getter function for harmony exports
/******/ 	__webpack_require__.d = function(exports, name, getter) {
/******/ 		if(!__webpack_require__.o(exports, name)) {
/******/ 			Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ 		}
/******/ 	};
/******/
/******/ 	// define __esModule on exports
/******/ 	__webpack_require__.r = function(exports) {
/******/ 		if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ 			Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ 		}
/******/ 		Object.defineProperty(exports, '__esModule', { value: true });
/******/ 	};
/******/
/******/ 	// 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 & 8|1: behave like require
/******/ 	__webpack_require__.t = function(value, mode) {
/******/ 		if(mode & 1) value = __webpack_require__(value);
/******/ 		if(mode & 8) return value;
/******/ 		if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ 		var ns = Object.create(null);
/******/ 		__webpack_require__.r(ns);
/******/ 		Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ 		if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ 		return ns;
/******/ 	};
/******/
/******/ 	// getDefaultExport function for compatibility with non-harmony modules
/******/ 	__webpack_require__.n = function(module) {
/******/ 		var getter = module && module.__esModule ?
/******/ 			function getDefault() { return module['default']; } :
/******/ 			function getModuleExports() { return module; };
/******/ 		__webpack_require__.d(getter, 'a', getter);
/******/ 		return getter;
/******/ 	};
/******/
/******/ 	// Object.prototype.hasOwnProperty.call
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";
/******/
/******/ 	// on error function for async loading
/******/ 	__webpack_require__.oe = function(err) { console.error(err); throw err; };
/******/
/******/ 	var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
/******/ 	var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
/******/ 	jsonpArray.push = webpackJsonpCallback;
/******/ 	jsonpArray = jsonpArray.slice();
/******/ 	for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
/******/ 	var parentJsonpFunction = oldJsonpFunction;
/******/
/******/
/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(__webpack_require__.s = "./src/index.js");
/******/ })
/************************************************************************/
/******/ ({

/***/ "./src/index.js":
/*!**********************!*\
  !*** ./src/index.js ***!
  \**********************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {

eval("__webpack_require__.e(/*! import() */ 0).then(__webpack_require__.bind(null, /*! ./test */ \"./src/test.js\")).then(function (fn) {\n  console.log(fn[\"default\"]());\n});\n\n//# sourceURL=webpack:///./src/index.js?");

/***/ })

/******/ });

点击展开0.js

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],{

/***/ "./src/test.js":
/*!*********************!*\
  !*** ./src/test.js ***!
  \*********************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return func; });\nfunction func() {\n  return 1;\n}\n\n//# sourceURL=webpack:///./src/test.js?");

/***/ })

}]);

1. 模块加载

webpack通过__webpack_require__加载模块代码

// bundle.js
function __webpack_require__(moduleId)
        // 如果模块已经加载,直接返回模块导出
	if(installedModules[moduleId]) {
		return installedModules[moduleId].exports;
	}

        // 模块导出和模块信息
	var module = installedModules[moduleId] = {
		i: moduleId,
		l: false,
		exports: {}
	}
        // 执行模块代码
	modules[moduleId].call(module.exports, module, module.exports, __webpack_require__)
	module.l = true // 标记模块已经加载完成
	return module.exports;
}
__webpack_require__('index.js')

然后执行index.js编译后的代码,如下。

Promise.all(
  [
    __webpack_require__.e(0)
  ]
).then(
  __webpack_require__.bind(null, "./src/test.js")
).then(function (fn) {
  console.log(fn.default());
}));

2. jsonp动态加载script

先一步步来,看下__webpack_require__.e这个方法,它是最先调用的。

// bundle.js

__webpack_require__.e = function requireEnsure(chunkId) {
	var promises = []
	var installedChunkData = installedChunks[chunkId];
        // 如果这个chunk已经加载过了 就不需要加载了
	if(installedChunkData !== 0) { // 0 means "already installed"
		if(installedChunkData) {
			promises.push(installedChunkData[2]);
		} else {

                        // 为这个chunk创建一个promise
			var promise = new Promise(function(resolve, reject) {
                               // 记录这个chunk对应promise的resolve和reject方法
				installedChunkData = installedChunks[chunkId] = [resolve, reject];
			});

                         // promises数组里添加这个chunk对应的promise
			promises.push(installedChunkData[2] = promise)

                        // ============== 动态创建script =================
			var script = document.createElement('script');
			var onScriptComplete
			script.charset = 'utf-8';
			script.timeout = 120;
			if (__webpack_require__.nc) {
				script.setAttribute("nonce", __webpack_require__.nc);
			}
			script.src = jsonpScriptSrc(chunkId)
			// create error before stack unwound to get useful stacktrace later
			var error = new Error();
                        // =================================================

			onScriptComplete = function (event) {
				// avoid mem leaks in IE.
				script.onerror = script.onload = null;
				clearTimeout(timeout);
				var chunk = installedChunks[chunkId];
				if(chunk !== 0) {
					if(chunk) {
						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;
						chunk[1](error);
					}
					installedChunks[chunkId] = undefined;
				}
			};
			var timeout = setTimeout(function(){
				onScriptComplete({ type: 'timeout', target: script });
			}, 120000);
			script.onerror = script.onload = onScriptComplete;
			document.head.appendChild(script);
		}
	}
	return Promise.all(promises);
};

总结一下,上述代码做的事情

  • 如果chunk没有被加载过,会为这个chunk创建一个promise对象
  • 将promise对象存在promises数组中
  • 将promise的resolve 和 reject存在installedChunks[chunkId]

3. 执行异步脚本

经过上面的过程,会动态加载0.js的脚本代码

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],{
"./src/test.js":
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"default\", function() { return func; });\nfunction func() {\n  return 1;\n}\n\n//# sourceURL=webpack:///./src/test.js?");
/***/ })
}]);

可以看到window上有一个webpackJsonp数组,那么这个东西是从哪里来的呢?,我们来看下面的代码。

// bundle.js
var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
jsonpArray.push = webpackJsonpCallback;
jsonpArray = jsonpArray.slice();
for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
var parentJsonpFunction = oldJsonpFunction;

其实一开始初始化时已经覆盖实现了webpackJsonp.push方法

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],{
  // test.js引入的模块代码
})
// 等价于
webpackJsonpCallback([[0],{
  // test.js引入的模块代码
})

下面再看看webpackJsonpCallback代码的实现

4. webpackJsonpCallback

// bundle.js
function webpackJsonpCallback(data) {
        // chunkid
	var chunkIds = data[0];
       // chunkid对应的模块
	var moreModules = data[1]
	var moduleId, chunkId, i = 0, resolves = [];

	for(;i < chunkIds.length; i++) {
		chunkId = chunkIds[i];
		if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {
                        // 收集chunk对应的resolve方法
			resolves.push(installedChunks[chunkId][0]);
		}
                // 标记该chunk已经加载
		installedChunks[chunkId] = 0;
	}
	for(moduleId in moreModules) {
		if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
                         // 添加chunk模块,到全局modules对象中
			modules[moduleId] = moreModules[moduleId];
		}
	}
	if(parentJsonpFunction) parentJsonpFunction(data)

         // 依次执行chunk对应promise的resolve方法
	while(resolves.length) {
		resolves.shift()();
	}
};

还是总结一下上面代码的过程

  • 收集chunk对应的resolve方法, 前面执行__webpack_require__.e时放在了installedChunks[chunkId]
  • 将异步chunk下的所有模块 添加到 全局modules
  • 依次执行chunk对应promise的resolve方法

5. 执行异步模块代码

回到一开始index.js的代码

Promise.all(
  [
    __webpack_require__.e(0)
  ]
).then(
  __webpack_require__.bind(null, "./src/test.js")
).then(function (fn) {
  console.log(fn.default());
}));

经过上面的步骤,此时promise已经resolve了,__webpack_require__.bind(null, "./src/test.js") 会被执行, 此时异步模块的代码已经在modules上了,所以可以直接加载。

最后,执行fn方法

console.log(fn.default());

流程图

Javascript webpack动态import

总结

webpack动态import的实现还是比较简单的,具体细节大家可以自己翻阅下打包后的代码~

到此这篇关于webpack动态import原理的文章就介绍到这了!

Javascript 相关文章推荐
javascript设置连续两次点击按钮时间间隔的方法
Oct 28 Javascript
浅析AngularJS Filter用法
Dec 28 Javascript
纯JS打造网页中checkbox和radio的美化效果
Oct 13 Javascript
js运动事件函数详解
Oct 21 Javascript
12 款 JS 代码测试必备工具(翻译)
Dec 13 Javascript
用jQuery旋转插件jqueryrotate制作转盘抽奖
Feb 10 Javascript
Javascript中this关键字指向问题的测试与详解
Aug 11 Javascript
详解微信小程序中的页面代码中的模板的封装
Oct 12 Javascript
浅谈Vue响应式(数组变异方法)
May 07 Javascript
分享JS表单验证源码(带错误提示及密码等级)
Jan 05 Javascript
使用Angular9和TypeScript开发RPG游戏的方法
Mar 25 Javascript
JavaScript封装单向链表的示例代码
Sep 17 Javascript
微信小程序APP页面的之间的相互传递参数以及自定义组件
微信小程序APP的事件绑定以及传递参数时的冒泡和捕获
微信小程序APP的生命周期及页面的生命周期
解决vue中provide inject的响应式监听
Apr 19 #Vue.js
vue3种table表格选项个数的控制方法
Apr 14 #Vue.js
vue项目配置sass及引入外部scss文件
Apr 14 #Vue.js
解决vue-router的beforeRouteUpdate不能触发
Apr 14 #Vue.js
You might like
PHP错误Parse error: syntax error, unexpected end of file in test.php on line 12解决方法
2014/06/23 PHP
PHP中设置一个严格30分钟过期Session面试题的4种答案
2014/07/30 PHP
php获取本周开始日期和结束日期的方法
2015/03/09 PHP
php获得网站访问统计信息类Compete API用法实例
2015/04/02 PHP
php实现留言板功能
2017/03/05 PHP
thinkPHP5框架路由常用知识点汇总
2019/09/15 PHP
JavaScript 动态将数字金额转化为中文大写金额
2009/05/14 Javascript
jquery 防止表单重复提交代码
2010/01/21 Javascript
jquery checkbox,radio是否选中的判断代码
2010/03/20 Javascript
js 取时间差去掉周六周日实现代码
2012/12/25 Javascript
浅析Js(Jquery)中,字符串与JSON格式互相转换的示例(直接运行实例)
2013/07/09 Javascript
Javascript Ajax异步读取RSS文档具体实现
2013/12/12 Javascript
js实现简单的可切换选项卡效果
2015/04/10 Javascript
Canvas 绘制粒子动画背景
2017/02/15 Javascript
vscode下的vue文件格式化问题
2018/11/28 Javascript
python查找第k小元素代码分享
2013/12/18 Python
python 去除二维数组/二维列表中的重复行方法
2019/01/23 Python
详解将Python程序(.py)转换为Windows可执行文件(.exe)
2019/07/19 Python
利用pandas合并多个excel的方法示例
2019/10/10 Python
python实现对列表中的元素进行倒序打印
2019/11/23 Python
Scrapy框架基本命令与settings.py设置
2020/02/06 Python
通过实例解析Python return运行原理
2020/03/04 Python
Python + selenium + crontab实现每日定时自动打卡功能
2020/03/31 Python
python opencv图像处理(素描、怀旧、光照、流年、滤镜 原理及实现)
2020/12/10 Python
美国乡村商店:Plow & Hearth
2016/09/12 全球购物
巴西宠物商店:Cobasi
2019/04/19 全球购物
德国高尔夫商店:Par71.de
2020/11/29 全球购物
五一服装活动方案
2014/01/11 职场文书
励志演讲稿500字
2014/08/21 职场文书
学用政策心得体会
2014/09/10 职场文书
中学生2014国庆节演讲稿:不屈的民族
2014/09/21 职场文书
个人委托书范文
2015/01/28 职场文书
幼师大班个人总结
2015/02/13 职场文书
前台岗位职责范本
2015/04/16 职场文书
在职证明格式样本
2015/06/15 职场文书
python中filter,map,reduce的作用
2022/06/10 Python