vue多頁面開發和打包的正確姿式

本文大約二千字,看完本文大概須要二十分鐘,動手嘗試須要一小時css

前段時間作項目,技術棧是vue+webpack,主要就是官網首頁加後臺管理系統 根據當時狀況,分析出三種方案html

  1. 一個項目代碼裏面嵌兩個spa應用(官網和後臺系統)
  2. 分開兩套項目源碼
  3. 一套項目源碼裏面就一個spa應用

思考:

  1. 直接否認了一套項目源碼裏一個spa應用(ui樣式會相互覆蓋,若是沒有代碼規範後期比較難維護)
  2. 兩套源碼的話,後臺可能開兩個端口,而後須要用nginx反向代理可能比較麻煩,並且前端開發也比較麻煩麻煩,畢竟須要維護兩個git倉庫,兩套git上線流程,可能會損耗不少時間。
  3. 對本身的技術(盲目)自信,也想嚐嚐鮮,分析出需求也不算很複雜。選了第一種方案,就是多個單頁面應用在一套源碼裏面

上一張多頁面的結構圖 前端

下載vue spa模板

npm install vue-cli -g
vue init webpack multiple-vue-amazing
複製代碼

改造多頁面應用

npm install glob --save-dev
複製代碼

修改src文件夾下面的目錄結構vue

在util.js裏面加入

/* 這裏是添加的部分 ---------------------------- 開始 */

// glob是webpack安裝時依賴的一個第三方模塊,還模塊容許你使用 *等符號, 例如lib/*.js就是獲取lib文件夾下的全部js後綴名的文件
var glob = require('glob')
// 頁面模板
var HtmlWebpackPlugin = require('html-webpack-plugin')
// 取得相應的頁面路徑,由於以前的配置,因此是src文件夾下的pages文件夾
var PAGE_PATH = path.resolve(__dirname, '../src/pages')
// 用於作相應的merge處理
var merge = require('webpack-merge')


//多入口配置
// 經過glob模塊讀取pages文件夾下的全部對應文件夾下的js後綴文件,若是該文件存在
// 那麼就做爲入口處理
exports.entries = function () {
    var entryFiles = glob.sync(PAGE_PATH + '/*/*.js')
    var map = {}
    entryFiles.forEach((filePath) => {
        var filename = filePath.substring(filePath.lastIndexOf('\/') + 1, filePath.lastIndexOf('.'))
        map[filename] = filePath
    })
    return map
}

//多頁面輸出配置
// 與上面的多頁面入口配置相同,讀取pages文件夾下的對應的html後綴文件,而後放入數組中
exports.htmlPlugin = function () {
    let entryHtml = glob.sync(PAGE_PATH + '/*/*.html')
    let arr = []
    entryHtml.forEach((filePath) => {
        let filename = filePath.substring(filePath.lastIndexOf('\/') + 1, filePath.lastIndexOf('.'))
        let conf = {
            // 模板來源
            template: filePath,
            // 文件名稱
            filename: filename + '.html',
            // 頁面模板須要加對應的js腳本,若是不加這行則每一個頁面都會引入全部的js腳本
            chunks: ['manifest', 'vendor', filename],
            inject: true
        }
        if (process.env.NODE_ENV === 'production') {
            conf = merge(conf, {
                minify: {
                    removeComments: true,
                    collapseWhitespace: true,
                    removeAttributeQuotes: true
                },
                chunksSortMode: 'dependency'
            })
        }
        arr.push(new HtmlWebpackPlugin(conf))
    })
    return arr
}
/* 這裏是添加的部分 ---------------------------- 結束 */
複製代碼

webpack.base.conf.js 文件node

/* 修改部分 ---------------- 開始 */
  entry: utils.entries(),
  /* 修改部分 ---------------- 結束 */
複製代碼

webpack.dev.conf.js 文件jquery

/* 註釋這個區域的文件 ------------- 開始 */
    // new HtmlWebpackPlugin({
    //   filename: 'index.html',
    //   template: 'index.html',
    //   inject: true
    // }),
    /* 註釋這個區域的文件 ------------- 結束 */
    new FriendlyErrorsPlugin()

    /* 添加 .concat(utils.htmlPlugin()) ------------------ */
  ].concat(utils.htmlPlugin())
複製代碼

webpack.prod.conf.js 文件webpack

/* 註釋這個區域的內容 ---------------------- 開始 */
    // new HtmlWebpackPlugin({
    //   filename: config.build.index,
    //   template: 'index.html',
    //   inject: true,
    //   minify: {
    //     removeComments: true,
    //     collapseWhitespace: true,
    //     removeAttributeQuotes: true
    //     // more options:
    //     // https://github.com/kangax/html-minifier#options-quick-reference
    //   },
    //   // necessary to consistently work with multiple chunks via CommonsChunkPlugin
    //   chunksSortMode: 'dependency'
    // }),
    /* 註釋這個區域的內容 ---------------------- 結束 */
    // copy custom static assets
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../static'),
        to: config.build.assetsSubDirectory,
        ignore: ['.*']
      }
    ])
    /* 該位置添加 .concat(utils.htmlPlugin()) ------------------- */
  ].concat(utils.htmlPlugin())
複製代碼

引入第三方ui庫

npm install element-ui bootstrap-vue --save
複製代碼

分別在不一樣的頁面引入不一樣的ui index.jsnginx

import BootstrapVue from 'bootstrap-vue'
Vue.use(BootstrapVue)
複製代碼

admin.jsgit

import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
Vue.use(ElementUI)
複製代碼

上面多頁面的配置是參考網上的,並且網上的思路大都很類似,核心就是改多個entry,配置完成了以後,開發的時候也是發現不了問題的,而後大概就開發了一個月,開發完以後對官網進行性能分析時發現,webpack打包的vendor.js網絡加載時間特別長,致使首屏的白屏時間很是長,最終經過-webpack-bundle-analyzer分析獲得告終論github

npm run build --report
複製代碼

你會發現vendor.js包含了index.html和admin.html的共同部分,因此這個vendor包註定會很大很冗餘

解決思路

既然是vendor過大引發加載速度慢,那就分離這個vendor就行了。我是這樣想的,把各個頁面中都使用到的第三方代碼提取至vendor.js中,而後各個頁面中用到的第三方代碼再打包成各自的vendor-x.js,例如現有頁面index.html、admin.html,則最終會打包出vendor.js、vendor-index.js、vendor-admin.js。

webpack.prod.conf.js 文件

new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor-admin',
      chunks: ['vendor'],
      minChunks: function (module, count) {
        return (
          module.resource &&
          /\.js$/.test(module.resource) &&
          module.resource.indexOf(path.join(__dirname, '../node_modules')) === 0 &&
          module.resource.indexOf('element-ui') != -1
        )
      }
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor-index',
      chunks: ['vendor'],
      minChunks: function (module, count) {
        return (
          module.resource &&
          /\.js$/.test(module.resource) &&
          module.resource.indexOf(path.join(__dirname, '../node_modules')) === 0 &&
          module.resource.indexOf('bootstrap-vue') != -1
        )
      }
    }),
複製代碼

再次分析,一切都很ok,vendor.js被分離成了vendor.js、vendor-index、vendor-admin.js

原本覺得解決了CommonsChunkPlugin的分離vendor.js的問題,就能夠了,而後打包出來發現index.html和admin.html都少了一個引入(各自對應的那個vendor-xx.js)

解決方案

這個問題其實就是HtmlWebpackPlugin的問題 把原來的 chunksSortMode: 'dependency'改爲自定義函數的配置,以下

util.js文件

chunksSortMode: function (chunk1, chunk2) {
          var order1 = chunks.indexOf(chunk1.names[0])
          var order2 = chunks.indexOf(chunk2.names[0])
          return order1 - order2
        },
複製代碼

抽離多個頁面之間公共的模塊common-api (2018/4/23)更新

其實多頁面之間抽離公共的文件的場景,中大型項目會用的比較多,最初是看到下面評論說須要抽離common-api的時候會多打包出來common-api.css,而且有同窗私信我遇到一些關於commonChunk的問題,我作一個更新,並提供思路

需求:項目中一些公共的js甚至是css,可複用到每個page裏邊,例如admin.js引用common-api.js,index.js也引用了common-api.js。如今抽離多個頁面之間公共的模塊common-api

添加common目錄

新建一個common/index.js 直接引用一個本地文件js(我這裏用jquery來代替公共js)

引用公共文件

在admin.js index.js裏引用

import $ from '../../common'
console.log($('body'))
複製代碼

打包明顯能發現jquery被打包了兩次,資源浪費了。

解決方案

  • 由於是多頁面,因此必須添加一個公共的入口common-api
  • commonChunkPlugin 抽取common-api(有坑,下面會講)
  • 修改htmlWebpackPlugin的chunks順序

坑就是必須指定chunks 否則會報錯webpack ERROR in CommonsChunkPlugin: While running in normal mode it's not allowed to use a non-entry chunk

一開始有位同窗問我這個錯誤怎麼解決,一開始我也不知道,後來查閱了一些資料,發現只要指定了chunks就能夠解決這個錯誤,貼一段github的issues,感興趣的能夠深刻了解

從新指定util.js 裏面的htmlPlugin的順序

let chunks = filename === 'admin' ?
      ['manifest', 'vendor', 'vendor-admin', 'common-api', filename] :
      ['manifest', 'vendor', 'vendor-index', 'common-api', filename]
複製代碼

最後看看結果,抽離出來了common-api,jq只被加載了一次, 而且並無多打包出來common-api.css,html裏面script的順序也是對的

最終實現

  • 每一個頁面加載各自的chunk
  • 每一個頁面有不一樣的參數
  • 每一個頁面能共享公共chunk
  • 瀏覽器緩存,性能更好
  • 若是還嫌慢的話,開啓gzip
  • 每一個頁面抽離公共函數common-api(更新於2018/4/23)

感想

大功告成了,雖然配置看起來很簡單,不過我當時開發的時候,思考了好久,因此假如你CommonsChunkPlugin和HtmlWebpackPlugin不熟悉或者只會用別人第三方的配置表,估計會踩大坑,好比說,CommonsChunkPlugin不指定chunks,默認是什麼?minChunks大多數人只會寫一個數值,然而自定義一個函數的寫法其實才是最強大的,根據我我的的經驗chunks結合minChunks自定義函數的寫法,能解決幾乎全部CommonsChunkPlugin靈異的事件。

webpack4

雖然本篇文章是基於webpack3來說的,可是webpack4多頁面配置和優化打包的思想其實也是同樣的,放心大膽的使用吧,有坑就解決它。

源碼

本文源碼喜歡點個贊

參考

多頁面配置參考

HtmlWebpackPlugin順序問題

相關文章
相關標籤/搜索