Vite-快速入门

Vite 的特性

直接放官网上的

vite特性.png

Vite 为什么快

首先要说明的是,Vite 是在开发环境下比较快,原因:

  1. 浏览器支持 ESM,Vite 在开发阶段采用 esbuild 依赖预构建,实现了按需加载,而不用整体对项目进行打包。
  2. esbuild 采用 Go 语言开发,Go 是编译型语言,代码直接被编译为原生机器码,不需要像 JavaScript 还要解析成字节码再转成机器码。

依赖预构建

Vite 在执行前会先进行预构建,首先找到依赖模块,将其转换成 ESM 规范,最后生成的预构建产物放在 node_modules 下的 .vite文件夹,在构建时会将第三方库的多个文件打包成一个文件 如引入 lodash 打包时,会将 lodash 包内的文件打包成一个文件。另外打包后会将第三方库的引用路径进行重写,使用我们预构建后的路径。对于依赖请求结果会进行强缓存,再次请求直接读取缓存。

关于缓存

Vite 对于已构建的的依赖进行了 文件系统缓存(存储在 node_modules/.vite 中) 和 浏览器缓存(使用 HTTP 头 max-age=31536000, immutable 进行强缓存),当缓存后,再次请求直接读取缓存来提高性能。

手动开启预构建

默认情况下,Vite 会自动帮我们开启预构建,如果不希望使用本地缓存,还可以手动清除缓存,然后手动进行预构建

  1. 删除 node_modules/.vite
  2. 配置 vite.config.ts
    1
    2
    3
    4
    5
    6
    7
    8
    export default defineConfig({
    // ...
    optimizeDeps: {
    // 设置为 true 可以强制依赖预构建,而忽略之前已经缓存过的、已经优化过的依赖
    force: true,
    }
    });

  3. 行执行 npx vite –force 或者 npx vite optimize

optimizeDeps

详细文档: 依赖优化选项

entries

用来定义预构建的入口文件,支持多种类型文件

exclude

在预构建中强制排除的依赖项。

include

定义预构建的依赖项,使用场景在于一般情况不对 node_modules 外的包进行预构建的,这可能会导致在运行时发现新的依赖(例如使用动态import)这时
Vite 会二次预构建并且刷新页面,很耗费性能,所以通过 include 手动去定义。

esbuildOptions

自定义 esbuild 配置,一般用来加入 esbuild 插件

force

上面提到的是否忽略缓存的依赖

disabled

禁用依赖优化,一般在开发阶段才会开启

needsInterop

定义一些依赖,Vite 会强制把他们进行 ESM 转换,加快冷启动速度

预构建解决了什么问题

  1. 解决不同模块的兼容性,Vite 会将 CommonJS 或 UMD 等形式提供的依赖项转换为 ES 模块
  2. 提高性能:上面有提到过的,Vite 会将一个包内多个文件打包成单独的文件,对于一个 import 来说,相当于发出一次 HTTP
    请求,打包成单独的文件能够减少大量 HTTP 请求

使用 Vite 快速搭建前端项目

前置工具:
安装 node
使用 yarn/pnpm 作为包管理工具,推荐 pnpm
编辑器 webStorm/vscode

使用命令初始化

npm create vite

执行后一次输入项目名、要选择的框架、使用的语言,这里我们选择 React + TypeScript
然后就是安装依赖,启动。

目录结构

vite目录结构.png

其中 index.html 是入口文件

1
2
3

<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>

该文件除了 id 为 root 的 div 作为根节点外,还有一个 script 标签,该标签 type 为 “module”,标志着使用的是 ESM,另外还指向一个 main.tsx 文件

1
2
3
4
5
6
7
8
9
10
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App/>
</React.StrictMode>,
)

首先,正常情况下我们写 html 文件,是无法引入 .tsx 文件的,浏览器根本不会识别,但在这里,Vite 对 .tsx 和 .jsx 通过 esbuild 进行了编译,返回浏览器可以识别的代码。这里就体现了 Vite 的 no-bundle 特性,使用 ESM 进行按需加载,对比 Webpack 需要将项目整体进行打包后加载,显然 Vite 省掉了复杂的打包过程,这也是它在开发环境更快的原因之一。

启动

在 package.json 中我们可以看到生成项目时,已经帮我们安装了 react、react-dom、eslint 等一系列包。主要看下 scripts 我们执行的命令有哪些

1
2
3
4
5
6
7
8
{
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
}
}

首先第一个命令 dev 就是我们在本地启动的命令,build 是我们对项目进行打包的命令,lint是对代码进行检查,preview 是启动我们打包后的产物

配置文件

vite.config.ts 就是 Vite 的配置文件

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

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

在我们生成项目时选择的 React,这里会自动帮我们配置 React 插件对我们的项目进行编译等功能。

关于样式方案

Vite 对于 CSS 的预处理器做了内部支持

SASS/SCSS

首先安装 SASS

pnpm i sass -D

对于 SASS、LESS 这种预处理器是可以定义变量来做到对多处用到的样式进行统一处理

我们在 src 目录下创建 variable.scc 作为样式抽离的文件,在其中定义

1
$theme-color: red;

然后在 src/component 创建一个组件 Header 在其目录下定义 index.tsx index.scss 文件

1
2
3
4
5
6
7
export default function Header() {
return (
<>
<p className='header'>Header</p>
</>
)
}
1
2
3
4
5
@import '../../variable.scss';

.header {
color: $theme-color;
}

在 App.tsx 文件中引入后启动,打开界面就能看到效果 一个红色的 Header

对于变量 theme-color ,如果多个地方使用到了,每个文件都需要引入比较繁琐,Vite 提供了一个方案可以自动注入

1
2
3
4
5
6
7
8
9
10
11
12
const variablePath = normalizePath('./src/variable.scss')

export default defineConfig({
plugins: [react()],
css: {
preprocessorOptions: {
scss: {
additionalData: `@import "${variablePath}";`
},
}
}
})

这里的 additionalData 会自动在每个 scss 文件注入,这时我们删掉 index.scss 的引入会发现依旧好用。

CSS Modules

在 Header 组件下新建 index.module.scss,在 index.tsx 中引入

1
2
3
4
5
6
7
8
9
// index.tsx
export function Header() {
return (
<>
<p className='header'>Header</p>
<p className={styles.font}>Font</p>
</>
)
}
1
2
3
4
5
// index.module.scss
.font {
font-size: 25px;
color: #ccc;
}

打开开发者工具可以看到第二个 p 标签的 class 已经变成哈希值,说明 CSS Modules 生效。对于这个哈希值,Vite 提供了让我们自定义格式的方案

1
2
3
4
5
6
7
8
9
10
// vite.config.ts
export default defineConfig({
plugins: [react()],
css: {
modules: {
generateScopedName: "[name]__[local]___[hash:base64:5]"
},
// ...
}
})

通过 generateScopedName 来自定义格式,name代表文件名,loacal代表类名,除了字符串,还支持 function。

ESLint

首先需要在项目中安装 ESLint 并且初始化

pnpm i eslint -D
npx eslint –init

最后会生成 .eslintrc.cjs 文件,这个就是 ESLint 的配置文件

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
module.exports = {
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended"
],
"overrides": [
{
"env": {
"node": true
},
"files": [
".eslintrc.{js,cjs}"
],
"parserOptions": {
"sourceType": "script"
}
}
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"@typescript-eslint",
"react"
],
"rules": {
"indent": [
"error",
"tab"
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"double"
],
"semi": [
"error",
"always"
]
}
};

env 是我们的运行环境
extends 是继承配置项,有三种情况:

  1. 继承 ESLint 本身
  2. 从 npm 包继承
  3. 从插件继承

overrides 是针对文件部分禁用
parser 是解析器,默认使用 Espree
parserOptions - 解析器选项
rules 代码启用的规则及其各自的错误级别
plugins 插件

在 Vite 中接入 ESLint

安装 ESLint 插件

pnpm i vite-plugin-eslint -D

在配置文件中配置

1
2
3
4
5
6
7
// vite.config.ts
export default defineConfig({
plugins: [
react(),
viteEslint(),
],
});

这样控制台就会提示错误信息了

环境变量

Vite 默认存在四个环境变量

  • MODE
  • BASE_URL
  • PROD
  • DEV

自定义环境变量

在根目录下新建 .env .env.development .env.production 这就是我们自定义环境变量的文件

.env 是通用的环境变量
.env.development: 开发环境需要用到的环境变量
.env.production: 生产环境需要用到的环境变量

需要注意的是,定义环境变量必须 VITE_ 开头。如果出现重名,.env 的环境变量将会被覆盖。

我们在 .env 中定义

1
VITE_TITLE=HELLO

在 App.tsx 中输出 import.meta.env 就能看到 VITE_TITLE 这个变量了。

静态资源

特殊资源后缀

我们在导入资源时,可以加特定的后缀来标志这是什么资源,例如:

1
2
3
4
import logo from './assets/xxx?url'
import logo from './assets/xxx?raw'
import logo from './assets/xxx?worker'
import logo from './assets/xxx?inline'

?url 代表该资源的路径,例如可以在 img 标签的 src 指向该引入
?raw 代表原始文件内容
?worker 代表是 Web Worker 脚本
?inline 代表资源强关联

JSON

Vite 支持直接导入 JSON 文件

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

import {config} from '../package.json'

HMR

什么是 HMR?

Hot Module Replacement,也叫模板热替换,简单来说就是在页面发生变化时,进行局部更新和状态保存。

如何访问

Vite 通过 import.meta.hot 暴露手动 HMR API,通过该属性访问 HMR 的属性和方法。另外在使用 HMR API 时,要先判断 import.meta.hot,因为该属性在生产环境中为 undefined 无需热更新,加判断可以在生产环境被 tree-shaking

1
2
3
if (import.meta.hot) {
// HMR 代码
}

如果我们使用 Vite 创建 React / Vue 项目,会看到 Vite 已经帮我们安了 react / vue 的插件

1
2
3
4
5
6
// react
plugins: [
react(),
]
// vue
plugins: [vue(), vueJsx()]

插件默认会开启 HMR ,如果你想手动处理,通过 server 来配置。

1
2
3
server : {
hmr: true
}

API

hot.accept(cb)

接收模块自身更新,参数是已更新模块的回调函数,接受的模块就是 HMR 的边界。简单来说就是通过该方法让我们手动 HMR,更新的 module 会作为 cb 的参数

hot.accept(deps, cb)

接收依赖模块更新,deps 是依赖的路径,当依赖模块发生更新,会把更新后的新模块作为 cb 的参数。

另外,deps 也可以是一个 Array,接收多个依赖模块更新,随之 cb 是一个和 deps 一一对应的 Array

hot.dispose(cb)

在模块更新、旧模块需要销毁时需要做的一些事情,如果旧模块存在一些副作用,如定时器等,可以在这个方法内清除。

hot.data

可以理解为模块更新中的持久化存储,在模块更新时,hot.data 内的数据会保留。

hot.prune(cb)

模块从页面上被删除时,使用此方法进行清理。

hot.invalidate(message?: string)

指定某个模块是不可热更新的。

hot.on(event, cb)

监听 HMR 事件。


Vite-快速入门
https://l1ushun.github.io/2023/08/04/vite-01/
作者
liu shun
发布于
2023年8月4日