一、浅谈构建工具
1、前端工程的痛点
(1)前端项目的组成部分(核心要素--文件资源)
①逻辑代码:js、ts、jsx
②样式代码:css、scss、less
③静态资源:jpg、png、webp
(2)四个方向的归纳
①模块化:ESM、CommonJS、UMD
把项目拆分为不同的模块,然后进行分别的开发和维护【分而治之】
②资源编译:高级语法的编译
高级语法浏览器是不认识的,所以需要编译成浏览器可以识别的形式
③产物质量:代码体积、代码性能
- 体积:线上的代码一般都比较大,容易影响性能和用户体验,需要压缩,将未使用到的模块在构建产物当中剔除掉,优化产物体积
- 兼容性:对于需要兼容移动端的项目,大部分情况需要兼容到安卓4.4、ios9(也就是需要兼容低端的浏览器,否则容易出现使用的高级语法,浏览器无法识别,出现白屏事故) ④开发效率:热更新
2、前端构建工具的意义
(1)模块化方案
①提供模块加载方案
②兼容不同模块规范
(2)语法转译
①高级语法转译,如Sass、TypeScript
②资源加载,如图片、字体、worker
(3)产物质量
产物压缩、无用代码删除、语法降级
(4)开发效率
热更新
二、Vite概要介绍
1、Vite概览
(1)定位:新一代前端构建工具
(2)两大组成部分
①No-bundle开发服务(NodeJs的devserver),源文件无需打包 (与传统工具最大的不同)
②生产环境基于Rollup的Bundler,将所有代码进行打包
(3)核心特征
①高性能,dev启动速度和热更新速度非常快
②简单易用,开发者体验好
2、业界案例
(1)Rollup -> Vite
- 启动时间:2分15秒->1.7秒
- 更新时间:23秒->1秒以内
(2)Webpack -> Vite
- 启动时间:2分36秒->6秒
- 热更新:13秒->1秒以内
3、当下问题
(1)开发体验问题
缓慢的启动->项目编译等待成本高
缓慢的热更新->修改代码后不能实时更新
(2)瓶颈
bundle打包带来的性能开销
JavaScript语言的性能瓶颈
4、两大行业趋势
(1)全球浏览器对原生ESM的普遍支持(目前占比92%以上)
(2)基于原生语言(Go、Rust)编写前端编译工具链
如Go语言编写的Esbuild、Rust 编写的SWC
5、浏览器原生ESM支持
(1)两大要素
- script标签增加type="module"属性
- ESM模块导入导出语法
(2)代码例子
浏览器在识别到import后,会发起请求,到foo.js下执行内容
html
<!DOCTYPE html>
<html lang="en">
<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>
javascript
export const foo = "foo";
6、基于原生ESM的开发服务优势
(1)无需打包项目源代码
(2)天然的按需加载
(3)可以利用文件级的浏览器缓存
当一个文件变更时,不会导致整个bundle失效,只会导致当前的请求缓存失效,达到更细密度的浏览器缓存
7、基于Esbuild的编译性能优化
(1)Esbuild:基于Golang开发的前端工具,具备如下能力
①打包器Bundler:对应webpack的工作
②编译器Transformer
③压缩器Minifier
(2)性能极高,在Vite 中被深度使用
8、内置的web构建能力
(1)Vite开箱即用的功能等价于webpack、webpack-dev-server、css-loader、style-loader、less-loader、sass-loader、postcss-loader、file-loader、MiniCssExtractPlugin、HTMLWebpackPlugin
(2)可以看出:vite是对常见web开发的需求进行一个封装
三、Vite上手实战
1、项目初始化
(1)安装pnpm
javascript
#提前安装 pnpm
npm i -g pnpm
#初始化命令
pnpm create vite
#安装依赖
pnpm install
#启动项目
pnpm run dev
2、使用Sass/Scss & CSS Modules
(1)安装sass
javascript
pnpm install sass -D
(2)例子
①新建组件:文件夹下=》新建component文件夹=》新建Header文件夹=》新建index.tsx文件
②编写index.tsx文件
javascript
import React from "react";
export function Header(){
return <div>Header</div>
}
③在App.tsx文件引入,使用
javascript
import { Header } from './components/Header'
// 在App()中使用
<Header></Header>
(3)叠加sass、css-module,编写样式
①Header文件夹下=》新建index.module.scss
vite会默认把.module.scss文件当作css-module
css
.header {
color: red;
}
②在index.tsx中引入、使用
javascript
import React from "react";
import styles from "./index.module.scss";
export function Header() {
return <div className={styles.header}>Header</div>;
}
③css-module作用
className的值并不是字符串,而是引入一个json字段,而这个字段变成了_header_gxlcr_1,由header+哈希值
这样的好处就是,当在其它组件用了同样的className,会出现不同组件的样式污染,使用css-module可以做到组件的样式隔离
3、使用静态资源
(1)静态资源在vite可以直接引入使用
这个路径会给vite的devserver发起svg请求,然后devserver会把文件内容返回给浏览器,进行图片的呈现
(2)除了常见的图片格式, Vite也内置了对于JSON、Worker、WASM资源的加载支持 官方文档
4、HMR
可以保存组件的局部状态
当改变index下的内容时,App下的count值会保留
5、生产环境Tree Shaking
(1)把代码中没有的内容删除
(2)优化原理
①基于ESM 的import/export语句依赖关系(依赖关系是可以静态确定的),与运行时状态无关
相对于NodeJs的规范就不能,因为require可能是运行时的结果,就无法进行一些静态分析,当需要强制删除时,可能会删掉一些不应该删掉的内容,是有风险的
因此Tree Shaking只能应用于ESM
②在构建阶段将未使用到的代码进行删除
③Tree Shaking在Vite中无需配置,默认开启
(3)代码例子
javascript
//main.ts
import { add } from './util';
console.log(add(1,2));
javascript
//util.ts
export const add = (a: number, b: number): number => a + b;
export const multi = (a: number, b: number): number => a * b;
在util.ts中编写了两个方法add(),multi(),在main.ts使用时,只使用了add()方法,则在最后打包的产物中不会有multi()
(4)实践操作 ①在src文件夹下=》新建util.ts文件
javascript
export const add = (a: number, b: number): number => a + b;
export const multi = (a: number, b: number): number => a * b;
②在index.tsx文件引入,使用
javascript
import React from "react";
import styles from "./index.module.scss";
import { add } from "../../util";
export function Header() {
return <div className={styles.header}>Header{add(1, 2)}</div>;
}
(5)调试观察
①停止项目,查看package.json中的build,发现会先进行tsc的编译,就是进行相关类型的检查,然后编译vite build
②在vite.config.js中
在生产环境,会默认进行压缩,这边要调试,暂时将压缩的功能关闭
javascript
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
// 关闭压缩功能
build: {
minify: false,
}
})
③进行生产环境的打包
javascript
pnpm run build
④查看打包产物dist文件夹下的文件
通过图片中的文件搜索,可以找到add被打包进去,而multi是没有的
(6)总结
Vite最直观的印象就是
①响应迅速
②开箱即用
四、Vite整体架构
1、开发环境下的依赖预打包
(1)node_modules
①非常不可控,一个依赖文件往往会有很多其它延伸的依赖文件
②代码产物的格式是不规范的,有可能是CommonJS,在浏览器上无法运行
因此vite进行依赖预打包
(2)预打包步骤
①在dev服务启动前,扫描代码中用到的依赖
②对依赖代码采用Esbuild进行预打包
③打包完,将业务中的import语句进行改写,指定依赖为预构建产物路径
javascript
// 改写前
import React from "react";
// 改写后
import React from "/node_modules/.vite/react.js";
2、单文件编译
(1)用Esbuild编译TS/JSX
(2)Esbuild优势:编译速度提升10-100倍
(3)Esbuild局限性
①不支持类型检查
②不支持语法降级到ES5
3、代码压缩
(1)生产环境中非常重要的阶段,同时是非常耗时的阶段
(2)Esbuild 作为默认压缩工具,替换传统的Terser、Uglify.js 等压缩工具
4、插件机制
(1) 开发阶段->模拟Rollup 插件机制 生产环境->直接使用Rollup
(2)注意:并不是所有的rollup插件都能兼容vite
vite官网之插件兼容性https://vite-rollup-plugins.patak.dev/
五、Vite进阶路线
1、深入双引擎
vite底层非常依赖的两个构建引擎
(1)官方文档
Esbuildhttps://esbuild.github.io/
(2)推荐学习顺序
①先了解基本使用,动手尝试各项常用配置
②然后学习其插件开发
2、插件开发
(1)为什么需要插件开发
①抽离核心逻辑,将devserver中server端的能力抽离出来,把构建相关的能力封装为一个个的插件,达到解耦的效果,构建和devserver逻辑是分开的,更容易维护的
②易于拓展,社区能够给vite贡献一些插件
(2)一些vite插件的钩子函数
在不同阶段,插入自定义的逻辑
(3)插件示例
①开发vite插件
②配置文件,引入插件
javascript
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: null //如果可行将提供source map
}
}
}
}
}
javascript
// vite.config.js
import plugin from './myPlugin'
export default defineConfig({
plugins:[plugin()]
})
(4)插件开发参考资料
②复杂度较低的插件:json加载插件
③复杂度中等的插件:Esbuild接入插件
④复杂度较高的插件:官方React插件
先看文档,过一遍插件钩子的功能然后多学习其它插件的实现,掌握套路
3、代码分割(拆包)
(1)问题
①无法进行并发请求:以前的启动过程只产出一个bundle,即一个产物文件
②缓存复用率低:当一个文件改动,整个产物全部失效
(2)拆包
意味着改动某一个文件,不影响整个产物,达到更好的缓存复用的效果,从而提升页面加载速度,间接提升用户体验
(3)配置rollup的代码分割
①构建选项
4、JS编译工具(Babel)
(1)出现原因
①JavaScript语法标准繁多,浏览器支持程度不一
②开发者需要用到高级语法
(2)参考资料
babel官方站点https://babeljs.io/docs/
babel插件手册https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/plugin-handbook.md
(3)Babel实现原理
Parse:将代码解析成AST(抽象语法树),每个单词都是语法树的节点
Transform:AST转换成一些低级语法的AST
Generator:生成器,将低级语法的AST转换成代码
从而达到语法降级的效果
5、语法安全降级
(1)以Promise语法为例,IE11没有支持
IE11没有内置Promise语法,很可能会导致代码报错,浏览器页面白屏
(2)解决方案
①上层解决方案:@vitejs/plugin-legacy
②底层原理
- 借助Babel进行语法自动降级
- 提前注入Polyfill实现,如core-js、regenerator-runtime
(3)参考资料
@babel/preset-env文档https://babeljs.io/docs/babel-preset-env
Vite官方降级插件文档https://github.com/vitejs/vite/tree/main/packages/plugin-legacy
6、服务端渲染(SSR)
一种常见的渲染模式,用于提升首屏性能和SEO优化
(1)构建阶段
(2)代码执行阶段
(3)参考资料
7、深入了解底层标准
(1)重点特性
CS规范、ESM规范、HTTP 2.0特性
(2)参考资料
HACKShttps://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/
ESM & CJShttps://antfu.me/posts/publish-esm-and-cjs
8、社区生态
(1)Github 40k+ star (可参考webpack 61.3 K, rollup 21.8 K),并且目前还在持续维护
(2)官方提供插件
@vitejs/plugin-vue,提供 Vue 3支持
@vitejs/plugin-vue-jsx,提供 Vue 3JSX支持
@vitejs/plugin-react,提供 React支持
@vitejs/plugin-legacy,提供低版本浏览器降级支持
(3)海量社区插件
github.com/vitejs/awes...https://github.com/vitejs/awesome-vite
(4)vite框架内置
Nuxt、SvelteKit、Astro、Vitepress
六、总结
1、通过本节学习,认识vite的基础原理,实现简单操作,同时建立了从基础学习到进阶学习的方向 2、本节印象最深的是生产环境Tree Shaking 在大型项目代码构建下,属于关键的一步,学习了Tree Shaking原理步骤,对项目构建也有很大帮助