6

引言

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则会去运行installedChunkschunkId,也就是promise的resolve。到此整个webpack的代码分割也就梳理的非常清楚了

总结

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


求实亭下
142 声望13 粉丝

有着深度学习技能的前端开发工程师