Webpack打包优化实战经验与性能提升关键点解析

夏侯乙涵 前端 阅读 578
赞 15 收藏
二维码
手机扫码查看
反馈

打包体积太大,首屏加载慢到离谱

前几天刚上线的项目,QA同学跑过来跟我说,首屏加载时间太长了,简直没法忍。我一听就有点慌,赶紧打开开发者工具看了一下,果然发现vendor.js居然有3MB多,这谁顶得住啊。

Webpack打包优化实战经验与性能提升关键点解析

这里我踩了个坑,之前一直觉得webpack配置挺简单的,就是常规的那些套路:babel-loader、css-loader之类的。但这次问题明显出在打包优化上,尤其是第三方库的体积太大了。

折腾了半天,终于找到罪魁祸首

先说排查过程吧。我用了webpack-bundle-analyzer这个插件来分析打包结果:

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

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin()
  ]
}

运行完打包命令后,可视化报告一出来我就傻眼了——lodash居然被打包进去了,而且是完整版!我记得明明只用到了几个方法,怎么整个库都被打进去了?

后来试了下发现,原来是我直接import的方式有问题:

// 这种写法会把整个lodash都打包进去
import _ from 'lodash';

三种方案对比,最后选了最靠谱的

针对这个问题,我试了三种解决方案:

  • 第一种:手动按需引入
  • 第二种:使用babel-plugin-lodash
  • 第三种:换成lodash-es

先说手动按需引入,虽然确实能解决问题,但太麻烦了,每个文件都要改:

// 手动按需引入
import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle';

第二种方案看起来不错,只需要在babel配置里加个插件:

// babel.config.js
module.exports = {
  presets: ['@vue/cli-plugin-babel/preset'],
  plugins: ['lodash']
}

但是这里又踩了个坑,这个插件和tree-shaking不兼容,导致其他库的体积也变大了。

最终我选择了第三种方案——直接换成lodash-es,配合webpack的optimization.splitChunks:

// webpack.config.js
const path = require('path');

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    filename: '[name].[contenthash].js',
    path: path.resolve(__dirname, 'dist')
  },
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendors: {
          test: /[\/]node_modules[\/]/,
          name: 'vendors',
          chunks: 'all'
        }
      }
    }
  },
  module: {
    rules: [
      {
        test: /.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  }
}

核心代码就这几行,效果立竿见影

除了处理lodash的问题,我还做了几个关键优化:

首先是开启生产环境的压缩:

mode: 'production'

然后是处理moment.js的locale问题,只保留需要的语言包:

const webpack = require('webpack');

module.exports = {
  plugins: [
    new webpack.ContextReplacementPlugin(/moment[/\]locale$/, /zh-cn/)
  ]
}

最后是处理css提取和压缩:

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');

module.exports = {
  module: {
    rules: [
      {
        test: /.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader']
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash].css'
    })
  ],
  optimization: {
    minimizer: [new OptimizeCSSAssetsPlugin({})]
  }
}

优化后的成果,还有个小瑕疵

经过这一顿操作,vendor.js从原来的3MB降到了500KB左右,首屏加载时间直接砍掉了一半多。不过这里还有个小问题,因为用了contenthash,每次发布都会生成新的文件名,CDN缓存命中率会受影响,但这点小问题无伤大雅。

以上是我踩坑后的总结,如果你有更好的方案欢迎评论区交流。说实话,打包优化这块水还挺深的,我准备接下来继续研究一下动态导入和更细粒度的代码分割策略。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论