使用babel-standalone实现动态预览React组件

1,986 阅读2分钟

背景:公司中台推出了一个模块,在快速搭建表单模块添加了在线代码,在线编辑代码保存后在对应页面查看效果。
得到需求后去研究了下CodePen这样的项目,太难了,看着头疼。
想到了babel-standalone这样支持在线编译的库,实现对react组件、ES6代码转为ES5以支持现代浏览器。
实现需要两个部分:
1.代码编辑器:这个网上比较多,个人比较喜欢vscode。
2.解析代码。
自己实现的解析代码部分(支持IE11):
先贴上自个的开源库及npm组件:GithubNPM组件
入口为createElement方法,最终返回一个对象,对象内包含转换后的代码transformCode以及React组件node。

import { transform as BabelTransform } from 'babel-standalone';

const getCodeFunc = (code: string, scope: { [key: string]: any }) => {
	const keys = Object.keys(scope);
	const values = keys.map((key) => scope[key]);

	const res = new Function(...keys, code);
	return res(...values);
};

const transform = (code: string) => {
	const text: string = `
    () => {
      ${code}
    }
  `;
	const option = {
		presets: ['es2015', 'react', 'stage-1'],
	};
	let transformCode = BabelTransform(text, option).code;
        //过滤转换后的代码
	return transformCode
		.replace(/(\(function \(\) \{)[\r\n]/, '')
		.replace(/\}\)\;$/, '');
};

/**
 * es6代码转换为es5
 */
interface transformReturns {transformCode: string;node:ReactNode;};
type FuncType = (code: string, scope: { [key: string]: any }) => transformReturns;
//code代码
//scope函数作用域内支持的模块,比如传入React
export const createElement: FuncType = (code = '', scope = {}) => {
	const transformCode = transform(code.trim().replace(/;$/, ''));
	return { transformCode, node: getCodeFunc(transformCode, scope) };
};

这样在页面中使用createElement方法获取组件进行渲染即可,同时捕获代码中的报错信息。

import React from 'react';
import { useMemo } from 'react';

const CodeNode = ({ code, scope={} }) => {
    const TransformNode = useMemo(() => {
        let currNode:any = null;
        try {
                let { transformCode, node } = createElement(code, scope);
                currNode = node;
        } catch (error) {
                currNode = error.toString();
        }
        return currNode;
    }, [code]);
    
    if(!TransformNode) return null;
    //报错时输出信息
    if(typeof TransformNode === 'string') return TransformNode;
    //渲染组件
    return <TransformNode />
}

export default () => {
    //代码字符串
    const code = `() => {
        const {text, setText} = React.useState('');
        React.useEffect(() => {
            setText('hello world');
        }, []);
        return <div>{text}</div>
    }`;
    //作用域
    const scope = { React };
    
    return <CodeNode code={code} scope={scope} />;
}

存在的缺点:

  1. 只支持预先传入函数内可用的模块。
  2. 只支持行内样式。
    等闲暇下来再进行改造,琢磨琢磨有啥好的方法解决模块的问题。