1. 程式人生 > >Vue+ElementUI專案使用webpack輸出MPA

Vue+ElementUI專案使用webpack輸出MPA

目錄

  • 一. 需求分析
  • 二. 原方案分析
  • 三. 多頁面改造3步走
  • 四. 小結

示例程式碼託管在:http://www.github.com/dashnowords/blogs

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

華為雲社群地址:【你要的前端打怪升級指南】

一. 需求分析

為另一個專案提供可嵌入的功能單頁,大部分頁面使用時都是獨立功能頁,個別頁面帶有左側邊欄(相當於3-4個頁面的整合形態),由於資源定位地址的限定,每個頁面打包為單頁後,入口html檔案需要定製命名,且指令碼和樣式檔案需要放在指定的路徑下,公共資源地址也必須替換成特殊字元以適配母系統的呼叫邏輯(比如下面結構中應用jquery.min.js

的路徑可能是{{publicRoot}}/{{publicLib}}/jquery.minjs)。假設原工程中擁有AB這2箇舊頁面,現在需要開發CDE這3個頁面,目錄結構要求如下:

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

二. 原方案分析

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

  1. 將構建過程中需要定製的量提取到config.js檔案中進行統一管理,大致形式如下:

    //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中取出打包需要的設定引數,然後將目標頁面打包為獨立頁面,其他頁面雖然也在工程中,但並不參與打包。

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

上述打包過程在使用中出現了很多問題:

  • 公共依賴沒有剝離,vueElementUI會被打包進每一個單頁面,使得每個打包出的index.js幾乎有1.2MB大小,這種空間浪費是沒必要的。
  • 公共樣式沒有形成獨立檔案,這使得每當有樣式細節發生變更,就需要手動將每個頁面逐一進行重新出包。
  • 頁面增多後在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的打包過程進行優化,本文就不再贅述