webpack实际实践优化项目

参考:
如何通过性能优化,将包的体积压缩了62.7%
雅虎35条
20210526-webpack深入学习,搭建和优化react项目

本文只专注于性能优化的这个部分。

总体来说分为两个方面:第一是开发环境中主要优化打包速度,第二是线上环境中主要优化分包大小。

打包阶段

使用speed-measure-webpack-plugin插件,显示打包过程中的每个步骤耗时大小。然后根据耗费的时长去优化。

比如下图:

安装cache-loader打包缓存
复制代码
  {
    test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
    use: [
      cacheLoader,
      {
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        }
      }
    ]
  }
happypack .在打包的时候开启多线程打包
UglifyJsPlugin开启多线程优化时间,有两个方式,可以都试一下看看哪个效果好一点。
  1. 开启多线程

    // 在minimizer属性内添加
    // 自定义js优化配置,将会覆盖默认配置
    new UglifyJsPlugin({
    parallel: true, //使用多进程并行运行来提高构建速度
    sourceMap: false,
    uglifyOptions: {
    warnings: false,
    compress: {
    unused: true,
    drop_debugger: true,
    drop_console: true,
    },
    output: {
    comments: false // 去掉注释
    }
    }
    })

  2. 使用:webpack-parallel-uglify-plugin
    上图可以看出UglifyJsPlugin这个包花费时间太长,可以使用相同效果的另一个包,开启多核同步压缩增加压缩的效率。

    const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
    new ParallelUglifyPlugin({
    uglifyJS: {
    output: {
    beautify: false, // 是否保留空格和制表符,设置为不保留
    comments: false, // 是否保留代码中的注释,设置为不保留
    },
    compress: {
    drop_console: true, // 是否删除代码中的console语句,设置为删除
    collapse_vars: false, // 是否内嵌虽然已经定义了,但是只用到一次的变量,设置为不使用
    reduce_vars: false, // 是否提取出现了多次但是没有定义成变量去引用的静态值,设置为不转换
    },
    warnings: false // 是否在删除没有用到的代码时输出警告信息,设置为不警告
    },
    }),

线上优化阶段

这一部分主要在于分离包(组件懒加载、路径懒加载、按需引入)、合并包(optimization.splitChunks)、压缩包(NGINX: gzip)

具体可以看以前的文章20210526-webpack深入学习,搭建和优化react项目

使用webpack-bundle-analyzer插件,显示打包后的包大小、包组成,然后针对去处理。
将大包拆小:

异步引入,路由懒加载 分包 import('@/components/Index')

按需引入组件,看看是不是直接把一个依赖都引入进来了,比如lodash需要引入babel-plugin-lodash等。

优化代码和合并相同的包

看看analyzer显示的第三方包有没有重复引入、打包到不同包的情况,这样的话就比较浪费空间,最好将第三方包合并一下,这里就使用到 optimization.splitChunks

压缩代码、去除无用代码

引入Uglifyjs进行代码压缩(第一步时间优化已经做了) Uglifyjs还会进行Tree-shaking剔除无用代码。比如引入了第三方包却没有用上的情况也会删除。

在服务器上的优化
  1. 图片和其他文件进行无损压缩,并上传到cdn上。

    推荐网站:https://tinyjpg.com

  2. nginx配置开通gzip ,从线上拉取的代码能压缩2/3

    巨强。

Next项目的实际实践

一年半年前进行过一次next项目的webpack优化,那时候的优化方向主要是splitChunks 分包的数据。

半年前的参考文章可看博客列表:结合Next项目实际认识webpack.splitChunks

Next版本:12.3.4(因为使用next-plugin-antd-less 只能支持到next12。。)
Node版本:16.20.2

推荐阅读:v12.3.X的Next文档

经过半年多的持续优化,在webpack这方面又有了新的优化目标:

  1. 发现treeShaking没有生效,引入但是没有使用的资源仍然打包进去了。
  2. console.log正式环境禁用。
  3. 同一个组件在不同的包里被反复打包,导致一个页面上被多次下载。
  4. 部署构建太慢,想要加快速度。

Treeshaking

结论:next使用terser-plugin做的压缩和摇树,本地构建不会开,但是在构建环境会使用,因此确实会把没有引用的组件shake出去,不会影响使用。

调研过程:

  1. 一开始从本地上看以为失效了,查看了很多terser有关的问题,列在如下:
  2. 推荐使用next13+可以解决这个问题,于是重新实验,但是本地情况下仍然没有shake出去。
  3. nvm安装(next13+必须要node18以上)教程:
  4. 按照方案二,增加terser的参数passes:2可以解决,但是next并没有开放terserOptions给我们,因此只能另辟蹊径,找了半天源码,找到了一个v12版本可行的options插入方法。注意这个为后续的drop_console方案提供了思路。(不过事实证明passes也没什么变化)
javascript 复制代码
  // next v12版本的实验特性,用于配置swc压缩属性
  experimental: { swcMinifyDebugOptions: { compress: { passes: 2 } } },
  1. modularizeImports。经过不懈寻找,找到了这么一个属性,可以用于内部组件的按需引入。

    官方推荐按需引入来达到treeShaking效果:https://github.com/vercel/next.js/issues/45687

    https://vercel.com/blog/how-we-optimized-package-imports-in-next-js

  2. 最后发现:原来是没有部署到线上,正式构建环境是可以treeshaking成功的。

drop console

线上环境我们并不想显示意外的console,因此删除console也是一个需求。

next提供的没有起作用。

复制代码
  compiler: {
    removeConsole: {
      exclude: ['error'],
    },
  },

根据上面对源码的研究,找到了插入terserOptions的方式,增加了如下代码,push到构建环境后就能正常生效了。

复制代码
  experimental: {
    swcMinifyDebugOptions: { compress: { passes: 2, drop_console: true } },
  },

PS.中间有一段自行重新引入了terser-webpack-plugin没有成功。

复制代码
        config.optimization.minimizer = [
          new TerserPlugin({
            parallel: true,
            sourceMap: true,
            terserOptions: {
              compress: { drop_console: isProduction },
            },
          }),
        ];

部署构建太慢,想要加快速度。

  1. 引入速度衡量依赖speed-measure-webpack-plugin

    使用参考:https://github.com/vercel/next.js/discussions/33678

    复制代码
    config.plugins.push(smp);
  2. 分析:注意vscode setting原本默认的展示日志行数太少,可以通过 "terminal.integrated.scrollback":1000000配置。

经过测试速度插件,可以看出耗时较久的是next\ no-loaders\ less-loader,前两个应该是next内部自行实现的(很无奈),那我们只能从less-loader下手了。

接下来是省流版:本项目使用next-plugin-antd-less已经不再维护,并且不支持多线程处理,因此费劲了九牛二虎之力仍然没有效果。作为一次探究过程放在下面。

打印出next的loader配置,经过对next-plugin-antd-less源码的研究,实际上对于css的处理next都是放在oneOf中的,我们要处理也要优先处理oneOf中的loader。

我考虑的方案是:

  1. 采用thread-loader添加多线程处理。

  2. 或者采用happyPack添加多线程处理。

    const withConsole = (config) => {
    // console.log('----resolveLoader----\n',config.resolveLoader)
    // console.log('----rules----\n',config.module.rules)
    // console.log('---oneOf----\n',console.log(config.module.rules.map((rule) => Array.isArray(rule.oneOf) && rule)))

    return {
    ...config,
    webpack(webpackConfig, nextConfig) {

    复制代码
       // const _oneOf = [];
       // webpackConfig.module.rules.forEach((rule) => {
       //   if (Array.isArray(rule)) {
       //     rule.forEach((loader) => {
       //       Array.isArray(loader.oneOf) && _oneOf.push(loader);
       //     });
       //   } else {
       //     Array.isArray(rule.oneOf) && _oneOf.push(rule);
       //   }
       // });
       // // console.log('---oneOf----\n');
       
       // _oneOf.forEach(rule=>{
       //   rule.oneOf.forEach(_d=>{
    
       //     if(_d.test?.toString() === /\.module\.(scss|sass)$/.toString()){
       //       console.log(
       //         '---sass----\n',
       //         '🌈',
       //         _d
       //       )
       //     }
    
       //     console.log(_d)
       //   })
    
       // })
    
       const config = nextConfig;
       console.log('---config----\n', config);
       return webpackConfig;
     },

    };
    };

    {
    test: /(antd/.?/style|@ant-design).(?<![.]js)/, use: 'null-loader' }, { issuerLayer: 'edge-asset', type: 'asset/source' }, { dependency: 'url', loader: 'next-middleware-asset-loader', type: 'javascript/auto', layer: 'edge-asset' }, { test: /\.wasm/,
    loader: 'next-middleware-wasm-loader',
    type: 'javascript/auto',
    resourceQuery: /module/i
    },
    { test: /.m?js/, resolve: { fullySpecified: false } },
    {
    test: /.(js|cjs|mjs)/, issuerLayer: 'api', parser: { url: true } }, { oneOf: [ [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object], [Object] ] }, { test: /\.(png|jpg|jpeg|gif|webp|avif|ico|bmp|svg)/i,
    loader: 'next-image-loader',
    issuer: { not: /.(css|scss|sass)/ }, dependency: { not: [Array] }, options: { isServer: true, isDev: false, basePath: '', assetPrefix: '' } }, { oneOf: [ [Object], [Object] ] }, { test: /\.+(js|jsx|mjs|ts|tsx)/,
    use: [ 'thread-loader', [Object] ],
    include: [Function (anonymous)],
    type: 'javascript/auto'
    },
    {
    test: /.(jpg|jpeg|png|svg|gif|ico|webp|jp2|avif)/, issuer: /\.\w+(?/i,
    exclude: undefined,
    use: [ [Object] ]
    }
    ]

但是问题点在于,我打印出了所有的loader配置以后发现 找不到less-loader。。

然后我没有办法,把next-plugin-antd-less源码的插件引入了项目测试一下效果,在手动引入的代码文件中增加了thread-loader.

报错:UnhandledPromiseRejectionWarning: TypeError: loaderContext.getLogger is not a function

查找了一下这个问题是因为less-loader的版本问题导致的,降低了一下less-loader的版本。。。

https://github.com/webpack-contrib/thread-loader/issues/135

最后效果看起来确实少了5-6秒,但是不太值得。最终放弃了这个方案,以及happyPack也是同样的原因,做不到再把plugin引进源码文件再处理一次。

相关推荐
前端小菜鸟一枚s3 分钟前
`ConstantPositionProperty` 的使用与应用
前端·javascript·cesium
JohnsonXin3 分钟前
怎么使用vue3实现一个优雅的不定高虚拟列表
前端·javascript·css·html5
17Knight5 分钟前
我的个性化 VSCode
前端
狠狠的学习14 分钟前
antd表格行hover效果性能处理
前端·css
在下小航18 分钟前
前端本地大模型 window.ai 最新教程
前端·人工智能
一颗奇趣蛋19 分钟前
vue哪些情况称作“销毁组件”
前端·vue.js
Ody20 分钟前
循环滚动列表浅析
前端
阿里云云原生24 分钟前
HTML 开发者的智能助手:通义灵码在 VSCode 中的应用
前端·html
Neverthe1ess26 分钟前
创新项目实训开发日志1
前端·javascript·vue.js·vue
阿里云云原生27 分钟前
如何使用通义灵码学习JavaScript和DOM
前端·javascript