浅聊Vite

1,578 阅读16分钟

慢慢认识世界,慢慢更新自己。

大家好,我是柒八九

由于,新公司的项目打包是用的Vite,而之前的所参与的项目都是用Webpack作为打包工具,原来对Vite的了解,只是一个把玩工具,没有过多的深入了解。本着干一行,爱一行的职业态度。所以,就找了很多相关资料学习和研究。

以下的内容,都是基于本人对Vite的个人见解。不一定对,随便看看。

你能所学到的知识点

  1. vite 是个啥? 推荐阅读指数 ⭐️⭐️⭐️
  2. vite 打包阶段 推荐阅读指数 ⭐️⭐️⭐️⭐️⭐️
  3. 打包阶段的插件执行顺序 推荐阅读指数 ⭐️⭐️⭐️⭐️⭐️
  4. Vite+React的项目打包优化(简单版) 推荐阅读指数 ⭐️⭐️⭐️⭐️

好了,天不早了,干点正事哇。

这里再多絮叨几句,下面的大部分内容,都是从Vite打包阶段聊,针对HMR一些内容,没有涉及。


vite 是个啥?

Vite是一种现代化的前端构建工具,它的目标是提供一种快速、简单和易于使用的开发体验。Vite使用了一些新的技术来实现快速的开发体验,这些技术包括ES模块即时编译热重载

  • ES模块是一种新的JavaScript模块格式,它是浏览器原生支持的。ES模块提供了一种简单、可读、可扩展的方式来组织代码,同时还提供了静态分析优化的机会Vite利用ES模块的特性,将应用程序拆分成更小的代码块,使得应用程序的加载时间更快。

  • Vite使用即时编译来实现更快的开发体验。

    • 即时编译是指在代码更改时,Vite会立即编译代码并将其发送到浏览器中。
    • 这意味着开发人员不需要等待编译过程完成,就可以看到更改后的效果。
    • 这大大缩短了开发周期,并提高了开发效率。
  • 最后,Vite还使用热重载技术。

    • 热重载是指在开发过程中,如果更改了代码,应用程序将自动重新加载,而无需手动刷新页面。这使得开发人员能够更快地看到更改后的效果,并且不会丢失任何数据。

想了解更多关于Vite的介绍,可以参考官网


vite 打包阶段

在讨论Vite时,通常关注的是其作为开发服务器以及它如何在开发过程中实现即时反馈。但是,在生产环境下,Vite也会大放异彩。

下面,我们就一起来了解一下Vite是如何在生产环境下,对你的项目代码进行打包/优化处理的。本文假定你已经对Vite有一定的了解。如果第一次听说,你可以先移步到为什么选 Vite来了解相关的设计理念和解决哪些痛点。

用上帝视角看Vite如何处理资源

在一个 Vite 项目中,index.html 在项目最外层而不是在 public 文件夹内。这是有意而为之的:在本地开发阶段 Vite 是一个资源服务器,而 index.html 是该 Vite 项目的入口文件。

Viteindex.html 视为源码和模块图的一部分。

这个HTML文件用于引入网站资源信息

  1. 通过设置type="module"script标签引入JS资源(外部资源和内联脚本)
  2. 通过设置 rel="stylesheet"link引入外部样式
<!DOCTYPE html>
<html>
  <head>
    <script type="module" src="/src/main.ts"></script>
    <script type="module">
      // 内联 script
    </script>
    <link rel="stylesheet" href="/src/main.css">
    <style>
      internal-style: {}
    </style>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>

Vite接收HTML文件,来查找每个加载的JS模块、内联脚本和CSS样式表。

  1. JS源代码通过Rollup处理,解析和转换内部依赖项,生成一个不经常更新vendor.js(带有依赖项)和一个index.js(应用程序的其余部分)。
    • 这些源文件被转换成的带有 hash 值的文件,以实现强缓存
  2. 任何在JS中被引用的CSS文件都会被打包到index.css文件中
    • 内部样式不会被处理。

scriptimport可以指向任何文件类型,只要Vite知道如何转译它们即可。在上面的情况下,main.ts文件需要在打包过程中转换为JS文件。在Vite中,使用基于Go的打包工具esbuild实现对应资源的转换。

在资源被转换后,也会生成一个新的资源地址,用它们替换原来的资源地址。并且Vite执行import静态分析,为每个JS插入模块预加载标记rel="modulepreload" ),使浏览器可以并行加载这些资源,从而避免加载瀑布效应。

<!DOCTYPE html>
<html>
  <head>
    <script type="module" src="/assets/index.d93758c6.js"></script>
    <link rel="modulepreload" href="/assets/vendor.a9c538d6.js">
    <link rel="stylesheet" href="/assets/index.3015a40c.css">
    <style>
      internal-style: {}
    </style>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>

Vite针对JSCSS资源支持代码分割。当遇到动态导入时,会生成一个异步JS块和一个CSS块。

其他资源,如图像、视频、wasm,可以使用相对路径导入。在生成其输出文件时,Vite还会对这些文件进行hash处理,并重写JS和CSS文件中的URL并指向它们。

另一方面,public文件夹中的资源会按原样复制到输出根目录,并允许用户通过绝对路径引用这些文件

每个JS和CSS块都需要在生产中进行压缩。自从Vite 2.6.0以来,Vite也使用esbuild为两种语言执行压缩任务,以加快构建过程。

Rollup的二次封装

Vite 应用的打包过程,是基于Rollup 的二次封装 。Vite中可以直接使用现有的Rollup插件,实现很多开箱即用的功能。

但是,需要注意一些与 Rollup 插件的兼容性问题,但大多数来自 Rollup 生态系统的插件都可以直接作为 Vite 插件工作

Vite内部打包流程

下图展示了,Vite打包的大体流程。 上面的流程主要分四个步骤

  1. 收集打包配置信息
  2. 确认打包后的资源路径
  3. 使用rollup打包处理
  4. 资源输出

当你执行vite build时,Vite CLI被运行。运行命令是用cac实现的。该操作触发了 build 函数。

await build({ root, base, mode, config, logLevel, clearScreen, buildOptions })

build又调用 doBuild。实现逻辑如下。

async function doBuild(inlineConfig: InlineConfig = {}): RollupOutput{
  // ①收集打包配置信息
  const config = await resolveConfig(inlineConfig, 'build', 'production')
  // ②确认打包后的资源路径
  const outDir = resolve(config.build.outDir)

  prepareOutDir(outDir, config)
  
  // ③打包处理
  const bundle = await rollup.rollup({
    input: resolve('index.html'),
    plugins: config.plugins
  })
  // ④资源输出
  return await bundle.write({
    dir: outDir,
    format: 'es',
    exports: 'auto',
    sourcemap: config.build.sourcemap,
    entryFileNames: path.join(config.assetsDir, `[name].[hash].js`),
    chunkFileNames: path.join(config.assetsDir, `[name].[hash].js`),
    assetFileNames: path.join(config.assetsDir, `[name].[hash].[ext]`),
    manualChunks: createMoveToVendorChunkFn()
  })
}

首先,调用resolveConfig用于解析用户配置项目配置文件Vite默认值来生成一个具体的ResolvedConfig

const config = await resolveConfig(inlineConfig, 'build', 'production')

接下来,确认好输出目录,并在生成资产之前清空它。这个函数还将publicDir的内容复制到项目dist文件夹

const outDir = resolve(config.build.outDir)
prepareOutDir(outDir, config)

然后,基于index.htmlconfig.plugins创建了rollupbundle对象。

const bundle = await rollup.rollup({
    input: resolve('index.html'),
    plugins: config.plugins
  })

最后bundle.write被调用,用于生成输出目录中的资源信息。

return await bundle.write({
    dir: outDir,
    format: 'es',
    exports: 'auto',
    sourcemap: config.build.sourcemap,
    entryFileNames: path.join(options.assetsDir, `[name].[hash].js`),
    chunkFileNames: path.join(options.assetsDir, `[name].[hash].js`),
    assetFileNames: path.join(options.assetsDir, `[name].[hash].[ext]`),
    manualChunks: createMoveToVendorChunkFn()
  })

createMoveToVendorChunkFn函数定义了默认的分块策略,定义了JS被打包后,何种资源被分配到index.jsvendor.js中。

具体实现如下:

function createMoveToVendorChunkFn() {
  return (id, { getModuleInfo }) => {
    if (
      id.includes('node_modules') &&
      !isCSSRequest(id) &&
      staticImportedByEntry(id, getModuleInfo)
    ) {
      return 'vendor'
    }
  }
}

通过对Vite的打包做了一个简单的分析,我们可以得知:

Vite的构建过程,就是以Rollup为基础,借助插件对资源的二次处理过程。


常见的插件

插件在开发阶段打包阶段是通过resolvedPlugins进行解析。Vite打包时通过resolveBuildPlugins插入额外的插件,以处理压缩和其他优化

有一些关键插件。

  • vite:build-htmlvite:html-inline-proxy-plugin用于处理HTML,将JSCSS替换为经Vite优化过的对应资源。
  • vite:cssvite:css-post用于处理CSS和预处理器。
  • vite:esbuild用于为每个模块转换TypeScriptJSX
  • vite:asset用于管理静态资源。
  • vite:build-import-analysis用于预加载优化、支持全局导入和URL重写。
  • vite:esbuild-transpile用于将chunks转换为合适的目标和压缩对应资源。

还有一些插件是官方Rollup插件

  • alias
  • commonjs
  • rollup-plugin-dynamic-import-variables

打包阶段的插件执行顺序

webpack来说,plugins的作用在于强化其构建过程中,所遇到的一些工程化的问题,比如代码压缩,资源压缩等,所以vite作为新时代的构建工具,理应当也具有插件系统来解决构建项目的整个生命周期中所遇到的工程化的问题,说白了,插件就是为了解决某一类型的问题而出现的一个或一种工具函数。比如lodash,他被称之为一个库,也可以认作是一个插件。所以vite会在不同的生命周期中调用不同的插件去达成不同的目的。

让我们深入了解每个插件的使用方式和职责权限。

一个 Vite 插件可以额外指定一个 enforce 属性来调整它的应用顺序。enforce 的值可以是prepost。解析后的插件将按照以下顺序排列:

  1. Alias
  2. 带有 enforce: 'pre' 的用户插件(前置插件)
  3. Vite 核心插件
  4. 没有 enforce 值的用户插件(常规插件)
  5. Vite 构建用的插件
  6. 带有 enforce: 'post' 的用户插件(后置插件)
  7. Vite 后置构建插件(最小化,manifest,报告)

Vite构建运行的插件的执行顺序如下:

  1. alias
  2. 带有 enforce: 'pre' 的用户插件(前置插件)
  3. vite:modulePreload
  4. vite:resolve
  5. vite:html-inline-proxy-plugin
  6. vite:css
  7. vite:esbuild
  8. vite:json
  9. vite:wasm
  10. vite:worker
  11. vite:asset
  12. 没有 enforce 值的用户插件(常规插件)
  13. vite:define
  14. vite:css-post
  15. vite:build-html
  16. commonjs
  17. vite:data-uri
  18. rollup-plugin-dynamic-import-variables
  19. vite:asset-import-meta-url
  20. 带有 enforce: 'post' 的用户插件(后置插件)
  21. vite:build-import-analysis
  22. vite:esbuild-transpile
  23. vite:terser
  24. vite:manifest
  25. vite:ssr-manifest
  26. vite:reporter

1. alias

该插件,用于在打包时定义别名,与Webpack中的resolve.alias相同。

  1. 可以是一个对象
    • Record<string, string>
  2. 或一个 { find, replacement, customResolver } 的数组
    • Array<{ find: string | RegExp, replacement: string, customResolver?: ResolverFunction | ResolverObject }>

用以下方法配置Vite

resolve: {
  alias: { 
      '@components': path.resolve(__dirname, 'src/components') 
        }
}

可以让你从源码中的任何地方导入你想要导出的数据

import Button from '@components/Button.tsx'

这个插件解析路径并将它们转译为真实的路径

import Button from '../../components/Button.tsx'

2. 带有 enforce: 'pre' 的用户插件(前置插件)

这些是带有enforce: 'pre'的插件。例如,@rollup/plugin-image配置了该属性,它就在Vite的内置插件之前运行。

import image from "@rollup/plugin-image"

export default {
  plugins: [
    {
      ...image(),
      enforce: 'pre',
    },
  ]
}

3. vite:modulePreload

  • 类型: boolean | { polyfill?: boolean, resolveDependencies?: ResolveModulePreloadDependenciesFn }
  • 默认值: { polyfill: true }

默认情况下,一个模块预加载 polyfill 会被自动注入。该 polyfill 会自动注入到每个 index.html 入口的的代理模块中。

如果构建通过 build.rollupOptions.input 被配置为了使用非 HTML 入口的形式,那么必须要在你的自定义入口中手动引入polyfill

import 'vite/modulepreload-polyfill'

polyfill 实现

<script>
  function processPreload () {
    const fetchOpts = {};
    if (script.integrity)
      fetchOpts.integrity = script.integrity;
    if (script.referrerpolicy)
      fetchOpts.referrerPolicy = script.referrerpolicy;
    if (script.crossorigin === 'use-credentials')
      fetchOpts.credentials = 'include';
    else if (script.crossorigin === 'anonymous')
      fetchOpts.credentials = 'omit';
    else
      fetchOpts.credentials = 'same-origin';
    
   fetch(link.href, fetchOpts)
      .then(res => res.ok && res.arrayBuffer());
  }
  
  const links = document.querySelectorAll('link[rel=modulepreload]');
  for (const link of links)
    processPreload(link);
</script>

polyfill允许Vite预加载模块以避免加载瀑布,支持非Chromium浏览器。

4. vite:resolve

它使用Node解析算法来定位node_modules中的第三方模块。它与官方的rollup插件不同,因为需要对Vite特定功能(SSRdevServer)进行特殊处理。

Node 文件定位

5. vite:html-inline-proxy-plugin

该插件将入口HTML文件中的内联脚本作为单独的模块加载。这些脚本由vite:build-html插件将其从HTML中删除,并替换为一个type="module"script

6. vite:css

该插件与vite:css-post插件一起使用来实现Vite的CSS功能。支持预处理器(postCSSsassless),包括解析导入的URL。

7. vite:esbuild

类型: ESBuildOptions | false

ESBuildOptions 继承自 esbuild 转换选项

最常见的用例是自定义 JSX:

export default defineConfig({
  esbuild: {
    jsxFactory: 'h',
    jsxFragment: 'Fragment',
  },
})

默认情况下,esbuild 会被应用在 tsjsxtsx 文件。你可以通过 esbuild.includeesbuild.exclude 对要处理的文件类型进行配置。

此外,你还可以通过 esbuild.jsxInject 来自动为每一个被 esbuild 转换的文件注入 JSX helper

export default defineConfig({
  esbuild: {
    jsxInject: `import React from 'react'`,
  },
})

8. vite:json

处理JSON文件的导入。

// 导入整个对象
import json from './example.json'

// 导入一个根字段作为命名的出口, 便于tree shaking
import { field } from './example.json'

9. vite:wasm

此插件允许用户直接导入预编译的.wasm文件。

预编译的 .wasm 文件可以通过 ?init 来导入。默认导出一个初始化函数,返回值为所导出 wasm 实例对象的 Promise

import init from './example.wasm?init'

init().then((instance) => {
  instance.exports.test()
})

在生产构建当中,体积小于 assetInlineLimit.wasm 文件将会被内联为 base64 字符串。否则,它们将作为资源复制到 dist 目录中,并按需获取。

10. 'vite:worker'

通过构造器导入

一个 Web Worker 可以使用 new Worker()new SharedWorker() 导入。与 worker 后缀相比,这种语法更接近于标准,是创建 worker 的 推荐 方式。

const worker = new Worker(
          new URL('./worker.js', import.meta.url)
      )

worker 构造函数会接受可以用来创建 “模块” worker 的选项:

const worker = new Worker(
      new URL('./worker.js', import.meta.url), 
      { type: 'module',}
)

带有查询后缀的导入

你可以在导入请求上添加 ?worker?sharedworker 查询参数来直接导入一个 web worker 脚本。默认导出会是一个自定义 worker 的构造函数:

import MyWorker from './worker?worker'

const worker = new MyWorker()

默认情况下,worker 脚本将在生产构建中编译成单独的 chunk。如果你想将 worker 内联为 base64 字符串,请添加 inline 查询参数:

import MyWorker from './worker?worker&inline'

如果你想要以一个 URL 的形式读取该 worker,请添加 url 这个 query

import MyWorker from './worker?worker&url'

11. 'vite:asset'

该插件用于资源的处理。

将资源引入为 URL

引入一个静态资源会返回解析后的公共路径:

import imgUrl from './img.png'
document.getElementById('hero-img').src = imgUrl

例如,imgUrl 在开发时会是 /img.png,在生产构建后会是 /assets/img.2d8efhg.png

  • 常见的图像媒体字体文件类型被自动检测为资源。你
    • 可以使用 assetsInclude 选项扩展内部列表。(当从 HTML 引用它们或直接通过 fetchXHR 请求它们时,它们将被插件转换管道排除在外。)
  • 引用的资源作为构建资源图的一部分包括在内,将生成散列文件名,并可以由插件进行处理以进行优化。
  • 较小的资源体积小于 assetsInlineLimit 选项值 则会被内联为 base64 data URL

显式 URL 引入

未被包含在内部列表assetsInclude 中的资源,可以使用 ?url 后缀显式导入为一个 URL。

import workletURL from 'extra-scalloped-border/worklet.js?url'
CSS.paintWorklet.addModule(workletURL)

将资源引入为字符串

资源可以使用 ?raw 后缀声明作为字符串引入。

import shaderString from './shader.glsl?raw'

public 目录

如果你有下列这些资源:

  • 不会被源码引用(例如 robots.txt)
  • 必须保持原有文件名(没有经过 hash)
  • ...或者你压根不想引入该资源,只是想得到其 URL。

那么你可以将该资源放在指定的 public 目录中,它应位于你的项目根目录。该目录中的资源在开发时能直接通过 / 根路径访问到,并且打包时会被完整复制到目标目录的根目录下

目录默认是 <root>/public,但可以通过 publicDir 选项 来配置。

请注意:

  • 引入 public 中的资源永远应该使用根绝对路径
    • 举个例子,public/icon.png 应该在源码中被引用为 /icon.png
  • public 中的资源不应该被 JavaScript 文件引用。

12. 常规插件

没有 enforce 值的用户插件

13. 'vite:define'

定义全局常量替换方式。其中每项在开发环境下会被定义在全局,而在构建时被静态替换

类型: Record<string, any>

String 值会以原始表达式形式使用,所以如果定义了一个字符串常量,它需要被显式地打引号。(例如使用 JSON.stringify

define: {
  __APP_VERSION__: `JSON.stringify(${version})`
}

对于使用 TypeScript 的项目,还需要 env.d.tsvite-env.d.ts 文件中添加类型声明,以获得类型检查以及代码提示。

// vite-env.d.ts
declare const __APP_VERSION__: string

14. vite:css-post

这个插件用esbuild对CSS资源进行最小化。

css资源的URL占位符被解析为其最终的构建路径。

它还实现了CSS代码拆分Vite 会自动地将一个异步 chunk 模块中使用到的 CSS 代码抽取出来并为其生成一个单独的文件。这个 CSS 文件将在该异步 chunk 加载完成时自动通过一个 <link> 标签载入,该异步 chunk 会保证只在 CSS 加载完毕后再执行,避免发生 FOUC

{无样式内容的闪光|flash of unstyled content}FOUC)是指在加载外部CSS样式表之前,网页以浏览器的默认样式短暂出现的情况,这是由于网络浏览器引擎在检索到所有信息之前渲染了该网页。

下面是Vite中预加载插件的的简化版本。

function createLink(dep) {
  // JS  -> <link rel="modulepreload" href="dep" />
  // CSS -> <link rel="stylesheet" href="dep" />
}

function preload(importModule, deps) {
  return Promise.all(
    deps.map(dep => {
      if (!alreadyLoaded(dep)) { 
        document.head.appendChild(createLink(dep))      
        if (isCss(dep)) {
          // 等CSS资源加载,避免出现FOUC 
          return new Promise((resolve, reject) => {
            link.addEventListener('load', resolve)
            link.addEventListener('error', reject)
          })
        }
      }
    })
  ).then(() => importModule())
}

这个插件将使用上面的辅助函数来转换动态导入。以下是

import('./async.js')

将被转换为

preload(
  () => import('/assets/async.js),
  ['/assets/async.css','/assets/async-dep.js']
)

如果build.cssCodeSplit的值为false,这些块会被vite:build-html插件作为<link>注入。

15. vite:build-html

这个插件会将 HTML 文件中的 <script> 标签编译成一个 JS 模块。

  • 它会在 transform 钩子中移除 HTML 中的 script 标签,生成一个 JS 文件,用于引入每个模块和资源文件。
  • 随后,在 generateBundle 钩子中插入 JS 文件,并使用 vite:asset 插件插入资源文件。

16. commonjs

它将CommonJS模块转换为ES6,这样它们就可以被包含在Rollup的包中。

  • 在开发过程中,Vite使用esbuild进行资源的pre-bundling,它负责将CommonJS转换为ES6
  • 但在构建过程中,没有pre-bundling的这步,所以需要commonjs插件。

17. vite:data-uri

它从data-URI导入模块。

Data URL,即前缀为 data: 协议的 URL,其允许内容创建者向文档中嵌入小文件。

  • Data URL 由四个部分组成:前缀(data:)、指示数据类型的 MIME 类型、如果非文本则为可选的 base64 标记、数据本身:
  • data:[<mediatype>][;base64],<data>

我们可以从DataURL中导入模块。

import batman from 'data:application/json;base64, eyAiYmF0bWFuIjogInRydWUiIH0=';

18. rollup/plugin-dynamic-import-vars

用于支持动态导入中的变量。

它是用build.dynamicImportVarsOptions来配置的。

import dynamicImportVars from '@rollup/plugin-dynamic-import-vars';

export default {
  plugins: [
    dynamicImportVars({
      // options
    })
  ]
};

允许用户编写动态解析的导入:

function importLocale(locale) {
  return import(`./locales/${locale}.js`);
}

19. vite:asset-import-meta-url

new URL(path, import.meta.url)转换为内置URL

import.meta.url 是一个 ESM 的原生功能,会暴露当前模块的 URL。将它与原生的 URL 构造器 组合使用,在一个 JavaScript 模块中,通过相对路径我们就能得到一个被完整解析的静态资源 URL

const imgUrl = new URL('./img.png', import.meta.url).href

document.getElementById('hero-img').src = imgUrl

这个模式同样还可以通过字符串模板支持动态 URL:

function getImageUrl(name) {
  return new URL(`./dir/${name}.png`, import.meta.url).href
}

在生产构建时,Vite 才会进行必要的转换保证 URL 在打包和资源哈希后仍指向正确的地址。然而,这个 URL 字符串必须是静态的,这样才好分析。否则代码将被原样保留、因而在 build.target 不支持 import.meta.url 时会导致运行时错误。

// Vite 不会转换这个
const imgUrl = new URL(imagePath, import.meta.url).href

20. 后置插件

带有 enforce: 'post' 的用户插件。

21. vite:build-import-analysis

这个插件会对 URL 导入进行词法分析、解析、重写和分析。

动态导入会增加预加载指令。在客户端代码中注入一个辅助函数,用于在异步块本身异步加载时并行预加载 CSS 和直接导入的异步块。

Glob 导入会被识别并使用 transformImportGlob 进行转译。例如:

const modules = import.meta.glob('./dir/*.js')

被转化为

const modules = {
  './dir/foo.js': () => import('./dir/foo.js'),
  './dir/bar.js': () => import('./dir/bar.js')
}

22. vite:esbuild-transpile

这个插件对每个渲染的块进行转译,以支持配置的目标。

如果build.minify"esbuild"Vite3+的版本是默认值),它也将使用esbuild来最小化代码,避免了对terser 的需求。它比 terser 快 20-40 倍,压缩率只差 1%-2%

23. vite:terser

如果build.minify'terser',这个插件就会被用来使用terser对每个渲染的块进行最小化。

24. vite:manifest

当设置为 true,构建后将会生成 manifest.json 文件,包含了没有被 hash 过的资源文件名和 hash 后版本的映射。可以为一些服务器框架渲染时提供正确的资源引入链接。当该值为一个字符串时,它将作为 manifest 文件的名字。

25. vite:ssr-manifest

当设置为 true 时,构建也将生成 SSRmanifest 文件,以确定生产中的样式链接与资产预加载指令。当该值为一个字符串时,它将作为 manifest 文件的名字。

26. vite:reporter

一个记录进度的插件,以及一份包含生成块和资源信息的报告。

$ vite build
vite v3.1.0 building for production...
✓ 34 modules transformed.
dist/assets/favicon.17e50649.svg   1.49 KiB
dist/assets/logo.ecc203fb.svg      2.61 KiB
dist/index.html                    0.52 KiB
dist/assets/index.3015a40c.js      1.39 KiB / gzip:  0.73 KiB
dist/assets/index.d93758c6.css     0.77 KiB / gzip:  0.49 KiB
dist/assets/vendor.a9c538d6.js   129.47 KiB / gzip: 41.77 KiB
Done in 2.90s.

Vite+React的项目打包优化(简单版)

可以从以下几点出发

  1. 代码分割
  2. 预取/预加载
  3. 代码压缩
  4. 图片压缩
  5. 缓存策略

代码分割

使用代码分割可以将代码划分成较小的块,从而减少页面加载时间。可以使用Vite提供的import()函数ReactReact.lazy()函数来实现代码分割。

import React, { lazy, Suspense } from 'react';

const LazyComponent = lazy(() => import('./LazyComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <LazyComponent />
      </Suspense>
    </div>
  );
}

预取/预加载

使用预取/预加载可以在用户访问页面之前预加载页面所需的资源,从而加快页面加载时间。可以使用Vite提供的<link rel="prefetch" href="..." />标签来实现预取/预加载。

<link rel="prefetch" href="./lazy-component.js" />

代码压缩

使用代码压缩可以减小文件大小,从而加快页面加载时间。可以在Vite配置文件中启用代码压缩选项。

// vite.config.ts
import { defineConfig } from 'vite';
import reactRefresh from '@vitejs/plugin-react-refresh';
import { terser } from 'rollup-plugin-terser';

export default defineConfig({
  plugins: [reactRefresh(), terser()],
});

图片压缩

使用图片压缩可以减小图片大小,从而加快页面加载时间。可以使用Vite提供的imagemin插件来实现图片压缩。

// vite.config.ts
import { defineConfig } from 'vite';
import reactRefresh from '@vitejs/plugin-react-refresh';
import { imagemin } from 'rollup-plugin-imagemin';

export default defineConfig({
  plugins: [
    reactRefresh(),
    imagemin({
      plugins: [
        // add imagemin plugins here
      ],
    }),
  ],
});

缓存策略

使用缓存策略可以减少重复的网络请求,从而加快页面加载时间。可以在Vite配置文件中配置缓存策略选项。

// vite.config.ts
import { defineConfig } from 'vite';
import reactRefresh from '@vitejs/plugin-react-refresh';

export default defineConfig({
  plugins: [reactRefresh()],
  build: {
    // set cache options here
    cacheDir: '.vite-cache',
  },
});

后记

分享是一种态度

参考资料:

  1. vite-plugin
  2. vite-github
  3. vite-build

全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。

本文正在参加「金石计划」