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)];
}

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

相关推荐
newxtc27 分钟前
【爱给网-注册安全分析报告-无验证方式导致安全隐患】
前端·chrome·windows·安全·媒体
dream_ready1 小时前
linux安装nginx+前端部署vue项目(实际测试react项目也可以)
前端·javascript·vue.js·nginx·react·html5
编写美好前程1 小时前
ruoyi-vue若依前端是如何防止接口重复请求
前端·javascript·vue.js
flytam1 小时前
ES5 在 Web 上的现状
前端·javascript
喵喵酱仔__1 小时前
阻止冒泡事件
前端·javascript·vue.js
GISer_Jing1 小时前
前端面试CSS常见题目
前端·css·面试
八了个戒2 小时前
【TypeScript入坑】什么是TypeScript?
开发语言·前端·javascript·面试·typescript
不悔哥2 小时前
vue 案例使用
前端·javascript·vue.js
anyup_前端梦工厂3 小时前
Vuex 入门与实战
前端·javascript·vue.js
你挚爱的强哥3 小时前
【sgCreateCallAPIFunctionParam】自定义小工具:敏捷开发→调用接口方法参数生成工具
前端·javascript·vue.js