vue路由懒加载原理解析--第一部分

5,535 阅读3分钟

引言

Vue Router中提供了解决整体JavaScript文件过大,影响页面加载的方案--路由懒加载

路由懒加载的实现

结合 Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。下面是一段简单的代码。

const Foo = () => import('./Foo.vue')

const router = new VueRouter({
  routes: [
    { path: '/foo', component: Foo }
  ]
})

下面我们就从vue的异步组件和Webpack 的代码分割两个方面去看。

Webpack 的代码分割功能

官方文档 首先可以回顾一下没有代码分割的webpack模块。 接下来就可以来看看使用动态按需的加载的import()是怎么实现的吧。 两步即可:

  1. npm install babel-plugin-dynamic-import-webpack --save
  2. .babelrc配置中增加一个配置"plugins": ["babel-plugin-dynamic-import-webpack"]

然后我就写了下面的两个文件

//index.js
import('./a').then(a => {
    const bar = a.bar;
    bar();
});

//a.js
function bar () {
    return 1;
}

module.exports = {
    bar
}

看一下这两个文件最后webpack打包后的产物 会有两个文件main.js和0.main.js很容易猜到,这个0.main.js也就是按需分割出来的代码,也就是a.js的内容。 这里main.js就只说与普通的webpack模块不一样的地方了。

(function(module, exports, __webpack_require__) {

"use strict";
eval("\n\nnew Promise(function (resolve) {\n  __webpack_require__.e(/*! require.ensure */ 0).then((function (require) {\n    resolve(__webpack_require__(/*! ./a */ \"./src/a.js\"));\n  }).bind(null, __webpack_require__)).catch(__webpack_require__.oe);\n}).then(function (a) {\n  var bar = a.bar;\n  var foo = a.foo;\n  bar();\n  foo();\n});\n\n//# sourceURL=webpack:///./src/index.js?");

/***/ })

格式化一下main.js其实就是变成了下面这样

new Promise(function(resolve) {
    __webpack_require__.e(0)
        .then((function (require) {
            resolve(__webpack_require__("./src/a.js"));
            }).bind(null,__webpack_require__))
        .catch()
}).then(function(a) {
    ...
})

webpack_require.e 函数


/******/ 	__webpack_require__.e = function requireEnsure(chunkId) {
/******/ 		var promises = [];
/******/ 		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 head = document.getElementsByTagName('head')[0];
/******/ 				var script = document.createElement('script');
/******/ 				var onScriptComplete;
/******/
/******/ 				script.charset = 'utf-8';
/******/ 				script.timeout = 120;
/******/ 				script.src = jsonpScriptSrc(chunkId);
/******/
/******/ 				onScriptComplete = function (event) {
/******/ 					// avoid mem leaks in IE.
/******/ 					script.onerror = script.onload = null;
/******/ 					var chunk = installedChunks[chunkId];
/******/ 					if(chunk !== 0) {
/******/ 						错误处理
/******/ 					}
/******/ 				};
/******/ 				head.appendChild(script);
/******/ 			}
/******/ 		}
/******/ 		return Promise.all(promises);
/******/ 	};

基本上就是做了两个部分的工作一个就是创建promise,这个很好理解因为这里必须是要变成promise给后面调用的,另外一个就是创建script标签,因为需要的js文件,这种js的异步加载的方式应该来说是非常多见的了。 不过这里可以发现只有错误的时候有promise的reject执行了,resolve没有在上面代码中执行。如果不在这那么一定就是在0.main.js里面出现了。 下面是0.main.js

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],{
/***/ "./src/a.js":(function(module, exports, __webpack_require__) {

"use strict";
eval('...');

/***/ })

}]);

删除一部分没啥价值的代码后发现,这个文件其实变成了jsonp的调用方式,也就是说,a.js不仅有自己模块的代码,还会去往window["webpackJsonp"]里面把增加一个数组,chunkid和chunk模块的代码

最后就是看一下这个jsonp到底做了什么就可以了

var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
jsonpArray.push = webpackJsonpCallback;

所以说这个push方法其实是被劫持了的,也就是等价于运行了webpackJsonpCallback方法。webpackJsonpCallback则会去运行installedChunks[chunkId][0],也就是promise的resolve。到此整个webpack的代码分割也就梳理的非常清楚了

总结

由于篇幅和时间原因vue异步组件原理的部分将在下个部分中进行分析。