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 相关文章推荐
jquery固定底网站底部菜单效果
Aug 13 Javascript
鼠标滑过出现预览的大图提示效果
Feb 26 Javascript
jQuery 和 CSS 的文本特效插件集锦
Dec 12 Javascript
浅谈angularjs module返回对象的坑(推荐)
Oct 21 Javascript
jquery加载单文件vue组件的方法
Jun 20 jQuery
浅谈angular2 组件的生命周期钩子
Aug 12 Javascript
Angularjs中的验证input输入框只能输入数字和小数点的写法(推荐)
Aug 16 Javascript
从parcel.js打包出错到选择nvm的全部过程
Jan 23 Javascript
浅析java线程中断的办法
Jul 29 Javascript
JS Object.preventExtensions(),Object.seal()与Object.freeze()用法实例分析
Aug 25 Javascript
vue vant中picker组件的使用
Nov 03 Javascript
在vue中嵌入外部网站的实现
Nov 13 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
Zend Framework自定义Helper类相关注意事项总结
2016/03/14 PHP
thinkPHP5.0框架简单配置作用域的方法
2017/03/17 PHP
JavaScript.The.Good.Parts阅读笔记(一)假值与===运算符
2010/11/16 Javascript
JavaScript 放大镜 移动镜片效果代码
2011/05/09 Javascript
Jjcarousellite 实现图片列表滚动的简单实例
2013/11/29 Javascript
探讨JavaScript中声明全局变量三种方式的异同
2013/12/03 Javascript
动态添加option及createElement使用示例
2014/01/26 Javascript
jquery获取radio值实例
2014/10/16 Javascript
深入分析JQuery和JavaScript的异同
2014/10/23 Javascript
Angularjs中使用指令绑定点击事件的方法
2017/03/30 Javascript
Angular 4依赖注入学习教程之Injectable装饰器(六)
2017/06/04 Javascript
EasyUI Tree树组件无限循环的解决方法
2017/09/27 Javascript
vue 开发之路由配置方法详解
2019/12/02 Javascript
[01:29:46]DOTA2上海特级锦标赛C组资格赛#1 OG VS LGD第二局
2016/02/27 DOTA
[46:20]TFT vs Secret Supermajor小组赛C组 BO3 第二场 6.3
2018/06/04 DOTA
Python中使用PIPE操作Linux管道
2015/02/04 Python
python自动化生成IOS的图标
2018/11/13 Python
在python中按照特定顺序访问字典的方法详解
2018/12/14 Python
使用python Telnet远程登录执行程序的方法
2019/01/26 Python
python 使用pygame工具包实现贪吃蛇游戏(多彩版)
2019/10/30 Python
selenium+python配置chrome浏览器的选项的实现
2020/03/18 Python
Python使用socket_TCP实现小文件下载功能
2020/10/09 Python
python+requests实现接口测试的完整步骤
2020/10/27 Python
django中cookiecutter的使用教程
2020/12/03 Python
纯CSS3实现手风琴风格菜单具体步骤
2013/05/06 HTML / CSS
HTML如何让IMG自动适应DIV容器大小的实现方法
2020/02/25 HTML / CSS
英国家具、照明、家居用品网上商店:Wayfair.co.uk
2020/02/13 全球购物
linux比较文件内容的命令是什么
2013/03/04 面试题
计算机开发个人求职信范文
2013/09/26 职场文书
计算机专业职业规划
2014/02/28 职场文书
培训研修方案
2014/06/06 职场文书
爱护花草树木的标语
2014/06/11 职场文书
教师批评与自我批评(群众路线)
2014/10/15 职场文书
2014镇党委书记党建工作汇报材料
2014/11/02 职场文书
2015年社区党务工作总结
2015/04/21 职场文书
2015年社区重阳节活动总结
2015/07/30 职场文书