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 相关文章推荐
IE和Firefox的Javascript兼容性总结[推荐收藏]
Oct 19 Javascript
JS按字节截取字符长度实例
Nov 20 Javascript
Jquery ajaxStart()与ajaxStop()方法(实例讲解)
Dec 18 Javascript
JS+DIV实现鼠标划过切换层效果的方法
May 25 Javascript
JavaScript实现添加、查找、删除元素
Jul 02 Javascript
11种ASP连接数据库的方法
Sep 18 Javascript
BootStrap中按钮点击后被禁用按钮的最佳实现方法
Sep 23 Javascript
AngularJS入门教程之模块化操作用法示例
Nov 02 Javascript
vue图片加载与显示默认图片实例代码
Mar 16 Javascript
vue.js中npm安装教程图解
Apr 10 Javascript
JavaScript实现队列结构过程
Dec 06 Javascript
vue使用refs获取嵌套组件中的值过程
Mar 31 Vue.js
微信小程序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开发框架总结收藏
2008/04/24 PHP
PHP闭包实例解析
2014/09/08 PHP
Yii2-GridView 中让关联字段带搜索和排序功能示例
2017/01/21 PHP
PHP严重致命错误处理:php Fatal error: Cannot redeclare class or function
2017/02/05 PHP
Laravel中encrypt和decrypt的实现方法
2017/09/24 PHP
JS实现浏览器菜单命令
2006/09/05 Javascript
JS是否可以跨文件同时控制多个iframe页面的应用技巧
2007/12/16 Javascript
javascript innerHTML使用分析
2010/12/03 Javascript
jquery左右滚动焦点图banner图片鼠标经过显示上下页按钮
2013/10/11 Javascript
各种页面定时跳转(倒计时跳转)代码总结
2013/10/24 Javascript
node.js中的fs.chown方法使用说明
2014/12/16 Javascript
jQuery中DOM树操作之复制元素的方法
2015/01/23 Javascript
window.onerror()的用法与实例分析
2016/01/27 Javascript
jquery制做精致的倒计时特效
2016/06/13 Javascript
KnockoutJS 3.X API 第四章之数据控制流component绑定
2016/10/10 Javascript
基于JQuery实现的跑马灯效果(文字无缝向上翻动)
2016/12/02 Javascript
javascript history对象详解
2017/02/09 Javascript
通过一个简单的例子学会vuex与模块化
2017/11/22 Javascript
express默认日志组件morgan的方法
2018/04/05 Javascript
node前端开发模板引擎Jade的入门
2018/05/11 Javascript
vue实现一个炫酷的日历组件
2018/10/08 Javascript
JS前端广告拦截实现原理解析
2020/02/17 Javascript
Layui弹框中数据表格中可双击选择一条数据的实现
2020/05/06 Javascript
python opencv之SIFT算法示例
2018/02/24 Python
Flask框架WTForm表单用法示例
2018/07/20 Python
Python 做曲线拟合和求积分的方法
2018/12/29 Python
从numpy数组中取出满足条件的元素示例
2019/11/26 Python
Python3的socket使用方法详解
2020/02/18 Python
THE OUTNET英国官网:国际设计师品牌折扣网站
2016/08/14 全球购物
生物科学专业职业规划书范文
2014/02/11 职场文书
村党的群众路线教育实践活动工作总结
2014/10/25 职场文书
毕业实习感受与体会
2015/05/26 职场文书
社区志愿服务活动感想
2015/08/07 职场文书
信息技术远程培训心得体会
2016/01/09 职场文书
2016年师德先进个人事迹材料
2016/02/29 职场文书
python 离散点图画法的实现
2022/04/01 Python