react项目结构
- React(宿主环境的公用方法)
- React-reconciler(协调器的实现,宿主环境无关)
- 各种宿主环境的包
- shared(公用辅助方法,宿主环境无关)
当前实现的JSX转换属于 react****包
初始化react包
先创建react package并初始化
更新package.json文件:
{
"name": "react",
"version": "1.0.0",
"description": "react公用方法",
"module": "index.ts",
"keywords": [],
"author": "",
"license": "ISC"
}
JSX转换是什么
jsx在线转换
包括两部分
- 编译时
- 运行时:jsx方法或react.createElement方法的实现(包括dev、prod两个环境)
实现运行时 jsx 转换
编译时由babel编译实现,我们实现运行时,工作量包括:
- 实现jsx方法
- 实现打包流程
- 实现调试打包结果的环境
包括:
- jsxDEV方法(dev环境)
- jsx方法(prod环境)
- React.craeteElement方法
实现:react/src/jsx.ts:
import { REACT_ELEMENT_TYPE } from '@/shared/ReactSymbols'
import {
Type,
Key,
Ref,
Props,
ElementType,
ReactElementType,
} from '@/shared/ReactTypes'
// ReactElement 构造函数实现
const ReactElement = function (
type: Type,
key: Key,
ref: Ref,
props: Props
): ReactElementType {
const element = {
$$typeof: REACT_ELEMENT_TYPE, // 内部字段, 指明当前字段是reactElement
type,
key,
ref,
props,
__mark: 'khs', // 该字段是为了与真实的react项目区分开
}
return element
}
// jsx 函数实现
export const jsx = (type: ElementType, config: any, ...maybeChildren: any) => {
let key: Key = null
let ref: Ref = null
const props: Props = {}
// 遍历config
for (const prop in config) {
const val = config[prop]
// 1. 单独找出 key和ref字段
if (prop === 'key') {
if (val !== undefined) {
key = '' + val
}
continue
}
if (prop === 'ref') {
if (val !== undefined) {
ref = val
}
continue
}
// 2. 剩下的如果是config自身的prop, 则正常取出
if ({}.hasOwnProperty.call(config, prop)) {
props[prop] = val
}
}
const maybeChildrenLength = maybeChildren.length
if (maybeChildrenLength) {
// [child] 或 [child, child, child]
if (maybeChildrenLength === 1) {
props.child = maybeChildren[0]
} else {
props.child = maybeChildren
}
}
return ReactElement(type, key, ref, props)
}
// jsxDEV 函数实现
export const jsxDEV = (type: ElementType, config: any) => {
let key: Key = null
let ref: Ref = null
const props: Props = {}
// 遍历config
for (const prop in config) {
const val = config[prop]
// 1. 单独找出 key和ref字段
if (prop === 'key') {
if (val !== undefined) {
key = '' + val
}
continue
}
if (prop === 'ref') {
if (val !== undefined) {
ref = val
}
continue
}
// 2. 剩下的如果是config自身的prop, 则正常取出
if ({}.hasOwnProperty.call(config, prop)) {
props[prop] = val
}
}
return ReactElement(type, key, ref, props)
}
react/index.tsx:
/**
* 打包出的React包
*/
import { jsxDEV } from './src/jsx'
export default {
version: '0.0.0',
createElement: jsxDEV,
}
同时因为react引入了shared包,所以为react/package.json添加依赖:
{
"name": "react",
"version": "1.0.0",
"description": "react公用方法",
"module": "index.ts",
"dependencies": {
"shared": "workspace: *"
},
"keywords": [],
"author": "",
"license": "ISC"
}
对应上述3方法,打包对应文件
- react/jsx-dev-runtime.js(dev环境)
- react/jsx-runtime.js(prod环境)
- react
1、安装rollup Plugin
- 兼容commonjs:
@rollup/plugin-commonjs
- ts解析为js:
rollup-plugin-typescript2
- 生成package.json文件:
rollup-plugin-generate-package-json
pnpm i -D -w rollup-plugin-typescript2
pnpm i -D -w @rollup/plugin-commonjs
pnpm i -D -w rollup-plugin-generate-package-json
- react包rollup打包配置:
scripts/rollup/react.config.js:
import { getBaseRollupPlugins, getPackageJSON, resolvePkgPath } from './utils'
import generatePackageJson from 'rollup-plugin-generate-package-json'
const { name, module } = getPackageJSON('react')
// react包的路径
const pkgPath = resolvePkgPath(name)
//react产物路径
const pkgDistPath = resolvePkgPath(name, true)
export default [
// react 的包
{
input: `${pkgPath}/${module}`,
output: {
file: `${pkgDistPath}/index.js`,
name: 'index.js',
format: 'umd', // 该格式能够兼容commonjs
},
plugins: [
...getBaseRollupPlugins(),
// 生成package.json文件
generatePackageJson({
inputFolder: pkgPath,
outputFolder: pkgDistPath,
baseContents: ({ name, description, version }) => ({
name,
description,
version,
main: 'index.js',
}),
}),
],
},
// jsx-runtime 和 jsx-dev-runtime 的包
{
input: `${pkgPath}/src/jsx.ts`,
output: [
// jsx-runtime
{
file: `${pkgDistPath}/jsx-runtime.js`,
name: 'jsx-runtime',
format: 'umd',
},
// jsx-dev-runtime
{
file: `${pkgDistPath}/jsx-dev-runtime.js`,
name: 'jsx-dev-runtime.js',
format: 'umd',
},
],
plugins: getBaseRollupPlugins(),
},
]
- 测试打包
安装清除上次打包的文件工具:rimraf
pnpm i -D -w rimraf
添加脚本:
"build:dev": "rimraf dist && rollup --bundleConfigAsCjs --config scripts/rollup/react.config.js"
运行脚本
pnpm build:dev
打包结果:
Pnpm link
npm link是一种把包链接到包文件夹的方式,即:可以在不发布npm模块的情况下,调试该模块,并且修改模块后会实时生效,不需要通过npm install进行安装
- 优点:可以模拟实际项目引用React的清空
- 缺点:略显繁琐,达不到热更新的效果
先去到模块目录,把它 link 到全局:
cd .\dist\node_modules\react\
pnpm link --global
在外部新建一个react项目,然后将我们实现的react link到该项目
npx create-react-app react-demo
cd ./react-demo
pnpm link react --global
修改react-demo/index.js:
import React from 'react'
const jsx = (
<div key={123} ref={'khs'}>
hello
<span>big-react</span>
</div>
)
console.log(React)
console.log(jsx)
控制台打印调试后的结果: