Vite知识体系 | 青训营

218 阅读13分钟

Vite 知识体系 - 笔记

浅谈构建工具

🤔 前端为什么需要构建工具?

前端工程一直有一些痛点,我们需要去解决它们:

  • 📦 模块化 - ESM、CommonJSUMD

  • 🔨 资源编译 - 高级语法的编译

  • 👷‍♂️ 产物质量 - 代码体积、代码性能

  • 🚀 开发效率 - 热更新

构建工具的意义就是解决以上的问题:

  • 📦 模块化方案

  • 提供统一的模块加载方案,兼容不同的模块规范

  • 🔨 语法转译

  • 高级语法转译,如 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 的开发服务带来以下 🥳好处:

  1. 无需打包项目源代码

通常情况下,传统的前端构建工具需要将项目的源代码进行打包,将多个模块合并为一个或多个捆绑包。但 Vite 使用原生 ESM 的开发服务,源代码无需打包,只需要按需加载具体的模块。这样可以提高开发效率,因为修改代码后只需要重新加载被修改的模块,而不需要重新打包整个应用。

  1. 天然的按需加载

Vite Dev Server 可以根据需要动态按需加载模块,只有在需要时才会获取和执行特定的模块。这种按需加载的方式可以减少初始加载时间,提升应用的性能。同时,当模块发生改变时,只会重新加载受影响的模块,而不会重新加载所有模块,提高了开发过程中的响应速度。

  1. 利用浏览器缓存

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 Rollup Plugin 👈

Vite进阶路线

引擎

深入双引擎,推荐先了解基本使用,动手实践尝试使用各项配置,然后学习其插件开发。

官方文档都写的很好,直接去看就可以了,里面还涉及了一些设计思想,值得阅读:

插件开发

插件是构建工具的一种扩展机制,开发人员可以使用插件来自定义和增强构建工具的行为,以适应项目的特殊需求。这里的插件开发是指开发用于扩展构建工具功能的模块或插件。

我们为什么需要 🔩插件机制?

  • 抽离核心逻辑

构建工具通常需要处理各种复杂的任务,如文件转换、代码压缩、静态资源处理等。为了保持构建工具本身的简洁和可维护性,将这些功能抽象为插件可以使核心逻辑更加清晰。插件承担了特定功能的实现,将具体的任务逻辑与构建工具本身解耦,使代码结构更加模块化和可扩展。

  • 易于拓展

通过插件机制,开发人员可以方便地新增、替换或定制构建工具的功能。插件提供了一个扩展点,允许开发者根据项目需求自由地添加自定义的处理逻辑。这样,无论是在构建过程中的特殊需求还是额外的功能要求,都可以通过编写和配置插件来实现,而无需对构建工具本身进行修改或重新编译。

概括插件的 Hook 的一张图,拿来看看:

emmm,Vite 插件开发是一个相当大的话题,老师建议先看文档,过一遍插件钩子的功能(上面这张图),然后多学习其他插件的实现,掌握套路。以下是一些参考资料,可以供日后学习使用:

代码分割/拆包

之前,打包的时候都是产出一个 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 社区生态

简而言之就是非常 🔥火爆,官方也提供了很多插件(前文都有提到,这里不做整理),可以去下面这个链接看看

闲聊

讲道理,每次认真记东西,都会觉得知识点很多😮‍💨,需要很长时间去消化。

阿菇,がんばって 🎉!