likes
comments
collection
share

一次vue2项目打包构建优化过程

作者站长头像
站长
· 阅读数 50

记一次vue2项目构建优化过程。

项目基本情况

项目是一个vue2+webpack4的管理平台,

接下来看一下项目的打包耗时情况和产物依赖情况

耗时情况

首先使用SpeedMeasurePlugin来看一下项目的构建过程中各个阶段的耗时情况。

const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();

module.exports = {
  configureWebpack: smp.wrap({
    // 在这里配置原本的Webpack配置
    // 可以定义entry、output、module等配置
  })
};

使用SpeedMeasurePlugin分析打包情况,项目总体耗时43s,其中有一些loader耗时过长,会帮我们标红,尤其是vue-loadersass-loaderpostcss-loader耗时过长,后续可以考虑使用缓存,并行处理使用thread-loader来优化。

产出依赖

webpack-bundle-analyzer使用这个插件,可以帮助分析项目的构建结果,以识别过大的模块、重复的依赖和不必要的代码。一旦你生成了可视化分析界面,可以按照以下方式来进行分析:

  1. 查看模块大小

    • 通过可视化界面,可以看到每个模块的大小。这可以帮助识别哪些模块尺寸过大,从而考虑是否需要对其进行优化,例如拆分代码、按需加载等。
  2. 检查重复的依赖

    • 可以查看依赖关系图,识别是否有重复的依赖被打包进了多个bundle中。这可能意味着某些依赖被重复引入,可以考虑通过Webpack的代码拆分功能或其他优化策略来避免重复打包。
  3. 识别不必要的代码

    • 通过分析模块之间的依赖关系,可以发现是否有一些不必要的代码被打包进了bundle中。这可能是因为某些模块被错误引入,或者存在无用的代码。通过分析依赖关系,可以识别并清理这些不必要的代码。
  4. 查看模块间的依赖关系

    • 了解模块之间的依赖关系可以帮助你优化代码拆分策略,以减少不必要的依赖关系,提高构建性能。

首先安装插件npm install --save-dev webpack-bundle-analyzer

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

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

浏览器打开了一个http://127.0.0.1:8888/的页面,页面上就是我们项目的产物依赖情况,

发现比较大的产物就是echarts、element-ui.common,js和wangeditor,这个后续可以考虑按需加载、使用CDN引入和压缩。

开始优化
升级webpack

为啥把webpack升级放到第一个,个人喜好吧,觉得这个升级可能是性能提升最大的一个。

首先下载一个npm-check,查看当前npm依赖包的情况,有些可能会出现npm证书的情况,比如我的旧项目,做法也比较简单,切换到最新的淘宝镜像源,然后删掉node_modules,重新安装即可。

运行npx npm-check就可以了,或者也可以全局安装。

npm-check是一个用于检查项目中npm依赖包是否有更新版本的工具。通过运行npm-check命令,可以列出当前项目中已安装的npm包,并显示它们是否有可用的更新版本。这样可以帮助开发者及时了解项目依赖包的更新情况,及时更新以确保项目的安全性和稳定性。

会出现这么几种情况,根据具体的保存信息自行调整。 😎 MAJOR UP 有一个或多个主要版本更新可用,建议及时更新以获取最新功能和修复bug。 😕 NOTUSED 表示该依赖包未被项目使用,可以考虑移除以减少项目的依赖项数量。

😟 PKG ERR!表示在检查依赖包时出现错误,可能是由于网络问题或依赖包本身存在问题导致的。 😟 MISSING!表示在项目中缺少某个依赖包,可能会影响项目的正常运行,需要及时安装该依赖包。

接下来使用npm-check-updates 来检查依赖库可更新的版本

运行npx npm-check-updates即可

npm-check-updates是一个npm包,它可以帮助你检查当前项目中的npm依赖包是否有可用的更新版本。通过运行npm-check-updates命令,你可以快速了解哪些依赖包可以更新到最新版本,以便及时更新你的项目依赖,保持项目的安全性和稳定性。这个工具可以帮助你轻松地管理npm依赖版本,提高项目的维护效率。npm check-updates包含以下常用命令:

  1. npm-check-updates:检查当前项目中哪些npm包可以更新到最新版本。
  2. npm-check-updates -u:将package.json文件中的依赖版本号更新为最新版本。
  3. npm-check-updates -a:显示所有可用的更新,包括主要版本更新。
  4. npm-check-updates -g:检查全局安装的npm包是否有可用更新。
  5. npm-check-updates -f <filter>:根据提供的过滤器筛选要检查的依赖包。
  6. npm-check-updates -x <exclude>:排除特定的依赖包不进行检查。

npm-check-updates -p <packageManager>:指定要使用的包管理器,如npm或yarn。

运行npx npm-check-updates -u在package.json中的依赖便被更新了

因为这里只关注webpack及相关依赖,不涉及vue,于是将vue-router,vuex,vue-template-compiler还原。

运行npm i再重新安装这些依赖即可。

安装完毕后,运行vue inspect > webpack-config.js重新查看配置情况

报错

Error: Cannot find module 'webpack/lib/RequestShortener'

于是手动更新webpack,npm install webpack@latest --save-dev

再运行vue inspect > webpack-config.js ERROR Error: Cannot call .tap() on a plugin that has not yet been defined. Call plugin('preload').use() first. Error: Cannot call .tap() on a plugin that has not yet been defined. Call plugin('preload').use() first.

这个错误通常是由于Webpack插件的使用顺序问题导致的。具体来说,Webpack要求在调用.tap()方法之前,必须先调用.use()方法来定义插件。

需要更改下插件的调用方式

比如原先的:

config.plugin("preload").tap(() => [
      {
        rel: "preload",
        // to ignore runtime.js
        // https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/config/app.js#L171
        fileBlacklist: [/.map$/, /hot-update.js$/, /runtime..*.js$/],
        include: "initial",
      },
    ]);

改成:

const PreloadWebpackPlugin = require('preload-webpack-plugin');
config.plugin('preload').use(PreloadWebpackPlugin, [
      {
        rel: 'preload',
        fileBlacklist: [/.map$/, /hot-update.js$/, /runtime..*.js$/],
        include: 'initial',
      },
    ]);

接着依次更改下所使用的插件

随后运行vue inspect > webpack-config.js

报错:

Error: Cannot find module 'preload-webpack-plugin'

查找了些资料发现:

在Webpack 5中,一些插件或工具可能需要额外安装,这可能是因为Webpack 5对插件系统或依赖项有所改变,导致某些插件不再默认包含在Webpack中。这可能是为了减少Webpack的体积,提高灵活性,或者是对插件生态系统的调整。

于是安装npm install preload-webpack-plugin

再次运行vue inspect > webpack-config.js

可以看到根目录下出现了一个webpack.config.js配置文件。

开始运行项目npm run serve

问题1:

发现报错 ERROR TypeError: compiler.plugin is not a function TypeError: compiler.plugin is not a function 发现 preload-webpack-plugin和webpack5不兼容,

暂时先干掉

问题2

发现报错 options has an unknown property 'overlay'. These properties are valid:webpack

在将webpack4升级到webpack5时,webpack-dev-server的配置项发生了一些变化。对于overlay属性,webpack5中已经不再支持这个属性,而是使用了新的方式来处理警告和错误。可以将overlay属性替换为client属性,并设置overlay为true来实现类似的功能。

原来的

devServer: {
    port: port,
    open: true,
    overlay: {
      warnings: false,
      errors: true,
    },
    ...

现在的

devServer: {
    port: port,
    open: true,
    client: {
      overlay: {
        warnings: false,
        errors: true
      }
    },

问题3:

继续报错

[@vue/compiler-sfc] the >>> and /deep/ combinators have been deprecated. Use :deep() instead. >>>和/deep/这两个组合选择器已经被弃用,取而代之的是使用:deep()伪类选择器来实现相同的功能。 这个需要更改原先的业务代码,而且改动较大,暂时搁置

问题4:

代码有很多不规范的写法导致编译报错

VueCompilerError: <template v-for> key should be placed on the <template> tag

Vue编译器错误,提示你在使用<template v-for>时应该将key属性放在<template>标签上而不是放在内部元素上。

将不规范的地方改过来即可。

问题4:

项目启动之后,获取不到process.env.VUE_APP_BASE_API,经过各种搜索发现

因为Webpack在构建过程中会将.env文件中的变量注入到process.env中,但这些变量在Vue组件中不会直接可见。

为了在Vue组件中访问.env文件中的变量,可以使用webpack.DefinePlugin插件将这些变量注入到Vue应用的全局变量中。你可以在vue.config.js中进行如下配置

chainWebpack: config => {
    config.plugin('define').use(require('webpack').DefinePlugin, [
      {
        'process.env': {
          VUE_APP_BASE_API: JSON.stringify(process.env.VUE_APP_BASE_API)
        }
      }
    ]);
  }

问题5:

BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.

If you want to include a polyfill, you need to:
        - add a fallback 'resolve.fallback: { "path": require.resolve("path-browserify") }'
        - install 'path-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
        resolve.fallback: { "path": false }

Webpack 5不再默认包含Node.js核心模块的polyfill,需要手动配置polyfill来解决这个问题。

手动安装npm install path-browserify --save

添加配置

resolve: {
      alias: {
        ...
      },
      fallback: {
        "path": require.resolve("path-browserify")
      }
    },

问题6:

ERROR in ./node_modules/vue-i18n/dist/vue-i18n.mjs 452:27-35
export 'computed' (imported as 'computed') was not found in 'vue' (possible exports: default)
 @ ./src/main.js 36:0-31

这个是版本不兼容导致的,在package.json中对vue-i18n进行还原,然后重新安装

至此我们的项目已经可以跑起来了。

运行打包命令,发现时间比我们没升级前要长,可能是

  1. 缺少缓存:Webpack 5 默认启用了持久化缓存(persistent caching),但是在升级过程中可能没有正确配置缓存导致重新构建所有模块,从而增加了打包时间。
  2. 模块解析速度变慢:Webpack 5 在模块解析方面进行了一些改进,但这也可能导致解析速度变慢。

当我们设置缓存和打包环境:

configureWebpack: {
    cache: {
      type: 'filesystem',
    },
    mode: 'production',
},

打包时间一下子缩减到20s,优化幅度很大了。后续再从其他方面进一步优化。

缓存

这里主要给babel添加loader缓存

{
  loader: 'cache-loader', // 使用缓存
  options: {
    cacheDirectory: path.resolve(__dirname, '.cache/babel-loader'), // 缓存目录
  },
},
{
  loader: 'babel-loader',
  options: {
    cacheDirectory: true, // 同样启用Babel自身的缓存
    presets: ['@babel/preset-env'],
    plugins: ['@babel/plugin-proposal-class-properties'],
  },
},

再添加个vue-loader缓存

configureWebpack: config => {
    config.module
      .rule('vue')
      .test(/.vue$/)
      .use('cache-loader')
      .before('vue-loader')
      .loader('cache-loader')
      .options({
        cacheDirectory: path.resolve(__dirname, '.cache/vue-loader'), // 设置缓存目录
      });
  },

General output time took 14.6 secs,时间降低到14.6,很快了。

压缩

在webpack5中,当mode设置为production时,默认会压缩代码的,这里实际上不需要额外的设置。然而涉及到图片,还是需要处理下的

{
          test: /.(png|jpe?g|gif)$/i,
          use: [
            {
              loader: 'url-loader',
              options: {
                limit: 8192, // 小于8KB的图片将被转换为base64编码
              },
            },
            {
              loader: 'image-webpack-loader',
              options: {
                mozjpeg: {
                  progressive: true,
                  quality: 65,
                },
                optipng: {
                  enabled: false,
                },
                pngquant: {
                  quality: [0.65, 0.9],
                  speed: 4,
                },
                gifsicle: {
                  interlaced: false,
                },
              },
            },
          ],
        },
        {
          test: /.svg$/,
          use: [
            {
              loader: 'url-loader',
              options: {
                limit: 8192, // 小于8KB的SVG将被转换为base64编码
              },
            },
            {
              loader: 'svgo-loader',
              options: {
                plugins: [
                  { removeViewBox: false },
                  { removeDimensions: true },
                ],
              },
            },
          ],
        },

General output time took 13.47 secs

并行构建
config.module
      .rule('js')
      .use('thread-loader')
      .loader('thread-loader')
      .options({
        workers: 4 // 指定worker
      })
      .end();

理想的worker数量通常不应超过你的CPU核心数。如果你有一个4核CPU,通常设置2到4个worker是合理的。设置过多的worker可能会导致上下文切换开销,反而降低效率。最好的办法就是多尝试:比如我的电脑就是8核的,不定的设置workers数,8、6、4、2,发现4效果最好,那就是他了。

General output time took 13.15 secs

最后打包时间为13s左右,发现其实效果没有之前那么显著了,

代码分隔
config.optimization.splitChunks({
      chunks: 'all',
      minSize: 30000,
      minChunks: 1,
      cacheGroups: {
        vendor: {
          test: /[\/]node_modules[\/]/,
          name: 'vendors',
          priority: -10,
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    });

webpack会根据这些规则将公共的依赖模块提取到单独的文件中,避免重复加载。

General output time took 13.57 secs

打包优化时间也是没什么变化。

至于cdn引入,按需加载第三方那个库,这就涉及到业务代码的改动,投入产出不成正比,就不动它了。还有在.gitignore文件中添加*.cache,因为新建了缓存目录,导致会多出很多文件,这个没必要添加到git上去的。

至此构建优化就到此为止了,打包时间从43s优化至13.57s,算是很大的优化了,热更新的时间也是1s左右的。

也想过换成vite试试,查过资料后换vite更是麻烦,一般的流程就是新建一个vite项目,然后将业务代码迁移过去,怕出错,就不试了。

转载自:https://juejin.cn/post/7373659736300584987
评论
请登录