一个基于原生 ESM 的前端开发和构建工具,提供极速的服务启动、热重载、按需编译、类型支持和插件系统。
优点
1.服务器启动速度快:通过将应用中的模块区分为依赖和源码两类
- 依赖:大多为在开发时不会变动的纯 JS
- 使用 esbuild 预构建依赖
- esbuild 使用 Go 编写,并且比以 JS 编写的打包器预构建依赖快 10-100 倍
- 源码:包含一些非.js文件(例如 JSX,CSS 或者 组件)
- 并不是所有的源码都需要同时被加载(例如基于路由拆分的代码模块)
- 以原生 ESM 方式提供源码。实际上是让浏览器接管了打包程序的部分工作:Vite 只需要在浏览器请求源码时进行转换并按需提供源码
- 依赖:大多为在开发时不会变动的纯 JS
2.快速更新
- 在 Vite 中,HMR 是在原生 ESM 上执行的。当编辑一个文件时,精确地使已编辑的模块与其最近的 HMR 边界之间的链失活。
- 同时利用 HTTP 头来加速整个页面的重新加载
- 源码模块的请求会根据 304 Not Modified 进行协商缓存
- 依赖模块请求则会通过 Cache-Control: max-age=31536000,immutable 进行强缓存,一旦被缓存将不需要再次请求。
组成部分
- 一个开发服务器,基于原生 ES 模块 提供了丰富的内建功能,如速度快到惊人的模块热更新(HMR)。
- 一套构建指令,使用 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’),并执行以下操作:
- 预构建,提高页面加载速度,并将 CommonJS/UMD 转换为 ESM 格式。预构建由 esbuild 执行,这使冷启动时间快。
- 重写导入为合法的 URL以便浏览器能够正确导入。
- 依赖是强缓存的,通过 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 = applyColorCSS 预处理器:要安装依赖
- .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 | 导入一个静态资源会返回解析后的 URL: |
- 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
)
- const module = await import(
WebAssembly
- 预编译的 .wasm 文件可以通过 ?init 来导入。默认导出一个初始化函数,返回值为所导出 wasm 实例对象的 Promise
1
2
3
4
5
6
7
8
9
10import 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 | const worker = new Worker(new URL('./worker.js', import.meta.url), { |
- 带有查询后缀的导入
- 在导入请求上添加 ?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 和它们在打包出的 HTML 中的直接引入自动生成
- 异步chunk加载优化
- 将使用一个预加载步骤自动重写代码,来分割动态导入调用,以实现当 A 被请求时,C 也将 同时 被请求:Entry —> (A + C)
插件
- 添加一个插件
- 安装好插件,然后在vite.config.js 配置文件中的 plugins 数组中引入
强制插件排序
- 使用 enforce 修饰符来强制插件的位置:
- pre:在 Vite 核心插件之前调用该插件
- post:在 Vite 构建插件之后调用该插件
- default:在 Vite 核心插件之后调用该插件
1
2
3
4
5
6
7
8export default defineConfig({
plugins: [
{
...image(),
enforce: 'pre'
}
]
})
按需应用
- 如果插件在服务或构建期间按需使用,请使用 apply 属性指明它们仅在 ‘build’ 或 ‘serve’ 模式时调用:
1
2
3
4
5
6
7
8
9
10import 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
10export default defineConfig({
optimizeDeps: {
include: ['linked-dep']
},
build: {
commonjsOptions: {
include: [/linked-dep/, /node_modules/]
}
}
})
自定义行为
- optimizeDeps.include:明确包含依赖依赖项很大或者是 CommonJS
- optimizeDeps.exclude:明确排除依赖项很小,并且已经是有效的 ESM
缓存
- 文件系统缓存
- Vite 会将预构建的依赖缓存到 node_modules/.vite。它基于以下几个源来决定是否需要重新运行预构建步骤:
- 补丁文件夹的修改时间
- 包管理器的 lockfile,例如 package-lock.json, yarn.lock,或者 pnpm-lock.yaml或者 bun.lockb
- vite.config.js 中的相关字段
- NODE_ENV 的值
- 只有在上述其中一项发生更改时,才需要重新运行预构建。
- 强制重新构建依赖,用 –force 命令行选项启动开发服务器,或者手动删除 node_modules/.vite 目录。
- Vite 会将预构建的依赖缓存到 node_modules/.vite。它基于以下几个源来决定是否需要重新运行预构建步骤:
- 浏览器缓存
- 已预构建的依赖请求使用 HTTP 头 max-age=31536000,immutable 进行强缓存,一旦被缓存将不需要再次请求。
- 如安装了不同的版本的依赖项,则会通过附加的版本查询自动失效。
- 如果想通过本地编辑来调试依赖项,则:
- 通过浏览器调试工具的 Network 选项卡暂时禁用缓存;
- 重启 Vite 开发服务器指定 –force 选项,来重新构建依赖项
- 重新载入页面。
静态资源处理
将资源引入为 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"
}
}
}
注意:
- 如果 package.json 不包含 “type”: “module”,Vite 会生成不同的文件后缀名以兼容 Node.js。.js 会变为 .mjs 而 .cjs 会变为 .js 。
- 在库模式下,所有 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
19image: 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
}
如果代码依赖于浏览器环境的类型,比如 DOM 和 WebWorker,可以在 tsconfig.json 中修改 lib 字段来获取类型支持。
{
"lib": ["WebWorker"]
}
模式
- 默认情况下,开发服务器 (dev 命令) 运行在 development模式,而 build 命令则运行在 production 模式。
- 通过传递 –mode 选项标志来覆盖命令使用的默认模式: vite build –mode staging
服务端渲染(SSR)
- 情景逻辑
- 如果需要执行 SSR 和客户端间情景逻辑,可以使用:
-if (import.meta.env.SSR) { // … 仅在服务端执行的逻辑 }
- 如果需要执行 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
14const 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 配置后调用。使用这个钩子读取和存储最终解析的配置。
- 类型:(config: ResolvedConfig) => void |
- 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
7interface HmrContext {
file: string
timestamp: number
modules: Array<ModuleNode> //是受更改文件影响的模块数组。
read: () => string | Promise<string> //异步读函数,它返回文件的内容
server: ViteDevServer
}
- 类型: (ctx: HmrContext) =>
插件顺序
- 一个 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 | import { foo } from './foo.js' |
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 开发服务器。