banner
banner
banner
NEWS LETTER

Vite --- 新一代前端构建工具

Scroll down

一个基于原生 ESM 的前端开发和构建工具,提供极速的服务启动、热重载、按需编译、类型支持和插件系统。

优点

  • 1.服务器启动速度快:通过将应用中的模块区分为依赖和源码两类

    • 依赖:大多为在开发时不会变动的纯 JS
      • 使用 esbuild 预构建依赖
      • esbuild 使用 Go 编写,并且比以 JS 编写的打包器预构建依赖快 10-100 倍
    • 源码:包含一些非.js文件(例如 JSX,CSS 或者 组件)
      • 并不是所有的源码都需要同时被加载(例如基于路由拆分的代码模块)
      • 以原生 ESM 方式提供源码。实际上是让浏览器接管了打包程序的部分工作:Vite 只需要在浏览器请求源码时进行转换并按需提供源码
  • 2.快速更新

    • 在 Vite 中,HMR 是在原生 ESM 上执行的。当编辑一个文件时,精确地使已编辑的模块与其最近的 HMR 边界之间的链失活。
    • 同时利用 HTTP 头来加速整个页面的重新加载
      - 源码模块的请求会根据 304 Not Modified 进行协商缓存
      - 依赖模块请求则会通过 Cache-Control: max-age=31536000,immutable 进行强缓存,一旦被缓存将不需要再次请求。

组成部分

  1. 一个开发服务器,基于原生 ES 模块 提供了丰富的内建功能,如速度快到惊人的模块热更新(HMR)。
  2. 一套构建指令,使用 Rollup 打包代码,并且是预配置的,可输出用于生产环境的高度优化过的静态资源。

浏览器支持

  • 支持原生 ESM语法的script标签
  • 原生 ESM动态导入
  • import.meta的浏览器
  • 传统浏览器可以通过插件@vitejs/plugin-legacy支持

创建应用

  • 提示:node版本要16+,14.18+
  • 安装:npm create vite@latest 文件名 –template react-ts
  • 注意:如果出现vite不是内部命令,可以执行npm i在执行npm run dev
  • create-vite 是一个快速生成主流框架基础模板的工具。
  • 可以用如 degit 之类的工具,使用社区模版来搭建项目。
    • npx degit user/project my-project
    • npx degit user/project#main my-project // 使用 main 作为默认分支

index.html与项目根目录

  • index.html
    • 是项目的入口文件。
    • Vite 将index.html视为源码和模块图的一部分。标签指向源码,url能自动转换
  • 根目录:即服务文件的位置,以 <root> 代称。
    • 源码中的绝对 URL 路径将以项目的 “根” 作为基础来解析。
    • 能够处理依赖关系,解析处于根目录外的文件位置。
    • Vite 也支持多个 .html 作入口点的 多页面应用模式。
  • 指定替代根目录
    • vite serve some/sub/dir

功能

npm依赖解析和预构建

  • 将会检测到所有被加载源文件中的此类裸模块导入(import { someMethod } from ‘my-dep’),并执行以下操作:
  1. 预构建,提高页面加载速度,并将 CommonJS/UMD 转换为 ESM 格式。预构建由 esbuild 执行,这使冷启动时间快。
  2. 重写导入为合法的 URL以便浏览器能够正确导入。
  3. 依赖是强缓存的,通过 HTTP 头来缓存请求得到的依赖。

模块热替换(create-vite 创建应用程序时已有)

  • 具有 HMR 功能的框架可以利用该 API 提供即时、准确的更新,而无需重新加载页面或清除应用程序状态。

TypeScript

  • 仅执行 .ts 文件的转译工作,不执行任何类型检查(构建命令之外运行 tsc –noEmit 或 单独的进程中运行 tsc –noEmit –watch 或 使用 vite-plugin-checker插件)
  • 使用 esbuild 将 ts 转译到 js,约是 tsc 速度的20~30倍,HMR 更新反映到浏览器的时间小于 50ms。
  • 使用仅含类型的导入和导出形式的语法可以避免 “仅含类型的导入被不正确打包” 的问题
  • TypeScript编译器选项
  • “isolatedModules”:true —支持转译功能
  • “skipLibCheck”: true —在一些库(如:vue)不支持isolatedModules时用
  • “useDefineForClassFields”: true —ts的target是ES或更高,默认。
  • 代码库难迁移,可以用rollup-plugin-friendly-type-imports

客户端类型

  • 默认类型定义是写给它的 Node.js API,要补充到Vite 应用的客户端代码环境中,
  • 添加一个 d.ts 声明文件:/// <reference types="vite/client" />
  • 或者将 vite/client 添加到 tsconfig.json 中的 compilerOptions.types 下:
    1
    2
    3
    4
    5
    {
    "compilerOptions": {
    "types": ["vite/client"]
    }
    }
  • 这将会提供以下类型定义补充:
    • 资源导入 (例如:导入一个 .svg 文件)
    • import.meta.env 上 Vite 注入的环境变量的类型定义
    • import.meta.hot 上的 HMR API 类型定义
    • TIP:要覆盖默认的类型定义,请在三斜线注释前添加定义。
      1
      2
      3
      4
      5
      6
      例如,要为 React 组件中的 *.svg 文件定义类型:
      declare module '*.svg' {
      const content: React.FC<React.SVGProps<SVGElement>>
      export default content
      }
      /// <reference types="vite/client" />

Vue

  • 第一优先级支持
  • Vue3 单文件组件支持:@vitejs/plugin-vue
  • Vue3 JSX 支持:@vitejs/plugin-vue-jsx
  • Vue2.7 SFC支持:@vitejs/plugin-vue2
  • Vue2.7 JSX支持:underfin/plugin-vue2-jsx

JSX

  • 不在 React 或 Vue 中使用 JSX,自定义的 jsxFactory 和 jsxFragment 也可以使用 esbuild选项进行配置。
  • 示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    Preact
    // vite.config.js
    import { defineConfig } from 'vite'
    export default defineConfig({
    esbuild: {
    jsxFactory: 'h',
    jsxFragment: 'Fragment'
    }
    })
    使用 jsxInject为 JSX 注入 helper,以避免手动导入:
    // vite.config.js
    import { defineConfig } from 'vite'
    export default defineConfig({
    esbuild: {
    jsxInject: `import React from 'react'`
    }
    })

CSS

  • 导入 .css 文件将会把内容插入到 <style> 标签中,同时也带有 HMR 支持。也能够以字符串的形式检索处理后的、作为其模块默认导出的 CSS。

  • @import 内联和变基

    • 通过 postcss-import 预配置支持了 CSS @import 内联,Vite 的路径别名也遵从 CSS @import。
    • Sass 和 Less 文件也支持 @import 别名和 URL 变基。
  • PostCSS

    • 如果项目包含有效的 PostCSS 配置 (例如 postcss.config.js),将会自动应用于所有已导入的 CSS。
  • CSS Modules

    • 任何以 .module.css 为后缀名的文件都被认为是CSS modules 文件。导入会返回相应的模块对象
  • CSS modules 行为可以通过 css.modules 选项 进行配置。

  • css.modules.localsConvention 开启了 camelCase 格式变量名转换,可以使用按名导入。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /* example.module.css */
    .red {
    color: red;
    }
    /*.js*/
    import classes from './example.module.css'
    document.getElementById('foo').className = classes.red
    // .apply-color -> applyColor
    import { applyColor } from './example.module.css'
    document.getElementById('foo').className = applyColor
  • CSS 预处理器:要安装依赖

    • .scss and .sass:npm add -D sass
    • .less:npm add -D less
    • .styl and .stylus:npm add -D stylus
    • Sass 和 Less ,保证 Vite 别名也能被使用。
    • @import 别名和 URL 变基不支持 Stylus。
    • url() 中的相对路径引用的,与根文件不同目录中的 Sass/Less 文件会自动变基。
    • 单文件组件,可以通过 <style lang="sass">形式自动开启。
    • 在文件扩展名前加上 .module 来结合使用 CSS modules 和预处理器。
  • 禁用CSS注入页面

    • 自动注入 CSS 内容的行为可以通过 ?inline 参数来关闭。
    • 在关闭时,被处理过的 CSS 字符串将会作为该模块的默认导出,但样式并没有被注入到页面中。
      • import styles from ‘./foo.css’ // 样式将会注入页面
      • import otherStyles from ‘./bar.css?inline’ // 样式不会注入页面

静态资源处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
导入一个静态资源会返回解析后的 URL
import imgUrl from './img.png'
document.getElementById('hero-img').src = imgUrl
添加一些特殊的查询参数可以更改资源被引入的方式:
// 显式加载资源为一个 URL
import assetAsURL from './asset.js?url'
// 以字符串形式加载资源
import assetAsString from './shader.glsl?raw'
// 加载为 Web Worker
import Worker from './worker.js?worker'
// 在构建时 Web Worker 内联为 base64 字符串
import InlineWorker from './worker.js?worker&inline'
```
### JSON
- 可以被直接导入,支持具名导入:
- import json from './example.json' // 导入整个对象
- import { field } from './example.json' // 对一个根字段使用具名导入 —— 有效帮助 treeshaking!

### Glob导入
- 支持使用特殊的 import.meta.glob 函数从文件系统导入多个模块:
```js
//将dir文件夹下的所有js文件导入
const modules = import.meta.glob('./dir/*.js')
然后通过遍历modules的key来访问相应的模块
for (const path in modules) {
modules[path]().then((mod) => {
console.log(path, mod)
})
}
匹配到的文件默认是懒加载的,通过动态导入实现,并会在构建时分离为独立的 chunk。
//直接引入所有的模块,可以传入{eager:true},作为第二参数
const modules = import.meta.glob('./dir/*.js', { eager: true })
  • Glob 导入形式
    • 支持以字符串形式导入文件。
    • 使用了 Import Reflection 语法对导入进行断言:
    • const modules = import.meta.glob(‘./dir/*.js’, { as: ‘raw’ })
  • 多个匹配模式
    • 第一个参数是一个 glob 数组,例如:
    • const modules = import.meta.glob([‘./dir/.js’, ‘./another/.js’])
  • 反面匹配模式(以 ! 作为前缀):排除匹配
    • const modules = import.meta.glob([‘./dir/*.js’, ‘!**/bar.js’])
  • 具名导入(部分导入)
    • const modules = import.meta.glob(‘./dir/*.js’, { import: ‘setup’ })
  • tree-shaking:移除 JavaScript 上下文中的未引用代码
  • 与 eager 在一起时,可以对这些模块进行 tree-shaking
    • { import: ‘setup’, eager: true }
  • 设置 import 为 default 可以加载默认导出
    • {import: ‘default’,eager: true}
  • 自定义查询:使用 query 选项
    • {query: { foo: ‘bar’, bar: true }}
  • 动态导入
    • const module = await import(./dir/${file}.js)

WebAssembly

  • 预编译的 .wasm 文件可以通过 ?init 来导入。默认导出一个初始化函数,返回值为所导出 wasm 实例对象的 Promise
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import init from './example.wasm?init'
    init().then((instance) => {
    instance.exports.test()
    })
    还可以将传递给 WebAssembly.instantiate 的导入对象作为其第二个参数:
    init({
    imports: {
    someFunc: () => {}
    }}).then(() => {})
    在生产构建当中,体积小于 assetInlineLimit 的 .wasm 文件将会被内联为 base64 字符串。否则,它们将作为资源复制到 dist 目录中,并按需获取。

Web Workers

  • 通过构造器导入
    • Web Worker 可以使用 new Worker() 和 new SharedWorker() 导入。
    • type: ‘module’可以创建模块
1
2
3
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 内联为 base64 字符串,添加 inline 查询参数:
    • import MyWorker from ‘./worker?worker&inline’
  • 如果想要以一个 URL 的形式读取该 worker,添加 url 这个 query:
    • import MyWorker from ‘./worker?worker&url’

构建优化

  • CSS代码分割
    • 自动地将一个异步 chunk 模块中使用到的 CSS 代码抽取出来并为其生成一个单独的文件。这个 CSS 文件将在该异步 chunk 加载完成时自动通过一个 link 标签载入,该异步 chunk 会保证只在 CSS 加载完毕后再执行,避免发生 FOUC 。
    • 禁用 CSS 代码分割:通过设置 build.cssCodeSplit:false
  • 预加载指令生成
    • 为入口 chunk 和它们在打包出的 HTML 中的直接引入自动生成 <link rel="modulepreload"> 指令。
  • 异步chunk加载优化
    • 将使用一个预加载步骤自动重写代码,来分割动态导入调用,以实现当 A 被请求时,C 也将 同时 被请求:Entry —> (A + C)

插件

  • 添加一个插件
    • 安装好插件,然后在vite.config.js 配置文件中的 plugins 数组中引入

强制插件排序

  • 使用 enforce 修饰符来强制插件的位置:
    • pre:在 Vite 核心插件之前调用该插件
    • post:在 Vite 构建插件之后调用该插件
    • default:在 Vite 核心插件之后调用该插件
      1
      2
      3
      4
      5
      6
      7
      8
      export default defineConfig({
      plugins: [
      {
      ...image(),
      enforce: 'pre'
      }
      ]
      })

按需应用

  • 如果插件在服务或构建期间按需使用,请使用 apply 属性指明它们仅在 ‘build’ 或 ‘serve’ 模式时调用:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import typescript2 from 'rollup-plugin-typescript2'
    import { defineConfig } from 'vite'
    export default defineConfig({
    plugins: [
    {
    ...typescript2(),
    apply: 'build'
    }
    ]
    })

依赖预构建(仅会在开发模式下应用)

目的

  • CommonJS和UMD兼容性: 将作为 CommonJS/UMD 发布的依赖项转换为 ESM。
  • 将有许多内部模块的 ESM 依赖关系转换为单个模块,以提高后续页面加载性能。

自动依赖搜寻

  • 如果没有找到相应的缓存,将抓取源码,并自动寻找引入的依赖项,并将这些依赖项作为预构建包的入口点。
  • 在服务器已经启动之后,如果遇到一个新的依赖关系导入,且不在缓存中,将重新运行依赖构建进程并重新加载页面。

Monorepo 和链接依赖

  • monorepo 启动,该仓库中的某个包可能会成为另一个包的依赖。
  • Vite 会自动侦测没有从 node_modules 解析的依赖项,并将链接的依赖视为源码。
  • 会分析被链接依赖的依赖列表。但被链接的依赖导出要为 ESM 格式。如果不是,在配置里将此依赖添加到 optimizeDeps.include和build.commonjsOptions.include 这两项中。
  • 当这个被链接的依赖发生变更后,在重启开发服务器时在命令中带上 –force 选项让所有更改生效。
  • 传递依赖项错误的去重,导致运行出错,使用npm pack 来修复
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    export default defineConfig({
    optimizeDeps: {
    include: ['linked-dep']
    },
    build: {
    commonjsOptions: {
    include: [/linked-dep/, /node_modules/]
    }
    }
    })

自定义行为

  • optimizeDeps.include:明确包含依赖依赖项很大或者是 CommonJS
  • optimizeDeps.exclude:明确排除依赖项很小,并且已经是有效的 ESM

缓存

  • 文件系统缓存
    • Vite 会将预构建的依赖缓存到 node_modules/.vite。它基于以下几个源来决定是否需要重新运行预构建步骤:
      1. 补丁文件夹的修改时间
      2. 包管理器的 lockfile,例如 package-lock.json, yarn.lock,或者 pnpm-lock.yaml或者 bun.lockb
      3. vite.config.js 中的相关字段
      4. NODE_ENV 的值
    • 只有在上述其中一项发生更改时,才需要重新运行预构建。
    • 强制重新构建依赖,用 –force 命令行选项启动开发服务器,或者手动删除 node_modules/.vite 目录。
  • 浏览器缓存
    • 已预构建的依赖请求使用 HTTP 头 max-age=31536000,immutable 进行强缓存,一旦被缓存将不需要再次请求。
  • 如安装了不同的版本的依赖项,则会通过附加的版本查询自动失效。
  • 如果想通过本地编辑来调试依赖项,则:
    1. 通过浏览器调试工具的 Network 选项卡暂时禁用缓存;
    2. 重启 Vite 开发服务器指定 –force 选项,来重新构建依赖项
    3. 重新载入页面。

静态资源处理

将资源引入为 URL

  • 服务时引入一个静态资源会返回解析后的公共路径:
    • import imgUrl from ‘./img.png’
    • document.getElementById(‘hero-img’).src = imgUrl
  • 行为类似于 Webpack 的 file-loader。区别在于导入既可以使用绝对公共路径,也可以使用相对路径。
    • url() 在 CSS 中的引用也以同样的方式处理。
    • Vue插件,Vue SFC模板中的资源引用都将自动转换为导入。
    • 常见的图像、媒体和字体文件类型被自动检测为资源。使用 assetsInclude 选项扩展内部列表。
    • 引用的资源作为构建资源图的一部分包括在内,将生成散列文件名,并可以由插件进行处理以进行优化。
    • 较小的资源体积小于 assetsInlineLimit 选项值则会被内联为 base64 data URL。
    • Git LFS 占位符会自动排除在内联之外。要获得内联,请确保在构建之前通过 Git LFS 下载文件内容。
  • 默认情况下,TS 不会将静态资源导入视为有效的模块。要解决这个问题,需要添加 vite/client。

显示 URL 引入

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

将资源引入为 字符串

  • 资源可以使用 ?raw 后缀声明作为字符串引入。
  • import shaderString from ‘./shader.glsl?raw

导入脚本作为Worker

  • 通过 ?worker 或 ?sharedworker 后缀导入为 web worker。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // 在生产构建中将会分离出 chunk
    import Worker from './shader.js?worker'
    const worker = new Worker()
    // sharedworker
    import SharedWorker from './shader.js?sharedworker'
    const sharedWorker = new SharedWorker()
    // 内联为 base64 字符串
    import InlineWorker from './shader.js?worker&inline'
    ```
    ### public 目录
    - 存放不会被源码引用、必须保持原有文件名、不想引入,只想得到url
    - 目录默认是 <root>/public,但可以通过 publicDir 选项 来配置
    - 注意:该目录资源不应该被js文件引用,应该使用/绝对路径

    ### new URL(url, import.meta.url)
    - 功能:暴露当前模块的 URL。
    - 将它与原生的 URL 构造器 组合使用,在一个 JS 模块中,通过相对路径我们就能得到一个被完整解析的静态资源 URL:
    ```js
    // 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 build

浏览器兼容性

  • 支持原生 ESM script 标签、支持原生 ESM 动态导入 和 import.meta 的浏览器
  • 通过 build.target 配置项指定构建目标,最低支持 es2015。
  • 默认情况下 Vite 只处理语法转译,且默认不包含任何 polyfill。
  • 传统浏览器可以通过插件 @vitejs/plugin-legacy 来支持。兼容版的 chunk 只会在不支持原生 ESM 的浏览器中进行按需加载。

公共基础路径

  • 在嵌套的公共路径下部署项目,需指定 base 配置项,然后所有资源的路径都将据此配置重写
  • 也可以通过命令行参数指定,例如 vite build –base=/my/public/path/。
  • 当访问过程中需要使用动态连接的 url 时,可以使用全局注入的 import.meta.env.BASE_URL 变量,它的值为公共基础路径。
  • 注意:这个变量在构建时会被静态替换,因此必须按 import.meta.env.BASE_URL 的原样出现

自定义构建

  • 通过 build.rollupOptions 直接调整底层的 Rollup 选项

产物分块策略

  • 通过配置 build.rollupOptions.output.manualChunks 来自定义 chunk 分割策略.
  • 默认的策略是将 chunk 分割为 index 和 vendor。可以通过在配置文件中添加 splitVendorChunkPlugin 来继续使用 “分割 Vendor Chunk” 策略
  • 也可用工厂函数 splitVendorChunk({ cache: SplitVendorChunkCache }) 来提供该策略,cache.reset() 需要在 buildStart 阶段被调用。

文件变化时重新构建

  • vite build –watch 来启用 rollup 的监听器。
  • 或直接通过 build.watch 调整底层的 WatcherOptions 选项。

多页面应用模式

  • 在构建过程中,只需指定多个 .html 文件作为入口点
  • 如果指定了另一个根目录,在解析输入路径时,__dirname 的值将仍然是 vite.config.js 文件所在的目录。因此,需要把对应入口文件的 root 的路径添加到 resolve 的参数中。

库模式

  • 这个库要进行发布构建时,使用 build.rollupOptions.external 配置项确保将那些不想打包进库的依赖进行外部化处理
  • 在 package.json 中使用如下格式。来暴露多个入口起点:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {
    "exports": {
    ".": {
    "import": "./dist/my-lib.mjs",
    "require": "./dist/my-lib.cjs"
    },
    "./secondary": {//暴露多个入口起点使用这种形式
    "import": "./dist/secondary.mjs",
    "require": "./dist/secondary.cjs"
    }
    }
    }

注意:

  1. 如果 package.json 不包含 “type”: “module”,Vite 会生成不同的文件后缀名以兼容 Node.js。.js 会变为 .mjs 而 .cjs 会变为 .js 。
  2. 在库模式下,所有 import.meta.env.* 用法在构建生产时都会被静态替换。但是,process.env.* 的用法不会被替换。如果不允许这样做,使用 define: { ‘process.env.NODE_ENV’: ‘“production”‘ } 。

部署静态站点

  • 构建应用:npm run build
  • 本地测试应用:构建后运行npm run preview
    1
    2
    3
    4
    5
    {
    "scripts": {
    "preview": "vite preview --port 8080" // 配置服务的运行端口
    }
    }

GitHub Pages

  • 1.在 vite.config.js 中设置正确的 base。
    • 部署在 https://<USERNAME>.github.io/ 上,省略 base 使其默认为 ‘/‘
    • 部署在 https://<USERNAME>.github.io/<REPO>/ 上,设置 base 为 '/<REPO>/'
  • 2.进入仓库 settings 页面的 GitHub Pages 配置,选择部署来源为“GitHub Actions”

GitLab Pages 配合 GitLab CI

  • 1.在 vite.config.js 中设置正确的 base。
  • 部署在 https://<USERNAME or GROUP>.gitlab.io/ 上,省略 base 使其默认为 ‘/‘。
  • 部署在 https://<USERNAME or GROUP>.gitlab.io/<REPO>/ 上,设置 base 为 '/<REPO>/'
  • 2.在项目根目录创建一个 .gitlab-ci.yml 文件,并包含以下内容
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    image: node:16.5.0
    pages:
    stage: deploy
    cache:
    key:
    files:
    - package-lock.json
    prefix: npm
    paths:
    - node_modules/
    script:
    - npm install
    - npm run build
    - cp -a dist/. public/
    artifacts:
    paths:
    - public
    rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

环境变量与模式

环境变量

  • import.meta.env.MODE: {string}应用运行的模式。
  • import.meta.env.BASE_URL: {string} 部署应用时的基本 URL。他由base 配置项决定。
  • import.meta.env.PROD: {boolean} 应用是否运行在生产环境。
  • import.meta.env.DEV: {boolean} 应用是否运行在开发环境 (永远与 import.meta.env.PROD相反)。
  • import.meta.env.SSR: {boolean} 应用是否运行在 server 上。

生产环境替换

  • 在生产环境中,在引用环境变量时请使用完全静态的字符串。
  • 对于 JavaScript 字符串,可以使用 unicode 零宽度空格来分割这个字符串,例如: ‘import.meta\u200b.env.MODE’。
  • 对于 Vue 模板或其他编译到 JavaScript 字符串的 HTML,可以使用 标签,例如:import.meta.env.MODE。

.env文件

  • .env # 所有情况下都会加载
  • .env.local # 所有情况下都会加载,但会被 git 忽略
  • .env.[mode] # 只在指定模式下加载
  • .env.[mode].local # 只在指定模式下加载,但会被 git 忽略
  • 环境加载优先级
    • Vite 执行时已经存在的环境变量有最高的优先级,不会被覆盖。
    • 一份用于指定模式的文件(例如 .env.production)会比通用形式的优先级更高(例如 .env)。
    • .env 类文件会在 Vite 启动一开始时被加载,而改动会在重启服务器后生效。
  • 加载的环境变量也会通过 import.meta.env 以字符串形式暴露给客户端源码
  • 为了防止意外地将一些环境变量泄漏到客户端,只有以 VITE_ 为前缀的变量才会暴露给经过 vite 处理的代码。

TypeScript 的智能提示

  • 获取以 VITE_ 为前缀的用户自定义环境变量的 TypeScript 智能提示:
  • 在 src 目录下创建一个 env.d.ts 文件,接着按下面这样增加 ImportMetaEnv 的定义:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /// <reference types="vite/client" />
    interface ImportMetaEnv {
    readonly VITE_APP_TITLE: string
    // 更多环境变量...
    }
    interface ImportMeta {
    readonly env: ImportMetaEnv
    }
    如果代码依赖于浏览器环境的类型,比如 DOMWebWorker,可以在 tsconfig.json 中修改 lib 字段来获取类型支持。
    {
    "lib": ["WebWorker"]
    }

模式

  • 默认情况下,开发服务器 (dev 命令) 运行在 development模式,而 build 命令则运行在 production 模式。
  • 通过传递 –mode 选项标志来覆盖命令使用的默认模式: vite build –mode staging

服务端渲染(SSR)

  • 情景逻辑
    • 如果需要执行 SSR 和客户端间情景逻辑,可以使用:
      -if (import.meta.env.SSR) { // … 仅在服务端执行的逻辑 }

生产环境构建

  • 1.正常生成一个客户端构建;
  • 2.再生成一个 SSR 构建,使其通过 import() 直接加载,这样便无需再使用 Vite 的 ssrLoadModule;
    • 注意:使用 –ssr 标志表明这将会是一个 SSR 构建。同时需要指定 SSR 的入口。
  • 3.在 server.js 中,通过 process.env.NODE_ENV 条件分支,需要添加一些用于生产环境的特定逻辑:
    • 使用 dist/client/index.html 作为模板,而不是根目录的 index.html,因为前者包含了到客户端构建的正确资源链接。
    • 使用 import(‘./dist/server/entry-server.js’)
    • 将 vite 开发服务器的创建和所有使用都移到 dev-only 条件分支后面,然后添加静态文件服务中间件来服务 dist/client 中的文件。

生成预加载指令

  • 使用 –ssrManifest 标志,这将会在构建输出目录中生成一份 ssr-manifest.json:
    • “build:client”: “vite build –outDir dist/client –ssrManifest”,
  • @vitejs/plugin-vue 会自动注册使用的组件模块 ID 到相关的 Vue SSR 上下文:

预渲染 / SSG

  • 预先知道某些路由所需的路由和数据,可以使用与生产环境 SSR 相同的逻辑将这些路由预先渲染到静态 HTML中。

SSR 外部化

  • 当运行 SSR 时依赖会由 Vite 的 SSR 转换模块系统作外部化。
  • 如果依赖需要被 Vite 的管道转换,可以添加到 ssr.noExternal
  • 对于采用链接的依赖,它们将默认不会被外部化

SSR 专有插件逻辑

  • 一些框架,会根据客户端渲染和服务端渲染的区别,将组件编译成不同的格式。可以向以下的插件钩子中,给 Vite 传递额外的 options 对象,对象中包含 ssr 属性来支持根据情景转换:
    • resolveId
    • load
    • transform

SSR 构建目标

  • SSR 构建的默认目标为 node 环境,但也可以让服务运行在 Web Worker 上。可以将ssr.target 设置为 webworker,以将目标配置为 Web Worker。

SSR 构建产物

  • 通过设置 ssr.noExternal 为 true 来将 SSR 打包成单个 JavaScript 文件。这将会做两件事:
    • 将所有依赖视为 noExternal(非外部化)
    • 若任何 Node.js 内置内容被引入,将抛出一个错误

Vite CLI

  • CLI 命令 $ vite dev 和 $ vite preview 也可以用于 SSR 应用:可以将SSR 中间件通过 configureServer 添加到开发服务器、以及通过 configurePreviewServer 添加到预览服务器。

SSR 格式

  • 默认情况下,Vite 生成的 SSR 打包产物是 ESM 格式。
  • 如果SSR 项目不能使用 ESM,可以通过 Vite v2 外部启发式方法设置 legacy.buildSsrCjsExternalHeuristics: true 生成 CJS 格式的产物。

后端集成(自定义)

  • 1.在的 Vite 配置中配置入口文件和启用创建 manifest:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // vite.config.js
    export default defineConfig({
    build: {
    // 在 outDir 中生成 manifest.json
    manifest: true,
    rollupOptions: {
    // 覆盖默认的 .html 入口
    input: '/path/to/main.js'
    }
    }
    })
    // 如果没有禁用 module preload 的 polyfill,还需在入口处添加此 polyfill:
    import 'vite/modulepreload-polyfill'
  • 2.在开发环境中,在服务器的 HTML 模板中注入以下内容:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 如果是在开发环境中
    <script type="module" src="http://localhost:5173/@vite/client"></script>
    <script type="module" src="http://localhost:5173/main.js"></script>
    "为了正确地提供资源,有两种选项:"
    1. 确保服务器被配置过,将会拦截代理资源请求给到 Vite 服务器
    2. 设置 server.origin 以求生成的资源链接将以服务器 URL 形式被解析而非一个相对路径
    "如果正使用 @vitejs/plugin-react 配合 React,还需要在上述脚本前添加下面这个,因为插件不能修改正在服务的 HTML"
    <script type="module">
    import RefreshRuntime from 'http://localhost:5173/@react-refresh'
    RefreshRuntime.injectIntoGlobalHook(window)
    window.$RefreshReg$ = () => {}
    window.$RefreshSig$ = () => (type) => type
    window.__vite_plugin_react_preamble_installed__ = true
    </script>
    // 如果是在生产环境中
    <link rel="stylesheet" href="/assets/{{ manifest['main.js'].css }}" />
    <script type="module" src="/assets/{{ manifest['main.js'].file }}"></script>
  • 3.在生产环境中:在运行 vite build 之后,一个 manifest.json 文件将与静态资源文件一同生成。
    • 清单是一个 Record<name, chunk> 结构的对象。
    • 对于 入口 或动态入口 chunk,键是相对于项目根目录的资源路径。
    • 对于非入口 chunk,键是生成文件的名称并加上前缀 _。
    • Chunk 将信息包含在其静态和动态导入上(两者都是映射到清单中相应 chunk 的键),以及任何与之相关的 CSS 和资源文件。
    • 可以使用这个文件来渲染链接或者用散列文件名预加载指令

插件API

约定

  • 如果插件不使用 Vite 特有的钩子,可以作为兼容 Rollup 的插件来实现:
    • Rollup 插件应该有一个带 rollup-plugin- 前缀、语义清晰的名称。
    • 在 package.json 中包含 rollup-plugin 和 vite-plugin 关键字
  • 对于 Vite 专属的插件:
    • Vite 插件应该有一个带 vite-plugin- 前缀、语义清晰的名称。
    • 在 package.json 中包含 vite-plugin 关键字。
  • 只适用于特定的框架的插件:
    • vite-plugin-vue- 前缀作为 Vue 插件
    • vite-plugin-react- 前缀作为 React 插件
    • vite-plugin-svelte- 前缀作为 Svelte 插件

插件配置

  • 用户会将插件添加到项目的 devDependencies 中并使用数组形式的 plugins 选项配置它们。该数组将在内部被扁平化(flatten)。
    1
    2
    3
    4
    5
    6
    // vite.config.js
    import vitePlugin from 'vite-plugin-feature'
    import rollupPlugin from 'rollup-plugin-feature'
    export default defineConfig({
    plugins: [vitePlugin(), rollupPlugin()] // 可以接受将多个插件作为单个元素的预设
    })
  • 转换自定义文件类型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const fileRegex = /\.(my-file-ext)$/
    export default function myPlugin() {
    return {
    name: 'transform-file',
    transform(src, id) {
    if (fileRegex.test(id)) {
    return {
    code: compileFileToJS(src),
    map: null // 如果可行将提供 source map
    }
    }
    }
    }
    }

虚拟模块

  • 一种很实用的模式,使可以对使用 ESM 语法的源文件传入一些编译时信息。然后在js文件中引入。
  • 在 Vite(以及 Rollup)中都以 virtual: 为前缀。

通用钩子

  • 以下钩子在服务器启动时被调用:
    • options
    • buildStart
  • 以下钩子会在每个传入模块请求时被调用:
    • resolveId
    • load
    • transform
  • 以下钩子在服务器关闭时被调用:
    • buildEnd
    • closeBundle
  • 注意 moduleParsed 钩子在开发中是不会被调用的。可以认为 Vite 的开发服务器只调用了 rollup.rollup() 而没有调用 bundle.generate()。

Vite 独有钩子

  • config
    • 类型:(config: UserConfig, env: {mode: string, command: string}) => UserConfig | null | void
  • 种类:async, sequential
    • 在解析 Vite 配置前调用。钩子接收原始用户配置和一个描述配置环境的变量,包含正在使用的 mode 和 command。它可以返回一个将被深度合并到现有配置中的部分配置对象,或者直接改变配置。
  • configResolved
    • 类型:(config: ResolvedConfig) => void | Promise<void>
    • 种类:async, parallel
    • 在解析 Vite 配置后调用。使用这个钩子读取和存储最终解析的配置。
  • configureServer
    • 类型:(server: ViteDevServer) => (() => void) | void | Promise<(() => void) | void>
    • 种类:async, sequential
    • 注入后置中间件:在内部中间件被安装前调用。如果想注入一个在内部中间件之后运行的中间件,可以从 configureServer 返回一个函数,将会在内部中间件安装后被调用
    • 存储服务器访问
  • configurePreviewServer
    • 类型:(server: {middlewares: Connect.Server, httpServer: http.Server}) => (() => void) | void | Promise<(() => void) | void>
    • 种类:async, sequential
    • 用于预览服务器,在其他中间件安装前被调用的。想要在其他中间件之后安装一个插件,可以从 configurePreviewServer返回一个函数,它将会在内部中间件被安装之后再调用
  • transformIndexHtml:转换 index.html 的专用钩子。
    • 类型: IndexHtmlTransformHook | { enforce?: ‘pre’ | ‘post’, transform: IndexHtmlTransformHook }
    • 种类: async, sequential
    • 接收当前的 HTML 字符串和转换上下文。上下文在开发期间暴露ViteDevServer实例,在构建期间暴露 Rollup 输出的包。
    • 这个钩子可以是异步的,并且可以返回以下其中之一:
      • 经过转换的 HTML 字符串
      • 注入到现有HTML中的标签描述符对象数组({tag, attrs, children})。每个标签也可以指定它应该被注入到哪里(默认是在 <head> 之前)
      • 一个包含 { html, tags } 的对象
  • handleHotUpdate
    • 类型: (ctx: HmrContext) => Array<ModuleNode> | void | Promise<Array<ModuleNode> | void>
    • 执行自定义 HMR 更新处理。钩子接收一个带有以下签名的上下文对象:
      1
      2
      3
      4
      5
      6
      7
      interface HmrContext {
      file: string
      timestamp: number
      modules: Array<ModuleNode> //是受更改文件影响的模块数组。
      read: () => string | Promise<string> //异步读函数,它返回文件的内容
      server: ViteDevServer
      }

插件顺序

  • 一个 Vite 插件可以额外指定一个 enforce 属性(类似于 webpack 加载器)来调整它的应用顺序。enforce 的值可以是pre 或 post。解析后的插件将按照以下顺序排列:
    • Alias
    • 带有 enforce: ‘pre’ 的用户插件
    • Vite 核心插件
    • 没有 enforce 值的用户插件
    • Vite 构建用的插件
    • 带有 enforce: ‘post’ 的用户插件
    • Vite 后置构建插件(最小化,manifest,报告

Rollup 插件兼容性

  • 只要 Rollup 插件符合以下标准,它就应该像 Vite 插件一样工作:
    • 没有使用 moduleParsed 钩子。
    • 它在打包钩子和输出钩子之间没有很强的耦合。
  • 如果一个 Rollup 插件只在构建阶段有意义,则在 build.rollupOptions.plugins 下指定即可。它的工作原理与 Vite 插件的 enforce: ‘post’ 和 apply: ‘build’ 相同。

路径规范化

  • Vite在解析路径时使用 POSIX 分隔符( / ),同时保留了 Windows 中的卷名。
  • Rollup 在默认情况下保持解析的路径不变,因此解析的路径在 Windows 中会使用 win32 分隔符( \ )。

过滤与include/exclde模式

  • 暴露 @rollup/pluginutils 的 createFilter 函数,以支持 Vite 独有插件和集成使用标准的 include/exclude 过滤模式

客户端与服务端间通信

  • 服务端到客户端
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    在插件一侧,我们可以使用 server.ws.send 去给所有客户端广播事件:
    // vite.config.js
    export default defineConfig({
    plugins: [{
    configureServer(server) {
    server.ws.send('my:greetings', { msg: 'hello' })
    }
    }]
    })
    在客户端侧,使用 hot.on 去监听事件:
    // client side
    if (import.meta.hot) {
    import.meta.hot.on('my:greetings', (data) => {
    console.log(data.msg) // hello
    })
    }
    ```
    - 客户端到服务端
    ```js
    为了从客户端向服务端发送事件,使用 hot.send
    // client side
    if (import.meta.hot) {
    import.meta.hot.send('my:from-client', { msg: 'Hey!' })
    }
    然后使用 server.ws.on 并在服务端监听这些事件:
    // vite.config.js
    export default defineConfig({
    plugins: [{
    configureServer(server) {
    server.ws.on('my:from-client', (data, client) => {
    console.log('Message from client:', data.msg) // Hey!
    // reply only to the client (if needed)
    client.send('my:ack', { msg: 'Hi! I got your message!' })
    })
    }
    }]
    })
  • 自定义事件的 TypeScript 类型定义指南
    1
    2
    3
    4
    5
    6
    7
    8
    9
    可以通过扩展 CustomEventMap 这个 interface 来为自定义事件标注类型:
    // events.d.ts
    import 'vite/types/customEvent'
    declare module 'vite/types/customEvent' {
    interface CustomEventMap {
    'custom:foo': { msg: string }
    // 'event-key': payload
    }
    }

客户端 HMR API:通过 import.meta.hot 对象暴露手动 HMR API。

必需的条件守卫

  • 确保用一个条件语句守护所有 HMR API 的使用
    • if (import.meta.hot) { // HMR 代码 }

TypeScript 的智能提示

  • 在 src 目录中创建env.d.ts,以便 TypeScript 获取类型定义:
  • /// <reference types="vite/client" />

hot.accept(cb):接收模块自身

  • 参数:接收已更新模块的回调函数:
  • “接受” 热更新的模块被认为是 HMR 边界。

hot.accept(deps, cb):接受直接依赖项的更新,而无需重新加载自身

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { foo } from './foo.js'
function setupSideEffect() {}
setupSideEffect()
foo()
if (import.meta.hot) {
import.meta.hot.accept('./foo.js', (newFoo) => {
// 回调函数接收到更新后的'./foo.js' 模块
newFoo?.foo()
// 你可以使用新的模块实例来决定是否使其失效。
if (cannotHandleUpdate(module)) {
import.meta.hot.invalidate()
}
})
// 也可以接受一个依赖模块的数组:
import.meta.hot.accept(
['./foo.js', './bar.js'],
([newFooModule, newBarModule]) => {
// 回调函数接收一个更新后模块的数组
}
)
import.meta.hot.dispose((data) => {
// 清理副作用
})
}

hot.dispose(cb):

  • 一个接收自身的模块或一个期望被其他模块接收的模块可以使用 hot.dispose 来清除任何由新副本产生的持久副作用

hot.prune(cb):

  • 注册一个回调,当模块在页面上不再被导入时调用

hot.data:

  • 在同一个更新模块的不同实例之间持久化.将信息从模块的前一个版本传递到下一本

hot.decline():

  • 目前是一个空操作并暂留用于向后兼容。

hot.invalidate():

  • 指明某模块是不可热更新的,建议在 accept 回调中调用 invalidate

hot.on(event, cb):

  • 监听自定义 HMR 事件。以下事件自动触发:
    • vite:beforeUpdate 当更新即将被应用时(例如,一个模块将被替换)
    • vite:afterUpdate 当更新已经被应用时
    • vite:beforeFullReload 当完整的重载即将发生时
    • vite:beforePrune 当不再需要的模块即将被剔除时
    • vite:invalidate 当使用import.meta.hot.invalidate()使一个模块失效时
    • vite:error 当发生错误时(例如,语法错误)

hot.send(event, data):

  • 发送自定义事件到 Vite 开发服务器。

Javascript API — 看手册

其他文章
cover
webpack --- 第二篇
  • 24/11/01
  • 16:52
  • 前端工程化工具
cover
Next --- React 开发框架
  • 24/11/01
  • 16:52
  • 前端工程化工具
目录导航 置顶
  1. 1. 优点
  2. 2. 组成部分
  3. 3. 浏览器支持
  4. 4. 创建应用
  5. 5. index.html与项目根目录
  6. 6. 功能
    1. 6.1. npm依赖解析和预构建
    2. 6.2. 模块热替换(create-vite 创建应用程序时已有)
    3. 6.3. TypeScript
    4. 6.4. 客户端类型
    5. 6.5. Vue
    6. 6.6. JSX
    7. 6.7. CSS
    8. 6.8. 静态资源处理
    9. 6.9. WebAssembly
    10. 6.10. Web Workers
    11. 6.11. 构建优化
  7. 7. 插件
    1. 7.1. 强制插件排序
    2. 7.2. 按需应用
  8. 8. 依赖预构建(仅会在开发模式下应用)
    1. 8.1. 目的
    2. 8.2. 自动依赖搜寻
    3. 8.3. Monorepo 和链接依赖
    4. 8.4. 自定义行为
    5. 8.5. 缓存
  9. 9. 静态资源处理
    1. 9.1. 将资源引入为 URL
    2. 9.2. 显示 URL 引入
    3. 9.3. 将资源引入为 字符串
    4. 9.4. 导入脚本作为Worker
  10. 10. 构建生产版本: vite build
    1. 10.1. 浏览器兼容性
    2. 10.2. 公共基础路径
    3. 10.3. 自定义构建
    4. 10.4. 产物分块策略
    5. 10.5. 文件变化时重新构建
    6. 10.6. 多页面应用模式
    7. 10.7. 库模式
    8. 10.8. 注意:
  11. 11. 部署静态站点
    1. 11.1. GitHub Pages
    2. 11.2. GitLab Pages 配合 GitLab CI
  12. 12. 环境变量与模式
    1. 12.1. 环境变量
    2. 12.2. 生产环境替换
    3. 12.3. .env文件
    4. 12.4. TypeScript 的智能提示
    5. 12.5. 模式
  13. 13. 服务端渲染(SSR)
    1. 13.1. 生产环境构建
    2. 13.2. 生成预加载指令
    3. 13.3. 预渲染 / SSG
    4. 13.4. SSR 外部化
    5. 13.5. SSR 专有插件逻辑
    6. 13.6. SSR 构建目标
    7. 13.7. SSR 构建产物
    8. 13.8. Vite CLI
    9. 13.9. SSR 格式
  14. 14. 后端集成(自定义)
  15. 15. 插件API
    1. 15.1. 约定
    2. 15.2. 插件配置
    3. 15.3. 虚拟模块
    4. 15.4. 通用钩子
    5. 15.5. Vite 独有钩子
    6. 15.6. 插件顺序
    7. 15.7. Rollup 插件兼容性
    8. 15.8. 路径规范化
    9. 15.9. 过滤与include/exclde模式
    10. 15.10. 客户端与服务端间通信
  16. 16. 客户端 HMR API:通过 import.meta.hot 对象暴露手动 HMR API。
    1. 16.1. 必需的条件守卫
    2. 16.2. TypeScript 的智能提示
    3. 16.3. hot.accept(cb):接收模块自身
    4. 16.4. hot.accept(deps, cb):接受直接依赖项的更新,而无需重新加载自身
    5. 16.5. hot.dispose(cb):
    6. 16.6. hot.prune(cb):
    7. 16.7. hot.data:
    8. 16.8. hot.decline():
    9. 16.9. hot.invalidate():
    10. 16.10. hot.on(event, cb):
    11. 16.11. hot.send(event, data):
  17. 17. Javascript API — 看手册
请输入关键词进行搜索