这是我参与「第五届青训营 」伴学笔记创作活动的第 8 天
Vite
浅谈构建工具
前端工程存在很多需要解决的问题,例如:
- 模块化:ESM、CommonJS、UMD
- 资源编译:高级语法的编译
- 产物的质量:代码体积、代码性能
- 开发效率:热更新
前端构建工具的意义:
-
模块化方案
- 提供模块加载方案
- 兼容不同模块规范
-
语法转译
- 高级语法转译,如 Sass、Typescript
- 资源加载,如图片、字体、worker
-
产物质量
- 产物压缩、无用代码删除、语法降级
-
开发效率
- 热更新
Vite 概要介绍
概览
定位:新一代前端构建工具
两大组成部分:
- No-bundle 开发服务,源文件无需打包
- 生产环境基于 Rollup 的 Bundle
核心特征:
- 高性能,dev 启动速度和热更新速度非常快
- 简单易用,开发者体验好
传统构建工具存在的问题
- 缓慢的启动 -> 项目编译成本高
- 缓慢的热更新 -> 修改代码后不能实时更新
瓶颈:
- bundle 带来的性能开销
- JavaScript 语言的性能瓶颈
行业趋势
-
全球浏览器普遍支持原生 ESM(目前占比 92% 以上)
在浏览器中使用原生 ESM 实例:
<!-- index.html --> <!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="utf-8"> <title>test esm</title> </head> <body> <script type="module"> import { foo } from './foo.js' console.log(foo) </script> </body> </html>// foo.js export const foo = 'foo'; -
基于原生语言(Go、Rust)编写前端编译工具链
-
如 Go 语言编写的 ESbuild、Rust 编写的 SWC
基于原生 ESM 的开发服务优势
- 无需打包项目源代码
- 天然的按需加载
- 可以利用文件级的浏览器缓存
基于 ESbuild 的编译性能优化
ESbuild 是基于 Golang 开发的前端工具,性能极高,在 Vite 中被深度使用。ESbuild 具备如下能力:
- 打包器 Bundler
- 编译器 Transformer
- 压缩器 Minifier
内置的 Web 构建能力
Vite 具有强大的构建能力,它拥有 Webpack 的众多功能,例如 dev-server、loader 和 plugin 等等。 以下是明显的配置对比:
webpack.config.js
等价于:
vite.config.js
Vite 上手实战
项目初始化
# 安装 pnpm
npm i -g pnpm
# 初试化命令
pnpm create vite
# 安装依赖
pnpm install
# 启动项目
npm run dev
使用 Sass / Scss & CSS Module
安装 Sass
pnpm i sass -D
使用 Sass
// components/Header/index.tsx
import styles from './index.module.scss'
// 使用 CSS Modules 模块化方案,防止 className 命名冲突
export function Header () {
return <p className={styles.header}>This is Header</p>
}
/* components/Header/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
vite 默认自动开启 HMR,无需额外配置。
生产环境 Tree Shaking
优化原理:
- 基于 ESM 的 import/export 语句依赖关系,与运行时状态无关
- 在构建阶段将未使用到的代码进行删除
Tree Shaking 在 Vite 中无需配置,默认开启。
注意:CommonJS 格式不能做到 Tree Shaking,require 的部分可能依赖运行时计算的结果
Vite 整体架构
依赖预打包
预打包的用意:
- 避免 node_modules 过多的文件请求
- 将 CommonJS 格式转换为 ESM 格式
实现原理:
-
服务启动前扫描代码中用到的依赖
-
用 ESbuild 对依赖代码进行预打包
-
改写 import 语句,指定依赖为预构建产物路径
// 改写前 import React from 'react'; // 改写后 import React from '/node_modoule/.vite/react.js';
单文件编译
用 ESbuild 编译 TS/JSX
优势:
- 编译速度提升 10 - 100 倍
缺点:
- 不支持类型检查
- 不支持语法降级到 ES5
代码压缩
ESbuild 作为默认压缩工具,在速度上有巨大的优势,替换了传统的 Terser、Uglify.js 等压缩工具。
插件机制
开发阶段 -> 模拟 Rollup 插件机制
生产阶段 -> 直接使用 Rollup
Vite 进阶路线
深入学习双引擎
继续深入学习 ESbuild 和 Rollup,学习顺序:
- 先了解基本使用,动手尝试各项常用配置;
- 然后学习其插件开发。
Vite 插件开发
插件机制的优势:
- 抽离核心逻辑
- 易于扩展
插件开发的各个阶段:
通过上述 hook 可以在不同的构建阶段插入自定义的逻辑。
转换自定义文件类型示例:
const 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
}
}
},
}
}
// vite.config.js
import plugin from './myPlugin'
export default defineConfig({
plugins: [plugin()]
})
开发过程:
- 开发 Vite 插件
- 配置文件引入插件
最好先看 Vite 官方文档,过一遍钩子的功能,然后多学习其它插件的实现,掌握套路。复杂度比较低的插件有 json 加载插件,复杂度中等的插件有 ESbuild 接入插件,复杂度较高的插件有官方 React 插件。
代码分割(拆包)
拆包前,引用资源会存在无法进行并发请求和缓存服用率低的情况。拆包后,能达到更好的缓存复用,提升页面加载速度。
JS 编译工具(Babel)
Babel 出现的原因:
- JavaScript 语法标准繁多,浏览器支持程度不一
- 开发者需要用到高级语法
语法安全降级
在构建产物中避免浏览器语法不兼容问题的解决方案:
-
上层解决方案:@vitesjs/plugin-legacy
-
底层原理
- 借助 Babel 进行语法自动降级
- 提前注入 Polyfill 实现,如 core-js、regenerator-runtime
服务端渲染(SSR)
服务端渲染,一种常见的渲染模式,用于提升首屏性能和 SEO 优化。
构建阶段:
代码执行阶段: