Vite
这是我参与第五届青训营伴学笔记创作活动的第 8 天,记录了 Vite 零基础入门的一些知识。
一、前端为什么需要构建工具
- 模块化:前端资源过多,JS、TS、JSX、CSS、SCSS、PNG、JPEG等等。
- 资源编译:高级语法浏览器并不认识,需要工具进行编译降级
- 产物质量:代码需要压缩,不然体积过大,影响性能
- 开发效率:代码的热更新
那么对应的,模块化工具就来解决这些问题
- 模块化方案:
- 提供模块加载方案:比如 webpack 的 webpack runtime 模块加载器,对每个模块的导入导出进行规范的统一
- 兼容不同模块规范
- 语法转译:
- 高级语法转译,如 Sass、TypeScript:例如 webpack 的各种Loader
- 资源加载,如图片、字体、worker
- 产物质量:
- 产物压缩、无用代码删除、语法降级:对不支持高级语法的浏览器进行语法降级,达到代码安全
- 开发效率
- 热更新:改动代码立即更新
二、Vite是什么
Vite简介
定位:新一代前端构建工具
两大组成部分:%
- No-bundle 开发服务,源文件无需打包
- 生产环境基于 Rollup 的 Bundler
核心特征:
- 高性能,dev 启动速度和热更新速度非常快
- 简单易用,开发者体验好
传统前端构建工具的问题
- 缓慢的启动 -> 项目编译等待成本高
- 缓慢的热更新 -> 修改代码后不能实时更新
代码体积越大,问题越显著
瓶颈在哪里
- bundle 带来的性能开销,都会将代码进行打包,打包消耗性能
- JavaScript 性能瓶颈,解释型单线程语言,不能像多线程语言那样利用多线程进行性能优化
衍生出量大行业趋势:
- 全球浏览器对原生 ESM 的普遍支持(目前占比 92% 以上)
- 基于原生语言(Go、Rust)编写前端编译工具链,如 Go 语言编写的 Esbuild、Rust 编写的 SWC
浏览器原生 ESM 支持
- script 标签增加 type="module" 属性
- 使用 ESM 模块导入导出语法
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
// type="module"
<script type="module">
import { foo } from "./foo.js";
console.log(foo); // foo
</script>
</body>
</html>
export const foo = 'foo';
基于原生 ESM 的开发服务优势 —— 以Vite Dev Server为例
原理就是利用了上面浏览器原生 ESM 支持的 type="module",让浏览器向 Vite Dev Server 发送一个文件请求,Dev Server在接受到请求之后就会对文件进行一些编译处理,返回给浏览器可以识别的内容
- 解决了 bundle 的瓶颈,无需打包项目源代码
- 天然的按需加载,请求什么文件对什么文件进行处理
- 可以利用文件级的浏览器缓存
基于 Esbuild 的编译性能优化
Esbuild——基于 Golang 开发的前端工具,具备如下能力:
- 打包器 Bundler——对标webpack
- 编译器 Transformer——对标Babel
- 压缩器 Minifier——对标Uglify等JS压缩工具
哪个领域都是性能极高,在 Vite 中被深度使用
Vite内置的 Web 构建能力
Vite开箱即用的功能等价于:
- webpack、webpac-dev-server
- css-loader、style-loader、less-loader、sass-loader、postcss-loader、file-loader
- MiniCssExtractPlugin、HTMLWebpackPlugin
- ……
用一个图进行对比
三、使用 Vite
项目初始化
# 提前安装pnpm
npm i -g pnpm
# 初始化命令
pnpm create vite
# 输入初始化参数
# cd 进入项目
# 安装依赖
pnpm install
# 启动项目
npm run dev
输入初始化参数:名称、框架、类型
在package.json中,"script"有三种命令
# dev 命令:开发阶段启动Dev Server
# build 命令:生产环境下进行项目的打包,底层使用 Rollup
# preview命令:打包后预览产物的内容
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
执行npm run dev启动项目后,即可看到相应网址
使用Sass/Scss & CSS Modules
# 安装 Sass
pnpm i sass -D
// index.tsx
import styles from './index.module.scss';
// 使用 CSS Modules 模块化方案,防止 className 命名冲突
// 导出 Header 组件
export function Header() {
return <p classname={styles.header}>This is Header</p>
};
/* index.module.scss*/
.header {
color: red;
}
// App.tsx
import { Header } from './components/Header';
function App() {
return (
<div>
<Header />
</div>
);
}
export default App;
使用静态资源
以 svg 图片为例
// App.tsx
import { Header } from './components/Header';
import logoUrl from './logo.svg';
function App() {
return (
<div>
<Header />
<img src={logoUrl} alt="" />
</div>
);
}
export default App;
除了常见的图片格式,Vite 也内置了对于 JSON、Worker、WASM 资源的加载支持
使用HMR——模块热替换
无须额外配置,自动开启,修改代码自动执行
生产环境 Tree Shaking
摇树优化就是删掉代码中的Dead Code,也就是一些没有任何用途的代码,比如永远不会到达的代码等等。
优化原理:
- 基于 ESM 的 import/export 语句依赖关系,与运行时状态无关
- 因此,CommonJS 格式不能做到 Tree Shaking,因为require() 的部分可能依赖运行时计算的结果,盲目删除会删除关键功能
- 在构建阶段将未使用到的代码进行删除
Tree Shaking 在 Vite 中无需配置,默认开启!
例:下面的代码中,multi没有用到,所以不会打包
四、Vite整体架构
预打包阶段(Development)
为什么要进行预打包?
- 避免 node_modules 过多的文件请求
- 将 CommomJS 格式转换为 ESM 格式
实现原理:
- 服务启动前扫描代码中用到的依赖
- 用 Esbuild 对依赖代码进行预打包
- 改写 import 语句,指定依赖为预构建产物路径
关键技术:单文件编译
用 Esbuild 编译 TS/JSX
优势:编译速度提升 10-100倍
局限性:
- 不支持类型检查
- 因此在 build 时要调用一次 tsc
- 不支持语法降级到 ES5 ,只能降到 ES6
关键技术:代码压缩(Production中minify部分)
Esbuild 作为默认压缩工具,替换传统的 Terser、Uglify.js 等压缩工具
关键技术:插件机制(Vite Plugin Pipeline)
- 开发阶段 -> 模拟 Rollup 插件机制
- 生产环境 -> 直接使用 Rollup
五、Vite 进阶路线
深入双引擎——esbuild、rollup.js
推荐学习顺序:
- 先了解基本使用,动手尝试各项常用配置
- 然后学习其插件开发
为什么需要插件机制
- 抽离核心逻辑
- 易于扩展
通过上述的 Hook,我们可以在不同的构建阶段插入自定义的逻辑
代码分割(拆包)
打包后由于项目变成了一个产物文件,改动一处全都改动。
- 由于只有一个产物,无法进行并发请求
- 缓存复用率低,一旦改动整个缓存全部失效
因此拆包可以保证一个组件改动,其余组件正常运行,提升加载速度和用户体验。
六、参考
- 字节录播课 - 《Vite 基本概要》
- 字节录播课 - 《Vite 上手实战与架构解析》
- 字节录播课 - 《Vite 进阶路线》