Vite 学习笔记

引言

介绍

随着 Vue 的开发, 前端也出现了一个更加方便, 速度更加快速的框架: Vite.

Vite 有如下一些特点:

  1. 开发时效率极高
  2. 开箱即用, 功能完备
  3. 社区丰富, 兼容 rollup
  4. 超高速热重载
  5. 预设应用和类库打包模式
  6. 前端类库无关

Vite 的速度不会随着项目规模的变大而改变, 可以显著提升我们的开发效率.

构建, 是前端逃不开的话题, 现代的前端框架已经不是一个 HTML 加一个 JS 就能跑的东西了. 不管用的是 Vue 还是 React, 本质上都是构建.

学习构建, 就是提升自己.

Vite 是什么

其实就是各种高阶构建工具的封装. 其中用的最多的就是 rollup, 起初可以认为是为了 Vue 3 来服务的, 但是后来已经成为了一个独立于前端框架的工具, 能够为各种前端进行服务.

Vite 的目标就是 使用简单 , 帮助我们封装了很多的配置, 让我们使用起来更加简单, 方便; 另外它也继承了一些东西, 可以开箱即用.

另外, 它的特性就是 , 用起来几乎是没有任何构建的感觉的, 可以快速的构建页面. 这得益于 esbuild 的速度和自己的架构.

其次, Vite 便于扩展, rollup 在 webpack 之前, 而 Vite 直接支持了 rollup, 大部分情况下都是完全兼容的.

对比传统构建工具的优势

Vite 具有的是 High Level API, Webpack 之类的是 Low Level API, 主要是注重一些的细节. Vite 本身是不包含编译能力的, 只是集成了各种工具.

Webpack 主要就是啥都有, 几乎包含了所有的东西, 但这也让它的学习成本更高, 更加复杂; rollup 则只是专注于 js, 不会考虑平台的东西.

可以说, 其他的都是工具, 但是 Vite 是工程, 专注的东西不在一个地方. Vite 减少了很多的配置量, 比如:

  1. dev server, 直接自动集成了
  2. 各种 loader, scss 之类的东西, 也是自动内置了 (插件的形式)
  3. build 命令, 可以直接 Build 类库或者项目, 不需要我们写太多东西

Vite 的优势

  1. 上手简单
  2. 开发效率极高 (速度极快)
  3. 社区成本低 (兼容 rollup 插件)

另外, Vite 没有太多晦涩的配置, 比如 React 项目, Webpack 可能要写很多的东西, 但是 Vite 中只需要一行代码, 使用一个插件就行.

如果需要修改一些很重要的配置, 还是需要碰一碰 Webpack 的.


另外, Vite 有自己的插件系统, 另外也兼容 rollup, 这就让插件的构建成本变的很低了; Vite 在编译的时候, 其实不是实时编译的, 只有访问页面, 才会进行编译和加载, 这就直接节省了大部分的时间.

同时因为集成了 esbuild, 速度只会更快了.

Vite 基础使用

创建 Vue 3 项目

因为 Vite 出生本来就是为了 Vue 3 服务的, 一开始的 1.0 版本就是单独为了 Vue 3, 后来 2.0 才开始为了所有的框架开放.

通过 Vite, 可以直接使用如下命令:

1
npm init @vitejs/app

运行后, 选择自己的框架, 就可以创建一个 Vue 3 项目了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@vitejs/create-app is deprecated, use npm init vite instead


◇ Project name:
│ vite-vue3

◇ Select a framework:
│ Vue

◇ Select a variant:
│ JavaScript

◇ Scaffolding project in D:\Documents\WorkSpace\study-projects\vite-vue3...

└ Done. Now run:

进入项目, 可以看到只有几个文件:

对于 Vite 的实现方式, 和 Webpack 不一样, 其他的入口都是一个 JS 文件, 但是 Vite 则是一个 HTML 文件开始加载, 通过 script 标签进行加载内容.

这个 index.html 不是可有可无的, 是必须要有的.


另外就是 vite.config.js 了, 里面存放了 Vite 的各种配置.

1
2
3
4
5
6
7
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vite.dev/config/
export default defineConfig({
plugins: [vue()],
})

这里非常简单, 就是用到了一个 Vue 的插件. 不过这个 Vue 不支持 jsx 的写法, 如果需要, 安装对应的插件就行.

[!tip] 注意
只建议使用 yarn 安装对应的依赖.

1
2
# 安装依赖
yarn

假如需要支持 jsx, 直接安装一个插件即可.

1
yarn add @vitejs/plugin-vue-jsx -D

再在配置文件中直接使用这个插件即可.

1
2
3
4
5
6
7
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'

export default defineConfig({
plugins: [vue(), vueJsx()],
})

至此, 就可以直接使用 jsx 文件了. 比如在 src 创建一个 App.jsx:

1
2
3
4
5
6
7
8
9
import { defineComponent } from "vue"

export default defineComponent({
setup() {
return () => {
return (<div>Hello Vue3 JSX</div>)
}
}
})

这个时候就可以删掉 App.vue 了, 直接在 main.js 中进行引入使用即可.

1
2
3
4
import { createApp } from 'vue'
import App from './App'

createApp(App).mount('#app')

这个时候直接启动, 就可以看到正常显示了.

创建 Vue 2 项目

构建的时候, Vue 2 是没有出现在构建列表的, 这是因为 Vue 2 不在官方支持的列表. 但是官方仍然提供了插件支持.

创建项目的时候, 我们应该选择 vanilla, 这个的意思就是啥都没有.

创建项目后, 可以看到连 vite.config.js 都没有, 这就需要我们手动来进行创建了. 同时我们需要使用 Vue 2 的插件, 所以安装一下, 并且引入, 使用即可.

需要安装的内容如下:

1
2
yarn add vue@^2.7.0-0
yarn add @vitejs/plugin-vue2 -D

回到 config:

1
2
3
4
5
6
import { defineConfig } from "vite";
import vue2 from '@vitejs/plugin-vue2'

export default defineConfig({
plugins: [vue2()]
})

现在, 就安装好 Vue 2 了. 创建 App.vue 并且运行, 下面是 main.js 的内容.

1
2
3
4
5
6
7
import Vue from "vue";
import App from "./App.vue";

new Vue({
el: '#app',
render: (h) => h(App)
}).$mount()

构建 React 项目

Vite 虽然是给 Vue 开发用的, 但是仍然会支持 React, 还是官方支持, 比 Vue 2 都要优先一些.

官方也是直接支持了 React 的项目的, 选择 React 框架创建项目后, 可以看到 config 如下:

1
2
3
4
5
6
7
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
})

另外, 观察 index.html, 可以看到引入的是一个 jsx 文件:

1
<script type="module" src="/src/main.jsx"></script>

这是因为 Vite 内部启动了一个服务, 前端访问, 只要返回的东西符合规范, 就能够正常使用, 而不是后缀名一定一致.

使用 CSS 的各种功能

普通 CSS

Vite 已经原生支持了 CSS 的语法, 直接引入 CSS 即可. 另外这里推荐使用原生 CSS variables, 其实就是 CSS 变量. Vite 内部已经包含了 PostCss, 可以直接定义 css 变量.

如果需要, 可以创建一个 postcss.config.js, 配置 postcss 相关的内容.

SCSS 之类的预编译工具

在 Vite 中, 这些其实都是本来就支持的, 只不过需要安装一下对应的语言支持. 比如我要使用 less, 我不需要安装对应的与编译器, 只需要安装 less 即可.

1
yarn add less

接下来就可以直接创建 less 文件并且使用了.

TypeScript 集成

基础配置

虽然 Vite 本身就支持了 TS, 但是其实只是能够进行编译, 而没有校验. 就是不会查看语法错误, 仅仅是允许使用 Ts 进行编程.

假如我们现在有这样的一个测试 ts 文件:

1
2
3
4
5
6
7
8
interface User {
name: string
}

export const userObj: User = {
name: "kaede",
age: 18
}

显然, 直接使用会导致报错, 因为 age 不存在原始类型上.

但是, 如果在 App.vue 之类的 Vite 项目文件中中引入这个对象并且进行使用, 则不会有任何报错:

1
2
3
4
5
6
7
8
9
10
<script setup lang="ts">
import { userObj } from './test';
</script>

<template>
<h1>{{ userObj.name }} = {{ userObj }}</h1>
</template>

<style scoped>
</style>

启动项目后, 命令行不会出现任何错误.


为了解决这个问题, 首先编辑器可以注意一下, 目前常用的编辑器 (VSCode, WebStorm) 都是直接支持 TS 的, 应该可以直接看到问题.

另外, 就是可以修改一下编译命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"name": "vite-project",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc --noEmit && vue-tsc -b && vite build",
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.5.18"
},
"devDependencies": {
"@vitejs/plugin-vue": "^6.0.1",
"@vue/tsconfig": "^0.7.0",
"typescript": "~5.8.3",
"vite": "^7.1.2",
"vue-tsc": "^3.0.5"
}
}

在命令前面加上一个 tsc --noEmit && xxx, 随后再添加一个 tsconfig 文件.

1
tsc init

配置文件可以参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"compilerOptions": {
// module 必须使用esmodule, 否则报错
"module": "esnext",
"target": "esnext",
"moduleResolution": "node",
"strict": true,
// 对于vue, react不允许使用, 所以需要设置一下, 使用preserve, 不编译jsx语法
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
// 方便进行import, 不写的话就是 import * as React from "react"
"esModuleInterop": true,
// 设置需要包含的类库
"lib": ["ESNext", "DOM"]
},
"include": [
"src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"
]
}

这个时候跑 build, 就会报校验错误了.

1
2
3
4
5
6
src/test.ts:7:5 - error TS2353: Object literal may only specify known properties, and 'age' does not exist in type 'User'.

7 age: 18
~~~

Found 1 error.

对于 Vue, 我们需要使用 vue-tsc, 以上操作不变, 换一个工具就行.

1
yarn add vue-tsc

安装后, 命令也会发生变化.

1
2
3
{
"build": "tsc --noEmit && vue-tsc -b && vite build",
}

额外配置

另外, 对于 Vue 来说, 我们也需要额外配置一个参数, 与 compilerOptions 平级:

1
2
3
{
"isolatedModules": true,
}

因为 Vite 只会编译自己的语法, 不会读取别的文件的信息, 这会导致有一些 TS 的功能无法使用, 比如引入一个类型后, 再次暴露类型, 则由于 JS 没有类型的概念, 直接 export 就会让 Vite 找不到这个东西.

我们加上这个东西, 开发的时候就会得到对应的错误提示了.

现在直接在编辑器中尝试, 可以看到已经有代码提示了.

一般来说, 这个分为了 Asset Imports, Env 和 HMR (.hot), 后面会进行介绍. 如果没有配置, 则 TS 是不认识 png 之类的资源文件的, 那么自然就无法正常引入. 只有告诉它引入了以后是一个 string, 才能正常进行使用.

client types

Vite 会提供一些内置的对象, 方法. 比如可以通过 import.meta.xxx 来获取一些环境变量之类的东西. 无论如何, 这里需要让 TS 认识这个东西, 否则可能报错之类的.

这里就需要配置一下 types 字段, 用一个数组来表示默认就提供的一些类型.

1
2
3
{
"types": ["vite/client"]
}

配置路径别名

另外, Vite 里面的 css 也是支持路径别名的. 首先需要在 config 中, 配置一下别名:

1
2
3
4
5
6
7
8
9
10
11
12
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
plugins: [react()],
// 配置路径别名
resolve: {
alias: {
"@": "/src"
}
}
})

现在就可以使用 @ 作为 src 目录进行使用了.

Vite 处理静态资源的方法

虽然 Vite 可以直接引入图片资源, 但是还有一些别的方法:

  1. url
  2. raw
  3. worker

url

例如, 在引入一个图片资源的时候, 后面加上一个 query 参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import reactLogo from './assets/react.svg?url'

console.log(reactLogo)

function App() {
return (
<>
<div>
<img src={reactLogo}></img>
</div>
</>
)
}

export default App

那么这个时候, 这个图片就是一个路径了.

|290x68

这就是 ?url 的作用, 直接将一个文件作为静态资源进行返回.

raw

其实是顾名思义, 这里会直接打印一个文件的内容.

1
2
3
import reactLogo from './assets/react.svg?raw'

console.log(reactLogo)

我们就可以直接拿到字符串进行使用了.

worker

这个是帮我们提高性能的工具, 如果计算量大, 那么 JS 就会很卡; 所以我们如果有一个新的线程, 就可以帮我们完成这些耗时任务, 避免卡顿.

比如创建一个新的 TS 文件, 作为我们的耗时任务.

1
2
3
4
5
6
7
8
9
let i = 0;

const timedCount = () => {
i++;
postMessage(i)
setTimeout(timedCount, 500)
}

timedCount()

使用的时候, 直接创建一个 Worker 对象, 随后使用 worker 进行信息的接收即可.

1
2
3
4
5
6
7
// 引入的时候, 后面加worker参数
import testWorker from './test?worker'

const worker = new testWorker()
worker.onmessage = (e) => {
console.log(e)
}

这里就不会阻塞到页面渲染了, 并且会不断的运行.

读取 JSON

Vite 中可以直接引入 json 文件. 比如引入 package.json 并且输出内容:

1
2
3
import pkg from '../package.json'

console.log(pkg)

|424x179

这样就成功引入 JSON 了.

环境变量

基础使用

我们通过 import.meta.env 来判断当前的环境是什么. 默认来说, Vite 有几个默认的环境:

  • MODE 各种模式
  • BASE_URL 请求的基地址
  • PROD 是否是正式环境
  • DEV 是否是开发环境
  • SSR 是否是服务端渲染
1
console.log(import.meta.env)

直接输出可以看到这些:

|234x156

这里需要注意, 对于 production 环境, 我们无法通过这个方法来获取当前的环境了. 正式环境中, 这个会自动打包成一个真正的值, 不再是变量了.

自定义 env

直接在项目根目录创建不同的 env 文件, 就可以直接获取这个环境了.

比如创建一个 .env 文件, 里面写点东西:

这里的变量名必须以 VITE 开头

1
VITE_TITLE=HELLO

在代码中直接就可以拿到了.

1
console.log(import.meta.env.VITE_TITLE)

环境后缀

当然, 我们的环境变量文件不只有一个, 可能还会有 .env.production 文件, 这个时候的环境变量, 会在不同环境自动加载. 开发环境的时候, 注入开发环境的相关环境, 生产环境的时候注入生产环境的相关配置.

特殊的, 后面还可以加上一个 .local, 这个环境变量的意思是本地开发的环境, 别的地方运行的效果还是对应的开发环境的环境变量.

配置代码提示

直接写一般是没有代码提示的, 我们需要告诉编辑器, 我们存在这个类型. 可以直接在 vite-env.d.ts 中写上这样的一个类型声明:

1
2
3
4
/// <reference types="vite/client" />
interface ImportMetaEnv {
VITE_TITLE: string
}

这样就能获得代码提示, 提升开发效率了.

|450x143

Vite 进阶使用

Vite 的热更新

介绍

如何开启呢? 大部分情况下, 使用 vite 创建的项目, 都是默认开启热更新的, 并且是根据具体框架进行实现的, 并不是所有框架通用的.

Vue 有 Vue 的实现方式, React 有 React 的实现方式. Vite 中有一套 HMR API, 专门用来实现热更新. 一般来说, 开发的时候会自动使用插件中的热更新部分.

核心需要注意的是, 如何实现热更新.

实现基础的热更新

创建一个空项目, 没有任何框架的空项目.

|371x292

我们如果修改页面, 会发现上面是进行了页面的刷新的, 这不是热更新, 热更新是页面不会刷新的, 这里我们需要自己实现热更新的相关流程.

首先第一步, 就是把渲染改成一个函数, 而不是直接写死:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import './style.css'
import typescriptLogo from './typescript.svg'
import viteLogo from '/vite.svg'

function render() {
document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
<div>
<a href="https://vite.dev" target="_blank">
<img src="${viteLogo}" class="logo" alt="Vite logo" />
</a>
<a href="https://www.typescriptlang.org/" target="_blank">
<img src="${typescriptLogo}" class="logo vanilla" alt="TypeScript logo" />
</a>
<h1>Vite + TypeScript</h1>
</div>
`
}

render()

随后, 我们集成一下 hmr, 这个一般写在一个判断条件里面, 接着在该文件后面进行书写:

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
import './style.css'

export function render() {
document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
<div>
<h1>Vite + TypeScript</h1>
</div>
`
}

render()

// 这里需要判断 因为生产环境是没有hot的
// 也可以理解为一个范式
if (import.meta.hot) {
// 接收, 代表这个文件会接收自己的热更新
import.meta.hot.accept((newModule) => {
// 接受了更新要干嘛
// 这里可以得到一个模块, 就是这个文件自己
// 另外, 考虑到需要从模块拿东西, 所以上面的render需要export

// 如何实现热更新呢? 其实就是通过render重新渲染, 调用函数:
newModule.render()
})
}

至此, 已经实现该文件的热更新了, 刷新是没有变的, 并且页面立马就进行了更新.

[!info] 注意
这里需要使用 newModule.render(), 不能直接 render(), 因为存在闭包问题.

普遍性问题

上面的是针对性的, 我们的代码不一定返回 render 函数, 也不一定有一些东西. 无论如何, hmr 最适合的还是 Vue 文件的热更新, 因为 Vue 组件文件返回的东西是固定的.

实现原理

可以查看网络请求:

|548x278

可以看到, 其实就是开启了一个 WebSocket, 接收请求, 接收到请求以后就进行更新. 没有热更新的话, 就是直接刷新页面.

Vite 的 glob-import

简单说, 就是可以直接引入一组的 JS 文件, 而不是一个一个的引入多个 JS 文件. 例如目录结构如下:

|208x144

我们希望直接引入所有的这些 JS 文件, 则可以直接使用 glob import.

直接在 main.tsx 或者其他的入口文件中, 按照如下格式写入:

1
2
const globModules = import.meta.glob('./glob/*')
console.log(globModules)

这里需要注意, 必须要有一个 *, 这代表引入的是所有文件, 不能直接写一个文件夹. 同时, 也可以使用正则表达式进行引入, 这里按照正则的规则写就行.

无论如何, 这是 Vite 的功能, 可能存在兼容性问题.

预编译优化

Vite 中用到的第三方库, 都会进行一个预编译, 开发的时候就会非常快了. 在 node_modules 目录下, 存在这样一个缓存目录:

|288x272

这样就会非常快, 只要需要, 直接读取就行, 不需要进行二次编译.

%% ## Vite 插件开发与实战 %%

%% ## Vite 的源码解析 %%