1. 為什麼需要按需載入?
對於vue單頁應用來講,我們常見的做法把頁面上所有的程式碼都打包到一個bundle.js檔案內,但是隨著專案越來越大,檔案越來越多的情況下,那麼bundle.js檔案也會越來越大,檔案大的時候會導致開啟頁面使用者體驗相對來說會變慢。因此按需載入程式碼是很有必要的,每次開啟某一個頁面的時候,只按需載入那個頁面的程式碼,這樣的話,專案中其他程式碼就不會被載入,這樣的話,bundle.js檔案也不會把所有專案頁面檔案程式碼打包進去,檔案也不會很大。其他的頁面對應的程式碼第一次都是按需載入的,載入完成後,就會在瀏覽器快取中了。
2. 如何使用webpack+vue+router 實現vue頁面按需載入?
還是和之前一樣,在實現功能之前,我們還是看下我們專案中的整個目錄架構如下:
### 目錄結構如下: demo1 # 工程名 | |--- dist # 打包後生成的目錄檔案 | |--- node_modules # 所有的依賴包 | |--- app | | |---index | | | |-- views # 存放所有vue頁面檔案 | | | | |-- home.vue | | | | |-- index.vue | | | | |-- xxx.vue | | | |-- components # 存放vue公用的元件 | | | |-- app.js # vue入口配置檔案 | | | |-- router.js # 路由配置檔案 | |--- views | | |-- index.html # html檔案 | |--- webpack.config.js # webpack配置檔案 | |--- .gitignore | |--- README.md | |--- package.json | |--- .babelrc # babel轉碼檔案
webpack中提供了 require.ensure()來實現頁面按需載入。以前我們引入路由的時候都是通過 import 這樣的方式引入的,但是使用import引入vue檔案它是不會進行頁面按需載入的。首先可以看下我們路由頁面。如下:
app/index/router.js 之前程式碼使用import引入如下:
import Vue from 'vue'; import VueRouter from 'vue-router'; // 引入元件 import home from './views/home'; import xxx from './views/xxx'; // 告訴 vue 使用 vueRouter Vue.use(VueRouter); const routes = [ { path: '/home', component: home }, { path: '/xxx', component: xxx }, { path: '*', // 其他沒有的頁面都重定向到 home頁面去 redirect: '/home' } ] var router = new VueRouter({ routes: routes }); export default router;
如上程式碼,每一個vue單頁面都是使用import引用進來的,它不會進行頁面懶載入的,所以在打包的時候都會打包到bundle.js檔案內,因此檔案會變得越來越大。因此我們需要使用 require.ensure()實現按需載入。
2.1、學習使用 require.ensure()
webpack 在編譯時,會靜態地解析程式碼中的 require.ensure(),同時將模組新增到一個分開的 chunk 當中。這個新的 chunk 會被 webpack 通過 jsonp 來按需載入。
基本使用語法如下:require.ensure(dependencies: String[], callback: function(require), chunkName: String);
引數dependencies:該引數是一個字串陣列,通過這個引數,在所有的回撥函式的程式碼被執行前,我們可以將所有需要用到的模組進行宣告。
引數callback: 當所有的依賴都載入完成後,webpack會執行這個回撥函式。require 物件的一個實現會作為一個引數傳遞給這個回撥函式。因此,我們可以進一步 require() 依賴和其它模組提供下一步的執行。
引數chunkName: chunkName 是提供給這個特定的 require.ensure() 的 chunk 的名稱。
下面我們來看一個demo,在app/index 下新建一個資料夾為js。在js資料夾內分別新建a.js和b.js,程式碼分別如下:
a.js 程式碼如下:
alert('aaaaaa');
b.js 程式碼如下:
alert('bbbbbbb');
然後入口檔案app.js 程式碼如下:
require('./js/a'); require.ensure([], function(require) { require('./js/b'); });
webpack.config.js 打包如下:
module.exports = { // 入口檔案 entry: { main: './app/index/app.js' }, output: { filename: process.env.NODE_ENV === 'production' ? '[name].[contenthash].js' : 'bundle.js', // 將輸出的檔案都放在dist目錄下 path: path.resolve(__dirname, 'dist') } }
通過執行這個專案的 webpack 構建,我們發現 webpack 建立了2個新的檔案束, bundle.js 和 0.bundle.js。如下圖所示:
app.js 和 a.js 被打包進 bundle.js.
其中 0.bundle.js 程式碼如下:
(window.webpackJsonp=window.webpackJsonp||[]).push([[0],{"./app/index/js/b.js":function(b,n){alert("bbbbbbb")}}]);
bundle.js 程式碼如下:
!function(u){function e(e){for(var n,t,r=e[0],o=e[1],a=0,i=[];a<r.length;a++)t=r[a],p[t]&&i.push(p[t][0]),p[t]=0;for(n in o)Object.prototype.hasOwnProperty.call(o,n)&&(u[n]=o[n]);for(l&&l(e);i.length;)i.shift()()}var t={},p={main:0};function c(e){if(t[e])return t[e].exports;var n=t[e]={i:e,l:!1,exports:{}};return u[e].call(n.exports,n,n.exports,c),n.l=!0,n.exports}c.e=function(a){var e=[],t=p[a];if(0!==t)if(t)e.push(t[2]);else{var n=new Promise(function(e,n){t=p[a]=[e,n]});e.push(t[2]=n);var r,o=document.getElementsByTagName("head")[0],i=document.createElement("script");i.charset="utf-8",i.timeout=120,c.nc&&i.setAttribute("nonce",c.nc),i.src=c.p+""+a+".bundle.js",r=function(e){i.onerror=i.onload=null,clearTimeout(u);var n=p[a];if(0!==n){if(n){var t=e&&("load"===e.type?"missing":e.type),r=e&&e.target&&e.target.src,o=new Error("Loading chunk "+a+" failed.\n("+t+": "+r+")");o.type=t,o.request=r,n[1](o)}p[a]=void 0}};var u=setTimeout(function(){r({type:"timeout",target:i})},12e4);i.onerror=i.onload=r,o.appendChild(i)}return Promise.all(e)},c.m=u,c.c=t,c.d=function(e,n,t){c.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:t})},c.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},c.t=function(n,e){if(1&e&&(n=c(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var t=Object.create(null);if(c.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var r in n)c.d(t,r,function(e){return n[e]}.bind(null,r));return t},c.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return c.d(n,"a",n),n},c.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},c.p="",c.oe=function(e){throw e};var n=window.webpackJsonp=window.webpackJsonp||[],r=n.push.bind(n);n.push=e,n=n.slice();for(var o=0;o<n.length;o++)e(n[o]);var l=r;c(c.s="./app/index/app.js")}({"./app/index/app.js":function(e,n,t){t("./app/index/js/a.js"),t.e(0).then(function(e){t("./app/index/js/b.js")}.bind(null,t)).catch(t.oe)},"./app/index/js/a.js":function(e,n){alert("aaaaaa")}});
如上程式碼分析可以看到,通過 require.ensure([], function(require) { require('./js/b'); }) 動態載入的b函式程式碼會單獨生成一個0.bundle.js檔案,如果頁面有多個 require.ensure 這個動態載入檔案的話,它就會生成多個xx.bundle.js檔案了,因此對於vue路由頁面來講可以使用這種方法來動態載入vue頁面了。
如上webpack.config.js 檔案,我們可以再加個配置項 chunkFilename,該配置項,可以指定chunk的名字,如下配置:
module.exports = { // 入口檔案 entry: { main: './app/index/app.js' }, output: { filename: process.env.NODE_ENV === 'production' ? '[name].[contenthash].js' : 'bundle.js', // 將輸出的檔案都放在dist目錄下 path: path.resolve(__dirname, 'dist'), chunkFilename: 'chunks/[name].chunk.js' } }
app/index/app.js 程式碼如下,加了第三個引數, 名字為b:
require('./js/a'); require.ensure([], function(require) { require('./js/b'); }, 'b');
然後會在 dist/chunks 下生成 b.chunk.js, 如下圖所示:
2.2. 在vue-router中使用require.ensure() 方法動態載入不同的vue頁面。
因此我們現在可以在 app/index/router.js 裡面如下對vue頁面檔案進行按需載入了,如下程式碼:
import Vue from 'vue'; import VueRouter from 'vue-router'; // 告訴 vue 使用 vueRouter Vue.use(VueRouter); const routes = [ { path: '/home', name: 'home', component: resolve => require.ensure([], () => resolve(require('./views/home')), 'home') }, { path: '/xxx', name: 'xxx', // component: resolve => require(['./views/xxx'], resolve) component: resolve => require.ensure([], () => resolve(require('./views/xxx')), 'xxx') }, { path: '*', // 其他沒有的頁面都重定向到 home頁面去 redirect: '/home' } ] var router = new VueRouter({ base: '/app/index', // 配置單頁應用的基路徑 routes: routes }); export default router;
webpack.config.js 程式碼配置如下:
module.exports = { // 入口檔案 entry: { main: './app/index/app.js' }, output: { filename: process.env.NODE_ENV === 'production' ? '[name].[contenthash].js' : 'bundle.js', // 將輸出的檔案都放在dist目錄下 path: path.resolve(__dirname, 'dist'), chunkFilename: 'chunks/[name].chunk.js' } }
在dist/chunks 目錄下會生成如下檔案,如下圖所示