后端辅助工具链二十年科孔:https://mp.weixin.qq.com/s/FBxVpcdVobgJ9rGxRC2zfg
Webpack、Rollup 、Esbuild、Vite ?
webpack: 如前所述 JavaScript 合作开发的后端装箱构筑架构,透过倚赖搜集,组件导出,聚合 chunk,最后输入聚合的装箱乙醛,是两个BundleBased的架构,优点是雄猫,优点是实用性繁杂。Rollup: Rollup 是专门针对特别针对C#展开装箱,它的优点是轻巧而著眼,那时许多我们津津乐道的库都都采用它展开装箱,比如说:Vue、React 和 three.js 等。
Esbuild: 两个如前所述 Go 撰写的高效能构筑工具,和其它构筑辅助工具较之,速率慢到 10-100x,其内建了一些 Loader 能导出校对常用的 JS(X)、TS(X)等文档,与此同时全力支持透过应用程序的方式处置其它类别的文档。
Vite: Vite 如前所述_ESMBased_devServer 在合作开发自然环境实现了加速开启、按需校对、即刻组件热预览等潜能,与此同时特别针对同这份标识符,在生产自然环境透过 Rollup 展开装箱,聚合圣戈当斯区乙醛。
Vite 概要
大背景驱动力
目前比较成熟的后端合作开发构筑辅助工具,如 webpack 等,基本是透过“装箱”的方式来展开源标识符构筑,即透过对源标识符展开倚赖搜集、构筑处置,最后聚合可在应用程序运转的 JS 文档,不过随著工程项目快速增长,他们存有下列难题
装箱构建天数也会随著快速增长,工程项目邻近地区开启较慢预览较慢,即便采用 HMR 合作开发,也需要几秒钟的天数标识符更改就可以充分反映到网页上,严重影响合作开发新体验得力于那时后端生态系的加速发展,Vite 如前所述上面两个新优点去化解前述存有的难题
应用程序开始全力支持 原生植物 ES 组件愈来愈多 JavaScript 辅助工具采用校对型词汇如 Go 等展开撰写,大力推进了构筑速率核心理念机能
邻近地区合作开发自然环境,利用应用程序全力支持原生植物 ESM 文档的优点,不对源标识符展开装箱操作,应用程序直接动态引入资源,并在 devServer 对请求的资源展开处置,最后返回应用程序可运转的内容倚赖预构筑,首次开启的时候,透过 Esbuild 对工程项目的倚赖展开预构筑,并缓存有邻近地区,后续应用程序请求的时候可以直接返回
更高效的HMR组件,利用应用程序的缓存优点,优化资源的请求,使得无论应用大小如何,HMR 始终能保持加速预览
在生产乙醛构筑时,如前所述 Rollup 展开装箱,并提供了一套 构筑优化 的 构筑命令,开箱即用。
Vite 核心理念组件原理
本次分享主要介绍最核心理念的两个机能的实现原理
倚赖预构筑应用程序组件加载流程源标识符初识
源标识符版本:v2.8.2
以后有简单了解过 Webpack 的源码,看的一头雾水,这一层层的 callback 都是些啥?不过 vite 架构的源标识符看起来就很简洁明了,非常易懂
./src
├── client # 客户端运转时WEB SOCKET以及HMR相关的标识符
│ ├── client.ts
│ ├── env.ts
│ ├── overlay.ts
│ └── tsconfig.json
└── node # 邻近地区服务器相关标识符
├── __tests__
├── build.ts # 生产自然环境rollup build标识符
├── certificate.ts
├── cli.ts # cli,入口
├── config.ts
├── constants.ts
├── http.ts
├── importGlob.ts
├── index.ts # 导出出口
├── logger.ts
├── optimizer # 倚赖预构筑
├── packages.ts
├── plugin.ts
├── plugins # 应用程序
├── preview.ts # build构筑后,在预览模式下开启Vite Server,以模拟生产部署
├── server # server文档夹,dev自然环境主要标识符
├── ssr
├── tsconfig.json
└── utils.ts
7 directories, 18 files
我们主要关注server目录下的标识符,架构透过在邻近地区开启两个 http+connect 的服务器,然后在开启以后做一些优化操作主入口在src/server/index.ts的createServer函数中,这个函数里做了下列几件事情
流程初始化1)调用resolveConfig函数,导出合并各种实用性
2)初始化两个http+connect服务器
3)创建应用程序容器 ,createPluginContainer方法,把应用程序的各个钩子函数串联起来,后续在请求处置的过程中直接执行挂载好的钩子函数
4)聚合两个server对象,包含实用性信息、服务器信息、一些辅助函数等
5)实用性一系列内建中间件,各个中间件做的事情,可以参考文章https://www.modb.pro/db/966326)返回 server 对象
调用 server 的 listen 方法
1)运转应用程序container的buildStart钩子,进而运转所有应用程序的buildStart钩子
2)展开倚赖预构筑,运转runOptimize函数。
3)开启服务,监听端口
请求处置流程
1)主要处置流程在tansformMiddleware中间件处置,这部分后面的内容会详细介绍
倚赖预构筑
展开倚赖预构筑有两个目的:
CommonJS 和 UMD 兼容性: 合作开发阶段中,Vite 的合作开发服务器将所有标识符视为原生植物 ES 组件。因此,Vite 必须先将作为 CommonJS 或 UMD 发布的倚赖项转换为 ESM。性能:Vite 将有许多内部组件的 ESM 倚赖关系转换为单个组件,以提高后续网页加载性能。例如将 lodash 中的小组件装箱成两个大的文档参数实用性
首先看一下,vite 实用性中关于 optimizeDeps 的入参
export interface DepOptimizationOptions {
/**
* 入口文档,默认从html文档展开导出搜集倚赖,如果实用性了的话,就从实用性文档开始展开导出
*/
entries?: string | string[]
/**
* 需要展开预构筑的文档
*/
include?: string[]
/**
* 不需要展开预构筑的倚赖
*/
exclude?: string[]
/**
* 预构筑是透过esbuild展开的,所以可以自定义实用性esbuild参数
*/
esbuildOptions?: Omit<
EsbuildBuildOptions,
| bundle
| entryPoints
| external
| write
| watch
| outdir
| outfile
| outbase
| outExtension
| metafile
>
}
预构筑结果
预构筑的结果默认保存有node_modules/.vite中,具体预构筑的倚赖列表在_metadata.json 文档中,其中_metadata.json 的内容为两个 json 结构
{
// 实用性的hash值
hash : afcda65e ,
/**
* 主要用
* 在倚赖变化时,应用程序能预览缓存
*/
browserHash : c369dd06 ,
optimized : { // 预构筑的优化列表
react : {
// 构筑后的文档地址
file : /Users/zhachunliu/Desktop/own/demo/vite-demo/vite-react-project/node_modules/.vite/react.js ,
// 原始文档地址
src : /Users/zhachunliu/Desktop/own/demo/vite-demo/vite-react-project/node_modules/react/index.js ,
// 记录那些在倚赖预构筑时,采用了commonjs语法的倚赖
// 如果采用了commonjs语法,那么 needsInterop 为 true
needsInterop : true
},
react-dom : {
file : /Users/zhachunliu/Desktop/own/demo/vite-demo/vite-react-project/node_modules/.vite/react-dom.js ,
src : /Users/zhachunliu/Desktop/own/demo/vite-demo/vite-react-project/node_modules/react-dom/index.js ,
needsInterop : true
},
lodash : {
file : /Users/zhachunliu/Desktop/own/demo/vite-demo/vite-react-project/node_modules/.vite/lodash.js ,
src : /Users/zhachunliu/Desktop/own/demo/vite-demo/vite-react-project/node_modules/lodash/lodash.js ,
needsInterop : true
},
react/jsx-dev-runtime : {
file : /Users/zhachunliu/Desktop/own/demo/vite-demo/vite-react-project/node_modules/.vite/react_jsx-dev-runtime.js ,
src : /Users/zhachunliu/Desktop/own/demo/vite-demo/vite-react-project/node_modules/react/jsx-dev-runtime.js ,
needsInterop : true
}
}
}
预构筑过程
入口文档:src/node/optimizer/index.ts,入口函数:optimizeDeps,构筑过程如下
调用getDepHash()函数去计算当前倚赖相关的的 hash 值,影响倚赖预构筑 hash 值的内容有包管理器的 lockfile,例如 package-lock.json,yarn.lock,或者 pnpm-lock.yamlvite.config.js 中的部分相关实用性,如 plugins、optimizeDeps 的 include 和 exclude 等读取邻近地区_metadata.json 中的 hash 值,判断和计算出来的是否一致,一致且未设置强制构筑的话,则直接结束预构筑过程,否则需要进入预构筑过程
透过({ deps, missing } = await “scanImports“(config));展开倚赖扫描,得到需要处置的倚赖,deps 是两个对象,是倚赖的包名和文档系统中的路径的映射,如下图所示
其中,scanImports方法会扫描根目录下的所有 .html 文档或者用户实用性对 optimizeDeps.entries 文档,然后找到文档中所有的 script 标签,这样就找到了入口 js 文档,之后调用 esbuild,透过实用性的应用程序,就可以一层层的找到对应的倚赖项了采用es-module-lexer的parse处置所有的 deps,获得其中exportsData内容 ,并得到倚赖 id 到exportsData的映射,用于之后esbuild构筑时展开倚赖图分析并装箱到两个文档里面,parse 导出后的结构如下图所示调用esbuild展开倚赖的预构筑,并将构筑之后的文档写入缓存目录node_modules/.vite,得力于 esbuild 比传统构筑辅助工具快 10-100 倍的速率,所以倚赖预构筑也是非常快的将 metadata 信息写进邻近地区缓存目录下,后续可以直接采用缓存的倚赖倚赖访问过程
在进行了倚赖预构筑之后,如何访问这些已经构筑的倚赖呢
1)在加载资源文档的时候,会透过vite:import-analysis应用程序展开倚赖导出,碰到已经展开预构筑的倚赖,直接替换,将import React from react替换成import __vite__cjsImport2_react from /node_modules/.vite/react.js?v=0f16c3f0这样的方式
2)在浏览器去请求资源的时候,透过resolvePlugin应用程序去导出,获得真正的邻近地区文档,匹配到对应的邻近地区缓存资源
组件加载
对于应用程序请求,特别针对两个文档的访问,vite 会如何展开处置呢?
主要由下列两个中间件来统一处置请求的内容,并在中间件处置的流程中调用 vite 应用程序容器的相关钩子函数
transformMiddleware:核心理念中间件处置标识符indexHtmlMiddleware:html 相关请求处置中间件vite 应用程序体系
在这里,我们先了解一下 vite 的应用程序体系,Vite 应用程序扩展了设计出色的 Rollup 接口,带有一些 Vite 独有的实用性项。
因此,你只需要撰写两个 Vite 应用程序,就可以与此同时为合作开发自然环境和生产自然环境工作。vite 的应用程序其实就是定义两个对象,该对象包含了一系列的 hook 函数实用性
export default function myPlugin() {
const virtualModuleId = @my-virtual-module
const resolvedVirtualModuleId = \0 + virtualModuleId
return {
name: my-plugin, // 必须的,将会在 warning 和 error 中显示
resolveId(id) {
if (id === virtualModuleId) {
return resolvedVirtualModuleId
}
},
load(id) {
if (id === resolvedVirtualModuleId) {
return `export const msg = from virtual module `
}
}
}
}
Rollup 应用程序兼容性相当数量的 Rollup 应用程序将直接作为 Vite 应用程序工作,但并不是所有的,因为有些应用程序钩子在非构筑式的合作开发服务器上下文中没有意义。
一般来说,只要 Rollup 应用程序符合下列标准,它就应该像 Vite 应用程序一样工作:
没有采用moduleParsed钩子。它在装箱钩子和输入钩子之间没有很强的耦合。和rollup保持一致的通用钩子下列钩子在服务器开启时被调用:
optionsbuildStart下列钩子会在每个传入组件请求时被调用:
resolveIdload
transform
下列钩子在服务器关闭时被调用:
buildEndcloseBundleVite 独有钩子Vite 应用程序也可以提供钩子来服务于特定的 Vite 目标。这些钩子会被 Rollup 忽略。
configconfigResolved
transformIndexHtml
handleHotUpdate
具体应用程序执行过程1)在 dev 自然环境模拟了一套和 rollup 保持一致的应用程序运转自然环境,确保在合作开发自然环境和生产环节的核心理念环节执行同样的流程
2)vite 透过createPluginContainer创建了两个应用程序容器,将每个应用程序中对应的 hook 搜集起来
3)最后在各个生命周期阶段,执行对应的已经搜集好的钩子
组件请求加载过程
GET /当访问网页的时候,实际是有两个 GET / => /index.html 的重定向进入 indexHtmlMiddleware 这个过程,主要做了一件事情,注入 dev 自然环境需要的一些倚赖,@vite/client 主要用来和服务器展开 ws 通信并处置一些 hmr 相关的工作,@react/refresh这段标识符,是 vite-plugin-react 应用程序注入的标识符,用来处置 dev 自然环境的一些潜能
GET /@vite/client前面讲到,@vite/client 里面的标识符主要用于与服务器展开 ws 通信来展开 hmr 热预览、以及重载网页等操作。
这个请求会直接进入 transformMiddleware 中间件中,进入中间件的处置过程:中间件会调用transformRequest(url, server, options = {})函数
@vite/client 是如何映射到对应的内容呢,在调用pluginContainer.resolveId的过程中会遇到 aliasPlugin 应用程序的钩子,执行名称替换,最后替换成vite/dist/client/client.mjs继续将改写过的路径传给下两个应用程序,最后进入resolvePlugin应用程序的tryNodeResolve函数,最后导出获得该文档的 id 为/Users/zhachunliu/.nvm/versions/node/v14.17.0/lib/node_modules/vite/dist/client/client.mjs
,将转换后的标识符透过send方法发送给应用程序
GET /src/main.tsx特别针对普通的 tsx 文档的请求,流程基本上和上面介绍的GET /@vite/client一致,不同点在于采用的应用程序钩子内容不一样,因为需要对 tsx 文档展开处置成 js
透过 resolveId 钩子函数,将/src/main.tsx 映射到邻近地区文档系统调用 load 钩子函数,加载邻近地区文档到内存中
透过 vite:react-babel 应用程序,将 jsx 语法展开转换,转换成 js 标识符
透过 vite:esbuild 应用程序,展开标识符格式化
透过 vite:import-analysis 应用程序,将标识符中所有的 import 内容,转换成对应的邻近地区文档,方便后续直接请求
返回结果
其它的所有请求,都是经过类似的应用程序处置流程,最后返回给应用程序一段可执行的 JS 标识符,就不一一介绍了。
vite 调试辅助工具
vite-plugin-inspect(应用程序调试辅助工具,强推)
在学习、调试或创作应用程序时,建议在你的工程项目中引入vite-plugin-inspect。它可以帮助你检查 Vite 应用程序的中间状态。安装后,你可以访问localhost:3000/__inspect/来检查你工程项目的组件和栈信息。请查阅vite-plugin-inspect 文档中的安装说明。
Vite debug 模式
透过vite –force –debug命令,可以明确的了解到,开启过程和请求过程,经历了什么应用程序,具体的执行流程等,方便调试
参考资料
后端辅助工具链二十年科孔:https://mp.weixin.qq.com/s/FBxVpcdVobgJ9rGxRC2zfg如何调试 vite 源标识符:https://maximomussini.com/posts/debugging-javascript-libraries
源标识符理解:https://jishuin.proginn.com/p/763bfbd5f00e