Vite | 青训营笔记

45 阅读6分钟

Vite

这是我参与第五届青训营伴学笔记创作活动的第 8 天,记录了 Vite 零基础入门的一些知识。

一、前端为什么需要构建工具

  • 模块化:前端资源过多,JS、TS、JSX、CSS、SCSS、PNG、JPEG等等。
  • 资源编译:高级语法浏览器并不认识,需要工具进行编译降级
  • 产物质量:代码需要压缩,不然体积过大,影响性能
  • 开发效率:代码的热更新

那么对应的,模块化工具就来解决这些问题

  • 模块化方案:
    1. 提供模块加载方案:比如 webpack 的 webpack runtime 模块加载器,对每个模块的导入导出进行规范的统一
    2. 兼容不同模块规范
  • 语法转译:
    1. 高级语法转译,如 Sass、TypeScript:例如 webpack 的各种Loader
    2. 资源加载,如图片、字体、worker
  • 产物质量:
    • 产物压缩、无用代码删除、语法降级:对不支持高级语法的浏览器进行语法降级,达到代码安全
  • 开发效率
    • 热更新:改动代码立即更新

二、Vite是什么

Vite简介

定位:新一代前端构建工具

两大组成部分:%

  1. No-bundle 开发服务,源文件无需打包
  2. 生产环境基于 Rollup 的 Bundler

核心特征:

  1. 高性能,dev 启动速度和热更新速度非常快
  2. 简单易用,开发者体验好

传统前端构建工具的问题

  • 缓慢的启动 -> 项目编译等待成本高
  • 缓慢的热更新 -> 修改代码后不能实时更新

代码体积越大,问题越显著

瓶颈在哪里

  • bundle 带来的性能开销,都会将代码进行打包,打包消耗性能
  • JavaScript 性能瓶颈,解释型单线程语言,不能像多线程语言那样利用多线程进行性能优化

衍生出量大行业趋势:

  • 全球浏览器对原生 ESM 的普遍支持(目前占比 92% 以上)
  • 基于原生语言(Go、Rust)编写前端编译工具链,如 Go 语言编写的 Esbuild、Rust 编写的 SWC

浏览器原生 ESM 支持

  1. script 标签增加 type="module" 属性
  2. 使用 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在接受到请求之后就会对文件进行一些编译处理,返回给浏览器可以识别的内容

1.png

  • 解决了 bundle 的瓶颈,无需打包项目源代码
  • 天然的按需加载,请求什么文件对什么文件进行处理
  • 可以利用文件级的浏览器缓存

基于 Esbuild 的编译性能优化

Esbuild——基于 Golang 开发的前端工具,具备如下能力:

  1. 打包器 Bundler——对标webpack
  2. 编译器 Transformer——对标Babel
  3. 压缩器 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
  • ……

用一个图进行对比

2.png

三、使用 Vite

项目初始化

# 提前安装pnpm
npm i -g pnpm
# 初始化命令
pnpm create vite
# 输入初始化参数

# cd 进入项目

# 安装依赖
pnpm install
# 启动项目
npm run dev

输入初始化参数:名称、框架、类型

3.png

在package.json中,"script"有三种命令

# dev    命令:开发阶段启动Dev Server
# build  命令:生产环境下进行项目的打包,底层使用 Rollup
# preview命令:打包后预览产物的内容
"scripts": {
    "dev": "vite",  
    "build": "tsc && vite build",
    "preview": "vite preview"
  },

执行npm run dev启动项目后,即可看到相应网址

4.png

使用Sass/Scss & CSS Modules

5.png

# 安装 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;

使用静态资源

5.png

以 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,也就是一些没有任何用途的代码,比如永远不会到达的代码等等。

优化原理:

  1. 基于 ESM 的 import/export 语句依赖关系,与运行时状态无关
    • 因此,CommonJS 格式不能做到 Tree Shaking,因为require() 的部分可能依赖运行时计算的结果,盲目删除会删除关键功能
  2. 在构建阶段将未使用到的代码进行删除

Tree Shaking 在 Vite 中无需配置,默认开启!

例:下面的代码中,multi没有用到,所以不会打包

6.png

四、Vite整体架构

7.png

预打包阶段(Development)

为什么要进行预打包?

  1. 避免 node_modules 过多的文件请求
  2. 将 CommomJS 格式转换为 ESM 格式

实现原理:

  1. 服务启动前扫描代码中用到的依赖
  2. 用 Esbuild 对依赖代码进行预打包
  3. 改写 import 语句,指定依赖为预构建产物路径

8.png

关键技术:单文件编译

用 Esbuild 编译 TS/JSX

9.png

优势:编译速度提升 10-100倍

局限性:

  • 不支持类型检查
    • 因此在 build 时要调用一次 tsc
  • 不支持语法降级到 ES5 ,只能降到 ES6

关键技术:代码压缩(Production中minify部分)

Esbuild 作为默认压缩工具,替换传统的 Terser、Uglify.js 等压缩工具

关键技术:插件机制(Vite Plugin Pipeline)

  • 开发阶段 -> 模拟 Rollup 插件机制
  • 生产环境 -> 直接使用 Rollup

五、Vite 进阶路线

深入双引擎——esbuild、rollup.js

推荐学习顺序:

  • 先了解基本使用,动手尝试各项常用配置
  • 然后学习其插件开发

为什么需要插件机制

  • 抽离核心逻辑
  • 易于扩展

10.png

通过上述的 Hook,我们可以在不同的构建阶段插入自定义的逻辑

代码分割(拆包)

打包后由于项目变成了一个产物文件,改动一处全都改动。

  • 由于只有一个产物,无法进行并发请求
  • 缓存复用率低,一旦改动整个缓存全部失效

11.png

因此拆包可以保证一个组件改动,其余组件正常运行,提升加载速度和用户体验。

12.png

六、参考

  • 字节录播课 - 《Vite 基本概要》
  • 字节录播课 - 《Vite 上手实战与架构解析》
  • 字节录播课 - 《Vite 进阶路线》