react18源码-实现jsx转换及项目打包配置

React项目结构:

  • react(宿主环境无关的公用方法)
  • react-reconciler (协调器的实现,宿主环境无关)
  • 各种宿主环境的包
  • shared (公用辅助方法,宿主环境无关)

此次实现的jsx转换属于react包。

jsx转换是什么

jsx转换包括两部分:

  • 编译时
  • 运行时

jsx方法或者React.createElement方法的实现(包括dev、prod两个环境)

编译时babel实现,我们来实现运行时,其中包括

  • 实现jsx方法
  • 实现打包流程
  • 实现调试打包结果的环境

实现jsx方法

packages目录下创建reactshared文件夹,在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.jsutils.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)];
}

在项目根目录生成打包后的产物:

相关推荐
前端大卫28 分钟前
Vue3 + Element-Plus 自定义虚拟表格滚动实现方案【附源码】
前端
却尘43 分钟前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare44 分钟前
浅浅看一下设计模式
前端
Lee川1 小时前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
Ticnix1 小时前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人1 小时前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl1 小时前
OpenClaw 深度技术解析
前端
崔庆才丨静觅1 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人2 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼2 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端