Vue專案Webpack優化實踐,構建效率提高50%

我是你的超級英雄發表於2018-12-23

       公司的前端專案使用Vue框架,Vue框架使用Webpack進行構建,隨著專案不斷迭代,專案逐漸變得龐大,然而專案的構建速度隨之變得緩慢,於是對Webpack構建進行優化變得刻不容緩。經過不斷的摸索和實踐,通過以下方法優化後,專案的構建速度提高了50%。現將相關優化方法進行總結分享。

1、縮小檔案的搜尋範圍

1.1、優化Loader配置

       由於Loader對檔案的轉換操作很耗時,所以需要讓儘可能少的檔案被Loader處理。我們可以通過以下3方面優化Loader配置:(1)優化正則匹配(2)通過cacheDirectory選項開啟快取(3)通過include、exclude來減少被處理的檔案。實踐如下:

專案原配置:

{
  test: /\.js$/,
  loader: 'babel-loader',
  include: [resolve('src'), resolve('test')]
},複製程式碼

優化後配置:

{
  // 1、如果專案原始碼中只有js檔案,就不要寫成/\.jsx?$/,以提升正規表示式的效能
  test: /\.js$/,
  // 2、babel-loader支援快取轉換出的結果,通過cacheDirectory選項開啟
  loader: 'babel-loader?cacheDirectory',
  // 3、只對專案根目錄下的src 目錄中的檔案採用 babel-loader
  include: [resolve('src')]
},
複製程式碼

1.2、優化resolve.modules配置

       resolve.modules 用於配置Webpack去哪些目錄下尋找第三方模組。resolve.modules的預設值是[node modules],含義是先去當前目錄的/node modules目錄下去找我們想找的模組,如果沒找到,就去上一級目錄../node modules中找,再沒有就去../ .. /node modules中找,以此類推,這和Node.js的模組尋找機制很相似。當安裝的第三方模組都放在專案根目錄的./node modules目錄下時,就沒有必要按照預設的方式去一層層地尋找,可以指明存放第三方模組的絕對路徑,以減少尋找。

優化後配置:

resolve: {
// 使用絕對路徑指明第三方模組存放的位置,以減少搜尋步驟
modules: [path.resolve(__dirname,'node_modules')]
},
複製程式碼

1.3、優化resolve.alias配置

       resolve.alias配置項通過別名來將原匯入路徑對映成一個新的匯入路徑。

如專案中的配置使用:

alias: {
  '@': resolve('src'),
},
// 通過以上的配置,引用src底下的common.js檔案,就可以直接這麼寫
import common from '@/common.js';
複製程式碼

1.4、優化resolve.extensions配置 

       在匯入語句沒帶檔案字尾時,Webpack 會在自動帶上字尾後去嘗試詢問檔案是否存在。預設是:extensions :[‘. js ‘,’. json ’] 。也就是說,當遇到require ( '. /data ’)這樣的匯入語句時,Webpack會先去尋找./data .js 檔案,如果該檔案不存在,就去尋找./data.json 檔案,如果還是找不到就報錯。如果這個列表越長,或者正確的字尾越往後,就會造成嘗試的次數越多,所以 resolve .extensions 的配置也會影響到構建的效能。 

 優化措施:

 • 字尾嘗試列表要儘可能小,不要將專案中不可能存在的情況寫到字尾嘗試列表中。

 • 頻率出現最高的檔案字尾要優先放在最前面,以做到儘快退出尋找過程。

 • 在原始碼中寫匯入語句時,要儘可能帶上字尾,從而可以避免尋找過程。例如在確定的情況下將 require(’. /data ’)寫成require(’. /data.json ’),可以結合enforceExtension 和 enforceModuleExtension開啟使用來強制開發者遵守這條優化

1.5、優化resolve.noParse配置

       noParse配置項可以讓Webpack忽略對部分沒采用模組化的檔案的遞迴解析和處理,這 樣做的好處是能提高構建效能。原因是一些庫如jQuery、ChartJS 龐大又沒有采用模組化標準,讓Webpack去解析這些檔案既耗時又沒有意義。 noParse是可選的配置項,型別需要是RegExp 、[RegExp]、function中的一種。例如,若想要忽略jQuery 、ChartJS ,則優化配置如下:

// 使用正規表示式 
noParse: /jquerylchartjs/ 
// 使用函式,從 Webpack3.0.0開始支援 
noParse: (content)=> { 
// 返回truefalse 
return /jquery|chartjs/.test(content); 
}
複製程式碼

2、減少冗餘程式碼

        babel-plugin-transform-runtime 是Babel官方提供的一個外掛,作用是減少冗餘的程式碼 。 Babel在將ES6程式碼轉換成ES5程式碼時,通常需要一些由ES5編寫的輔助函式來完成新語法的實現,例如在轉換 class extent 語法時會在轉換後的 ES5 程式碼裡注入 extent 輔助函式用於實現繼承。babel-plugin-transform-runtime會將相關輔助函式進行替換成匯入語句,從而減小babel編譯出來的程式碼的檔案大小。

3、使用HappyPack多程式解析和處理檔案

       由於有大量檔案需要解析和處理,所以構建是檔案讀寫和計算密集型的操作,特別是當檔案數量變多後,Webpack構建慢的問題會顯得更為嚴重。執行在 Node.之上的Webpack是單執行緒模型的,也就是說Webpack需要一個一個地處理任務,不能同時處理多個任務。Happy Pack ( https://github.com/amireh/happypack )就能讓Webpack做到這一點,它將任務分解給多個子程式去併發執行,子程式處理完後再將結果傳送給主程式。

專案中HappyPack使用配置:

(1)HappyPack外掛安裝:
    $ npm i -D happypack
(2)webpack.base.conf.js 檔案對module.rules進行配置
    module: {
     rules: [
      {
        test: /\.js$/,
        // 將對.js 檔案的處理轉交給 id 為 babel 的HappyPack例項
          use:['happypack/loader?id=babel'],
          include: [resolve('src'), resolve('test'),   
            resolve('node_modules/webpack-dev-server/client')],
        // 排除第三方外掛
          exclude:path.resolve(__dirname,'node_modules'),
        },
        {
          test: /\.vue$/,
          use: ['happypack/loader?id=vue'],
        },
      ]
    },
(3)webpack.prod.conf.js 檔案進行配置    const HappyPack = require('happypack');
    // 構造出共享程式池,在程式池中包含5個子程式
    const HappyPackThreadPool = HappyPack.ThreadPool({size:5});
    plugins: [
       new HappyPack({
         // 用唯一的識別符號id,來代表當前的HappyPack是用來處理一類特定的檔案
         id:'vue',
         loaders:[
           {
             loader:'vue-loader',
             options: vueLoaderConfig
           }
         ],
         threadPool: HappyPackThreadPool,
       }),

       new HappyPack({
         // 用唯一的識別符號id,來代表當前的HappyPack是用來處理一類特定的檔案
         id:'babel',
         // 如何處理.js檔案,用法和Loader配置中一樣
         loaders:['babel-loader?cacheDirectory'],
         threadPool: HappyPackThreadPool,
       }),
    ]
複製程式碼

 4、使用ParallelUglifyPlugin多程式壓縮程式碼檔案

       由於壓縮JavaScript 程式碼時,需要先將程式碼解析成用 Object 抽象表示的 AST 語法樹,再去應用各種規則分析和處理AST ,所以導致這個過程的計算量巨大,耗時非常多。當Webpack有多個JavaScript 檔案需要輸出和壓縮時,原本會使用UglifyJS去一個一個壓縮再輸出,但是ParallelUglifyPlugin會開啟多個子程式,將對多個檔案的壓縮工作分配給多個子程式去完成,每個子程式其實還是通過UglifyJS去壓縮程式碼,但是變成了並行執行。所以 ParallelUglify Plugin能更快地完成對多個檔案的壓縮工作。

 專案中ParallelUglifyPlugin使用配置:

(1)ParallelUglifyPlugin外掛安裝:
     $ npm i -D webpack-parallel-uglify-plugin
(2)webpack.prod.conf.js 檔案進行配置
    const ParallelUglifyPlugin =require('webpack-parallel-uglify-plugin');
    plugins: [
    new ParallelUglifyPlugin({
      cacheDir: '.cache/',
      uglifyJs:{
        compress: {
          warnings: false
        },
        sourceMap: true
      }
     }),
    ]
複製程式碼

5、使用自動重新整理 

       藉助自動化的手段,在監聽到本地原始碼檔案發生變化時,自動重新構建出可執行的程式碼後再控制瀏覽器重新整理。Webpack將這些功能都內建了,並且提供了多種方案供我們選擇。

 專案中自動重新整理的配置:

devServer: {
  watchOptions: {
    // 不監聽的檔案或資料夾,支援正則匹配
    ignored: /node_modules/,
    // 監聽到變化後等300ms再去執行動作
    aggregateTimeout: 300,
    // 預設每秒詢問1000次
    poll: 1000
  }
},
複製程式碼

相關優化措施: 

(1)配置忽略一些不監聽的一些檔案,如:node_modules。 

(2)watchOptions.aggregateTirneout 的值越大效能越好,因為這能降低重新構建的頻率。

(3) watchOptions.poll 的值越小越好,因為這能降低檢查的頻率

6、開啟模組熱替換 

       DevServer 還支援一種叫做模組熱替換( Hot Module Replacement )的技術可在不重新整理整個網頁的情況下做到超靈敏實時預覽。原理是在一個原始碼發生變化時,只需重新編譯發生變化的模組,再用新輸出的模組替換掉瀏覽器中對應的老模組 。模組熱替換技術在很大程度上提升了開發效率和體驗 。 

專案中模組熱替換的配置:

devServer: {
  hot: true,
},
plugins: [
  new webpack.HotModuleReplacementPlugin(),
// 顯示被替換模組的名稱
  new webpack.NamedModulesPlugin(), // HMR shows correct file names
]
複製程式碼

7、提取公共程式碼 

        如果每個頁面的程式碼都將這些公共的部分包含進去,則會造成以下問題 : 

 • 相同的資源被重複載入,浪費使用者的流量和伺服器的成本。

 • 每個頁面需要載入的資源太大,導致網頁首屏載入緩慢,影響使用者體驗。 

       如果將多個頁面的公共程式碼抽離成單獨的檔案,就能優化以上問題 。Webpack內建了專門用於提取多個Chunk中的公共部分的外掛CommonsChunkPlugin。 

專案中CommonsChunkPlugin的配置:

// 所有在 package.json 裡面依賴的包,都會被打包進 vendor.js 這個檔案中。
new webpack.optimize.CommonsChunkPlugin({
  name: 'vendor',
  minChunks: function(module, count) {
    return (
      module.resource &&
      /\.js$/.test(module.resource) &&
      module.resource.indexOf(
        path.join(__dirname, '../node_modules')
      ) === 0
    );
  }
}),
// 抽取出程式碼模組的對映關係
new webpack.optimize.CommonsChunkPlugin({
  name: 'manifest',
  chunks: ['vendor']
}),
複製程式碼

8、按需載入程式碼 

       通過vue寫的單頁應用時,可能會有很多的路由引入。當打包構建的時候,javascript包會變得非常大,影響載入。如果我們能把不同路由對應的元件分割成不同的程式碼塊,然後當路由被訪問的時候才載入對應的元件,這樣就更加高效了。這樣會大大提高首屏顯示的速度,但是可能其他的頁面的速度就會降下來。 

專案中路由按需載入(懶載入)的配置:

const Foo = () => import('./Foo.vue')
const router = new VueRouter({
  routes: [
    { path: '/foo', component: Foo }
  ]
})複製程式碼

9、優化SourceMap 

       我們在專案進行打包後,會將開發中的多個檔案程式碼打包到一個檔案中,並且經過壓縮,去掉多餘的空格,且babel編譯化後,最終會用於線上環境,那麼這樣處理後的程式碼和原始碼會有很大的差別,當有bug的時候,我們只能定位到壓縮處理後的程式碼位置,無法定位到開發環境中的程式碼,對於開發不好調式,因此sourceMap出現了,它就是為了解決不好調式程式碼問題的。

 SourceMap的可選值如下:

Vue專案Webpack優化實踐,構建效率提高50%

開發環境推薦: cheap-module-eval-source-map 

生產環境推薦: cheap-module-source-map 

原因如下: 

1. 原始碼中的列資訊是沒有任何作用,因此我們打包後的檔案不希望包含列相關資訊,只有行資訊能建立打包前後的依賴關係。因此不管是開發環境或生產環境,我們都希望新增cheap的基本型別來忽略打包前後的列資訊。 

2. 不管是開發環境還是正式環境,我們都希望能定位到bug的原始碼具體的位置,比如說某個vue檔案報錯了,我們希望能定位到具體的vue檔案,因此我們也需要module配置。 

3. 我們需要生成map檔案的形式,因此我們需要增加source-map屬性。 

4. 我們介紹了eval打包程式碼的時候,知道eval打包後的速度非常快,因為它不生成map檔案,但是可以對eval組合使用 eval-source-map使用會將map檔案以DataURL的形式存在打包後的js檔案中。在正式環境中不要使用 eval-source-map, 因為它會增加檔案的大小,但是在開發環境中,可以試用下,因為他們打包的速度很快。

10、構建結果輸出分析 

       Webpack輸出的程式碼可讀性非常差而且檔案非常大,讓我們非常頭疼。為了更簡單、直觀地分析輸出結果,社群中出現了許多視覺化分析工具。這些工具以圖形的方式將結果更直觀地展示出來,讓我們快速瞭解問題所在。接下來講解vue專案中用到的分析工具:webpack-bundle-analyzer 。

專案中在webpack.prod.conf.js進行配置:

if (config.build.bundleAnalyzerReport) {
  var BundleAnalyzerPlugin =   require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
  webpackConfig.plugins.push(new BundleAnalyzerPlugin());
}
執行 $ npm run build --report 後生成分析報告如下:複製程式碼

Vue專案Webpack優化實踐,構建效率提高50%



相關文章