本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《 阿里云开发者社区用户服务协议 》和 《 阿里云开发者社区知识产权保护指引 》。如果您发现本社区中有涉嫌抄袭的内容,填写 侵权投诉表单 进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。


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

大家好,我是 柒八九

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

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

你能所学到的知识点

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

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

image

这里再多絮叨几句,下面的大部分内容,都是从 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 项目的入口文件。

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

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

  1. 通过设置 type="module" script 标签引入 JS 资源(外部资源和内联脚本)
  2. 通过设置 rel="stylesheet" link 引入外部样式
<!DOCTYPE html>
    <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>
    <div id="app"></div>
  </body>
</html>
复制代码

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

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

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

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

<!DOCTYPE html>
    <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>
    <div id="app"></div>
  </body>
</html>
复制代码

Vite 针对 JS CSS 资源支持代码分割。当遇到 动态导入 时,会生成一个异步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 打包的大体流程。 image 上面的流程主要分四个步骤

  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.html config.plugins 创建了 rollup bundle 对象。

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.js vendor.js 中。

具体实现如下:

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

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

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


常见的插件

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

有一些关键插件。

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

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

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

打包阶段的插件执行顺序

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

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

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

  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
  1. 或一个 { 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';
      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 特定功能( SSR devServer )进行特殊处理。

Node 文件定位

image

5. vite:html-inline-proxy-plugin

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

6. vite:css

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

7. vite:esbuild

类型: ESBuildOptions | false

ESBuildOptions 继承自 esbuild 转换选项

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

export default defineConfig({
  esbuild: {
    jsxFactory: 'h',
    jsxFragment: 'Fragment',
复制代码

默认情况下, esbuild 会被应用在 ts jsx tsx 文件。你可以通过 esbuild.include esbuild.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 引用它们或直接通过 fetch XHR 请求它们时,它们将 被插件转换管道排除在外 。)
  • 引用的资源作为构建资源图的一部分包括在内,将生成散列文件名,并可以由插件进行处理以进行优化。
  • 较小的资源体积小于 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 目录中,它应位于你的 项目根目录 。该目录中的资源 在开发时能直接通过 / 根路径访问到,并且打包时会被完整复制到目标目录的根目录下

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

请注意:

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

12. 常规插件

没有 enforce 值的用户插件

13. 'vite:define'

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

类型: Record

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

define: {
  __APP_VERSION__: `JSON.stringify(${version})`
复制代码

对于使用 TypeScript 的项目,还需要 env.d.ts vite-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 加载完成时 自动通过 一个 标签载入,该 异步 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 插件作为注入。

15. vite:build-html

这个插件会将 HTML 文件中的 标签编译成一个 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:[][;base64],

我们可以从 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 时,构建也将生成 SSR manifest 文件,以确定生产中的样式链接与资产预加载指令。当该值为一个字符串时,它将作为 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()函数 React React.lazy() 函数来实现代码分割。

import React, { lazy, Suspense } from 'react';
const LazyComponent = lazy(() => import('./LazyComponent'));
function MyComponent() {
  return (
      <Suspense fallback={<div>Loading...</div>}>
        <LazyComponent />
      </Suspense>
复制代码

预取/预加载

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

<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

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

image


在 ESM 出现之前,Javascript 是没有一个标准的模块方案。 比如说 `CJS` 是用于 Node 服务端的模块化方案,`AMD` 是用于浏览器的模块化方案。为了解决这个模块共用性问题,出现了 `UMD` 用于兼容这两种模块规范。 鉴于上面共用性问题,实际开发中配置的打包方式,采用的还是 UMD 模式。因为这样可以避免打包而产生的规范问题,并且在 ESM 不能使用的情况下也会选择 UMD。