关于vue.js:性能优化-使用-esbuild-为你的构建提速-🚀

背景

最近发现我的项目(基于Vue2)构建比较慢, 一次上线公布须要 15 分钟, 效率低下。

现在这个时代,工夫就是金钱,效率就是生命

于是这两天抽空对我的项目做了一次构建优化,线上(多国家)构建工夫, 从 10分钟 优化到 4分钟, 本地单次构建工夫, 从 300秒 优化到 90秒, 成果还不错。

明天把 具体的革新过程 和 相干 技术原理 整理出来分享给大家, 心愿对大家有所帮忙。

注释

首先看一下摆在面前的问题:

能够显著看出: 整体构建环节耗时过长, 效率低下,影响业务的公布和回滚

线上构建流程:

其中, Build baseBuild Region 阶段存在优化空间。

Build base 阶段的优化, 和运维团队沟通过, 后续会减少缓存解决。

本次次要关注 Build Region 阶段。

初步优化后,达到成果如下:

上面介绍这次优化的细节。

我的项目优化实战

面对耗时大这个问题,首先要做耗时数据分析。

这里引入 SpeedMeasurePlugin, 示例代码如下:

# vue.config.js

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

configureWebpack: (config) => {
  config.plugins.push(new SpeedMeasurePlugin());
}

失去后果如下:

失去: 

SMP  ⏱  Loaders

cache-loader, and 

vue-loader, and 

eslint-loader took 3 mins, 39.75 secs

  module count = 1894

cache-loader, and 

thread-loader, and 

babel-loader, and 

ts-loader, and 

eslint-loader took 3 mins, 35.23 secs

  module count = 482

cache-loader, and 

thread-loader, and 

babel-loader, and 

ts-loader, and 

cache-loader, and 

vue-loader took 3 mins, 16.98 secs

  module count = 941

cache-loader, and 

vue-loader, and 

cache-loader, and 

vue-loader took 3 mins, 9.005 secs

  module count = 947

mini-css-extract-plugin, and 

css-loader, and 

vue-loader, and 

postcss-loader, and 

sass-loader, and 

cache-loader, and 

vue-loader took 3 mins, 5.29 secs

  module count = 834

modules with no loaders took 1 min, 52.53 secs

  module count = 3258

mini-css-extract-plugin, and 

css-loader, and 

vue-loader, and 

postcss-loader, and 

cache-loader, and 

vue-loader took 27.29 secs

  module count = 25

css-loader, and 

vue-loader, and 

postcss-loader, and 

cache-loader, and 

vue-loader took 27.13 secs

  module count = 25

file-loader took 12.049 secs

  module count = 30

cache-loader, and 

thread-loader, and 

babel-loader took 11.62 secs

  module count = 30

url-loader took 11.51 secs

  module count = 70

mini-css-extract-plugin, and 

css-loader, and 

postcss-loader took 9.66 secs

  module count = 8

cache-loader, and 

thread-loader, and 

babel-loader, and 

ts-loader took 7.56 secs

  module count = 3

css-loader, and 

// ...


Build complete.

fetch translations

en has been saved!

id has been saved!

sp-MX has been saved!

vi has been saved!

zh-TW has been saved!

zh-CN has been saved!

th has been saved!

$ node ./script/copy-static-asset.js

✨  Done in 289.96s.

统计出耗时比拟大的几个loader:


Vue-loader 
eslint-loader
babel-loader
Ts-loader,
Thread-loader,
cache-loader

一般而言, 代码编译工夫和代码规模正相干。

依据以往优化教训,代码动态查看可能会占据比拟多工夫,眼光锁定在 eslint-loader 上。

在生产构建阶段, eslint 提示信息价值不大, 思考在 build 阶段去除,步骤前置

比方在 commit 的时候做查看, 或者在 merge 的时候加一条流水线,专门做动态查看。

给出局部示例代码:

image: harbor.shopeemobile.com/shopee/nodejs-base:16

stages:
  - ci

ci_job:
  stage: ci
  allow_failure: false
  only:
    - merge_requests
  script:
    - npm i -g pnpm
    - pnpm pre-build && pnpm lint && pnpm test
  cache:
    paths:
      - node_modules
    key: project

于此,初步确定两个优化方向:

  1. 优化构建流程, 在生产构建阶段去除不必要的查看。
  2. 集成 esbuild, 放慢底层构建速度。

1. 优化构建流程

查看我的项目的配置发现:

# vue.config.js

lintOnSave: true,

批改为:

# vue.config.js

lintOnSave: process.env.NODE_ENV !== 'production',

即: 生产环境的构建不做 lint 查看。

Vue 官网对此也有相干形容:https://cli.vuejs.org/zh/conf…

再次构建, 失去如下数据:

 SMP  ⏱  Loaders
cache-loader, and 
vue-loader took 1 min, 34.33 secs
  module count = 2841
cache-loader, and 
thread-loader, and 
babel-loader, and 
ts-loader took 1 min, 33.56 secs
  module count = 485
vue-loader, and 
cache-loader, and 
thread-loader, and 
babel-loader, and 
ts-loader, and 
cache-loader, and 
vue-loader took 1 min, 31.41 secs
  module count = 1882
vue-loader, and 
mini-css-extract-plugin, and 
css-loader, and 
postcss-loader, and 
sass-loader, and 
cache-loader, and 
vue-loader took 1 min, 29.55 secs
  module count = 1668
css-loader, and 
vue-loader, and 
postcss-loader, and 
sass-loader, and 
cache-loader, and 
vue-loader took 1 min, 27.75 secs
  module count = 834
modules with no loaders took 59.89 secs
  module count = 3258
...

Build complete.
fetch translations
vi has been saved!
zh-TW has been saved!
en has been saved!
th has been saved!
sp-MX has been saved!
zh-CN has been saved!
id has been saved!
$ node ./script/copy-static-asset.js

✨  Done in 160.67s.

有肯定晋升,其余 loader 耗时数据无显著异样。

上面开始集成 esbuid。

集成 esbuild

这部分的工作,次要是:集成 esbuild 插件到脚手架中

具体代码的批改,要看具体情况,大体分为两类:

  1. 本人用 webpack 实现了打包逻辑。
  2. 用的是 cli 自带的打包配置, 比方 vue-cli。

这两种形式我都会介绍,尽管模式上有所差别, 然而原理都是一样的

外围思路如下:

rules: [
    {
        test: /\.(js|jsx|ts|tsx)$/,
        loader: 'esbuild-loader',
        options: {
            charset: 'utf8',
            loader: 'tsx',
            target: 'es2015',
            tsconfigRaw: require('../../tsconfig.json'),
        },
        exclude: /node_modules/,
    },
    ...
]
const { ESBuildMinifyPlugin } = require('esbuild-loader');

optimization: {
    minimizer: [
        new ESBuildMinifyPlugin({
            target: 'es2015',
            css: true,
        }),
    ],
    ...
}

具体实现上,简略辨别为两类, 具体配置如下:

一、webpack.config.js

npm i -D esbuild-loader

1. Javascript & JSX transpilation (eg. Babel)

In webpack.config.js:

  module.exports = {
    module: {
      rules: [
-       {
-         test: /\.js$/,
-         use: 'babel-loader',
-       },
+       {
+         test: /\.js$/,
+         loader: 'esbuild-loader',
+         options: {
+           loader: 'jsx',  // Remove this if you're not using JSX
+           target: 'es2015'  // Syntax to compile to (see options below for possible values)
+         }
+       },

        ...
      ],
    },
  }

2. TypeScript & TSX

In webpack.config.js:

  module.exports = {
    module: {
      rules: [
-       {
-         test: /\.tsx?$/,
-         use: 'ts-loader'
-       },
+       {
+         test: /\.tsx?$/,
+         loader: 'esbuild-loader',
+         options: {
+           loader: 'tsx',  // Or 'ts' if you don't need tsx
+           target: 'es2015',
+            tsconfigRaw: require('./tsconfig.json'), // If you have a tsconfig.json file, esbuild-loader will automatically detect it.
+         }
+       },

        ...
      ]
    },
  }

3. JS Minification (eg. Terser)

esbuild 在代码压缩上,也有不错的体现:

具体比照数据见:https://github.com/privatenum…

In webpack.config.js:

+ const { ESBuildMinifyPlugin } = require('esbuild-loader')

  module.exports = {
    ...,

+   optimization: {
+     minimizer: [
+       new ESBuildMinifyPlugin({
+         target: 'es2015'  // Syntax to compile to (see options below for possible values)
+         css: true  // Apply minification to CSS assets
+       })
+     ]
+   },
  }

4. CSS in JS

如果你的 css 款式不导出为 css 文件, 而是通过比方’style-loader’加载的,也能够通过esbuild来优化。

In webpack.config.js:


  module.exports = {
    module: {
      rules: [
        {
          test: /\.css$/i,
          use: [
            'style-loader',
            'css-loader',
+           {
+             loader: 'esbuild-loader',
+             options: {
+               loader: 'css',
+               minify: true
+             }
+           }
          ]
        }
      ]
    }
  }

更多 esbuild 案例, 能够参考: https://github.com/privatenum…

二、vue.config.js

配置比较简单,间接贴代码了:

// vue.config.js

const { ESBuildMinifyPlugin } = require('esbuild-loader');

module.exports = {
  // ...

  chainWebpack: (config) => {
    // 应用 esbuild 编译 js 文件
    const rule = config.module.rule('js');

    // 清理自带的 babel-loader
    rule.uses.clear();

    // 增加 esbuild-loader
    rule
      .use('esbuild-loader')
      .loader('esbuild-loader')
      .options({
        loader: 'ts', // 如果应用了 ts, 或者 vue 的 class 装璜器,则须要加上这个 option 配置, 否则会报错: ERROR: Unexpected "@"
        target: 'es2015',
        tsconfigRaw: require('./tsconfig.json')
      })

    // 删除底层 terser, 换用 esbuild-minimize-plugin
    config.optimization.minimizers.delete('terser');

    // 应用 esbuild 优化 css 压缩
    config.optimization
      .minimizer('esbuild')
      .use(ESBuildMinifyPlugin, [{ minify: true, css: true }]);
  }
}

这一番组合拳打完,本地单次构建:

成果还是比拟显著的。

一次线上构建, 整体工夫从 10 分钟缩短为 4 分钟。

开心不到两分钟,发现隔壁我的项目居然能够做到 2 分钟…,

这我就不服气了,同样是 esbuild , 为何你的就这么秀?

去钻研了一下, 找到了起因。

  1. 他们的我的项目是 React + TSX, 我这次优化的我的项目是 Vue, 在文件的解决上就须要多过一层 vue-loader
  2. 他们的我的项目采纳了微前端, 对我的项目对了拆分,主我的项目只须要加载基座相干的代码, 子利用各自构建。 须要构建的主利用代码量大大减少, 这是次要起因。

这种微前端的拆分形式在我之前的文章中提到过, 看趣味的能够去看看。

你须要理解的 esbuild

第一局部次要介绍了一些实际中的细节, 根本都是配置, 没有太多有深度的内容, 这部分将介绍 更多 esbuild 原理性的内容作为补充。

结语

esbuild 是一个弱小的工具,心愿大家能充沛应用起来, 为业务带来更大价值。

好了,明天的内容就这么多,心愿对大家有所启发。

满腹经纶,文章若有谬误,欢送留言指出。

参考资料

https://cli.vuejs.org/zh/conf…
https://esbuild.github.io/get…
https://morioh.com/p/cfd2609d…
https://battlehawk233.cn/post…
https://esbuild.github.io/api…
https://webpack.docschina.org…
https://github.com/privatenum…

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理