Vite使用整理


注:这是一个demo,未编写完成

Vite 是一个前端构建工具

vue-cli 内部使用是 webpack 进行打包,webpack 的热更新为以当前修改的文件作为入口重新 build 打包,所有涉及到的依赖也会被重新加载一次。

vite 开发时会启动一台静态页面的服务器,不会对文件代码进行打包,而是根据客户端的请求加载不同的模块,实现按需加载。

具有快速冷启动、按需编译等优秀特质。

vue-cli 的开发模式是使用的 webpack 的 webpack-dev-server,需要进行打包才可以使用。而 vite 则是基于 ESM(ES Module),不需要打包直接可以运行。

vue-cli 的热更新基于 webpack 即重新打包,vite 的热更新则是基于缓存

两种 vite 创建 vue3 项目方式:

1
2
3
4
# vue 官网
npm init vue@latest
# vite 官网
npm create vite@latest

注:弹出的预设选项中,Vanilla 是原生 JavaScript/TypeScript 项目的意思。

前者可以自动整合 piniavue-routeresLint 等工具。

Vite 内部使用名为 Rollup 的打包工具。

Rollup:与 Webpack 类似,但专注于 ES6 的模块打包工具,亮点在于在打包时会静态分析 ES6 模块代码中的 import,排除未实际使用的代码,有助于减小构建包的体积。(Webpack 也有,但概念为 Rollup 最先提出)

插件 @vitejs/plugin-vue 用于为 vite 提供 vue3 单文件组件的支持。

起步

下面来创建一个最简单的 vite 项目。

首先新建目录,使用 npm 初始化该目录,并安装 vite。

1
2
3
mkdir vite-min-test && cd vite-min-test
npm init -y
npm i vite -D

在根目录下新建 index.htmlindex.js,在 index.html 中引入 index.js,注意 type 应设置为 module

1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html>
<head>
<title>Vite App</title>
</head>
<body>
<script type="module" src="./index.js"></script>
</body>
</html>

修改 package.json,设置启动与打包命令

1
2
3
4
5
6
{
"scripts": {
"dev": "vite",
"build": "vite build"
}
}

启动项目,获得一个开发服务器地址,开始修改 index.js 查看效果。

1
npm run dev

打包项目则执行 build 命令即可。

1
npm run build

值得注意的是,vite 打包后依然是以 ESM 方式引入 js,而 ESM 要求必须要通过 URL 方式加载,因此打包后本地 file 协议是无法运行的。
而默认情况下,打包生成的 index.html 对入口文件的引入为绝对路径,例如此时为 /assets/index-f271f06b.js,这就要求了所启动的服务器,例如 vscode 的 live server,必须以打包目录为根目录,这对预览来说非常不方便。因此 vite 又提供了一个预览命令。新增 package.json 如下

1
2
3
4
5
{
"scripts": {
"preview": "vite preview"
}
}

该命令将会以打包目录为根目录启动一个服务,执行如下命令即可实现打包结果的预览。

1
npm run preview

特点

外置的 index.html

在 vite 中,index.html 被放置在了根目录下。这是由于在 vite 中,index.html 被定义为了开发服务器的入口文件。在打包时,该 index.html 中的 url 将会被自动转换。

预构建

由于在开发阶段中,vite 的开发服务器将所有的代码视为 ESM,这对于部分使用 CommonJS / UMD 模块化的依赖将出现无法兼容的问题。且同时,部分依赖内部存在许多内部模块,分别引入将会有很严重的性能问题。

因此便有了预构建操作,预构建将会把 CommonJS / UMD 转换为 ESM,使得允许开发者直接使用 ESM 导入裸模块;并将有许多内部模块的 ESM 依赖关系转换为单个模块,以提高后续页面加载性能。

在首次执行 vite 服务启动后,将会对 node_modules 模块使用 esbuild 进行预构建,esbuild 使用 Go 语言编写,性能比 JavaScript 编写的打包器预构建要快得多。

TypeScript

Vite 天然支持 .ts 文件的引入,但仅进行 TypeScript 到 JavaScript 的转译工作,不会执行类型的检查。这一过程使用 esbuild 实现,速度较 tsc 要快速许多。

PostCSS

当项目中存在有效的 PostCSS 配置时(例如 postcss.config.js),将会被自动应用于所有已导入的 css。

且 CSS 压缩将在 PostCSS 处理之后再执行,并会使用 build.cssTarget 选项。

预处理器

Vite 提供了对诸如 .less.scss 等文件的内置支持,无需配置 loader,但需要手动安装预处理器

1
2
3
4
5
# .scss and .sass
npm i sass -D

# .less
npm i less -D

CSS Modules

对于 .module.css 为后缀的 CSS 文件,将会被认为一个 CSS modules 文件,再导入时将会返回一个模块对象。

1
2
3
4
5
6
/* test.module.css */
.red {
width: 100px;
height: 100px;
background-color: red;
}

在入口文件中为元素设置类名,此时页面中可以得到一个红色正方形:

1
2
3
4
5
6
7
8
9
// index.ts
import testStyle from "./test.module.css";

console.log( testStyle ); // {red: '_red_mwzml_1'}

const div = document.createElement( "div" );
document.body.appendChild( div );
// 设置类名
div.className = testStyle.red;

同样的,也可以同时使用 css modules 与 预处理器,例如 test.modules.scss

例如对于 test.module.scss

1
2
3
4
5
6
7
8
9
10
11
/* test.module.scss */
.red {
background-color: red;
.deep {
filter: brightness(0.5);
}
}

.green {
background-color: green;
}

引入并查看打印结果:

1
2
3
4
5
// index.ts
import testStyle from "./test.module.scss";

console.log( testStyle );
// {red: '_red_i95ym_1', deep: '_deep_i95ym_4', green: '_green_i95ym_8'}

具名导入 css

vite 允许具名导入 CSS 文件来获取处理过后的 CSS 字符串,但需要添加 ?inline,此时导入的 CSS 文件将不会被注入到页面中。

1
2
3
4
5
6
/* test.css */
.red {
width: 100px;
height: 100px;
background-color: red;
}

入口文件中导入并查看打印结果:

1
2
3
4
5
6
7
8
import testStyle from "./test.css?inline";

console.log(testStyle);
// .red {
// width: 100px;
// height: 100px;
// background-color: red;
// }

同样的,该特性也允许与预处理器共同使用,且得到的字符串为转义过后的 CSS 字符串。

例如对于 test.scss

1
2
3
4
5
6
7
8
9
10
.red {
background-color: red;
.deep {
filter: brightness(0.5);
}
}

.green {
background-color: green;
}

入口文件中导入并查看打印结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
import testStyle from "./test.scss?inline";

console.log(testStyle);
// .red {
// background-color: red;
// }
// .red .deep {
// filter: brightness(0.5);
// }
//
// .green {
// background-color: green;
// }

导入 json

与 webpack 相同,Vite 允许导入 json,以及获取 json 中的某一个属性。

1
2
3
import { version } from "./package.json";

console.log( version ); // 1.0.0

将资源引入为 URL

引入一个资源时将会返回解析后的公共路径。

1
2
3
import img from "./assets/images/小孤独.png";

console.log(img); // /src/assets/images/小孤独.png

且在生产构建后将会自动拼接哈希值,例如此处在开发环境下为 /src/assets/images/小孤独.png,生产环境下将变为 /src/assets/images/小孤独.5d1fye3.png

该行为类似于 webpack 中的 file-loader,当资源体积小于 build.assetsInlineLimit 时也同样会被内联为 base64 data URL。区别在于导入既可以使用绝对公共路径(基于开发期间的项目根路径),也可以使用相对路径。

url() 在 CSS 中的引用、vue 插件的 SFC 模板中也以同样的方式处理。

显式 URL 引入

常见的图像、媒体和字体文件类型被自动检测为资源,即导入默认获得公共路径,若需要扩展默认列表,啧需要修改选项 build.assetsInclude

而对于未被包含在 build.assetsInclude 内的资源,可以通过添加 ?url 后缀来显示的导入为一个 URL。

1
2
3
import importTest from "./import-test.ts?url";

console.log( importTest ); // /src/import-test.ts

public 目录

默认情况下,被放在 public 目录下的文件有以下特点:

  • 打包时会被完整复制到目标目录的根目录下
  • 开发时能直接通过 / 根路径访问到
  • 名称不会被追加 hash 值

注:永远不要在 JavaScript 代码中引用 public 目录中的文件

完整解析静态资源

在原生 ESM 中存在一个 import.meta.url,它可以暴露当前模块的 URL。将它与原生的 URL 构造器结合使用,可以完整的解析静态资源 URL:

1
2
3
const src = new URL( "./target.ts", import.meta.url ).href;

console.log( src ); // http://127.0.0.1:5173/src/target.ts

注:由于 import.meta.url 在浏览器和 node.js 环境下语义并不相同,因此不要再 SSR 中使用该方法。

客户端类型

Vite 相关的默认的类型定义是编写给自己的 Node.js API 的,要将其补充到一个 Vite 应用的客户端代码环境中,需要在根目录下添加一个 d.ts 声明文件,可暂定为 env.d.ts

1
/// <reference types="vite/client" />

或在 tsconfig.json 中的 compilerOptions.types 作如下配置:

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

点击进入可以发现,vite/client 是一个名为 client.d.ts 的声明文件,里面包含了 import.meta*.module.css*.mp4?url 等所有内置变量、常用文件、CSS Module和拼接参数的类型声明,以便于 TypeScript 实现正常的类型判断。如果你不希望编写代码引入文件时出现奇奇怪怪的 ts 类型报错的话,请务必添加此配置。

注:若要覆盖默认的类型定义,需要在三斜线的上方添加定义,例如:

1
2
3
4
5
declare module '*.svg' {
// ...
}

/// <reference types="vite/client" />

构建生产版本

默认情况下,Vite 的目标是能够支持原生 ESM script 标签、支持原生 ESM 动态导入 和 import.meta 的浏览器,允许通过 build.target 配置项指定构建目标,最低支持 es2015

同时,默认情况下 Vite 只处理语法转译,且不包含任何 polyfill。若有兼容低版本浏览器的需求,可以通过插件 @vitejs/plugin-legacy 来实现 polyfill 的语法填充。

环境变量和模式

vite 在一个特殊的 import.meta.env 上暴露环境变量,下面是一些所有情况下都可以使用的内建变量:

1
2
3
4
5
6
7
8
9
10
// 应用运行的模式
import.meta.env.MODE // "development"
// 部署应用时的基本 URL,由 base 配置项决定。
import.meta.env.BASE_URL // "/"
// 是否运行在生产环境
import.meta.env.PROD // false
// 是否运行在开发环境
import.meta.env.DEV // true
// 是否运行在 server 上
import.meta.env.SSR // false

在生产环境下,这些变量将会被静态替换。因此在使用时务必使用静态写法,诸如 import.meta.env["MODE"] 的取值是无效的。

偶尔可能会出现 JavaScript 或 Vue 模板中的例如 "import.meta.env.MODE" 字符串也被替换的情况(官网说的,什么怪东西)。

.env

Vite 使用 dotenvenvDir 配置项所指定的目录下(默认根路径)的以下文件中加载环境变量,加载的环境变量中以 VITE_ 为前缀的将会被挂载到 import.meta.env 上面,其他的则不会(该前缀可以通过配置项 envPrefix 进行修改)。

1
2
3
4
.env                # 所有情况下都会加载
.env.local # 所有情况下都会加载,但会被 git 忽略
.env.[mode] # 只在指定模式下加载
.env.[mode].local # 只在指定模式下加载,但会被 git 忽略

.env 文件有以下特点:

  • 专门指定了 mode 的环境变量文件的优先级将会高于通用的优先级,即 .env.asuka 的优先级比 .env 要高。
  • Vite 本身存在的环境变量不会被 .env 文件覆盖。
  • .env 的文件需要重启 Vite 服务器才能生效。
  • 如果需要在环境变量中使用 $ 符号,则必须使用 \ 对其进行转义。

mode

上面的 mode 与内置变量 import.meta.MODE 的值相同。

默认情况下,在开发服务器即执行 vite 命令时,modedevelopment;而当执行 vite build 所生成的代码,则默认 modeproduction。此时会对应的加载 .env.development.env.production

可以通过在命令后追加 --mode flag 来自行指定 mode。例如如下命令:

1
2
3
4
5
{
"scripts": {
"asuka": "vite --mode asuka"
}
}

此时运行 npm run asuka,则会加载 .env.asuka 文件(如果有)。

TypeScript 提示

尽管 vite/client 做出了较为全面的类型声明,但它并不包含我们在 .env 中自定义的环境变量,此时我们需要在 env.d.ts 配置我们自己自定义的环境变量的类型声明。

1
2
3
4
5
6
7
8
9
10
/// <reference types="vite/client" />

interface ImportMetaEnv {
readonly VITE_ASUKA_MARI: string
// 更多环境变量...
}

interface ImportMeta {
readonly env: ImportMetaEnv
}

问题

vite 创建项目后,启动时提示 permission denied 127.0.0.1:5173

vite 3.0 后将默认端口改为了 5173,使用如下命令查看系统保留端口:

1
netsh int ipv4 show excludedportrange protocol=tcp

发现该端口的确被列为了保留端口(下图中的 5160 - 5259)。

保留端口范围

此时只能修改 vite.config.js 配置,修改为保留端口范围以外的端口。

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

// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
server: {
port: 5260
}
})