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, // 内部字段, 指明当前字段是reactElementtype,key,ref,props,__mark: 'khs', // 该字段是为了与真实的react项目区分开}return element
}// jsx 函数实现
export const jsx = (type: ElementType, config: any, ...maybeChildren: any) => {let key: Key = nulllet ref: Ref = nullconst props: Props = {}// 遍历configfor (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.lengthif (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 = nulllet ref: Ref = nullconst props: Props = {}// 遍历configfor (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-typescript2pnpm i -D -w @rollup/plugin-commonjspnpm 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-demopnpm 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)
控制台打印调试后的结果: