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 相关文章推荐
关于取不到由location.href提交而来的上级页面地址的解决办法
Jul 30 Javascript
js href的用法
May 13 Javascript
TBCompressor js代码压缩
Jan 05 Javascript
CSS鼠标响应事件经过、移动、点击示例介绍
Sep 04 Javascript
js控制容器隐藏出现防止样式变化的两种方法
Apr 25 Javascript
简介JavaScript中Boolean.toSource()方法的使用
Jun 05 Javascript
JS使用正则实现去掉字符串左右空格的方法
Dec 27 Javascript
Webpack 服务器端代码打包的示例代码
Sep 19 Javascript
vue2.0与bootstrap3实现列表分页效果
Nov 28 Javascript
angular6 利用 ngContentOutlet 实现组件位置交换(重排)
Nov 02 Javascript
详解vue 不同环境配置不同的打包命令
Apr 07 Javascript
vue中使用vee-validator完成表单校验方案
Nov 01 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
DOTA2 玩家自创拉野攻略 特色英雄快速成长篇
2020/04/20 DOTA
Yii2处理密码加密及验证的方法
2019/05/12 PHP
php 的多进程操作实践案例分析
2020/02/28 PHP
硬盘浏览程序,保存成网页格式便可使用
2006/12/03 Javascript
js 实现 input type=&quot;file&quot; 文件上传示例代码
2013/08/07 Javascript
《JavaScript DOM 编程艺术》读书笔记之DOM基础
2015/01/09 Javascript
JavaScript中模拟实现jsonp
2015/06/19 Javascript
js淡入淡出的图片轮播效果代码分享
2015/08/24 Javascript
JS实现为排序好的字符串找出重复行的方法
2016/03/02 Javascript
移动端使用localStorage缓存Js和css文的方法(web开发)
2016/09/20 Javascript
微信小程序 wx:key详细介绍
2016/10/28 Javascript
Vue.JS入门教程之自定义指令
2016/12/08 Javascript
JS实现标签滚动切换效果
2017/12/25 Javascript
Vue 应用中结合vux使用微信 jssdk的方法
2018/08/28 Javascript
Vue中使用clipboard实现复制功能
2018/09/05 Javascript
Vuex 单状态库与多模块状态库详解
2018/12/11 Javascript
说说Vue.js中的functional函数化组件的使用
2019/02/12 Javascript
ligerUI的ligerDialog关闭刷新的方法
2019/09/27 Javascript
vue 清空input标签 中file的值操作
2020/07/21 Javascript
[03:22]DOTA2超级联赛专访单车:找到属于自己的英雄
2013/06/08 DOTA
Python中使用wxPython开发的一个简易笔记本程序实例
2015/02/08 Python
从源码解析Python的Flask框架中request对象的用法
2016/06/02 Python
python清除字符串前后空格函数的方法
2018/10/21 Python
Python读取yaml文件的详细教程
2020/07/21 Python
html5在移动端的屏幕适应问题示例探讨
2014/06/15 HTML / CSS
iphoneX 适配客户端H5页面的方法教程
2017/12/08 HTML / CSS
土耳其时尚购物网站:Morhipo
2017/09/04 全球购物
应聘编辑自荐信范文
2014/03/12 职场文书
保护环境建议书400字
2014/05/13 职场文书
2013年最新自荐信范文
2014/06/23 职场文书
小学语文教研活动总结
2014/07/01 职场文书
医学专业大学生求职信
2014/07/12 职场文书
英文版辞职信
2015/02/28 职场文书
学校禁毒宣传活动总结
2015/05/08 职场文书
运动员代表致辞
2015/07/29 职场文书
超市主管竞聘书
2015/09/15 职场文书