webpack打包體積優化


webpack打包的體積越小,對於部署應用的網站來說,性能越好,加載速度越快。

1. 分析打包文件

1. 生成統計信息文件

首先需要通過webpack命令生成統計信息的文件。在package.json的腳本中添加命令

  "scripts": {
    "stats": "webpack --config webpack.prod.js --profile --json > stats.json", 
     //...
  }

上面的命令會在根目錄下生成一個stats.json的打包統計信息文件。

2. 可視化分析

使用插件可視化分析插件:webpack-bundle-analyzer

npm install --save-dev webpack-bundle-analyzer

配置插件的使用信息;

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.export = {
   //...
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'disabled',  // 關閉默認啟動的展示信息的http服務器
      generateStatsFile: true  // 打包的時候生成stats.json文件;
  }),
}

從上面配置信息可知,使用該插件,不需要再手動生成stats.json文件,第一步可以省略。

然后,在腳本中添加手動啟動分析器的http

// 分析命令的使用,應該在打包命令之后。因為它的作用就是分析打包后的文件
 "scripts": {
    "build": "webpack --config webpack.prod.js",
    "analyzer": "webpack-bundle-analyzer ./dist/stats.json --port 8081"
  }

3. 優化建議

將生成的stats.json文件拖入該網站https://webpack.jakoblind.no/optimize/

會給出打包體積太大的優化措施。

該插件基於webpack-optimize-helper插件。

2. 抽離css並壓縮

1.抽離css

使用mini-css-extract-plugin抽離css

npm install --save-dev mini-css-extract-plugin

在plugins配置文件中使用插件

module.exports = {
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/[name].[contenthash:8].css'  // 將css文件統一放入css文件夾
    })
  ]  
}

2. 壓縮css

使用optimize-css-assets-webpack-plugin壓縮css文件

npm install -D optimize-css-assets-webpack-plugin

在optimization中使用該插件

module.exports = {
    optimization: {
        minimize: true,
        minimizer: [
            new OptimizeCssAssetsWebpackPlugin()
        ]
    }    

3. 移除未使用的css

在大型項目中,經常會有很多樣式內容,在代碼中根本未使用,但是會被打包,這些樣式需要打包時應該移除。

使用purgecss-webpack-plugin移除未使用的css樣式。

npm i purgecss-webpack-plugin -D

在pluigns中配置插件

const glob = require('glob'); // 根據路徑查找文件
module.exports = {
  plugins:[ new PurgecssWebpackPlugin({ //paths要求是絕對路徑 paths: glob.sync(`${path.join(__dirname, 'src/**/*')}`, { nodir: true }) })
]

3. 復用babal轉化時runtime代碼

@babel/plugin-transform-runtime

✅該插件的引入只能通過.babelrc文件,不能在babel-loader的options中,否則報錯。

4. 使用CDN

1. 第三方庫使用CDN

    new HtmlWebpackExternalsPlugin({
      externals: [
        {
          module: 'lodash',
          global: '_',
          entry: 'https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js'
        }
      ]
    })

然后為了避免用戶在模塊中手動又導入,需要添加

  externals: {
    lodash: '_'
  },

2. 項目生成的靜態文件可以部署到CDN服務器中

需要配置publicPath為CDN服務器的域名

// output, module->MiniCssWebpackLoader.loader,  image等可以部署publicPath
publicPath: 'http://lyralee.com'

5. webpack.IgnorePlugin

忽略第三方庫中多余的文件夾。

new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)

上面忽略了moment庫中的語言包

6. 圖片壓縮image-webpack-loader

      {
        test: /\.(gif|png|jpe?g|svg)$/i,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 2 * 1024,
              outputPath: 'images',
              name: '[name].[contenthash:8].[ext]'
            }
          },
          {
            loader: 'image-webpack-loader',
            options: {
              mozjpeg: {
                progressive: true,
                quality: 65
              },
              // optipng.enabled: false will disable optipng
              optipng: {
                enabled: false
              },
              pngquant: {
                quality: [0.65, 0.90],
                speed: 4
              },
              gifsicle: {
                interlaced: false
              },
              // the webp option will enable WEBP
              webp: {
                quality: 75
              }
            }
          }
        ]
      }

7. 使用babel-polyfill的替代方案

如果項目中允許使用外部鏈接,可以使用

<script src="https://polyfill.io/v3/polyfill.min.js/"></script>

它按照瀏覽器類型返回需要的內容

 8. TreeShaking

1. 原理:

1)利用es6模塊的靜態結構,依賴模塊命令import/export,進行代碼分析,分析出無用/永遠不會訪問的代碼。

1)對於自己寫的代碼,禁止babel將esModule轉為require
rules: [
      {
        test: /\.jsx?$/,
        use: {
          loader: 'babel-loader',
          options: {
            // 設置{moduels: false} 則保留ES6的import/export
            presets: [['@babel/preset-env', {modules: false}],  '@babel/preset-react'],
            plugins: [
              ['@babel/plugin-proposal-decorators', {legacy: true}],
              ['@babel/plugin-proposal-class-properties', {loose: true}]
            ]
          },
        },
        exclude: /node_modules/
      }
    ]
2) 對於第三方庫,要使用es模塊的包, 如: lodash-es(有問題,先不用)
import {join } from 'lodash-es'

2)通過terser-webpack-plugin移除分析出的多余代碼

// ⚠️只要寫了optimization屬性(即使是空),即使是production,也不會啟動該Terser插件 ; 如果是完整的配置,但是mode=development,該屬性也不起作用
optimization: {
    minimizer: [
      new TerserWebpackPlugin({
        parallel: true,
        cache: true
      })
    ],
     ...
  },

2. 注意事項

1. 要使TreeShaking起作用,必須使用ES6語法import/export
2. 減少sideEffects(副作用)代碼的書寫
3. 引入的第三方庫,需要引入他們的ES版本

3. 特殊情形

當多入口文件存在時,只要有一個入口中使用了某方法,就不會被TreeShaking掉

9. 代碼分割

1. 入口文件代碼分割

entry: {
    index: './src/index.js',
    vendors: ['lodash']
}

2. 動態import導入實現懶加載

當代碼中觸發使用import()動態引入文件時,會發起請求,並返回一個webpackJsonp方法,

該方法中實現代碼的按需加載。

另外,React16.6.0中引入的React.lazy()就是基於該方法實現模塊的代碼分割和懶加載。

為了提高網站的性能,可以配合webpackPrefetch實現預加載。

window.addEventListener('click', function(e) {
  import(/*webpackPrefetch: true */ /*webpackChunkName: 'hello' */'./test.js').then(() => {
    console.log('代碼分割')
  })
});

它的實際作用是在入口文件中生成一段js腳本,腳本用於向html頁面插入以下標簽

<link rel="prefetch" as="script" href="hello.c671.js"></link>

實際應用

react16.6.0中添加了React.lazy()和React.Suspense組件。結合React-router用於實現代碼分割。

React.lazy()加載的組件,必須用於React.Suspense組件中。React.Suspense組件含有一個fallback屬性,用於添加加載過程的過渡效果。

最好的是使用瀏覽器的prefetch性能。如: webpackPrefetch屬性

import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import React, { Suspense, lazy } from 'react';

const Home = lazy(() => import(/*webpackPrefetch: true*/'./routes/Home'));
const About = lazy(() => import(/*webpackPrefetch: true*/'./routes/About'));

const App = () => (
  <Router>
    <Suspense fallback={<div>Loading...</div>}>
      <Switch>
        <Route exact path="/" component={Home}/>
        <Route path="/about" component={About}/>
      </Switch>
    </Suspense>
  </Router>
);

3. 使用splitChunks插件

webpack4.0中默認加入了splitchunks插件,並設置了默認屬性。默認只分割異步加載(按需)的代碼。但是通過optimization.splitChunk,可以修改配置。

對於import()加載的代碼,任何情況下都可以分割,而且沒有大小限制。

非import()加載的代碼,如果想要實現webpack默認代碼分割,其規則如下:

1. 代碼塊在模塊之間共享或者來自node_modules
2. 代碼塊的大小至少30Kb,即30000byte
3. 並行加載的按需代碼塊<=5
4. 頁面初始化的並行加載代碼塊<=3

要滿足4,5 需要修改代碼塊的體積。

其默認配置如下:

 optimization: {
    splitChunks: {
      /**
       * chunks的有效類型:
       * 1. async: 默認值,異步;如import('./test.js),以及test.js中同步方式引入的模塊
       * 2. initial: 同步;設為該值,代碼中import,require引入的模塊會自動根據規則分割代碼
       * 3. all: 所有類型
       * 也可以是函數function(chunk) {}
       */
      chunks: 'async', 
      minChunks: 1, // 模塊至少被引用的次數 
      /**
       * 配置的優先級順序
       * maxInitialRequest/maxAsyncRequests < maxSize < minSize
       */
      minSize: 30000, // 約30Kb
      maxSize: 0, // 用於http2和長期緩存
      maxAsyncRequests: 5, // 異步並行加載的最大代碼塊數量
      maxInitialRequests: 3, // 頁面初始化加載的最大代碼塊數量
      automaticNameDelimiter: '~', // 自動生成的名字之間的間隔符號
      /**
       * 值為true表示基於cacheGroups的key值自動生成名稱;
       * 也可以是函數name (module, chunks, cacheGroupKey) {return }
       * ⚠️名稱不能和入口代碼塊名稱相同,否則會被移除
       * production模式下建議使用false
       */
      name: true,
      /**
       * cacheGroups可以繼承和覆蓋👆提到的屬性;
       * 它還有三個額外的屬性:
       * 1. test
       * 2. priority
       * 3. reuseExistingChunk
       * 禁止默認緩存行為可以設置default: false
       */
      cacheGroups: {
        // 默認node_modules中的代碼打入venders中
        vendors: { 
          /**
           * test指定被打入該代碼塊的模塊
           * 1. regExp
           * 2. 函數function(module, chunks) {}
           */
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          name: 'xxx',  // 覆蓋自動生成的名字
          enforce: true //忽略繼承的minSize等屬性,總是生成代碼塊
        },    
        // 默認本地代碼的打包行為
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
  },

可以根據上面的配置信息,自定義需要打包的模塊

// 將node_modules中的react和react-dom打包
  optimization: {
    splitChunks: {
      vendors: {
        test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
        name: 'vendors',
        chunks: 'all'
      }
    }
  },

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM