前言
这是我参与「第五届青训营」伴学笔记创作活动的第 14天,上章我们讲了webpack,这章我们来去讲vite,也是构建打包工具,vite构建和打包都比webpack要快的多,支持跨平台等优势,大有代替webpack之势,那我接下来就开始讲
内容大纲
- 浅谈构建工具
Vite概要介绍Vite上手实战- 整体架构
Vite: Vite进阶路线
为什么需要前端工具?
前端工程的痛点
核心要素 —— 资源
- JS
- TS
- JSX
- CSS
- SCSS
- LESS
- PNG
- JPEG
- WEBP
- ...
模块化
- ESM、CommJS、UMD
资源编译
- 高级语法的编译
产物质量
- 代码体积,代码性能
开发效率
- 热更新
前端构建工具的意义
模块化:
webpackruntime 就是对模块的导入导出的规范统一;语法转译:loader;产物质量:boulder,treeshaking,babel; 开发效率:HMR
Vite 是什么? Why Vite?
Vite 概览
- 定位: 新一代前端构建工具
- 两大组成部分
-
No-bundle开发服务,源文件无需打包
-
- 生产环境基于
Rollup的Bundler
- 生产环境基于
-
- 核心特征
-
- 高性能,
dev启动速度和热更新速度非常快!
- 高性能,
-
- 简单易用,开发者体验好
-
业界案例
Rollup -> Vite
- 启动时间: 2分15秒 ->
1.7 秒 - 更新时间: 23 秒->
1秒以内Webpack -> Vite
- 启动时间: 2分36秒 ->
6 秒 - 热更新从 13 秒->
1 秒以内
当下问题
- 缓慢的启动 ->项目编译等待成本高
- 缓慢的热更新-> 修改代码后不能实时更新
开发体验问题日渐显露
瓶颈在哪里?
bundle 带来的性能开销
JavaScript 语言的性能瓶
两大行业趋势
- 全球浏览器对原生 ESM 的普遍支持(目前占比92%以上)
- 基于原生语言(
Go、Rust)编写前端编译工具链 - 如
Go语言编写的Esbuild、Rust编写的SWC
浏览器原生 ESM 支持
// index.html
<!DOCTYPE htmm>
<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';
两大要素
- script 标签增加
type="module"属性 - 使用 ESM 模块导入导出语法
基于原生 ESM 的开发服务优势
- 无需打包项目源代码
- 天然的按需加载
- 可以利用文件级的浏览器缓存
- 去除了
boulder之前的开销,不需要项目打包源代码,一个文件就是一个请求;我需要哪些文件才按需引入,如:就只请求src/main.tsx的话,就只编译src/main.tsx,其他先不编译;浏览器达到了文件级,当文件变更,就不会导致整个boulder失效,只会导致当前请求失效,就可以达到更细腻的请求
基于 Esbuild 的编译性能优化
Esbuild --基于 Golang 开发的前端工具,具备如下能力:
- 打包器
Bundler - 编译器
Transformer - 压缩器
Minifier
性能极高,在 Vite 中被深度使用
内置的 Web 构建能力
Vite 开箱即用的功能等价于
- webpack
- webpack-dev-server
- css-loader
- style-loader
- less-loader
- sass-loader
- postcss-loader
- file-loader
- MiniCssExtractPlugin
- HTMLWebpackPlugin
- ...
// webpack.config.ts
export default {
entry: './src/index.ts',
module: {
rules: [
{
test: /\.[tj]sx?$/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-typescript'],
}
}
]
},
{
test: /\.s[ac]ss$/,
use: [
process.env.NODE_ENV == 'production' ? MiniCssExtractPlugin.loader : 'style-loader',
{
loader: 'css-loader',
options: {
// 开启 CSS Modules
modules: true
}
},
'postcss-loader',
'sass-loader'
]
},
{
test: /\.css$/,
use: [
process.env.NODE_ENV = 'production' ? MiniCssExtractPlugin.loader : 'style-loader',
{
loader: 'css-loader',
options: {
// 开启 CSS Modules
modules: true,
}
},
'postcss-loader',
]
},
]
},
plugins: [
new MiniCssExtractPlugin(),
new HTMLWebpackPlugin()
]
};
等价于
// vite.config.js
import { defineConfig } 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()]
})
那么,我们如何使用 Vite 呢?
Vite 上手使用
项目初始化
# 提前安装 pnpm
npm i -g pnpm
# 初始化命令
pnpm create vite
# 安装依赖
pnpm install
# 启动项目
npm run dev
- 输入初始化参数
- 启动后截图
启动完成后,打开浏览器访问对应地址即可
使用 Sass/Scss & CSS Modules
目录结构
src
|—— App.css
|—— App.tsx
|—— components
| |_ Header
| |_ index.module.scss
| |_ index.tsx
|—— favicon.svg
|—— index.css
|—— logo.svg
|—— main.tsx
|_ vite-env.d.ts
# 安装 Sass
pnpm i sass -D
// index.html
import styles from './index.module.scss';
// 使用 CSS Modules 模块化方案,防止 className 命名冲突
export function Header() {
return <p className={styles.header}>This is Header</p>
}
// index.module.scss
.header {
color: red;
}
使用 Scss & CSS Modules
目录结构
src
|—— App.css
|—— App.tsx
|—— components
| |_ Header
| |_ index.module.scss
| |_ index.tsx
|—— favicon.svg
|—— index.css
|—— logo.svg
|—— main.tsx
|_ vite-env.d.ts
引入 Header 组件
import { Header } from './components/Header';
function App() {
return (
<div>
<Header />
</div>
)
}
export default App;
使用静态资源
目录结构
src
|—— App.css
|—— App.tsx
|—— components
| |_ Header
| |_ index.module.scss
| |_ index.tsx
|—— favicon.svg
|—— index.css
|—— logo.svg
|—— main.tsx
|_ vite-env.d.ts
以 svg 图片为例
import { Header } from './components/Header';
import logoUrl from './logo.svg';
function App() {
return (
<div>
<Header />
</div>
)
}
export default App;
除了常见的图片格式,Vite 也内置了对于JSON、Worker、WASM 资源的加载支持
cn.vitejs.dev/guide/featu…
使用 HMR
无需额外配置,自动开启 详细请看Vite官方介绍
生产环境 Tree Shaking
优化原理:
- 基于 ESM 的
import/export语句依赖关系,.与运行时状态无关 - 在构建阶段将未使用到的代码进行删除
// CommonJS 格式不能做到 Tree Shaking
// require 的部分可能依赖运行时计算的结果
require(someVariable)
Tree Shaking 在 Vite 中无需配置,默认开启
Tree Shaking只能用于 ESM;因为nodejs是runtime 计算的结果,所以你就没办法将它们静态分析,如果你强制把它删掉的话,会删掉不应该删掉的东西;这是有风险的,因此Tree Shaking只能由于ESM
Vite 给你最直观的印象如何?
- 响应迅速
- 开箱即用
Vite 整体架构
关键技术:依赖预打包
为什么要进行预打包?
- 避免
node_modules过多的文件请求 - 将
CommonJS格式转换为ESM格式实现原理:
- 服务启动前扫描代码中用到的依赖
- 用
Esbuild对依赖代码进行预打包 - 改写
import语句,指定依赖为预构建产物路径
// 改写前
import React from 'react';
// 改写后
import React from '/node_modules/.vite/react.js'
关键技术:单文件编译
用 Esbuild 编译 TS/JSX
优势:
- 编译速度提升 10-100 x
局限性:
- 不支持类型检查
- 不支持语法降级到 ES5
关键技术:代码压缩
压缩性能对比
Esbuild 作为默认压缩工具,替换传统的 Terser、Uglify.js 等压缩工具
Vite 进阶路线
深入双引擎
参考资料
推荐学习顺序:
- 先了解基本使用,动手尝试各项常用配置
- 然后学习其插件开发。
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: nul1
// 如果可行将提供 source map
}
}
}
}
}
// vite.config.js
import plugin from './myPlugin'
export default defineConfig({
plugins:[plugin()]
})
- 开发
Vite插件 - 配置文件引入插件
参考资料:
先看文档,过一遍插件钩子的功能,然后多学习其它插件的实现,掌握套路
代码分割(拆包)
拆包前
问题:
- 无法进行并发请求
- 缓存复用率低
拆包后
参考资料:
cn.vitejs.dev/config/buil…
rollupjs.org/guide/en/#o…
JS 编译工具(Babel)
Babel 原理
出现的原因:
JavaScript语法标准繁多,浏览器支持程度不一- 开发者需要用到高级语法
参考资料:
语法安全降级
以 Promise 语法为例,IE11 没有支持
- 如何在构建产物中避免这类问题?
- 上层解决方案: @viteis/plugin-legacy
- 底层原理
- 借助
Babel进行语法自动降级 - 提前注入
Polyfill实现,如core-js、regenerator-runtime
- 借助
参考资料:
服务端渲染(SSR)
一种常见的渲染模式,用于提升首屏性能和 SEO 优化
构建阶段
代码执行阶段
参考资料:
深入了解底层标准
重点特性:
CJS规范ESM规范HTTP/1.1HTTP 2.0特性
参考资料:
Vite 社区生态
Github 40k+ star(可参考 webpack 61.3 K, rollup 21.8 k),并且目前还在持续维护
官方提供插件:
- @vitejs/plugin-vue,提供 Vue 3 支持
- @vitejs/plugin-vue-jsx,提供 Vue 3 JSX 支持
- @vitejs/plugin-react,提供 React 支持
- @vitejs/plugin-legacy,提供低版本浏览器降级支持
海量社区插件 qithub.com/vitejs/awes…
众多框架内置
- Nuxt
- SvelteKit
- Astro
- Vitepress