乐趣区

关于react.js:Vite-React-组件开发实践

简介:毫不夸大的说,Vite 给前端带来的相对是一次革命性的变动。或者也能够说是 Vite 背地整合的 esbuild、Browser es modules、HMR、Pre-Bundling 等这些社区中对于 JS 编译倒退的先进工具和思路,在 Vite 这样的整合推动下,给前端开发带来了革命性变动。


作者 | 风水
起源 | 阿里技术公众号

去年发表的《一个好的组件应该是什么样的?》一文介绍了借助 TypeScript AST 语法树解析,对 React 组件 Props 类型定义及正文提取,主动生成组件对应 截图、用法、参数阐明、README、Demo 等。在社区中获得了比拟好的反应,同时利用在团队中也获得了较为不错的后果,当初外部组件零碎中曾经累计应用该计划积淀 1000+ 的 React 组件。

之前咱们是借助了 webpack + TypeScript 做了一套用于开发 React 组件的脚手架套件,当开发者要组件开发时,即可间接应用脚手架初始化对应我的项目构造进行开发。

尽管主门路上的确解决了组件开发中所遇到的组件无图无假相、组件参数文档缺失、组件用法文档缺失、组件 Demo 缺失、组件无奈索引、组件产物不标准等外部组件治理和积淀上的问题,但 Webpack 的计划始终还是会让组件开发多一层编译,当一个组件库积淀超过 300+ 时,引入依赖一直增长,还是会带来组件编译上的负荷导致开发者开发体验降落。

一 Vite 带来的曙光

Vite 给前端带来的相对是一次革命性的变动,这么说毫不夸大。

或者应该说是 Vite 背地整合的 esbuild、Browser es modules、HMR、Pre-Bundling 等这些社区中对于 JS 编译倒退的先进工具和思路,在 Vite 这样的整合推动下,给前端开发带来了革命性变动。

我很早就说过,任何一个框架或者库的呈现最有价值的肯定不是它的代码自身,而是这些代码背地所带来的新思路、新启发。所以我在写文章的时候,也很重视能把我思考最初执行的整个过程讲清楚。

Vite 为什么快,次要是 esbuild 进行 pre-bundles dependencies + 浏览器 native ESM 动静编译,这里我不做过多赘述,具体参考:Vite: The Problems

在这个思路的背景下,回到咱们组件开发的场景再看会发现以下几个问题高度吻合:

  • 组件库开发,实际上不须要编译全副组件。
  • 组件开发,编译预览页面次要给开发者应用,浏览器兼容可控。
  • HMR(热更新)能力在 Vite 加持下更加显得空谷传声,是以往组件开发和调试破费工夫最多的中央。
  • Vite 中所有源码模块动静编译,也就是 TypeScript 类型定义和 JS 正文也能够做到动静编译,大大放大编译范畴。

那么,以往像 StoryBook 和之前咱们用于提取 tsx 组件类型定义的思路将能够做一个比拟大的扭转。

之前为了获取组件入参的类型数据会在 Wwebpack 层面做插件用于动态分析 export 的 tsx 组件,在该组件下动静退出一段 __docgenInfo 的动态属性变量,将从 AST 剖析失去的类型数据和正文信息注入进组件 JS Bundle,从而进一步解决为动静参数设置:

TypeScript 对组件 Props 的定义

剖析注入到 JS Bundle 中的内容

剖析转换后实现的参数交互设置

所以对于组件来说,实际上获取这一份类型定义的元数据对于组件自身来说是冗余的,不管这个组件中的这部分元数据有没有被用到,都会在 Webpack 编译过程中解析提取并注入到组件 Bundle 中,这显然是很低效的。

在 Vite 的思路中,齐全能够在应用到组件元数据时,再获取其元数据信息,比方加载一个 React 组件为:

import ReactComponent from './component1.tsx'

那么加载其元数据即:

import ComponentTypeInfo from './component1.tsx.type.json'; 

// or 
const ComponentTypeInfoPromise = import('./component1.tsx.type.json');

通过 Vite 中 Rollup 的插件能力加载 .type.json 文件类型,从而做到对应组件元数据的解析。同时借助 Rollup 自身对于编译依赖收集和 HMR 的能力,做到组件类型变动的热更新。

二 设计思路

以上是看到 Vite 的模块加载思路,失去的一些灵感和启发,从而做出的一个初步构想。

但如果真的要做这样一个基于 Vite 的 React、Rax 组件开发套件,除了组件入参元数据的获取以外,当然还有其余须要解决的问题,首当其冲的就是对于 .md 的文件解析。

1 组件 Usage

参照 dumi 及 Icework 所提供的组件开发思路,组件 Usage 齐全能够以 Markdown 写文档的模式写到任何一个 .md 文件中,由编译器动静解析其中对于 jsx、tsx、css、scss、less 的代码区块,并且把它当做一段可执行的 script 编译后,运行在页面中。

这样既是在写文档,又能够运行调试组件不同入参下组件体现状况,组件有多少中 Case,能够写在不同的区块中交由用户本人抉择查看,这个设计思路真是让人赞不绝口!

最初,如果能联合上述提到 Vite 的 esbuild 动静加载和 HMR 能力,那么整个组件开发体验将会再一次失去质的飞跃。

所以针对 Markdown 文件须要做一个 Vite 插件来执行对 .md 的文件解析和加载,预期要实现的能力如下:

import {content, modules} from “./component1/README.md”;

// content README.md 的原文内容
// modules 通过解析取得的jsx,tsx,css,scss,less 运行模块
预期构想成果,请点击放大查看:

2 组件 Runtime

一个惯例的组件库目录应该是什么样的?不论是在一个独自的组件仓库,还是在一个已有的业务我的项目中,其实组件的目录构造大同小异,大抵如下:

components
├── component1
│   ├── README.md 
│   ├── index.scss
│   └── index.tsx
├── component2
│   ├── README.md
│   ├── index.scss
│   └── index.tsx

在咱们的构想中你能够在任意一个我的项目中启动组件开发模式,在运行 vite-comp 之后就能够看到一个专门针对组件开发的界面,在下面曾经帮你解析并渲染进去了在 README.md 中编写的组件 Usage,以及在 index.tsx 定义的 interface,只须要拜访不同的文件门路,即可查看对应组件的体现状态。

同时,最初能够帮你能够将这个界面上的全部内容编译打包,截图公布到 NPM 上,他人看到这个组件将会清晰看到其组件入参,用法,截图等,甚至能够关上 Demo 地址,批改组件参数来查看组件不同状态下的体现状态。

如果要实现这样的成果,则须要一套组件运行的 Runtime 进行反对,这样才能够协调 React 组件、README.md、TypeScript 类型定义串联成咱们所须要的组件调试 + 文档一体的组件开发页面。

在这样的 Runtime 中,同样须要借助 Vite 的模块解析能力,将其 URL 为 //(README|).html 的申请,转换为一段可拜访的组件 Runtime Html 返回给浏览器,从而让浏览器运行真正的组件开发页面。

http://localhost:7000/components/component1/README.html
-> 
/components/component1/README.html 
->
/components/component1/README.md
-> 
Runtime Html

3 组件 Props Interface

正如我上述内容中讲到的,如果利用 Vite 增加一个对 tsx 的组件 props interface 类型解析的能力,也能够做成独立插件用于解析 .tsx.type.json 结尾的文件类型,通过 import 这种类型的文件,从而让编译器动静解析其 tsx 文件中所定义的 TypeScript 类型,并作为模块返回给前端生产。

![](https://ucc.alicdn.com/pic/developer-ecology/63f935b2e3774031863b9a04b5d4d0fd.png)

其加载过程就能够当做是一个虚构的模块,能够了解为你能够通过间接 import 一个虚构的文件地址,获取到对应的 React 组件元信息:

// React Component
import Component from './component1.tsx';
// React Component Props Interface
import ComponentTypeInfo from './component1.tsx.type.json';

// or
const ComponentTypeInfoPromise = import('./component1.tsx.type.json');

因为这种解析能力并不是借助于 esbuild 进行,所以在转换性能上无奈和组件主流程编译同步进行。

在申请到该文件类型时,须要思考在 Vite 的 Serve 模式下,新开线程进行这部分内容编译,因为整个过程是异步行为,不会影响组件主流程渲染进度。当申请返回响应后,再用于渲染组件 Props 定义及侧边栏面板局部。

在热更新过程中,同样须要思考到 tsx 文件批改范畴是否波及到 TypeScript 类型的更改,如果发现批改导致类型变动时,再触发 HMR 事件进行模块更新。

三 组件 Build

以上都是在探讨组件在 Vite 的 Serve 态(也就是开发态)下的状况,咱们上文中大量借助 Vite 利用浏览器 es module 的加载能力,从而做的一些开发态的动静加载能力的扩大。

然而 Vite 在组件最终 Build 过程中是没有 Server 服务启动,当然也不会有浏览器动静加载,所以为了让他人也能够看到咱们开发的组件,可能体验咱们开发时调试组件的样子,就须要思考为该组件编译产出一份能够被浏览器运行的 html。

所以在 Vite 插件开发过程中,是须要思考在 Build 状态下的编译门路的,如果是在 Build 状态下,Vite 将应用 Rollup 的编译能力,那么就须要思考手动提供所有组件的 rollup.input(entries)。

在插件编写过程中,肯定须要遵循 Rollup 所提供的插件加载生命周期,能力保障 Build 过程和 Serve 过程的模块加载逻辑和编译逻辑保持一致。

我一开始在实现的过程中,就是没有理解透彻 Vite 和 Rollup 的关系,在模块解析过程中依赖了大量 Vite 的 Server 提供的服务端中间件能力。导致在思考到 Build 态时,才意识到其中的问题,最初简直从新写了之前的加载逻辑。

四 总结

我权且把这个计划(套件)称之为 vite-comp,其大抵的形成就是由 Vite + 3 Vite Pugins 形成,每个插件互相不耦合,互相职责也不雷同,也就是说你能够拿到任意一个 Vite 插件去做别的用处,后续会思考独自开源,别离是:

  • Markdown,用于解析 .md 文件,加载后可获取原文及 jsx、tsx 等可运行区块。
  • TypeScript Interface,用于解析 .tsx 文件中对于 export 组件的 props 类型定义。
  • Vite Comp Runtime,用于运行组件开发态,编译最终组件文档。

联合 Vite,曾经实现了 Vite 模式下的 React、Rax 组件开发,它相比于之前应用 Webpack 做的组件开发,曾经体现出了以下几个大劣势:

  • 无惧大型组件库,即便有 2000 个组件在同一个我的项目中,启动仍旧是 <1000ms。
  • 高效的组件元数据加载流,我的项目所有依赖编译按需进行。
  • 毫秒级热更新响应,借助 esbuild 简直是按下保留的一瞬间,就能够看到改变成果。

预览体验:

启动

Markdown 组件文档毫秒级响应

TypeScript 类型辨认

Vite 当初还是只是刚刚起步,这种全新的编译模式,曾经给我带来了十分多的开发态收益,联合 Vite 的玩法将来肯定还会层出不穷,比方 Midway + lambda + Vite 的前端一体化计划也是看得让人赞不绝口,在这个欣欣向荣的前端大时代,置信不同前端产物都会和 Vite 联合出下一段传奇故事。

原文链接
本文为阿里云原创内容,未经容许不得转载。

退出移动版