为什么须要性能优化

在应用 Webpack 时,如果不留神性能优化,可能会产生性能问题,会导致在开发体验上不是十分丝滑,性能问题次要是编译速度慢,打包体积过大,因而性能优化也次要从这些方面来剖析。本文次要是本人平时的工作积攒和参考他人的文章,而进行总结,基于 Webpack4 版本。

构建剖析

编译速度剖析

对 Webpack 构建速度进行优化的首要任务就是去晓得哪些地方值得咱们留神。
speed-measure-webpack-plugin 插件可能测量 Webpack 构建速度

 SMP  ⏱  General output time took 38.3 secs SMP  ⏱  PluginsHtmlWebpackPlugin took 1.31 secsCopyPlugin took 0.016 secsOptimizeCssAssetsWebpackPlugin took 0.002 secsContextReplacementPlugin took 0.001 secsMiniCssExtractPlugin took 0 secsDefinePlugin took 0 secs SMP  ⏱  Loaders_babel-loader@8.1.0@babel-loader took 29.98 secs  module count = 1503_babel-loader@8.1.0@babel-loader, and _eslint-loader@3.0.4@eslint-loader took 18.74 secs  module count = 86_css-loader@3.6.0@css-loader, and _less-loader@5.0.0@less-loader took 16.45 secs  module count = 64modules with no loaders took 2.24 secs  module count = 7_file-loader@5.1.0@file-loader took 1.03 secs  module count = 17_style-loader@1.3.0@style-loader, and _css-loader@3.6.0@css-loader, and _less-loader@5.0.0@less-loader took 0.102 secs  module count = 64_html-webpack-plugin@3.2.0@html-webpack-plugin took 0.021 secs  module count = 1

竟然达到了惊人的38.3秒,尽管有点不是很精确,然而十分慢。发现 babel-loader、eslint-loader、css-loader、less-loader 占据了大头。

const webpackBase = require('./webpack.base.conf');const path = require('path');const SpeedMeasureWebpackPlugin = require('speed-measure-webpack-plugin');const smp = new SpeedMeasureWebpackPlugin();module.exports = smp.wrap({    // 配置源码显示方式    devtool: 'eval-source-map',    mode: 'development',    entry: {        app: ['./src/index.jsx']    },    output: {        path: path.resolve(__dirname, 'dist'),        filename: 'index.js'    },    resolve: webpackBase.resolve,    module: webpackBase.module,    stats: webpackBase.stats,    optimization: webpackBase.optimization,    plugins: [        webpackBase.plugins.html,        webpackBase.plugins.miniCssExtract,        webpackBase.plugins.optimizeCssAssets,        // webpackBase.plugins.progressBarPlugin,        webpackBase.plugins.ContextReplacementPlugin,        webpackBase.plugins.DefinePlugin,        // webpackBase.plugins.AntdDayjsWebpackPlugin,        webpackBase.plugins.CopyPlugin        // webpackBase.plugins.HotModuleReplacementPlugin    ],    devServer: webpackBase.devServer,    watchOptions: webpackBase.watchOptions,    externals: webpackBase.externals});

打包体积剖析

通过 webpack-bundle-analyzer 插件可能在 Webpack 构建完结后生成构建产物体积报告,配合可视化的页面,可能直观晓得产物中的具体占用体积。

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;module.exports = {  plugins: bundleAnalyzer: new BundleAnalyzerPlugin({ analyzerPort: 8081 })],};

效果图如下:

能够看出一个很显著的问题就是 ant、trtc、mobx 这些库,没有排除。

打包体积如下:

如何优化

放大构建指标

  • 优化 resolve.modules 配置(缩小模块搜寻层级和不必要的编译工作)
  • 优化 resolve.extensions 配置
  • 减少缓存
const path = require('path');module.exports = {    resolve: {        // 主动解析确定的扩大        extensions: ['.js', '.jsx', '.css', '.less', '.json'],        alias: {            // 创立 import 或 require 的别名,来确保模块引入变得更简略            'react': path.resolve( __dirname ,'./node_modules/react/dist/react.min.js')        },        // 当从 npm 包导入模块时,此选项将决定在 `package.json` 中应用哪个字段导入模块        // 默认值为 browser -> module -> main        mainFields: ['main']    },    module: {        rules: [            {                // 排除node_modules模块                test: /\.(js|jsx)$/,                exclude: /node_modules/,                // 开启缓存                loader: 'babel-loader?cacheDirectory=true'            }        ]    }};

应用thread-loader,开启多过程

thread-loader 会将你的 loader 搁置在一个 worker 池外面运行,每个 worker 都是一个独自的有 600ms 限度的 node.js 过程。同时跨过程的数据交换也会被限度。请在高开销的 loader 中应用,否则成果不佳。

module.exports = {  module: {    rules: [      {        test: /\.js$/,        include: path.resolve('src'),        use: [          'thread-loader',          // your expensive loader (e.g babel-loader)        ],      },    ],  },};

应用hard-source-webpack-plugin

在 Webpack4 中,hard-source-webpack-plugin 是 DLL 的更好替代者。

hard-source-webpack-plugin 是 Webpack 的插件,为模块提供两头缓存步骤。为了查看后果,您须要应用此插件运行 Webpack 两次:第一次构建将破费失常的工夫。第二次构建将显着放慢(大略晋升 90%的构建速度)。不过该插件很久没更新了,不太倡议应用。

去掉eslint-loader

因为我我的项目中应用了 eslint-loader 如果配置了 precommit,其实能够去掉的。

通过 externals 把相干的包,排除

Webpack

module.exports = {    // externals 排除对应的包,注:排除掉的包必须要用script标签引入下    externals: {        react: 'React',        'react-dom': 'ReactDOM',        'trtc-js-sdk': 'TRTC',        bizcharts: 'BizCharts',        antd: 'antd',        mobx: 'mobx',        'mobx-react': 'mobxReact'    }};

index.html

<!DOCTYPE html><html lang="zh">    <head>        <meta charset="utf-8" />        <meta            name="viewport"            content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"        />        <meta name="baidu-site-verification" content="ptk9VJudKz" />        <link            rel="stylesheet"            href="https://xxx/antd.min3.26.20.css"        />        <title>webpack</title>        <script            type="text/javascript"            src="https://xxx/17.0.0react.production.min.js"        ></script>        <script            type="text/javascript"            src="https://xxx/17.0.0react-dom.production.min.js"        ></script>        <script            type="text/javascript"            src="https://xxx/BizCharts3.5.8.js"        ></script>        <script            type="text/javascript"            src="https://xxx/trtc4.6.7.js"        ></script>        <script            type="text/javascript"            src="https://xxx/moment2.29.1.min.js"        ></script>        <script            type="text/javascript"            src="https://xxx/moment2.29.1zh-cn.js"        ></script>        <script            type="text/javascript"            src="https://xxx/polyfill.min7.8.0.js"        ></script>        <script            type="text/javascript"            src="https://xxx/antd.min3.26.20.js"        ></script>        <script            type="text/javascript"            src="https://xxx/mobx.umd.min5.13.1.js"        ></script>        <script            type="text/javascript"            src="https://xxx/mobx-react.index.min5.4.4.js"        ></script>    </head>    <body>        <div id="root"></div>    </body></html>

JS压缩

从 Webpack4 开始,默认状况下应用 terser 压缩生产环境下的输入后果。Terser 是一款兼容 ES2015 + 的 JavaScript 压缩器。与 UglifyJS(许多我的项目的晚期规范)相比,它是面向未来的抉择。有一个 UglifyJS 的分支—— uglify-es,但因为它不再保护,于是就从这个分支诞生出了一个独立分支,它就是 terser。

const TerserPlugin = require('terser-webpack-plugin');module.exports = {    optimization: {        minimizer: [            // 压缩js            new TerserPlugin({                test: /\.(jsx|js)$/,                extractComments: true,                parallel: true,                cache: true            })        ]    },};

CSS压缩

Webpack 4.0 当前,官网举荐应用 mini-css-extract-plugin 插件来打包 CSS 文件。

const MiniCssExtractPlugin = require('mini-css-extract-plugin');module.exports = {    module: {        rules: [            {                test: /\.(css|less)$/,                use: [MiniCssExtractPlugin.loader]            }        ]    },};

FAQ

ant 无奈加载

请确保加载程序,moment、polyfill 放在 ant 后面加载

mobx无奈加载

mobx引入 mobx.umd.min.js 库,mobx-react须要引入

package.json

{    "name": "webpack",    "version": "1.0.0",    "private": true,    "main": "index.js",    "dependencies": {        "antd": "^3.26.20",        "babel-eslint": "^10.0.3",        "babel-loader": "^8.0.0",        "babel-plugin-import": "^1.13.0",        "babel-plugin-react-css-modules": "^5.2.6",        "bizcharts": "^3.5.8",        "china-division": "^2.3.1",        "compression-webpack-plugin": "^3.0.1",        "copy-webpack-plugin": "^5.1.1",        "css-loader": "^3.2.0",        "eslint": "^6.8.0",        "eslint-config-prettier": "^6.11.0",        "eslint-config-standard": "^14.1.0",        "eslint-loader": "^3.0.4",        "eslint-plugin-import": "^2.20.0",        "eslint-plugin-promise": "^4.2.1",        "eslint-plugin-react": "^7.17.0",        "eslint-plugin-standard": "^4.0.1",        "html-webpack-plugin": "^3.2.0",        "less": "^3.8.1",        "less-loader": "^5.0.0",        "lint-staged": "^10.0.8",        "mini-css-extract-plugin": "^0.8.0",        "mobx": "^5.13.1",        "mobx-react": "^5.4.4",        "optimize-css-assets-webpack-plugin": "^5.0.1",        "pre-commit": "^1.2.2",        "progress-bar-webpack-plugin": "^1.12.1",        "react": "^17.0.0",        "react-dom": "^17.0.0",        "speed-measure-webpack-plugin": "^1.3.1",        "style-loader": "^1.2.1",        "terser-webpack-plugin": "^2.2.1",        "trtc-js-sdk": "^4.6.7",        "viewerjs": "^1.5.0",        "webpack": "^4.41.2",        "webpack-bundle-analyzer": "^3.6.0",        "webpack-cli": "^3.3.10",        "webpack-dev-server": "^3.10.1"    }}

最终成果

打包体积:

打包体积由原先 2.1M 变成了 882KB,能够说成果十分微小。

包依赖:

ant、trtc、mobx 这些库也没了

编译速度:

SMP  ⏱  General output time took 10.67 secs SMP  ⏱  PluginsHtmlWebpackPlugin took 1.69 secsBundleAnalyzerPlugin took 0.091 secsCopyPlugin took 0.011 secsMiniCssExtractPlugin took 0.003 secsOptimizeCssAssetsWebpackPlugin took 0.002 secsDefinePlugin took 0.001 secsContextReplacementPlugin took 0 secs SMP  ⏱  Loaders_babel-loader@8.1.0@babel-loader took 8.26 secs  module count = 277_babel-loader@8.1.0@babel-loader, and _eslint-loader@3.0.4@eslint-loader took 7.18 secs  module count = 86_css-loader@3.6.0@css-loader, and _less-loader@5.0.0@less-loader took 1.94 secs  module count = 28modules with no loaders took 0.728 secs  module count = 12_file-loader@5.1.0@file-loader took 0.392 secs  module count = 17_style-loader@1.3.0@style-loader, and _css-loader@3.6.0@css-loader, and _less-loader@5.0.0@less-loader took 0.052 secs  module count = 28_html-webpack-plugin@3.2.0@html-webpack-plugin took 0.026 secs  module count = 1

编译速度由原先 38.3 secs(理论编译速度大略15秒左右),缩小到 10.67 secs(理论编译速度10秒左右)。

国内外公共CDN地址

  • BootCDN
  • cdnjs

参考文献

  • Webpack Guidebook
  • Webpack 外围常识有哪些?

博客

欢送关注我的博客