Vite知识体系 | 青训营笔记

120 阅读10分钟

这是我参与「第四届青训营 」笔记创作活动的第1天

  • Vite是是下一代前端开发与构建工具。Vite意在提供开箱即用的配置,同时它的插件)API和JavaScript API 带来了高度的可扩展性,并有完整的类型支持。

-————引用自百度百科

为什么前端需要构建工具呢

首先我们需要了解前端工程的组成部分,也就是核心要素。简单来说它是由一系列的文件资源组成的,包括逻辑代码JS、TS、JSX,样式代码CSS、SCSS、LESS,以及一些静态资源png、jpg等。前端经过诸多迭代,工程也出现越来越多问题,痛点主要归纳为以下四类

  • 模块化——把项目拆分为不同的模块进行开发和维护,分而治之。在其他语言比如GO都有标准的统一规范,而在前端并没有标准的统一规范,目前主要有三个,ESM、CommonJs、UMD
    1. 提供模块加载方案。比如webpack里面就有一个runtime,这就是一个模块加载器
    2. 提供不同模块规范
  • 资源编译——目前TS、Sass等语法基本成为开发的标配,但是这些语法浏览器是无法识别的,所以我们需要把它编译成浏览器能识别的形式。背后就涉及了一系列工具链,这也成为了刚需
    1. 高级语法转译,如Sass、Ts。一般会集成一系列编译工具链,像webpack中的CSS-loader,TS-loader,这些就是语法转译的工具
    2. 资源加载,如图片、字体、worker
  • 产物质量——在工作中,需要考虑到线上代码压缩等问题。不压缩代码,代码体积就会很庞大,这样会影响到性能,从而影响用户体验,对于未使用的模块,我们需要将它从构建产物中剔除掉。还有兼容的问题,对于需要兼容移动端的业务来说,一般需要兼容到Android4.4,ios9,所以是需要兼容到低端的浏览器的,由此产物的语法兼容性也会是一个问题,不然可能会出现白屏的事故等。
    1. 产物压缩、无用代码删除(常提到的Tree Shaking——在vite中自动开启tree shaking,不需要配置,这得益于rollup的底层打包功能,因为tree shaking是基于rollup实现的。对于没用到函数,在生产环境中不会打包,它的优化原理是基于ESM的import/export语句依赖关系实现,与运行时状态无关,然后再构建阶段将未使用到的代码进行删除。相对而言nodejs的CommonJs规范不能做到Tree Shaking,因为require的部分可能依赖运行时的计算的结果,如果强制删掉可能删掉不该删的,这是由风险的)、语法降级达到安全
  • 开发效率——改动代码后想要看到立刻看到最新的效果,需要构建工具来做这一层
    1. 提供热更新的系统支持改动后立即看到效果

什么是Vite,为什么选择Vite

两大组成部分

  1. 开发阶段的No-bundle开发服务,简单来讲就是nodejs中的dev-server,在这个server中,源文件是不需要打包的,这是和传统不同的地方
  2. 生产环境基于Rollup(一个打包器)实现Bundler,将所有的业务代码进行打包

核心特征:

  1. 高性能、dev启动速度和热更新速度非常快
  2. 简单易用

image.png 上图一个是Rollup迁移到vite的项目,一个是webpack迁移到vite的项目

传统工具的问题

image.png

  • bundle带来的性能开销

    • 以上构建工具都有一个bundle的过程,有一个代码的打包,该过程是比较消耗性能
  • JS语言的性能瓶颈

    • JS是一门解释性的语言,也是单线程语言,所以无法像Go等语言做到多线程的优化

解决的两个方面

ESM

  1. 全球浏览器对原生ESM的普遍支持——以script标签,type="module属性",使用import,export导入导出语法
  2. 基于原生语言(GO、Rust)编写的前端编译工具链,如Esbuild、SWC

image.png 基于浏览器功能,vite开发了一套dev server,底层的原理就是刚刚说的ESM,针对上图HTML,浏览器会发送一个main.tsx的文件请求,dev server就会接受这个文件请求,接收这个请求之后就可以自定义的接受一些文件的编译,内容响应,然后浏览器拿到的就是一些能够识别的JS的内容。所以vite就是一个开发时的server,承接浏览器的请求,将浏览器能够识别的语法相应给浏览器

  1. 它去除了bunlde的开销,不需要打包项目的源代码,一个文件就是一个请求。
  2. 需要用到哪些文件再按需的编译。比如上图,HTML页面只引入了Main.tsx,所以我只会请求/src/main.tsx,工程包里面的其他ts文件暂时不会去编译
  3. 利用文件级别的浏览器缓存,模块的细度到达文件级别,文件变更的时候不会导致整个bunlde失效,只会导致当前文件请求的缓存失效,实现更细腻度的文件请求缓存

原生语言的工具

image.png Esbuild有三个垂直领域的功能

  1. 打包器Bundler——对标webpack部分的工具
  2. 编译器Transformer——对标Babel这部分能力
  3. 压缩器Minifier——对标业界Uglyfy的JS压缩等工具

内置了很多Web构建工具,集成度高

image.png

Vite

image.png 左边是开发环境,右边是生产环境,下面是在这两种环境用,有一些共有的插件

下面讲解一些关键技术

依赖预打包——开发环境

为什么要进行依赖预打包(pre-bundle)?

  1. 避免node_modules过多的文件请求。——node_modules是一个非常不可控的东西,比如你要引入lodash这个库里面的debounce方法,那么会导致六七百个文件的引入,这个文件的请求量是相当大的
  2. node_modules里面的一些格式是不规范的,可能是CommonJS格式,这样放在浏览器就跑不起来

针对如上的问题,vite是做了一个预打包,实现原理:

  1. dev server启动之前,扫描代码中用到的依赖
  2. 采用Esbuild对依赖代码进行预打包
  3. 改写import语句,指定依赖为预构建产物路径
// 改写前
import React from "react";
// 改写后
import React from './node_modules/.vite/react.js'

单文件编译

使用Esbuild(前面提到Esbuild是基于Go语言开发的JavaScript Bundler,也被Vite用于开发环境的依赖解析和Transform)编译TS/JSX。可通过以下链接浅了解Esbuild和SWC ESBuild & SWC浅谈: 新一代构建工具 - 掘金 (juejin.cn)

优势:编译速度提升10-100X 局限性:

  1. 不支持类型检查——也就是Vite为什么要在生产环境构建的时候调用一次TSC

参考文章:编译 ts 代码用 tsc 还是 babel? - 掘金 (juejin.cn) 2. 兼容性问题:Esbuild只能将语法降级到ES6,写再高级的浏览器也只能降到ES6,对于一些需要ES5的浏览器比如一些手机低端的浏览器,直接放进去会报错

代码压缩技术

代码压缩阶段一般也是非常耗时的一个阶段——比如webpack经常卡在92%这个点。使用Esbuild作为默认压缩工具,替换传统的Terser、Uglify.js等压缩工具,速度也是能提高二三十倍差不多

最重要的插件机制

image.png

开发阶段 ——> 模拟Rollup插件机制

生产环境 ——> 直接使用Rollup

进阶学习资料

  • 先学基本使用,动手配置,在学习其插件开发

esbuild官方文档

Rollup官方文档 (rollupjs.org)

为什么需要插件机制

  • 抽离核心逻辑——把dev server中的能力抽离出来,把构建相关的能力封装为一个个的插件,达到解构效果,更容易维护
  • 易于拓展、社区生态丰富

image.png

上图概括了Vite插件的一些钩子,因为Vite是一个dev sever,所以它在一些服务的启动包括请求和响应各个阶段,都包含了各个Hook,我们可以在不同的构建阶段插入自定义的逻辑。其中重点掌握的是服务启动阶段的config,请求响应阶段的resolve、load、transfrom

下面是一个开发插件示例

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 mao
                }
            }
        }
    }
}

// vite.config.js
import plugin from './myPlugin'

export default defineConfig({
    plugins:[plugin()]
})

Vite插件开发参考资料 插件 API {#plugin-api} | Vite中文网 (vitejs.cn)

复杂度较低的插件:JSON加载插件

复杂度中等的插件:Esbuild 接入插件

复杂度较高的插件:官方React插件

代码分割(拆包)

为什么要拆包?

  1. 以前前端工程只产出一个Bundle,就没有办法进行并发请求,用到浏览器的优势
  2. 复用率比较低,但凡一个文件改动,所有文件失效。拆包后,A组件文件改动,只需要改动A的bundle

Vite的代码分割很依赖Rollup的打包功能,因为生产环境依赖Rollup。所以需要学习Rollup的代码分割,相当于学了Vite的代码分割

构建选项 | Vite 官方中文文档 (vitejs.dev) rollup.js (rollupjs.org)

image.png

JS编译工具(Babel)

产生原因:

  • JS语法标准多,浏览器支持度不一
  • 开发者需要用到高级语言。在开发者需要用到高级语法的基础上,我们采用一系列编译工具对语法进行降级,然后支持各个浏览器

实现原理首先是Code 解析为AST,再对这个树进行各种各样的操作,再后续阶段通过babel-traverse转译转化成低级语法的AST,最后通过生成器把低级AST转化成语法

image.png

参考资料:babel插件开发手册

语法安全降级

  • 以Promise语法为例,IE11没有内置API,所以没有语法降级的话,会出现一些报错的问题,可能直接导致页面白屏

image.png image.png

如何解决?

  • 上层解决方案:使用官方插件:@vitejs/plugin-legacy
  • 底层原理是借助Babel进行语法自动奖励,以及提前注入Polyfill实现,如core-js

Babel降级的关键包:@babel/preset-env 文档

Vite官方降级文档:vite/packages/plugin-legacy at main · vitejs/vite · GitHub

服务端渲染(SSR)

  • 它是一种常见的渲染模式,用于提升首屏性能和SEO(搜索引擎)优化,对于一些面向用户的C端项目。
  • 和原来的不同是在构建阶段构建出一份SSR的产物,运行在服务端(Node.js),什么时候去运行呢?在代码执行的时候,服务端会拿到SSR的产物进行->数据预取->组件渲染->HTML拼接,这样前端就能拿到一个完整的HTML内容,而不是一个div.root标签,从而更快的渲染到页面上

Vite服务端渲染 | Vite中文网 (vitejs.cn)

深入了解底层标准

目前为止,ESM已经走向大一统的趋势,不光是之前所说的浏览器支持了ESM的原生加载,包括Node也支持了ESM的原生加载