🎯 目标:
-
理解 monorepo 结构
-
看懂
React.createElement
和 JSX 转换 -
明白 React 是如何通过 Rollup 构建的
📂 推荐文件:
-
/packages/react/src/ReactElement.js
-
/scripts/rollup/*
构建脚本 -
/packages/shared/*
公共方法
如何看懂 React.createElement
和 JSX 转换
一、核心概念:JSX 是 React.createElement
的语法糖
比如这段 JSX:
const element = <h1>Hello, world!</h1>;
React 编译时会转译为:
const element = React.createElement('h1', null, 'Hello, world!');
这就是 JSX 本质上做的事情。
二、React.createElement 的结构
React.createElement(type, props, ...children)
接受三个参数:
-
type
: 要创建的元素类型,字符串如'div'
或组件名如MyComponent
-
props
: 属性对象,传给元素的属性 -
...children
: 子节点,可以是文本、React 元素、数组等
比如:
const element = <div id="app">Hello</div>;
等价于:
const element = React.createElement('div', { id: 'app' }, 'Hello');
三、多个子元素的情况
const element = (
<ul>
<li>One</li>
<li>Two</li>
</ul>
);
转成:
const element = React.createElement(
'ul',
null,
React.createElement('li', null, 'One'),
React.createElement('li', null, 'Two')
);
children
参数支持多个,也就是使用剩余参数 ...children
的形式。
四、JSX 转换方式演示
你可以使用 babel 在线工具 查看 JSX 是如何被转换成 React.createElement
的:
-
打开 https://babeljs.io/repl
-
选择
@babel/preset-react
-
输入 JSX
-
右侧即可看到转换后的 JS 代码
五、使用 React 17+ 或 React 18 的 JSX 转换(新版)
从 React 17 开始,React 引入了新的 JSX 转换机制,使用 jsx
函数而不是 React.createElement
。但概念是一样的,只是底层实现方式优化了。
import { jsx } from 'react/jsx-runtime';
比如:
const element = <div>Hello</div>;
转译后是:
import { jsx as _jsx } from "react/jsx-runtime";
const element = _jsx("div", { children: "Hello" });
总结重点记住:
JSX | 等价 React.createElement |
---|---|
<div>Hello</div> | React.createElement('div', null, 'Hello') |
<div id="app" /> | React.createElement('div', { id: 'app' }) |
<Comp title="Hi" /> | React.createElement(Comp, { title: 'Hi' }) |
<ul><li>1</li><li>2</li></ul> | 嵌套多个 React.createElement |
理解react中使用的 monorepo 结构
什么是 Monorepo?
-
Monorepo(单一代码仓库)就是:
➔ 一个 Git 仓库里管理多个包(package)或模块(module)。
跟它对立的是 多仓库(Polyrepo),每个模块单独一个 Git 仓库。
在 Monorepo 里,通常每个模块都有自己独立的 package.json
,可以独立发布、独立构建,但又能共享代码、统一管理。
React 为什么要用 Monorepo?
React 官方(facebook/react 仓库)用了 Monorepo,主要原因有:
-
模块非常多,例如:
-
react
-
react-dom
-
scheduler
-
react-reconciler
-
react-test-renderer
-
react-refresh
-
react-devtools
-
...
-
-
模块之间强依赖,需要同步开发
比如react
和react-dom
有非常紧密的关系,如果分多个仓库很难同步管理。 -
代码共享和版本管理统一
-
统一构建脚本(build scripts)
-
统一发布流程
-
不容易版本错乱
-
-
方便大规模协作
跨团队开发不同子模块,但依然能统一在一个仓库内 CI/CD。
React 的 Monorepo 结构长什么样?
react/
├── packages/ # 所有子模块
│ ├── react/ # React 核心包
│ ├── react-dom/ # React DOM 渲染相关
│ ├── scheduler/ # 调度器
│ ├── shared/ # 公共代码
│ ├── react-reconciler/ # Diff 算法相关
│ └── ...
├── scripts/ # 构建和开发用的脚本
├── fixtures/ # 测试用例和 demo
├── build/ # 构建产物
├── .babelrc # Babel 配置
├── package.json # 顶层的包管理
└── yarn.lock
核心目录是 packages/
,里面每个文件夹就是一个独立的 npm 包。
比如:
-
packages/react/package.json
-
packages/react-dom/package.json
-
packages/scheduler/package.json
每个子包可以单独构建、测试、发布!
Monorepo 工具
React 官方没有用 Lerna,但是很多 Monorepo 项目用这些工具来管理:
这些工具可以帮你在 Monorepo 中:
更快地构建
更高效地运行测试
更方便地管理子包版本
明白 React 是如何通过 Rollup 构建的
1. 为什么 React 要用 Rollup?
-
React 是库,不是应用,所以需要构建成 多种格式(比如 CommonJS、ESM、UMD 等),适配各种使用场景。
-
Rollup 是专门为打包「库」设计的,输出体积小,tree-shaking 友好,非常适合 React 这种基础库。
-
相比 Webpack,Rollup 构建出来的包更加干净、轻量。
所以,React 团队选了 Rollup 来打包源代码。
2. React 的源码结构是怎样的?参考前一个模块
React 源码并不是单一的一个文件,而是一个 monorepo(多包仓库)
-
每个功能模块(比如
react
、react-dom
)是一个独立的 package。 -
scripts/rollup/
是专门写 Rollup 配置和打包逻辑的地方。 -
最重要的文件是:
-
bundles.js
—— 定义了要打哪些包(比如react.production.min.js
、react.development.js
)。 -
rollup.config.js
—— 定义了 Rollup 配置的生成逻辑。
-
3. React 的 Rollup 构建流程
阶段 | 详细内容 |
---|---|
Step 1 | 执行构建脚本,比如 yarn build react |
Step 2 | 读取 bundles.js ,根据里面的配置生成多个 bundle(产物列表) |
Step 3 | 每个 bundle 根据 rollup.config.js 动态生成对应的 Rollup 配置(entry、output、plugins 等) |
Step 4 | Rollup 根据配置文件打包,输出各种格式的产物(比如 CJS、ESM、UMD) |
Step 5 | 特别处理 development 和 production 两种环境(比如开发版保留警告信息,生产版去除警告并压缩) |
4. 深入细节:bundles.js
是什么?
简单来说,bundles.js
里面定义了:
-
打哪些入口文件(entry)
-
输出成什么名字(比如
react.development.js
) -
输出格式(如 CJS、UMD、ESM)
-
是否要区分开发版和生产版
举个小例子👇:
const ReactBundle = {
label: 'react',
bundleTypes: [
BUNDLE_TYPES.DEV,
BUNDLE_TYPES.PROD,
BUNDLE_TYPES.UMD_DEV,
BUNDLE_TYPES.UMD_PROD,
BUNDLE_TYPES.NODE_ESM,
],
entry: 'react',
};
module.exports = [ReactBundle];
就是说:
-
entry: 'react'
表示打包packages/react/src/index.js
-
打各种不同环境的版本(开发版、生产版、Node ESM版等等)
5. 深入细节:rollup.config.js
是什么?
这个文件是根据 bundles.js
生成最终 Rollup 配置的地方。
核心逻辑:
-
遍历
bundles.js
定义的 bundle -
为每个 bundle 设置 entry、output、plugins
-
插入自定义的 Rollup 插件,比如:
-
替换开发/生产环境变量(比如
process.env.NODE_ENV
) -
处理 Flow 类型注释
-
处理警告信息
-
最后压缩 production 版本(Terser)
-
React 自己还维护了一套 专用插件,比如 replace-process-env
、strip-dev-code
之类。
6. React 会输出哪些格式?
React 打包出来的格式有好几种,分别对应不同的使用场景:
产物 | 说明 | 用途 |
---|---|---|
CommonJS (cjs ) | Node 环境 / 老的 bundler | require('react') |
UMD (umd ) | 浏览器直接用 <script> 引入 | <script src="react.development.js"> |
ESM (esm ) | 现代前端项目 | import React from 'react' |
Facebook 内部专用版本 | 内部平台使用 |
7. 总结一句话
React 用 Rollup 根据 bundles.js 配置,动态生成多份构建产物(CJS/UMD/ESM),区分开发/生产环境,并且通过定制的 Rollup 插件实现代码替换、压缩等操作。