Vue項目Webpack優化實踐,構建效率提升50%

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

一、縮小文件的搜索範圍

1.一、優化Loader配置

     
因爲Loader對文件的轉換操做很耗時,因此須要讓儘量少的文件被Loader處理。咱們能夠經過如下3方面優化Loader配置:(1)優化正則匹配(2)經過cacheDirectory選項開啓緩存(3)經過include、exclude來減小被處理的文件。實踐以下:
項目原配置:前端

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

優化後配置:vue

{
  // 一、若是項目源碼中只有js文件,就不要寫成/\.jsx?$/,以提高正則表達式的性能
  test: /\.js$/,
  // 二、babel-loader支持緩存轉換出的結果,經過cacheDirectory選項開啓
  loader: 'babel-loader?cacheDirectory',
  // 三、只對項目根目錄下的src 目錄中的文件採用 babel-loader
  include: [resolve('src')]
},

1.二、優化resolve.modules配置

     
resolve.modules 用於配置Webpack去哪些目錄下尋找第三方模塊。resolve.modules的默認值是[node modules],含義是先去當前目錄的/node modules目錄下去找咱們想找的模塊,若是沒找到,就去上一級目錄../node modules中找,再沒有就去../ .. /node modules中找,以此類推,這和Node.js的模塊尋找機制很類似。當安裝的第三方模塊都放在項目根目錄的./node modules目錄下時,就沒有必要按照默認的方式去一層層地尋找,能夠指明存放第三方模塊的絕對路徑,以減小尋找。
優化後配置:java

resolve: {
// 使用絕對路徑指明第三方模塊存放的位置,以減小搜索步驟
modules: [path.resolve(__dirname,'node_modules')]
},

1.三、優化resolve.alias配置

resolve.alias配置項經過別名來將原導入路徑映射成一個新的導入路徑。
如項目中的配置使用:node

alias: {
  '@': resolve('src'),
},
// 經過以上的配置,引用src底下的common.js文件,就能夠直接這麼寫
import common from '@/common.js';

1.四、優化resolve.extensions配置

     
在導入語句沒帶文件後綴時,Webpack 會在自動帶上後綴後去嘗試詢問文件是否存在。默認是:extensions:[‘. js ‘,’. json ’] 。也就是說,當遇到require( '. /data ’)這樣的導入語句時,Webpack會先去尋找./data .js 文件,若是該文件不存在,就去尋找./data.json文件,若是仍是找不到就報錯。若是這個列表越長,或者正確的後綴越日後,就會形成嘗試的次數越多,因此 resolve.extensions 的配置也會影響到構建的性能。  
優化措施: 
• 後綴嘗試列表要儘量小,不要將項目中不可能存在的狀況寫到後綴嘗試列表中。
• 頻率出現最高的文件後綴要優先放在最前面,以作到儘快退出尋找過程。
• 在源碼中寫導入語句時,要儘量帶上後綴,從而能夠避免尋找過程。例如在肯定的狀況下將 require(’. /data ’)寫成require(’. /data.json ’),能夠結合enforceExtension 和 enforceModuleExtension開啓使用來強制開發者遵照這條優化**jquery

1.五、優化resolve.noParse配置

   
noParse配置項可讓Webpack忽略對部分沒采用模塊化的文件的遞歸解析和處理,這 樣作的好處是能提升構建性能。緣由是一些庫如jQuery、ChartJS 龐大又沒有采用模塊化標準,讓Webpack去解析這些文件既耗時又沒有意義。noParse是可選的配置項,類型須要是RegExp 、[RegExp]、function中的一種。例如,若想要忽略jQuery 、ChartJS ,則優化配置以下:webpack

// 使用正則表達式 
noParse: /jquerylchartjs/ 
// 使用函數,從 Webpack3.0.0開始支持 
noParse: (content)=> { 
// 返回true或false 
return /jquery|chartjs/.test(content); 
}

二、減小冗餘代碼

babel-plugin-transform-runtime 是Babel官方提供的一個插件,做用是減小冗餘的代碼 。 Babel在將ES6代碼轉換成ES5代碼時,一般須要一些由ES5編寫的輔助函數來完成新語法的實現,例如在轉換 class extent 語法時會在轉換後的 ES5 代碼裏注入 extent 輔助函數用於實現繼承。babel-plugin-transform-runtime會將相關輔助函數進行替換成導入語句,從而減少babel編譯出來的代碼的文件大小。git

三、使用HappyPack多進程解析和處理文件

因爲有大量文件須要解析和處理,因此構建是文件讀寫和計算密集型的操做,特別是當文件數量變多後,Webpack構建慢的問題會顯得更爲嚴重。運行在 Node.之上的Webpack是單線程模型的,也就是說Webpack須要一個一個地處理任務,不能同時處理多個任務。Happy Pack ( https://github.com/amireh/hap... )就能讓Webpack作到這一點,它將任務分解給多個子進程去併發執行,子進程處理完後再將結果發送給主進程。
項目中HappyPack使用配置:github

(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,
       }),
    ]

四、使用ParallelUglifyPlugin多進程壓縮代碼文件

因爲壓縮JavaScript 代碼時,須要先將代碼解析成用 Object 抽象表示的 AST 語法樹,再去應用各類規則分析和處理AST ,因此致使這個過程的計算量巨大,耗時很是多。當Webpack有多個JavaScript 文件須要輸出和壓縮時,本來會使用UglifyJS去一個一個壓縮再輸出,可是ParallelUglifyPlugin會開啓多個子進程,將對多個文件的壓縮工做分配給多個子進程去完成,每一個子進程其實仍是經過UglifyJS去壓縮代碼,可是變成了並行執行。因此 ParallelUglify Plugin能更快地完成對多個文件的壓縮工做。 
項目中ParallelUglifyPlugin使用配置:web

(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
      }
     }),
    ]

五、使用自動刷新

藉助自動化的手段,在監聽到本地源碼文件發生變化時,自動從新構建出可運行的代碼後再控制瀏覽器刷新。Webpack將這些功能都內置了,而且提供了多種方案供咱們選擇。

項目中自動刷新的配置:

devServer: {
  watchOptions: {
    // 不監聽的文件或文件夾,支持正則匹配
    ignored: /node_modules/,
    // 監聽到變化後等300ms再去執行動做
    aggregateTimeout: 300,
    // 默認每秒詢問1000次
    poll: 1000
  }
},

相關優化措施: 
(1)配置忽略一些不監聽的一些文件,如:node_modules。
(2)watchOptions.aggregateTirneout 的值越大性能越好,由於這能下降從新構建的頻率。
(3) watchOptions.poll 的值越小越好,由於這能下降檢查的頻率。

六、開啓模塊熱替換

DevServer 還支持一種叫作模塊熱替換( Hot Module Replacement )的技術可在不刷新整個網頁的狀況下作到超靈敏實時預覽。原理是在一個源碼發生變化時,只需從新編譯發生變化的模塊,再用新輸出的模塊替換掉瀏覽器中對應的老模塊 。模塊熱替換技術在很大程度上提高了開發效率和體驗 。
項目中模塊熱替換的配置:

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

七、提取公共代碼

若是每一個頁面的代碼都將這些公共的部分包含進去,則會形成如下問題 :  
• 相同的資源被重複加載,浪費用戶的流量和服務器的成本。 
• 每一個頁面須要加載的資源太大,致使網頁首屏加載緩慢,影響用戶體驗。        
若是將多個頁面的公共代碼抽離成單獨的文件,就能優化以上問題 。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']
}),

八、按需加載代碼

經過vue寫的單頁應用時,可能會有不少的路由引入。當打包構建的時候,javascript包會變得很是大,影響加載。若是咱們能把不一樣路由對應的組件分割成不一樣的代碼塊,而後當路由被訪問的時候才加載對應的組件,這樣就更加高效了。這樣會大大提升首屏顯示的速度,可是可能其餘的頁面的速度就會降下來。 
項目中路由按需加載(懶加載)的配置:

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

九、優化SourceMap

咱們在項目進行打包後,會將開發中的多個文件代碼打包到一個文件中,而且通過壓縮,去掉多餘的空格,且babel編譯化後,最終會用於線上環境,那麼這樣處理後的代碼和源代碼會有很大的差異,當有bug的時候,咱們只能定位到壓縮處理後的代碼位置,沒法定位到開發環境中的代碼,對於開發很差調式,所以sourceMap出現了,它就是爲了解決很差調式代碼問題的。 
SourceMap的可選值以下:

clipboard.png
開發環境推薦: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, 由於它會增長文件的大小,可是在開發環境中,能夠試用下,由於他們打包的速度很快

十、構建結果輸出分析

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 後生成分析報告以下:

clipboard.png

相關文章
相關標籤/搜索