Vite基础 | 青训营

125 阅读9分钟

1.Vite的基本概要

1.为什么需要构建工具?

前端工程的痛点:

  • 模块化:ESM、CommonJS、UMD
  • 资源编译:高级语法的编译
  • 产物质量:代码体积、代码性能
  • 开发效率:热更新

image.png

2.前端构建工具的意义

image.png

3.什么是 Vite ?

定位:新一代前端构建工具,可以显著改善开发体验
两大组成部分:

  1. 一个开发服务器:No-bundle 开发服务,源文件无需打包
  2. 一套构建指令:生产环境基于 Rollup 的 Bundler

核心特征:

  1. 高性能,dev 启动速度和热更新速度非常快(比较重的框架安装速度也非常快)
  2. 功能丰富,简单易用,开发者体验好

🔗Vite官方中文文档

3.1深入Vite:浅谈ESM的高阶特性

前言:总所周知,Vite是借助浏览器原生的ESM解析能力(type = "module")实现了开发阶段的 no-bundle,即不用打包也可以构建Web应用。当然,ESM也不仅仅是一个模块规范,还代表了前端社区生态的走向以及各项前端基础设施的未来。
1.高阶特性:
1.import map
在浏览器中我们可以使用包含type="module"属性的script标签来加载 ES 模块,而模块路径主要包含三种:

  • 绝对路径,如 cdn.skypack.dev/react
  • 相对路径,如./module-a
  • bare import即直接写一个第三方包名,如react、lodash

对于前两种模块路径浏览器是原生支持的,而对于 bare import,在 Node.js 能直接执行,因为 Node.js 的路径解析算法会从项目的 node_modules 找到第三方包的模块路径,但是放在浏览器中无法直接执行。而这种写法在日常开发的过程又极为常见,除了将 bare import 手动替换为一个绝对路径,还可以通过现代浏览器内置的 import map 解决上述的问题。
2.Nodejs包导入导出策略
在Node.js 中(>=12.20版本) 有一般如下几种方式可以使用原生 ES Module:

  • 文件以 .mjs 结尾
  • package.json 中声明 type:"module";

那么,Node.js在处理 ES Module 导入导出的时候,如果是处理 npm 包级别的情况,其中的细节可能比你想象中更加复杂。
导出:
首先来看看如何导出一个包,你有两种方式可以选择: main 和 exports 属性。这两个属性均来自于package.json,并且根据 Node 官方的 resolve 算法,exports 的优先级高于 main ,也就是说如果你同时设置了这两个属性,那么 exports会优先生效。并且,main 的使用比较简单,设置包的入口文件路径即可; export 属性就会更复杂些,包含了多种导出形式:默认导出、子路径导出 和 条件导出
导入:
导入包一般都是使用 package.json中的 imports 属性
2.Pure ESM
Pure ESM: 一方面是指让 npm 包都提供 ESM 格式的产物,另一方面是仅留下 ESM 产物,抛弃 CommonJS 等其他格式的产物
由于npm 上大部分的包还是属于基础库的范畴,那对于大部分包,我们采用导出ESM/CommonJS 两种产物的方案,会不会对项目的语法产生限制呢?
首先,在 ESM 中无法使用 CommonJS 中的 __dirname、__filename、require.resolve 等全局变量和方法,同样的,在 CommonJS 中我们也没办法使用 ESM 专有的 import.meta对象,那么如果要提供两种产物格式,这些模块规范相关的语法怎么处理呢?
在传统的编译构建工具中,我们很难逃开这个问题,但新一代的基础库打包器tsup给了我们解决方案。
3.新一代基础库打包器
tsup : 一个基于 Esbuild 的基础库打包器,主打无配置(no config)打包。借助它我们可以轻易地打出 ESM 和 CommonJS 双格式的产物,并且可以任意使用与模块格式强相关的一些全局变量或者 API,比如某个库的源码如下:

export interface Options {
  data: string;
}


export function init(options: Options) {
  console.log(options);
  console.log(import.meta.url);
}

由于代码中使用了 import.meta 对象,这是仅在 ESM 下存在的变量,而经过 tsup 打包后的 CommonJS 版本却被转换成了下面这样:

var getImportMetaUrl = () =>
  typeof document === "undefined"
    ? new URL("file:" + __filename).href
    : (document.currentScript && document.currentScript.src) ||
      new URL("main.js", document.baseURI).href;
var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();


// src/index.ts
function init(options) {
  console.log(options);
  console.log(importMetaUrl);
}

可以看到,ESM 中的 API 被转换为 CommonJS 对应的格式,反之也是同理。最后,我们可以借助之前提到的条件导出,将 ESM、CommonJS 的产物分别进行导出,如下所示。

{
  "scripts": {
    "watch": "npm run build -- --watch src",
    "build": "tsup ./src/index.ts --format cjs,esm --dts --clean"
  },
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.js",
      // 导出类型
      "types": "./dist/index.d.ts"
    }
  }
}

🤔tsup 在解决了双格式产物问题的同时,本身利用 Esbuild 进行打包,性能非常强悍,也能生成类型文件,同时也弥补了 Esbuild 没有类型系统的缺点,推荐使用~

4.当下构建工具的问题在哪?

缓慢的启动 -> 项目编译等待成本高
缓慢的热更新 -> 修改代码后不能实时更新
开发体验问题日渐显露!
🤔瓶颈在哪?

  1. bundle(打包) 带来的性能开销
  2. JS 语言的性能瓶颈
5.两大行业趋势
  • 全球浏览器对原生 ESM(原生浏览器支持 import 和 export 等 ES6 特性)的普遍支持(目前占比92% 以上)
  • 基于原生语言(Go、Rust)编写前端编译工具链的兴起
  • 如 Go 语言编写的 Esbuild、Rust 编写的 SWC
6.浏览器原生 ESM 支持

两大要素:

  1. script 标签增加 type = "module" 属性
  2. 使用 ESM 模块导入导出语法
7.基于原生 ESM 的开发服务优势

✅无需打包项目源代码
✅天然的按需加载
✅可以利用文件级的浏览器缓存
image.png

8.基于 Esbuild 的编译性能优化

image.png
Esbuild -- 基于 Golang 开发的前端工具,具备如下能力:

  1. 打包器 Bundler
  2. 编译器 Transformer
  3. 压缩器 Minifier

🔺 由于性能极高,所以在Vite中被深度使用~

9.内置的 Web 构建能力

Vite 开箱即用的能力极强

2.Vite 上手实战

  1. 项目初始化
  • 创建 vite 项目
  • 输入初始化参数(文件名等配置)
  • 启动项目
  1. 使用 Sass / Scss & CSS Modules

Sass :是采用 Ruby 语言编写的一款 CSS 预处理语言,是最大的成熟的 CSS 预处理语言
SASS 和 SCSS 的区别
Sass 和 SCSS 其实是同一种东西,我们平时都称之为 Sass,两者之间不同之处有以下两点:

  1. 文件扩展名不同。

Sass 是以“.sass”后缀为扩展名,而 SCSS 是以“.scss”后缀为扩展名。

  1. 语法书写方式不同。

Sass 是以严格的缩进式语法规则来书写,不带大括号( { } )和分号( ; ),而 SCSS 的语法书写和我们的 CSS 语法书写方式非常类似。
🔗Sass转css在线网址
❗ 注意:使用scss.module,可以做到不同组件的样式隔离,避免样式污染,同时,scss 中还支持样式嵌套,能提高开发效率

  1. 使用静态资源

除了常见的图片格式,Vite也内置了对于 JSON、Worker、WASM 资源的加载支持
🔗Vite官方文档-静态资源处理

  1. 使用 HMR :无需额外配置,自动开启
  2. 生产环境 Tree Shaking :无需配置,自动开启

优化原理:

  • 基于 ESM 的import / export 语句依赖关系(静态语法),与运行时状态无关
  • 在构建阶段将未使用到的代码进行删除

❗ 注意:

  • Common JS 格式不能做到 Tree Shaking
  • require 的部分可能依赖运行时计算的结果
Vite的直观特点:响应迅速、开箱即用😉

3.Vite 的整体架构

image.png

关键技术:依赖预打包

为什么要进行预打包?

1.避免 node _ modules 过多的文件请求
2.将 CommonJS 格式转换为 ESM 格式

实现原理:

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

//改写前
import React from "react";
//改写后
import React from '/node_modules/.vite/react.js';

关键技术:单文件编译

用 Esbuild 编译 TS/JSX
优势:编译速度提升 10-100x
局限性:

  • 不支持类型检查
  • 不支持语法降级到 ES5
关键技术:代码压缩

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

关键技术:插件机制

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

4.Vite 进阶路线

1.深入双引擎
  • Esbuild
  • Rollup

📘参考资料:
Esbuild官方文档
Rollup官方文档

2.Vite 插件开发

1.为什么需要插件机制?
  • 抽离核心逻辑
  • 易于拓展

推荐学习顺序:

  • 先了解基本使用,动手尝试各项常用配置
  • 然后学习插件开发(可以详细看一看Rollup插件开发的章节)

image.png
通过上述的 Hook(钩子函数),我们可以在不同的构建阶段插入自定义的逻辑
重点掌握:
config、resolveId、load、transform

2.插件开发

📘参考资料:
Vite插件开发文档
复杂度较低的插件:json加载插件
复杂度中等的插件:Esbuild接入插件
复杂度较高的插件:官方React插件
📋 建议:先看文档,过一遍钩子插件的功能,然后自动多动手去写,多学习其他插件的实现,掌握套路(模仿 + 总结规律)

3.代码分割(拆包)

拆包前:
image.png
问题:

  • 无法进行并发请求
  • 缓存复用率低

拆包后:
image.png
参考资料:
构建选项-Vite官方文档
构建选项-rollup官方文档

4.JS编译工具(Babel)

原理:
image.png
出现原因:

  • JS 语法标准繁多,浏览器支持程度不一
  • 开发者需要用到高级语法

📘参考资料:
babel官方站点
babel插件手册(学习插件开发推荐去看)

5.语法安全降级

如何在构建产物中避免这类问题?
上层解决方案:@vitejs/plugin-legacy
底层原理:

  • 借助 Babel 进行语法自动降级
  • 提前注入 Polyfill 实现,如 core-js、regenerator-runtime

📘参考资料:
@babel/preset-env 文档
Vite官方降级文档

6.服务端渲染(SSR)

一种常见的渲染模式,用于提升首屏性能和 SEO 优化
构建阶段:
image.png
代码执行阶段:
image.png
📘参考资料:
Vite-SSR文档
Vite官方ssr-demo项目
使用Vite搭建ssr工程

7.深入了解底层标准

重点规范:

  • CJS 规范
  • ESM 规范
  • HTTP 2.0 特性

📘参考资料:
链接1
链接2
什么是PureESM?

8.Vite 社区生态

🔨官方提供插件:
@ vitejs / plugin - vue ,提供 Vue 3支持
@ vitejs / plugin - vue - jsx ,提供 Vue 3 JSX 支持
@ vitejs / plugin - react ,提供 React 支持
@ vitejs / plugin - legacy ,提供低版本浏览器降级支持
💥 海量社区插件:插件