Vue Router中提供了解决总体JavaScript文件过大,影响页面加载的方案--路由懒加载。本文是但愿从路由懒加载的实现去分析出懒加载总体的实现的原理细节。html
结合 Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。下面是一段简单的代码。vue
const Foo = () => import('./Foo.vue') const router = new VueRouter({ routes: [ { path: '/foo', component: Foo } ] })
下面咱们就从vue的异步组件和Webpack 的代码分割两个方面去看。webpack
官方文档
首先能够回顾一下没有代码分割的webpack模块。
接下来就能够来看看使用动态按需的加载的import()是怎么实现的吧。
两步便可:web
npm install babel-plugin-dynamic-import-webpack --save
"plugins": ["babel-plugin-dynamic-import-webpack"]
而后我就写了下面的两个文件npm
//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模块不同的地方了。json
(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其实就是变成了下面这样segmentfault
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 = 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.jsapi
(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到底作了什么就能够了promise
var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); jsonpArray.push = webpackJsonpCallback;
因此说这个push方法实际上是被劫持了的,也就是等价于运行了webpackJsonpCallback方法。webpackJsonpCallback则会去运行installedChunkschunkId,也就是promise的resolve。到此整个webpack的代码分割也就梳理的很是清楚了
因为篇幅和时间缘由vue异步组件原理的部分将在下个部分中进行分析。