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異步組件原理的部分將在下個部分中進行分析。