理解 monorepo 结构
看懂 React.createElement
和 JSX 转换
明白 React 是如何通过 Rollup 构建的
/packages/react/src/ReactElement.js
/scripts/rollup/*
构建脚本
/packages/shared/*
公共方法
React.createElement
和 JSX 转换React.createElement
的语法糖比如这段 JSX:
const element = Hello, world!
;
React 编译时会转译为:
const element = React.createElement('h1', null, 'Hello, world!');
这就是 JSX 本质上做的事情。
React.createElement(type, props, ...children)
接受三个参数:
type
: 要创建的元素类型,字符串如 'div'
或组件名如 MyComponent
props
: 属性对象,传给元素的属性
...children
: 子节点,可以是文本、React 元素、数组等
比如:
const element = Hello;
等价于:
const element = React.createElement('div', { id: 'app' }, 'Hello');
三、多个子元素的情况
const element = (
- One
- Two
);
转成:
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 = Hello;
转译后是:
import { jsx as _jsx } from "react/jsx-runtime";
const element = _jsx("div", { children: "Hello" });
总结重点记住:
JSX | 等价 React.createElement |
---|---|
|
React.createElement('div', null, 'Hello') |
|
React.createElement('div', { id: 'app' }) |
|
React.createElement(Comp, { title: 'Hi' }) |
|
嵌套多个 React.createElement |
Monorepo(单一代码仓库)就是:
➔ 一个 Git 仓库里管理多个包(package)或模块(module)。
跟它对立的是 多仓库(Polyrepo),每个模块单独一个 Git 仓库。
在 Monorepo 里,通常每个模块都有自己独立的 package.json
,可以独立发布、独立构建,但又能共享代码、统一管理。
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
每个子包可以单独构建、测试、发布!
React 官方没有用 Lerna,但是很多 Monorepo 项目用这些工具来管理:
Lerna:老牌 Monorepo 工具,管理版本、发布。
Turborepo:Vercel 出品的现代 Monorepo 构建工具。
Nx:大型项目专用,提供模块依赖分析、增量构建等。
这些工具可以帮你在 Monorepo 中:
更快地构建
更高效地运行测试
更方便地管理子包版本
React 是库,不是应用,所以需要构建成 多种格式(比如 CommonJS、ESM、UMD 等),适配各种使用场景。
Rollup 是专门为打包「库」设计的,输出体积小,tree-shaking 友好,非常适合 React 这种基础库。
相比 Webpack,Rollup 构建出来的包更加干净、轻量。
所以,React 团队选了 Rollup 来打包源代码。
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版等等)
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
之类。
React 打包出来的格式有好几种,分别对应不同的使用场景:
产物 | 说明 | 用途 |
---|---|---|
CommonJS (cjs ) |
Node 环境 / 老的 bundler | require('react') |
UMD (umd ) |
浏览器直接用 引入 |
|
ESM (esm ) |
现代前端项目 | import React from 'react' |
Facebook 内部专用版本 | 内部平台使用 |
React 用 Rollup 根据 bundles.js 配置,动态生成多份构建产物(CJS/UMD/ESM),区分开发/生产环境,并且通过定制的 Rollup 插件实现代码替换、压缩等操作。