webpack 構(gòu)建速度和體積優(yōu)化

構(gòu)建速度優(yōu)化

使用 webpack 內(nèi)置的 stats

  • stats 構(gòu)建的統(tǒng)計(jì)信息

  • package.json 中使用 stats

"scripts": {
  "build:stats": "webpack --env production --json > stats.json"
},
  • 缺點(diǎn):顆粒度太粗,看不出問(wèn)題所在。

速度分析

  • 使用 speed-measure-webpack-plugin 可以看到每個(gè) loader 和插件執(zhí)行耗時(shí)
  • 安裝
yarn add speed-measure-webpack-plugin -D
  • 使用
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
 
const smp = new SpeedMeasurePlugin();
 
const webpackConfig = smp.wrap({
  plugins: [
    ...
  ]
});

分析體積大小 webpack-bundle-analyzer

  • 安裝
npm install --save-dev webpack-bundle-analyzer
or
yarn add -D webpack-bundle-analyzer
  • 使用
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
 
module.exports = {
  plugins: [
    new BundleAnalyzerPlugin()
  ]
}

使用高版本的 webpack 和 node.js

  • 因?yàn)?webpack 和 node 都在優(yōu)化和迭代,使用高版本的可以有效優(yōu)化打包構(gòu)建速度

使用 webpack4 優(yōu)化原因

  • v8 帶來(lái)的優(yōu)化(for of 替代 forEach、Map 和 Set 替代 Object,includes 替代 indexOf)
  • 默認(rèn)使用更快的 md4 hash 算法
  • webpack AST 可以直接從 loader 傳遞給 AST ,減少分析時(shí)間
  • 使用字符串方法替代正則表達(dá)式

使用 多進(jìn)程/ 多實(shí)例構(gòu)建

使用 HappyPack 解析資源

  • 原理: 每次 webapck 解析一個(gè)模塊,HappyPack 會(huì)將它及它的依賴分配給 worker 線程中。
const HappyPack = require('happypack');

exports.module = {
  loaders: {
    test: /.js$/,
    loader: 'happypack/loader',
    include: [
      // ...
    ],
  }
};

這個(gè)庫(kù)作者已經(jīng)不維護(hù)了,webpack4 后的推薦使用 thread-loader

使用 thread-loader 解析資源

  • 原理: 每次 webpack 解析一個(gè)模塊,thread-loader 會(huì)將它及它的依賴分配給 worker 線程中
// 安裝
npm install --save-dev thread-loader
// webapck.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        include: path.resolve('src'),
        use: [
          'thread-loader',
          // your expensive loader (e.g babel-loader)
        ],
      },
    ],
  },
};

使用 多進(jìn)程/ 多實(shí)例 并行壓縮

使用 parallel-uglify-plugin 插件

import ParallelUglifyPlugin from 'webpack-parallel-uglify-plugin';

module.exports = {
  plugins: [
    new ParallelUglifyPlugin({
      uglifyJS: {
        output: {
          beautify: false,
          comments: false
        },
        comperss: {
          // 是否在UglifyJS刪除沒(méi)有用到的代碼時(shí)輸出警告信息,默認(rèn)為輸出,可以設(shè)置為false關(guān)閉這些作用
          warnings: false,
          drop_console: true,
          collapse_vars: true,
          reduce_vars: true
        }
      },
    }),
  ],
};

使用 uglifyjs-webpack-plugin 開(kāi)啟 parallel 參數(shù)

  • 安裝
npm i -D uglifyjs-webpack-plugin
  • 使用
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

module.exports = {
  plugins: [
    new UglifyJsPlugin({
      uglifyOptions: {
        warnings: false,
        parse: {},
        compress: {},
        mangle: true,
        output: null,
        toplevel: false,
        nameCache: null,
        ie8: false,
        keep_fnames: false
      },
      parallel: true
    })
  ]
}

terser-webpack-plugin 開(kāi)啟 parallel 參數(shù)

  • 推薦使用
module.exports = {
  optimization: {
    minimizer: [
      new Terserplugin({
        parallel: true
      })
    ]
  }
}

分包構(gòu)建

使用 html-webpack-externals-plugin

  • 將基礎(chǔ)包通過(guò) cdn 引入,不打入 bundle 中
new HtmlWebpackExternalsPlugin({
  externals: [
    {
      module: 'react',
      entry: 'https://cdn.bootcdn.net/ajax/libs/react/17.0.0/cjs/react.production.min.js',
      global: 'React',
    },
  ],
})

缺點(diǎn)如果需要多個(gè)基礎(chǔ)包,引入的還是過(guò)多

使用預(yù)編譯資源模塊

  • 思路:將 react react-dom redux 等基礎(chǔ)包和業(yè)務(wù)基礎(chǔ)包打成一個(gè)文件
  • 方法: 使用 DLLPlugin 進(jìn)行分包,DllReferencePlugin 對(duì) manifest.json 引用

使用

  • 創(chuàng)建 webpack.dll.config.js
module.exports = {
  context: process.cwd(),
  entry: {
    library: [
      'react',
      'react-dom',
      'redux',
      'react-redux'
    ],
    // 如果有多個(gè),直接在增加一個(gè)
  }
  output: {
    // 這里打包后的文字是 library.dll.js
    filename: '[name].dll.js',
    path: path.resolve(__dirname, 'build/libarary'),
    // 暴露的庫(kù)的名字
    library: '[name]'
  },
  plugins: [
    // 指定包存放的位置
    new webpack.DllPlugin({
      name: '[name]',
      // 描述動(dòng)態(tài)鏈接庫(kù) mainfest 文件輸出時(shí)的文件名稱
      // path: 'manifest.json'
      path: path.resolve(__dirname, 'build/libarary/[name].json')
    })
  ]
}
    
  • 在 pageage.json scripts 中增加命令
"scripts": {
  "dll": "webpack --config webpack.dll.js"
}
  • 執(zhí)行 npm run dll 分包
  • 在 webpack.config.js 引入
module.exports = {
  plugins: [
    new webpack.DllReferencePlugin({
      // 剛打包后的json文件地址
      // manifest: require('xxx.json'),
      manifest: require('./build/library/libary.json'),
    })
    // 如果引入多個(gè),使用多次此插件
  ]
}

利用緩存提升構(gòu)建速度

  • 目的:提升二次構(gòu)建速度

  • 緩存思路

    • babel-loader 開(kāi)啟緩存
    • terser-webpack-plugin 開(kāi)啟緩存
    • 使用 cache-loader 或者 hard-source-webpack-plugin
  • 開(kāi)啟緩存后 node_modules 下會(huì)有一個(gè) .cache 目錄

babel-loader 開(kāi)啟緩存

use: [
  {
    loader: 'babel-loader',
    options: {
      cacheDirectory: true
    }
  }
]

terser-webpack-plugin 開(kāi)啟緩存

module.exports = {
  optimization: {
    minimizer: [
      new Terserplugin({
        parallel: true,
        cache: true
      })
    ]
  }
}

使用 hard-source-webpack-plugin 開(kāi)啟緩存

  • 安裝
npm install --save-dev hard-source-webpack-plugin or yarn add --dev hard-source-webpack-plugin
  • 使用
// webpack.config.js

const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');

plugins: [
  new HardSourceWebpackPlugin()
]

縮小構(gòu)建目標(biāo)來(lái)優(yōu)化構(gòu)建速度

  • 目的:盡可能的少構(gòu)建模塊
    • 如: babel-loader 不解析 node_modules

減少文件搜索范圍

合理使用 resolve.alias

  • 創(chuàng)建 import 或 require 的別名,來(lái)確保模塊引入變得更簡(jiǎn)單。例如,一些位于 src/ 文件夾下的常用模塊
module.exports = {
  resolve: {
    alias: {
      react: path.resolve(__dirname, './node_modules/react/dist/react.min.js'),
    }
  }
}

resolve.modules 配置 (減少模塊搜索層級(jí))

  • 告訴 webpack 解析模塊時(shí)應(yīng)該搜索的目錄。
module.exports = {
  resolve: {
    modules: [path.resolve(__dirname, "node_modules")]
  }
}

resolve.mainFields

  • 告訴 webpack 入口文件,此選項(xiàng)將決定在 package.json 中使用哪個(gè)字段導(dǎo)入模塊。
module.exports = {
  resolve: {
    //指定入口文件 main 意思是從 package.json 中 main 字段中查找
    mainFields: ['main']
  }
}

resolve.extensions

  • 自定義解析確定的擴(kuò)展。默認(rèn)值是:
  • 模塊路徑的查找,例如: import Index from 'index' 沒(méi)有寫(xiě)后綴,webpack 會(huì)先去找 index.js 文件,在去找 index.json
extensions: [".js", ".json"]
  • 可以避免 webpack 做沒(méi)意義的查找

構(gòu)建體積優(yōu)化

Tree Shaking

  • 概念: 1 個(gè)模塊可能有多個(gè)方法,只要其中某個(gè)方法使用到了,則整個(gè)問(wèn)價(jià)你都會(huì)被打包到 bundle 里面去,tree shaking 就是只把用到的方法打入 bundle,沒(méi)有到的會(huì)被 uglify 階段被擦除掉。

無(wú)用的 CSS 如何刪除掉的?

  • PurifyCSS:遍歷代碼,識(shí)別已經(jīng)用到的 CSS Class (已經(jīng)不維護(hù)了)

  • uncss:HTML 需要通過(guò) jsdom 加載,所有的樣式通過(guò) PostCss 解析,通過(guò) document.querySelector 來(lái)識(shí)別在 HTML 文件里面不存在的選擇器。

  • 使用 purgecss-webpack-plugin 和 mini-css-extract-plugin 配合使用

const path = require('path')
const glob = require('glob')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const PurgeCSSPlugin = require('purgecss-webpack-plugin')
const PATHS = {
  src: path.join(__dirname, 'src')
}

module: {
  rules: [
    {
      test: /\.css$/,
      use: [
        MiniCssExtractPlugin.loader,
        "css-loader"
      ]
    }
  ]
},
plugins: [
  new MiniCssExtractPlugin({
    filename: "[name].css",
  }),
  new PurgeCSSPlugin({
    paths: glob.sync(`${PATHS.src}/**/*`,  { nodir: true }),
  }),
]

圖片壓縮

  • 基于 Node 庫(kù) imagemin
  • 使用 配合 image-webpack-loader

imagemin 的優(yōu)點(diǎn)

  • 有很多定制選項(xiàng)
  • 可以引入更多第三方插件,如 pngqeant
  • 可以處理多種圖片格式

圖片壓縮的原理 png

  • pngquant: 是一款 PNG 壓縮器,通過(guò)將圖像轉(zhuǎn)換為具有 alpha 通道 (通常比24/32位 PNG文件小 60%-80%)的更高效的 8 位 PNG 格式,可以顯著減小文件大小。
  • pngcrush: 主要目的是通過(guò)嘗試不同的壓縮級(jí)別和 PNG 過(guò)濾方法來(lái)降低 PNG IDAT 數(shù)據(jù)流的大小。
  • optipng: 設(shè)計(jì)靈感來(lái)自于 pngcrush。optipng 可以將圖像文件重新壓縮更小尺寸,而不會(huì)丟失任何信息
  • tinypng: 也是將 24 位 PNG 文件轉(zhuǎn)化為更小有索引的 8 位圖片,同時(shí)所有非必要的 metadata 也會(huì)被剝離掉。

使用 image-webpack-loader

npm install image-webpack-loader --save-dev
// webpack.config.js
rules: [{
  test: /\.(gif|png|jpe?g|svg)$/i,
  use: [
    'file-loader',
    {
      loader: 'image-webpack-loader',
      options: {
        mozjpeg: {
          progressive: true,
        },
        // 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
        }
      }
    },
  ],
}]

動(dòng)態(tài) Polyfill

Polyfill 幾種方案的優(yōu)缺點(diǎn)

  1. babel-polyfill
  • 優(yōu)點(diǎn): React16官方推薦
  • 缺點(diǎn):體積過(guò)大,難以抽離Map、Set。項(xiàng)目中 react 是單獨(dú)用 CND 引入,使用時(shí)需要在 react 前加載。
  1. babel-plugin-transform-runtime
  • 優(yōu)點(diǎn):只能 polyfill 用到的類和方法,體積相對(duì)較小
  • 缺點(diǎn):不能 polyfill 原型上的方法,不適用于業(yè)務(wù)復(fù)雜項(xiàng)目。
  1. 自己map、set的polyfill
  • 優(yōu)點(diǎn):定制化高,體積小
  • 缺點(diǎn):就算體積小,但是所有用戶都需要加載,重復(fù)造輪子
  1. polyfill-service
  • 優(yōu)點(diǎn): 識(shí)別 User Agent,只給用戶返回需要的 polyfill
  • 缺點(diǎn):可能國(guó)內(nèi)部分奇葩瀏覽器 UA 無(wú)法識(shí)別 (但可以降級(jí)返回所需全部)

使用

  1. polyfill.io 官方提供的服務(wù)
https://polyfill.io/v3/polyfill.min.js
  1. 基于官方自建 polyfill 服務(wù)
https://polyfill.io/v3/polyfill.min.js?features=es2015%2Ces2016%2Ces2017
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容