Vite 知识体系 - 笔记
浅谈构建工具
🤔 前端为什么需要构建工具?
前端工程一直有一些痛点,我们需要去解决它们:
-
📦 模块化 - ESM、CommonJS、UMD
-
🔨 资源编译 - 高级语法的编译
-
👷♂️ 产物质量 - 代码体积、代码性能
-
🚀 开发效率 - 热更新
构建工具的意义就是解决以上的问题:
-
📦 模块化方案
-
提供统一的模块加载方案,兼容不同的模块规范
-
🔨 语法转译
-
高级语法转译,如 Sass、TypeScript
-
资源加载,如图片、字体、worker
-
👷♂️ 产物质量
-
产物压缩、无用代码删除、语法降级
-
🚀 开发效率
-
热更新
Vite是什么
Vite 中文官网,有问题建议先从官网查起,要相信一手资料的魅力。
简单介绍
Vite 给自己的 🔥定位是新一代前端构建工具。
Vite 有两大 💡组成部分:
-
No - bundle 开发服务,源文件无需打包
Vite 提供了一个具有热更新能力的开发服务器。这个开发服务器不需要将源文件打包成一个或多个捆绑包(bundles),而是直接使用 ES 模块的原始源码作为开发时的文件。当开发者在浏览器中访问应用程序时,Vite 会根据需要动态地按需加载模块,这意味着只有实际需要的模块才会被获取和执行,而不需要对整个应用进行重新构建和打包。这种无捆绑的开发模式可以显著提高开发速度,因为它避免了完整的打包和重新构建过程。
-
生产环境基于 Rollup 的 bundler
在构建生产环境时,Vite 使用 Rollup 作为其内部的打包工具。Rollup 是一个强大的 JavaScript 模块打包器,支持将多个模块打包成一个或多个捆绑包,以便在生产环境中进行部署。Vite 使用 Rollup 来生成用于生产环境的优化的捆绑包,包括代码压缩、模块合并等优化策略,以确保生成的文件尽可能小并具备最佳的性能。
Vite 的这种设计使得开发环境更加高效,并且在生产环境中能够产生高性能、轻量级的代码。
Vite 的 📝核心特征:
-
高性能,dev 启动速度和热更新速度飞快
-
简单易用,开发者体验好
当前构建工具存在的问题
-
🥲 启动慢 - 项目编译等待成本高
-
🥲 热更新慢 - 修改代码不能实时更新
为什么会出现这些而问题,瓶颈在哪里?
一方面是因为 bundle 带来的性能开销,对代码打包时相当消耗性能;另一方面是因为 JavaScript 本身的性能问题,JS 是一门解释性的、单线程的语言,无法像 go 语言一样使用多线程对性能进行优化。
两大行业趋势
针对上文中的两大问题,现在业内出现了两种趋势:
-
浏览器对原生 ESM 的普遍支持(目前占比 92% 以上)
-
基于原生语言(Go)编写前端工具链,如 Go-Esbuild、Rust-SWC
先说浏览器对原生 ESM 的普遍支持。
浏览器对原生 ESM 的支持主要体现在:html 文件的 script 标签增加了 type="moudle" 属性,支持开发者在带有该属性的 script 标签中编写 ESM 模块格式的语法。
具体到 Vite Dev Server,基于原生 ESM 的开发服务带来以下 🥳好处:
-
无需打包项目源代码
通常情况下,传统的前端构建工具需要将项目的源代码进行打包,将多个模块合并为一个或多个捆绑包。但 Vite 使用原生 ESM 的开发服务,源代码无需打包,只需要按需加载具体的模块。这样可以提高开发效率,因为修改代码后只需要重新加载被修改的模块,而不需要重新打包整个应用。
-
天然的按需加载
Vite Dev Server 可以根据需要动态按需加载模块,只有在需要时才会获取和执行特定的模块。这种按需加载的方式可以减少初始加载时间,提升应用的性能。同时,当模块发生改变时,只会重新加载受影响的模块,而不会重新加载所有模块,提高了开发过程中的响应速度。
-
利用浏览器缓存
Vite 使用原生 ESM 的开发服务可以更好地利用浏览器的缓存机制。由于每个模块都有自己的 URL,浏览器可以针对每个模块进行缓存。当模块没有发生变化时,浏览器可以直接使用缓存的版本,大大减少了网络请求和加载时间。
然后是基于原生语言编写的前端工具链。
Vite 深度使用了 Esbuild。Esbuild 是一个基于 Golang 开发的前端工具,🔥性能极高,具备以下能力:
-
打包器 - Bundler,对标 Webpack
-
编译器 - Transformer,对标 Bable
-
压缩器 - Minifier
Esbuild 的出色性能得益于其高度并行化的处理方式、快速的 JavaScript 解析器和优化的算法。这使得 Vite 在开发过程中能够提供极快的冷启动时间和实时更新,加速了前端开发流程。
Vite的使用
首先是项目初始化:
# 安装 pnpm
npm i -g pnpm
# 初始化命令
pnpm create vite
# 安装依赖
pnpm install
# 启动项目
npm run dev
按照以上流程操作时,会要求进行一些初始化的配置,主要是选择要使用的 JS 框架(这里以 React 为例),其他的自己看看 😇。
以下是成功启动 Vite 服务的截图,打开浏览器对应地址即可。
之后网课是在实战教学,我身为一只懒🐶肯定不会一一记录(也没啥必要),这里零散的记录一些知识点。
npm script
推荐阮一峰老师的 npm scripts 使用指南
我们可以打开我们的 package.json,里面有一些默认配置的 npm scripts:
{
...
"scripts": {
"dev": "vite", // 启动开发服务器
"build": "vite build", // 构建项目
"preview": "vite preview" // 预览构建项目
},
...
}
Module CSS
Module CSS 是一种用于组织和封装 CSS 样式的模块化方法,旨在解决传统全局作用域的 CSS 的一些问题,例如样式冲突和难以维护。
在 Module CSS 中,每个组件或模块都有自己的 🤌局部作用域,样式仅应用于该组件或模块的特定范围,这通过使用类名或其他选择器与组件绑定实现。通过将样式限制在组件的作用域内,可以避免全局样式的冲突和污染。
这里拿常见的 CSS Modules 举例。CSS Modules 是一种在构建过程中将 CSS 样式转换为局部作用域的方式。在使用 CSS Modules 时,每个 CSS 文件被视为一个独立的模块,其中定义的类名只在该模块的作用域内有效。
/* styles.css */
.container {
background-color: #eef;
padding: 20px;
}
.title {
font-size: 24px;
color: #333;
}
import styles from './styles.css';
function MyComponent() {
return (
<div className={styles.container}>
<h1 className={styles.title}>Hello, Module CSS!</h1>
</div>
);
}
在上述示例中,.container 和 .title 类名是局部作用域的,不会影响其他模块中的同名类名,可以避免样式冲突。
Tree Shaking
前端工程化的常见概念,这里简单说一下。
Tree Shaking 是指在前端应用程序构建过程中使用的一种优化技术。它旨在通过 🔪去除未使用的代码,减小应用程序的文件大小,并提高加载和执行性能。
Tree Shaking 的名称源自 🍂摇树,从树上摇下多余的叶子,只剩下所需的叶子。在开发过程中,前端应用程序通常会导入许多库、框架和模块,但只使用其中的一小部分功能或方法。这导致了大量未使用的代码被包含在最终的构建文件中,增加了文件的大小,降低了性能。
Tree Shaking 的优化原理是基于 JavaScript 模块系统(如 ES6 模块)的静态特性。它通过静态分析代码的依赖关系和引用,确定哪些代码被实际使用,哪些代码是可删除的。这样,在构建过程中,编译器可以自动去除未使用的代码,只保留被调用或引用的部分。
Vite 默认开启 Tree Shaking。
Vite整体架构
从网课拿张图过来用:
从图中可以看出,整体架构整体分为三个方向:开发、服务和插件,其中插件部分被开发以及服务共用。以下会介绍整体架构中一些关键性的技术。
预打包
在开发环境下,Vite 会进行一个“预打包”的过程。
大家熟知,node_modules 目录是一个“重包袱”,过于庞大的体积、繁多的冗余依赖都会降低打包的速度。“预打包”就是针对这一过程出现的优化方案。
在 Dev Server 使用前,Vite 会扫描代码中使用的依赖,对依赖代码使用 Esbuild 进行打包,在应用程序部署之前提前生成打包文件,以优化应用程序的加载和执行性能。
单文件编译
使用 Esbuild 编译 TS/JSX 文件,原生语言带来的强大优势就是 🚀大幅提高了编译速度,但也存在一定局限性:
-
不支持类型检查
-
不支持语法降级到 ES5,只能支持到 ES6
代码压缩
Esbuild 作为 Vite 的默认压缩工具,替换传统的 Terser、Uglify.js 等压缩工具,在压缩性能方面存在较大优势。可以参照以下对比图,可以发现 Esbuild 的压缩时间相比其他工具缩短了很多:
插件技术
开发阶段,Vite 使用了 Plugin Container,用来模拟 Rollup 的插件机制;生产环境阶段则直接使用 Rollup。
使用 Plugin Container 的优势在于可以统一开发环境和生产环境下的插件使用方式,从而降低开发成本并提供更好的开发体验。开发者可以使用相同的插件来处理源代码,无需独立为开发环境和生产环境编写不同的插件配置。
这样做存在一定兼容性问题,可以到以下网站详细查询:
Vite进阶路线
引擎
深入双引擎,推荐先了解基本使用,动手实践尝试使用各项配置,然后学习其插件开发。
官方文档都写的很好,直接去看就可以了,里面还涉及了一些设计思想,值得阅读:
插件开发
插件是构建工具的一种扩展机制,开发人员可以使用插件来自定义和增强构建工具的行为,以适应项目的特殊需求。这里的插件开发是指开发用于扩展构建工具功能的模块或插件。
我们为什么需要 🔩插件机制?
-
抽离核心逻辑
构建工具通常需要处理各种复杂的任务,如文件转换、代码压缩、静态资源处理等。为了保持构建工具本身的简洁和可维护性,将这些功能抽象为插件可以使核心逻辑更加清晰。插件承担了特定功能的实现,将具体的任务逻辑与构建工具本身解耦,使代码结构更加模块化和可扩展。
-
易于拓展
通过插件机制,开发人员可以方便地新增、替换或定制构建工具的功能。插件提供了一个扩展点,允许开发者根据项目需求自由地添加自定义的处理逻辑。这样,无论是在构建过程中的特殊需求还是额外的功能要求,都可以通过编写和配置插件来实现,而无需对构建工具本身进行修改或重新编译。
概括插件的 Hook 的一张图,拿来看看:
emmm,Vite 插件开发是一个相当大的话题,老师建议先看文档,过一遍插件钩子的功能(上面这张图),然后多学习其他插件的实现,掌握套路。以下是一些参考资料,可以供日后学习使用:
-
尝试开发,实战练习
-
🥉 低复杂度插件 - json 加载插件
-
🥈 中复杂度插件 - esbuild 接入插件
-
🥇 高复杂度插件 - React 官方插件
代码分割/拆包
之前,打包的时候都是产出一个 JS 文件,因为是单文件,所以浏览器无法进行并发请求,并且缓存复用率低,牵一发而动全身。
拆包技术出现后呢,我们可以只更改对应组件和入口组件,达到更好的缓存复用的效果,以提高页面加载速度,优化用户体验。
Vite 的代码分割功能高度依赖 Rollup 的打包功能。
可供参考的学习资料:
Babel
JavaScript 语法标准很多,不统一,浏览器支持程度也不一样,开发者也需要使用高级语法,所以 JS 编译工具出现了。
Babel 是一个 JS 编译工具,我们可以用一张简单的图来解释其原理(语法降级):
可供参考的学习资料:
语法安全降级
以 Promise 语法为例,IE11 没有支持该语法,如果我们强行使用会触发报错,导致页面白屏(🫠万恶的 IE)。
Vite 提供了上层解决方案 @vitejs/plugin-legacy 插件,以实现 JS 语法的自动降级。
底层原理:
-
借助Babel 进行语法自动降级
-
提前注入Polyfill 实现,如core-js、regenerator-runtime
服务端渲染(SSR)
SSR 是一种常见的渲染模式,它在服务器端生成完整的 HTML 页面,并将该页面发送给客户端进行展示。相对于传统的客户端渲染(Client-Side Rendering,CSR),SSR 在服务器端执行大部分渲染工作,可以提供更好的搜索引擎优化(SEO)、更快的首次加载速度和更好的性能表现。
可供参考的学习资料:
深入了解底层标准
参考下图:
提取一些重点特性:CJS规范、ESM 规范、HTTP 2.0 特性……
现在社区有在往 ESM 大一统的方向发展,PureESM 兴起。
Vite 社区生态
简而言之就是非常 🔥火爆,官方也提供了很多插件(前文都有提到,这里不做整理),可以去下面这个链接看看
闲聊
讲道理,每次认真记东西,都会觉得知识点很多😮💨,需要很长时间去消化。
阿菇,がんばって 🎉!