Vite project performance analysis and optimization

Performance optimization has always been a common topic in front-end engineering, and it is also an important optimization point for front-end project optimization. In fact, as projects get larger and larger, a little carelessness can create noticeable performance issues. In different scenarios, our focus on project performance is different. In the project development stage, we need to pay attention to the development experience and project construction performance; while in the production environment, we generally pay more attention to the online runtime performance of the project.

Regarding the build performance in the development phase, Vite has done quite a lot of optimization internally, realizing project startup in seconds and hot updates in milliseconds. The specific implementation does not belong to the scope of this article. The performance optimization introduced in this article mainly refers to The project loading performance optimization of the above environment, and the FCP, TTI and other indicators of the page.

For the loading performance optimization of Vite projects, common optimization methods focus on the following three aspects:

  • Network Optimization. Including HTTP2, DNS pre-analysis, Preload, Prefetch and other means.
  • Resource optimization. Including building product analysis, resource compression, product unpacking, on-demand loading and other optimization methods.
  • Pre-rendering optimization, this article mainly introduces two methods of server-side rendering (SSR) and static site generation (SSG).

However, no matter which of the above optimization methods, it is inseparable from the support of construction tools. That is to say, in these performance optimization scenarios, we will use Vite frequently to deeply analyze the construction capabilities of Vite itself. application or customization.

1. Network optimization

1.1 HTTP2

Traditional HTTP 1.1 has the problem of head-of-line blocking. Only one HTTP request can be processed at the same time in the same TCP pipeline, that is to say, if the current request is not processed, other requests are blocked. There are restrictions on the number of concurrent requests. For example, only 6 concurrent requests are allowed in Chrome, which means that when the number of requests exceeds 6, the extra requests can only be queued and waiting to be sent.

Therefore, in the HTTP 1.1 protocol, head-of-line blocking and request queuing issues can easily become performance bottlenecks at the network layer. The birth of HTTP 2 is to solve these problems, as long as it is reflected in the following capabilities:

  • multiplexing . The data is divided into multiple binary frames, and multiple request and response data frames are transmitted on the same TCP channel, which solves the previous head-of-line blocking problem. At the same time, under the HTTP2 protocol, the browser no longer has a limit on the number of concurrent requests for the same domain name, so the request queuing problem has also been solved.
  • Server Push , that is, the server-side push capability. It can make certain resources reach the browser in advance, for example, for an html request, we can simultaneously push the corresponding js and css resources to the browser through HTTP 2, saving the overhead of subsequent requests.

In Vite, we can enable HTTP2 on the local Dev Server through vite-plugin-mkcert, and this plugin needs to be installed before use:

npm i vite-plugin-mkcert -D

Then, use it in Vite configuration:

// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import mkcert from "vite-plugin-mkcert";


export default defineConfig({
  plugins: [react(), mkcert()],
  server: {
    // https 选项需要开启
    https: true,
  },
});

The principle of the plug-in is also relatively simple. Since HTTP2 relies on TLS handshake, the plug-in will automatically generate a TLS certificate for you, and then support the startup via HTTPS, and Vite will automatically upgrade the HTTPS service to HTTP2.

After using HTTP2, the problem of a large number of parallel requests will be significantly improved in some cases. Here is a multi-request sample project . After the download is complete, execute the following command:

npm run generate

100 jsx files can be generated. We test in a weak network environment, so that the comparison effect is more obvious. The actual situation is as follows:

image.png

It can be seen that in terms of the time to draw the first screen of the page (FCP), after enabling HTTP2, the page performance can be optimized by more than 60%. In contrast to the performance under HTTP 1.1, it is not difficult to find that most of the time overhead is spent on request queuing, and the performance plummets when there are many concurrent requests.

Therefore, for online projects, the performance improvement of HTTP2 is very considerable, and it has almost become a must. The vite-plugin-mkcert plug-in used in the demonstration just now is only used in the development stage. In the production environment, we will configure the online server to enable HTTP2 capabilities. If you use Nginx, you can refer to: Nginx HTTP2 configuration .

1.2 DNS pre-resolution

When a browser sends a request to a cross-domain server, it first performs DNS resolution to resolve the server domain name to the corresponding IP address. We use dns-prefetch technology to advance this process and reduce the delay time of DNS resolution. The specific usage is as follows:

<!-- href 为需要预解析的域名 -->
<link rel="dns-prefetch" href="https://fonts.googleapis.com/"> 

Generally, dns-prefetch will be used together with preconnect. The former is used to resolve DNS, while the latter is used to establish a connection with the server, establish a TCP channel and perform TLS handshake to further reduce request delay. The way to use it is as follows:

<link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin>
<link rel="dns-prefetch" href="https://fonts.gstatic.com/">

It is worth noting that the link tag of preconnect generally needs to add crorssorigin (cross-domain identification), otherwise preconnect will fail for some font resources.

1.3 Preload/Prefetch

For some more important resources, we can preload them through the Preload method, that is, load them before the resources are used, instead of loading them when they are used, so that the resources can reach the browser earlier. The specific usage is as follows:

<link rel="preload" href="style.css" as="style">
<link rel="preload" href="main.js" as="script">

Among them, we generally declare the href and as attributes, which represent the resource address and resource type respectively. The browser compatibility of Preload is also relatively good, and more than 90% of browsers currently support it.

image.png

Different from ordinary script tags, for native ESM modules, browsers provide modulepreload for preloading:

<link rel="modulepreload" href="/src/app.js" />

The compatibility of modulepreload is as follows:

image.png

Only about 70% of browsers support this feature, but in Vite we can configure a polyfill that enables modulepreload with one click, so that all browsers (more than 90%) that support native ESM can use this feature. The configuration is as follows:

// vite.config.ts
export default {
  build: {
    polyfillModulePreload: true
  }
}

In addition to Preload, Prefetch is also a commonly used optimization method. It is equivalent to telling the browser to preload the resources of other pages when it is idle. For example, such a link tag is inserted in page A:

<link rel="prefetch" href="https://B.com/index.js" as="script">

In this way, the browser will load resources under the domain name of B after page A is loaded. If the user jumps to page B, the browser will directly use the preloaded resources, thereby increasing the loading speed of page B. Compared with Preload, Prefetch's browser compatibility is not very optimistic, and the specific data is shown in the figure below.

image.png

2. Resource optimization

2.1 Product analysis report

In order to visually perceive the volume of the product, it is recommended that you use rollup-plugin-visualizer for product analysis. After installation, you can use it directly, as follows:

// 注: 首先需要安装 rollup-plugin-visualizer 依赖
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { visualizer } from "rollup-plugin-visualizer";


// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react(),
    visualizer({
      // 打包完成后自动打开浏览器,显示产物体积报告
      open: true,
    }),
  ],
});

After you execute the npm run build command, the browser will automatically open the product analysis page, as shown in the figure below.

image.png

From it, you can easily observe the distribution of product volume and improve the efficiency of troubleshooting, such as locating some packages that are too large in volume, and then optimize them in a targeted manner.

2.2 Resource Compression

In a production environment, for the ultimate code size, we generally use build tools to compress the product. Specifically, there are several types of resources that can be compressed: JavaScript code, CSS code, and image files.

2.2.1 JavaScript compression

During the construction of the Vite production environment, the JavaScript product code will be automatically compressed, and the relevant configuration parameters are as follows:

// vite.config.ts
export default {
  build: {
    // 类型: boolean | 'esbuild' | 'terser'
    // 默认为 `esbuild`
    minify: 'esbuild',
    // 产物目标环境
    target: 'modules',
    // 如果 minify 为 terser,可以通过下面的参数配置具体行为
    // https://terser.org/docs/api-reference#minify-options
    terserOptions: {}
  }
}

It is worth noting the target parameter, which is the target environment of the compression product. The default parameter of Vite is modules, which is the following browserlist:

['es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1']

You may have doubts, since it is compressed code, why does it have anything to do with the target environment? In fact, the understanding of JS code compression is not enough to just remove blank lines and confuse variable names. In order to achieve the ultimate compression effect, the compressor generally converts the code at the grammatical level according to the browser's goals. Like the following example:

// 业务代码中
info == null ? undefined : info.name

If you configure the target as exnext, which is the latest JS syntax, you will find that the compressed code becomes as follows:

info?.name

This is what the compression tool does behind the scenes, converting certain statements into a higher-level syntax after recognition, so as to achieve a better code size. Therefore, it is very important to set a suitable target. Once the setting of the target environment cannot cover all user groups, it is very likely that some low-end browsers will have syntax incompatibility problems, resulting in online accidents.

2.2.2 CSS Minification

For the compression of CSS code, the relevant configuration in Vite is as follows:

// vite.config.ts
export default {
  build: {
    // 设置 CSS 的目标环境
    cssTarget: ''
  }
}

By default, Vite will use Esbuild to compress CSS code, and generally we don't need to configure cssTarget. However, when we need to be compatible with the WebView of Android WeChat, we need to set build.cssTarget to chrome61 to prevent vite from converting the rgba() color into the form of #RGBA hexadecimal symbols, causing style problems.

2.2.3 Image Compression

Image resources are generally the bulk of the product volume. If the image size can be effectively compressed, the project size will be greatly optimized. In Vite, we generally use vite-plugin-imagemin to compress images.

2.2.4 Product unpacking

Generally speaking, if you do not code-segment (or unpack) the product and pack them all into one chunk, the following problems will arise:

  • The code loaded on the first screen is too large, even the code that is not needed for the current page will be loaded.
  • The reuse rate of the online cache is extremely low, and changing one line of code can invalidate the cache of the entire bundle product.

Vite has the following built-in code unpacking capabilities:

  • CSS code splitting, that is to realize that one chunk corresponds to one css file.
  • By default, there is a set of unpacking strategies, which package the code of the application and the code of the third-party library into two products, and package the dynamically imported modules into a chunk separately.

Of course, we can also customize the configuration through the manualChunks parameter.

// vite.config.ts
{
  build {
    rollupOptions: {
      output: {
        // 1. 对象配置
        manualChunks: {
          // 将 React 相关库打包成单独的 chunk 中
          'react-vendor': ['react', 'react-dom'],
          // 将 Lodash 库的代码单独打包
          'lodash': ['lodash-es'],
          // 将组件库的代码打包
          'library': ['antd'],
        },
        // 2. 函数配置
          if (id.includes('antd') || id.includes('@arco-design/web-react')) {
            return 'library';
          }
          if (id.includes('lodash')) {
            return 'lodash';
          }
          if (id.includes('react')) {
            return 'react';
          }
      },
    }
  },
}

Of course, in function configuration, we also need to pay attention to the problem of circular references.

2.2.5 On-demand loading

In a complete web application, the current page may not need some modules. If the browser also needs to load these unnecessary modules while loading the current page, it may cause serious performance problems. A better way is to dynamically introduce routing components, such as using @loadable/component in React applications to load components asynchronously.

import React from "react";
import ReactDOM from "react-dom";
import loadable from "@loadable/component";
import { BrowserRouter, Routes, Route } from "react-router-dom";


const Foo = loadable(() => import("./routes/Foo"));
const Bar = loadable(() => import("./routes/Bar"));


ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <Routes>
        <Route path="/foo" element={<Foo />} />
        <Route path="/bar" element={<Bar />} />
      </Routes>
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById("root")
);

In this way, in the production environment, Vite will also separately package the dynamically imported components into a chunk. Of course, for the logic inside the component, we can also delay the execution through dynamic import to further optimize the loading performance of the first screen, as shown in the following code:

function App() {
  const computeFunc = async () => {
    // 延迟加载第三方库
    // 需要注意 Tree Shaking 问题
    // 如果直接引入包名,无法做到 Tree-Shaking,因此尽量导入具体的子路径
    const { default: merge } = await import("lodash-es/merge");
    const c = merge({ a: 1 }, { b: 2 });
    console.log(c);
  };
  return (
    <div className="App">
      <p>
        <button type="button" onClick={computeFunc}>
          Click me
        </button>
      </p>
    </div>
  );
}


export default App;

3. Pre-rendering optimization

Pre-rendering is a relatively mainstream optimization method today, mainly including server-side rendering (SSR) and static site generation (SSG).

In the SSR scenario, the server generates complete HTML content and returns it directly to the browser. The browser can render the complete first-screen content according to the HTML without relying on JS loading, thereby reducing the rendering pressure of the browser; On the other hand, because the network environment of the server is better, the data required by the page can be obtained faster, and the time for the browser to request data can also be saved.

However, SSG can generate complete HTML content during the construction phase. The biggest difference between it and SSR is that the generation of HTML is completed during the construction phase, rather than when the server is running. SSG can also give browsers complete HTML content without relying on JS loading, which can effectively improve page loading performance. However, compared with SSR, the content of SSG is often not dynamic enough, so it is suitable for relatively static sites, such as documents, blogs and other scenarios.

Four. Summary

This article mainly focuses on the performance optimization theme of the Vite project, and introduces you to some optimization methods commonly used in the project from three dimensions: network optimization, resource optimization, and pre-rendering optimization: In terms of network optimization, I introduce HTTP2, DNS pre-analysis, and Preconenct , Preload and Prefetch these optimization measures, at the resource optimization level, introduced the means of building product analysis, resource compression, product unpacking, on-demand loading, etc. Finally, in terms of pre-rendering optimization, re-reviewed the relevant content of SSR and SSG.

Guess you like

Origin blog.csdn.net/xiangzhihong8/article/details/131493984