Vue+ElementUI項目使用webpack輸出MPA【華爲雲分享】

【摘要】 Vue+ElementUI多頁面打包改造css

示例代碼託管在:http://www.github.com/dashnowords/blogshtml

博客園地址:《大史住在大前端》原創博文目錄前端

一. 需求分析

爲另外一個項目提供可嵌入的功能單頁,大部分頁面使用時都是獨立功能頁,個別頁面帶有左側邊欄(至關於3-4個頁面的整合形態),因爲資源定位地址的限定,每一個頁面打包爲單頁後,入口html文件須要定製命名,且腳本和樣式文件須要放在指定的路徑下,公共資源地址也必須替換成特殊字符以適配母系統的調用邏輯(好比下面結構中應用jquery.min.js的路徑多是{{publicRoot}}/{{publicLib}}/jquery.minjs)。假設原工程中擁有AB這2箇舊頁面,如今須要開發CDE這3個頁面,目錄結構要求以下:vue

藍色部分爲舊資源,綠色部分爲新開發需求。node

二. 原方案分析

原方案採用Vue+ElementUI進行開發,構建過程基本是零配置的,開發效率很是高,頁面風格也統一,但零配置的構建過程只能生成SPA模式的應用,因此原方案的作法是:jquery

  1. 將構建過程當中須要定製的量提取到config.js文件中進行統一管理,大體形式以下:webpack

//config.js
module.exports = {
   A:{
       publicPath:'{{publicRoot}}/{{publicLib}}'
       prodFileName:'A.html',
       entryKey:'public/A',
       entryPath:'public/A/A.js'
   },
   B:{
       //...
   }
   //...
}

2. 開發過程當中使用統一的路由文件router.js,打包過程當中在main.js中引用對應頁面的XX.router.spa.js做爲路由,而將其餘頁面註釋掉,打包時傳入命令行參數--key=XXX,key值在打包腳本中被解析後從config.js中取出打包須要的設置參數,而後將目標頁面打包爲獨立頁面,其餘頁面雖然也在工程中,但並不參與打包。git

// 入口文件src/main.js
import router from './pages/C/router.spa';
//import router from './pages/D/router.spa';
//import router from './pages/E/router.spa';

上述打包過程在使用中出現了不少問題:github

  • 公共依賴沒有剝離,vueElementUI會被打包進每個單頁面,使得每一個打包出的index.js幾乎有1.2MB大小,這種空間浪費是不必的。web

  • 公共樣式沒有造成獨立文件,這使得每當有樣式細節發生變動,就須要手動將每一個頁面逐一進行從新出包。

  • 頁面增多後在main.js中會有不少獨立路由,若是開發中進行了跨頁面修改,極可能在main.js中激活的路由爲C頁面路由時,打包時--key參數的值卻傳成了D,這種狀況並不會引發報錯,但事實上構建結果確實錯誤的。

  • 因爲入口文件保持main.js沒有變化,因此在不一樣頁面打包時,結果都輸出在dist目錄下,須要手動與母工程中的地址去匹配,操做繁瑣。

三. 多頁面改造3步走

上面的問題實際上都是由於原方案將一個多頁面開發需求按照單頁面應用來實現而形成的,須要對自動化構建工程進行一些定製。

1.分離webpack配置

本例中開發環境和最終打包的主要差別在於路由上,開發中因爲可能須要進行跨頁面開發,可使用單入口和獨立路由,而進行生產環境構建時則須要輸出多頁面應用,因此首先要作的就是將本來的webpack.config.js文件拆分爲webpack.base.js,webpack.dev.js,webpack.prod.js三個文件,webpack.base.js爲環境無差異的配置,而後依據構建模式的不一樣,使用webpack-merge插件將環境相關的配置與基本配置進行合併:

/*webpack.base.js示例*/
const argv = require('yargs-parser')(process.argv.slice(2));
const env_short = argv.env.all ? 'all' : argv.p ? 'prod':'dev';
const webpackConfig = require(`./config/webpack.${env_short}`);//根據-p屬性加載webpack的dev配置或prod配置
const merge = require('webpack-merge');

//基本配置
const baseConfig = {
   //....
}

//輸出合併後的配置
module.exports = merge(baseConfig, webpackConfig);

webpack.dev.js保持本來的SPA開發的設置便可知足需求。

2. 抽離外部引用

本例中較大的外部應用是vueElementUI,不少開發者一直使用自動化腳手架工具,並無意識到這兩個庫做爲外部依賴該如何引入工程。公共庫的抽離須要在webpack配置中將其填寫在external配置項中:

module.exports = {
//...
 externals:{
     vue:'Vue',
     'element-ui':'ELEMENT'
 },
 //...
}

key爲引用的模塊名,value爲這個模塊引入後對應的全局命名,external配置項的含義是:請不要將這個模塊注入編譯後的JS文件裏,對於源代碼裏出現的任何import/require這個模塊的語句,請將它保留並根據模塊化標準進行依賴方式適配 。

Tips:

  1. Vue作爲外部依賴時有不少構建包,本例中由於使用webpack進行了構建,沒有在線編譯模板的需求,因此不須要引入完整的Vue,而只須要引入壓縮後的只包含運行時的版本vue.runtime.min.js便可。

  2. 外部引入庫時須要注意命名,好比上例中的ELEMENT,開發者一般會填寫爲本身在代碼中使用的ElementUI而引發報錯,當不肯定名稱時,有個簡單的辦法就是找一個CDN的資源看一下,一般代碼最開始都是UMD規範的固定結構,很容易看到關鍵詞(以下圖所示)。

而後將資源的CDN地址或是本地公共庫地址加入到index.html中,你可使用模板語法,而後從html-webpack-plugin插件實例化時傳入定製參數:

<!--html文件模板-->
<body>
 <div id="app"></div>
 <script src="<%= htmlWebpackPlugin.options.vue_path %>"></script>
 <script src="<%= htmlWebpackPlugin.options.elementUI_path %>"></script>
 <script src="<%= htmlWebpackPlugin.options.tpl_entryPath %>/index.js"></script>
</body>
//webpack.prod.js
module.exports = {
   //...
 plugins: [
   new HtmlWebpackPlugin({
     template: 'src/index.html',//生成index.html時依據的模板
     filename: '.....',
     inject:false,
     tpl_entryPath:'....',
     vue_path:'.....',
     elementUI_path:'.....',
   }),
   //new BundleAnalyzerPlugin()
 ],
}

最終打包後生成的index.html文件大體以下:

<body>
 <div id="app"></div>
 <script src="{{publicRoot}}/{{publicLib}}/vue.min.js"></script>
 <script src="{{publicRoot}}/{{publicLib}}/element-ui.js"></script>
 <script src="public/A/A.js"></script>
</body>

若是第三方庫從本地加載,則須要將/node_modules/element-ui/lib/index.js/node_modules/vue/dist/vue.runtime.min.js兩個依賴文件拷貝到lib文件夾中的對應地址,這樣訪問index.html時就能夠之外部依賴的形式將其加載進來。樣式文件的剝離直接使用插件完成便可,webpack4之前的版本使用extract-text-webpack-plugin,從4.0版本後統一使用mini-css-extract-plugin

3. 爲webpack定製多入口

多入口的配置是多頁面應用打包的關鍵,因爲打包結果存在嵌套目錄,因此須要對entry對象的鍵值進行一些定製,打包後的路徑信息是直接經過key值來定製的,同時須要實例化多個HtmlWebpackPlugin來爲每個入口文件生成一個對應的index.html訪問入口,定製參數能夠在實例化時傳入:

//webpack.prod.js
module.exports = {
   entry:{
       'C/index':'./src/pages/C/C.entry.js',
       'DESK/D/index':'./src/pages/D/D.entry.js',
       'DESK/E/index':'./src/pages/E/E.entry.js'
   }
   //...
   plugins:[
      new HtmlWebpackPlugin({...paramsC}),
      new HtmlWebpackPlugin({...paramsD}),
      new HtmlWebpackPlugin({...paramsE}),
   ]
}

固然你能夠將entryplugins數組的組裝過程剝離到其餘文件中,而後直接引用:

固然,每一個頁面的入口文件X.entry.js至關於舊方案中main.js文件中移除被註釋掉的未啓用路由信息後剩餘的部分,它足以支撐每一個單頁獨立被訪問。

四. 小結

經上述改造後,在dist目錄中輸出的結構和需求中public目錄下的結構就保持一致了,並且每一個頁面的index.js文件也縮小到了100K左右。固然你也可使用node.js去編寫一些自動化腳本,將後續的替換過程也自動化,或者繼續對webpack的打包過程進行優化,本文就再也不贅述了。做者:大史不說話