React项目结构:
- react(宿主环境无关的公用方法)
- react-reconciler (协调器的实现,宿主环境无关)
- 各种宿主环境的包
- shared (公用辅助方法,宿主环境无关)
此次实现的jsx转换属于react包。
jsx转换是什么
jsx转换包括两部分:
编译时
运行时
jsx
方法或者React.createElement
方法的实现(包括dev、prod两个环境)
编译时
由babel
实现,我们来实现运行时
,其中包括
- 实现
jsx
方法 - 实现打包流程
- 实现调试打包结果的环境
实现jsx方法
在packages目录
下创建react
、shared
文件夹,在react
文件夹下创建src
目录,里面创建jsx.ts
代码如下:
ts
import { REACT_ELEMENT_TYPE } from 'shared/ReactSymbols';
import {
ElementType,
Key,
Props,
ReactElementType,
Ref,
Type
} from 'shared/ReactTypes';
const ReactElement = function (
type: Type,
key: Key,
ref: Ref,
props: Props
): ReactElementType {
const element = {
$$typeof: REACT_ELEMENT_TYPE,
key,
type,
ref,
props,
_mark: 'wangf'
};
return element;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const jsx = (type: ElementType, config: any, ...maybeChildren: any) => {
let key: Key = null;
const props: Props = {};
let ref: Ref = null;
for (const prop in config) {
const val = config[prop];
if (prop === 'key') {
if (val !== undefined) {
key = '' + val;
}
continue;
}
if (prop === 'ref') {
if (val !== undefined) {
ref = val;
}
continue;
}
if ({}.hasOwnProperty.call(config, prop)) {
props[prop] = val;
}
const maybeChildrenLength = maybeChildren.length;
if (maybeChildrenLength) {
if (maybeChildrenLength === 1) {
props.chidren = maybeChildren[0];
} else {
props.chidren = maybeChildren;
}
}
}
return ReactElement(type, key, ref, props);
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const jsxDEV = (type: ElementType, config: any) => {
let key: Key = null;
const props: Props = {};
let ref: Ref = null;
for (const prop in config) {
const val = config[prop];
if (prop === 'key') {
if (val !== undefined) {
key = '' + val;
}
continue;
}
if (prop === 'ref') {
if (val !== undefined) {
ref = val;
}
continue;
}
if ({}.hasOwnProperty.call(config, prop)) {
props[prop] = val;
}
}
return ReactElement(type, key, ref, props);
};
在react
目录下创建一个index.ts
,写入如下代码
ts
import { jsxDEV } from './src/jsx';
export default {
version: '0.0.1',
createElement: jsxDEV
};
使用pnpm init
,在react文件夹下初始化一个package.json
文件, 修改配置,将main
改成module
json
"module": "index.ts",
"dependencies": {
"shared":"workspace:*"
},
实现对jsx方法类型的定义
在shared
目录下使用pnpm init
初始化一个package.json
目录,在根目录下,安装需要的安装包,rimraf
是用来编译时删除上一次打包的dist
目录的,rollup-plugin-generate-package-json
用于生成打包产物的json
文件,rollup-plugin-typescript2
用于解析typescript
文件,@rollup/plugin-commonjs
用于解析common.js
code
pnpm i -D -w rimraf rollup-plugin-generate-package-json rollup-plugin-typescript2 @rollup/plugin-commonjs
在根目录下的package.json
文件下新增一条命令"build:dev": "rimraf dist && rollup --bundleConfigAsCjs --config scripts/rollup/react.config.js"
,在scripts
目录rollup
创建两个文件react.config.js
、utils.js
js
// 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'
},
plugins: [
...getBaseRollupPlugins(),
//生成package.json
generatePackageJson({
inputFolder: pkgPath,
outputFolder: pkgDistPath,
baseContents: ({ name, version, description }) => ({
name,
version,
description,
main: 'index.js'
})
})
]
},
//jsx-runtime
{
input: `${pkgPath}/src/jsx.ts`,
output: {
file: `${pkgDistPath}/jsx-runtime.js`,
name: 'jsx-runtime.js',
format: 'umd'
},
plugins: getBaseRollupPlugins()
},
{
input: `${pkgPath}/src/jsx.ts`,
output: {
file: `${pkgDistPath}/jsx-dev-runtime.js`,
name: 'jsx-dev-runtime.js',
format: 'umd'
},
plugins: getBaseRollupPlugins()
}
];
js
import path from 'path';
import fs from 'fs';
import ts from 'rollup-plugin-typescript2';
import cjs from '@rollup/plugin-commonjs';
//包的路径
const pkgPath = path.resolve(__dirname, '../../packages');
const distPath = path.resolve(__dirname, '../../dist/node_modules');
export function resolvePkgPath(pkgName, isDist) {
if (isDist) {
return `${distPath}/${pkgName}`;
}
return `${pkgPath}/${pkgName}`;
}
export function getPackageJSON(pkgName) {
//包路径
const pathName = `${resolvePkgPath(pkgName)}/package.json`;
const str = fs.readFileSync(pathName, { encoding: 'utf-8' });
return JSON.parse(str);
}
//基础的rollupPlugins
export function getBaseRollupPlugins({ typescript = {} } = {}) {
return [cjs(), ts(typescript)];
}
在项目根目录生成打包后的产物: