mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-01-12 17:08:14 +00:00
feat: init rax-render
This commit is contained in:
parent
631bb1e1cf
commit
7167767f29
@ -6,7 +6,7 @@
|
||||
|
||||
#### 创建新包:
|
||||
|
||||
- `./create.sh <package-name>`
|
||||
- `./scripts/create.sh <package-name>`
|
||||
|
||||
#### 跑起来:
|
||||
|
||||
|
||||
49
packages/rax-render/README.md
Normal file
49
packages/rax-render/README.md
Normal file
@ -0,0 +1,49 @@
|
||||
# Rax Renderer
|
||||
|
||||
Rax 渲染模块。
|
||||
|
||||
## 安装
|
||||
|
||||
```
|
||||
$ npm install @ali/lowcode-engine-rax-renderer --save
|
||||
```
|
||||
|
||||
## 使用
|
||||
|
||||
```js
|
||||
import { createElement, render } from 'rax';
|
||||
import DriverUniversal from 'driver-universal';
|
||||
import RaxRenderer from '@ali/lowcode-engine-rax-renderer';
|
||||
|
||||
const components = {
|
||||
View,
|
||||
Text
|
||||
};
|
||||
|
||||
const schema = {
|
||||
componentName: 'Page',
|
||||
fileName: 'home',
|
||||
children: [
|
||||
{
|
||||
componentName: 'View',
|
||||
children: [
|
||||
{
|
||||
componentName: 'Text',
|
||||
props: {
|
||||
type: 'primary'
|
||||
},
|
||||
children: ['Welcome to Your Rax App']
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
render(
|
||||
<RaxRenderer
|
||||
schema={schema}
|
||||
components={components}
|
||||
/>,
|
||||
document.getElementById('root'), { driver: DriverUniversal }
|
||||
);
|
||||
```
|
||||
11
packages/rax-render/build.json
Normal file
11
packages/rax-render/build.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"plugins": [
|
||||
[
|
||||
"build-plugin-rax-component",
|
||||
{
|
||||
"type": "rax",
|
||||
"targets": ["web"]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
52
packages/rax-render/package.json
Normal file
52
packages/rax-render/package.json
Normal file
@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "@ali/lowcode-engine-rax-renderer",
|
||||
"version": "0.1.0",
|
||||
"description": "Rax renderer for Ali lowCode engine",
|
||||
"main": "lib/index.js",
|
||||
"module": "lib/index.js",
|
||||
"miniappConfig": {
|
||||
"main": "lib/miniapp/index",
|
||||
"main:wechat": "lib/wechat-miniprogram/index"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"es",
|
||||
"lib",
|
||||
"src",
|
||||
"types"
|
||||
],
|
||||
"keywords": [
|
||||
"low-code",
|
||||
"lowcode",
|
||||
"Rax"
|
||||
],
|
||||
"engines": {
|
||||
"npm": ">=3.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rax": "^1.1.0",
|
||||
"prop-types": "^15.7.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "build-scripts start",
|
||||
"build": "build-scripts build",
|
||||
"prepublish": "npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ali/iceluna-sdk": "^1.0.7-beta.12",
|
||||
"classnames": "^2.2.6",
|
||||
"debug": "^4.1.1",
|
||||
"lodash.isempty": "^4.4.0",
|
||||
"rax-find-dom-node": "^1.0.1",
|
||||
"rax-text": "^1.1.6",
|
||||
"rax-view": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@alib/build-scripts": "^0.1.0",
|
||||
"build-plugin-rax-component": "^0.1.4"
|
||||
},
|
||||
"publishConfig": {
|
||||
"registry": "https://registry.npm.alibaba-inc.com"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
19
packages/rax-render/src/comp/visualDom/index.css
Normal file
19
packages/rax-render/src/comp/visualDom/index.css
Normal file
@ -0,0 +1,19 @@
|
||||
.visual-dom .panel-container {
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #e9e9e9;
|
||||
}
|
||||
|
||||
.visual-dom .panel-container .title {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
background-color: #ebecf0;
|
||||
line-height: 28px;
|
||||
padding: 0 12px;
|
||||
border-bottom: 1px solid #e9e9e9;
|
||||
}
|
||||
|
||||
.visual-dom .panel-container .content {
|
||||
min-height: 20px;
|
||||
padding: 5px;
|
||||
}
|
||||
23
packages/rax-render/src/comp/visualDom/index.jsx
Normal file
23
packages/rax-render/src/comp/visualDom/index.jsx
Normal file
@ -0,0 +1,23 @@
|
||||
import { Component } from 'rax';
|
||||
import View from 'rax-view';
|
||||
import Text from 'rax-text';
|
||||
import './index.css';
|
||||
|
||||
export default class VisualDom extends Component {
|
||||
static displayName = 'VisualDom';
|
||||
static defaultProps = {
|
||||
children: null
|
||||
};
|
||||
render() {
|
||||
const { children, title, label, text, __componentName } = this.props;
|
||||
|
||||
return (
|
||||
<View className="visual-dom">
|
||||
<View className="panel-container">
|
||||
<Text className="title">{title || label || text || __componentName}</Text>
|
||||
<View className="content">{children}</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
4
packages/rax-render/src/context/appContext.js
Normal file
4
packages/rax-render/src/context/appContext.js
Normal file
@ -0,0 +1,4 @@
|
||||
import { createContext } from 'rax';
|
||||
|
||||
const context = createContext({});
|
||||
export default context;
|
||||
512
packages/rax-render/src/engine/base.jsx
Normal file
512
packages/rax-render/src/engine/base.jsx
Normal file
@ -0,0 +1,512 @@
|
||||
import { Component, createElement } from 'rax';
|
||||
import PropTypes from 'prop-types';
|
||||
import Debug from 'debug';
|
||||
import View from 'rax-view';
|
||||
import DataHelper from '@ali/iceluna-sdk/lib/utils/dataHelper';
|
||||
import {
|
||||
forEach,
|
||||
getValue,
|
||||
parseData,
|
||||
parseExpression,
|
||||
isEmpty,
|
||||
isSchema,
|
||||
isFileSchema,
|
||||
isJSExpression,
|
||||
isJSSlot,
|
||||
isJSFunction,
|
||||
transformArrayToMap,
|
||||
checkPropTypes,
|
||||
generateI18n,
|
||||
acceptsRef,
|
||||
} from '@ali/iceluna-sdk/lib/utils';
|
||||
import VisualDom from '../comp/visualDom';
|
||||
import AppContext from '../context/appContext';
|
||||
import CompWrapper from '../hoc/compWrapper';
|
||||
|
||||
const debug = Debug('engine:base');
|
||||
const DESIGN_MODE = {
|
||||
EXTEND: 'extend',
|
||||
BORDER: 'border',
|
||||
PREVIEW: 'preview',
|
||||
};
|
||||
const OVERLAY_LIST = ['Dialog', 'Overlay'];
|
||||
let scopeIdx = 0;
|
||||
|
||||
export default class BaseEngine extends Component {
|
||||
static dislayName = 'base-engine';
|
||||
static propTypes = {
|
||||
locale: PropTypes.string,
|
||||
messages: PropTypes.object,
|
||||
__appHelper: PropTypes.object,
|
||||
__components: PropTypes.object,
|
||||
__componentsMap: PropTypes.object,
|
||||
__ctx: PropTypes.object,
|
||||
__schema: PropTypes.object
|
||||
};
|
||||
static defaultProps = {
|
||||
__schema: {}
|
||||
};
|
||||
static contextType = AppContext;
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.appHelper = props.__appHelper;
|
||||
this.__compScopes = {};
|
||||
const { locale, messages } = props;
|
||||
this.i18n = generateI18n(locale, messages);
|
||||
this.__bindCustomMethods(props);
|
||||
}
|
||||
|
||||
async getSnapshotBeforeUpdate() {
|
||||
this.__setLifeCycleMethods('getSnapshotBeforeUpdate', arguments);
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
this.reloadDataSource();
|
||||
this.__setLifeCycleMethods('componentDidMount', arguments);
|
||||
}
|
||||
|
||||
async componentDidUpdate() {
|
||||
this.__setLifeCycleMethods('componentDidUpdate', arguments);
|
||||
}
|
||||
|
||||
async componentWillUnmount() {
|
||||
this.__setLifeCycleMethods('componentWillUnmount', arguments);
|
||||
}
|
||||
|
||||
async componentDidCatch(e) {
|
||||
this.__setLifeCycleMethods('componentDidCatch', arguments);
|
||||
console.warn(e);
|
||||
}
|
||||
|
||||
reloadDataSource = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
debug('reload data source');
|
||||
if (!this.__dataHelper) {
|
||||
this.__showPlaceholder = false;
|
||||
return resolve();
|
||||
}
|
||||
this.__dataHelper
|
||||
.getInitData()
|
||||
.then(res => {
|
||||
this.__showPlaceholder = false;
|
||||
if (isEmpty(res)) {
|
||||
this.forceUpdate();
|
||||
return resolve();
|
||||
}
|
||||
this.setState(res, resolve);
|
||||
})
|
||||
.catch(err => {
|
||||
if (this.__showPlaceholder) {
|
||||
this.__showPlaceholder = false;
|
||||
this.forceUpdate();
|
||||
}
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
__setLifeCycleMethods = (method, args) => {
|
||||
const lifeCycleMethods = getValue(this.props.__schema, 'lifeCycles', {});
|
||||
if (lifeCycleMethods[method]) {
|
||||
try {
|
||||
return lifeCycleMethods[method].apply(this, args);
|
||||
} catch (e) {
|
||||
console.error(`[${this.props.__schema.componentName}]生命周期${method}出错`, e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
__bindCustomMethods = (props = this.props) => {
|
||||
const { __schema } = props;
|
||||
const customMethodsList = Object.keys(__schema.methods || {}) || [];
|
||||
this.__customMethodsList &&
|
||||
this.__customMethodsList.forEach(item => {
|
||||
if (!customMethodsList.includes(item)) {
|
||||
delete this[item];
|
||||
}
|
||||
});
|
||||
this.__customMethodsList = customMethodsList;
|
||||
forEach(__schema.methods, (val, key) => {
|
||||
this[key] = val.bind(this);
|
||||
});
|
||||
};
|
||||
|
||||
__generateCtx = ctx => {
|
||||
const { pageContext, compContext } = this.context;
|
||||
const obj = {
|
||||
page: pageContext,
|
||||
component: compContext,
|
||||
...ctx
|
||||
};
|
||||
forEach(obj, (val, key) => {
|
||||
this[key] = val;
|
||||
});
|
||||
};
|
||||
|
||||
__parseData = (data, ctx) => {
|
||||
const { __ctx } = this.props;
|
||||
return parseData(data, ctx || __ctx || this);
|
||||
};
|
||||
|
||||
__initDataSource = (props = this.props) => {
|
||||
const schema = props.__schema || {};
|
||||
const appHelper = props.__appHelper;
|
||||
const dataSource = (schema && schema.dataSource) || {};
|
||||
this.__dataHelper = new DataHelper(this, dataSource, appHelper, config => this.__parseData(config));
|
||||
this.dataSourceMap = this.__dataHelper.dataSourceMap;
|
||||
// 设置容器组件占位,若设置占位则在初始异步请求完成之前用loading占位且不渲染容器组件内部内容
|
||||
this.__showPlaceholder =
|
||||
this.__parseData(schema.props && schema.props.autoLoading) &&
|
||||
(dataSource.list || []).some(item => !!this.__parseData(item.isInit));
|
||||
};
|
||||
|
||||
__render = () => {
|
||||
const schema = this.props.__schema;
|
||||
this.__setLifeCycleMethods('render');
|
||||
|
||||
const engine = this.context.engine;
|
||||
if (engine) {
|
||||
engine.props.onCompGetCtx(schema, this);
|
||||
// 画布场景才需要每次渲染bind自定义方法
|
||||
if (engine.props.designMode) {
|
||||
this.__bindCustomMethods();
|
||||
this.dataSourceMap = this.__dataHelper && this.__dataHelper.updateConfig(schema.dataSource);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
__getRef = ref => {
|
||||
this.__ref = ref;
|
||||
};
|
||||
|
||||
__createDom = () => {
|
||||
const { __schema, __ctx, __components = {} } = this.props;
|
||||
const self = {};
|
||||
self.__proto__ = __ctx || this;
|
||||
return this.__createVirtualDom(__schema.children, self, {
|
||||
schema: __schema,
|
||||
Comp: __components[__schema.componentName]
|
||||
});
|
||||
};
|
||||
|
||||
// 将模型结构转换成react Element
|
||||
// schema 模型结构
|
||||
// self 为每个渲染组件构造的上下文,self是自上而下继承的
|
||||
// parentInfo 父组件的信息,包含schema和Comp
|
||||
// idx 若为循环渲染的循环Index
|
||||
__createVirtualDom = (schema, self, parentInfo, idx) => {
|
||||
if (!schema) return null;
|
||||
// rax text prop 兼容处理
|
||||
if (schema.componentName === 'Text') {
|
||||
if (typeof schema.props.text === 'string') {
|
||||
schema = Object.assign({}, schema);
|
||||
schema.children = [schema.props.text];
|
||||
}
|
||||
}
|
||||
|
||||
const { __appHelper: appHelper, __components: components = {}, __componentsMap: componentsMap = {} } =
|
||||
this.props || {};
|
||||
const { engine } = this.context || {};
|
||||
if (isJSExpression(schema)) {
|
||||
return parseExpression(schema, self);
|
||||
}
|
||||
if (typeof schema === 'string') return schema;
|
||||
if (typeof schema === 'number' || typeof schema === 'boolean') {
|
||||
return schema.toString();
|
||||
}
|
||||
if (Array.isArray(schema)) {
|
||||
if (schema.length === 1) return this.__createVirtualDom(schema[0], self, parentInfo);
|
||||
return schema.map((item, idx) =>
|
||||
this.__createVirtualDom(item, self, parentInfo, item && item.__ctx && item.__ctx.lunaKey ? '' : idx)
|
||||
);
|
||||
}
|
||||
|
||||
//解析占位组件
|
||||
if (schema.componentName === 'Flagment' && schema.children) {
|
||||
let tarChildren = isJSExpression(schema.children) ? parseExpression(schema.children, self) : schema.children;
|
||||
return this.__createVirtualDom(tarChildren, self, parentInfo);
|
||||
}
|
||||
|
||||
if (schema.$$typeof) {
|
||||
return schema;
|
||||
}
|
||||
if (!isSchema(schema)) return null;
|
||||
let Comp = components[schema.componentName] || View;
|
||||
|
||||
if (schema.loop !== undefined) {
|
||||
return this.__createLoopVirtualDom(
|
||||
{
|
||||
...schema,
|
||||
loop: parseData(schema.loop, self)
|
||||
},
|
||||
self,
|
||||
parentInfo,
|
||||
idx
|
||||
);
|
||||
}
|
||||
const condition = schema.condition === undefined ? true : parseData(schema.condition, self);
|
||||
if (!condition) return null;
|
||||
|
||||
let scopeKey = '';
|
||||
// 判断组件是否需要生成scope,且只生成一次,挂在this.__compScopes上
|
||||
if (Comp.generateScope) {
|
||||
const key = parseExpression(schema.props.key, self);
|
||||
if (key) {
|
||||
// 如果组件自己设置key则使用组件自己的key
|
||||
scopeKey = key;
|
||||
} else if (!schema.__ctx) {
|
||||
// 在生产环境schema没有__ctx上下文,需要手动生成一个lunaKey
|
||||
schema.__ctx = {
|
||||
lunaKey: `luna${++scopeIdx}`
|
||||
};
|
||||
scopeKey = schema.__ctx.lunaKey;
|
||||
} else {
|
||||
// 需要判断循环的情况
|
||||
scopeKey = schema.__ctx.lunaKey + (idx !== undefined ? `_${idx}` : '');
|
||||
}
|
||||
if (!this.__compScopes[scopeKey]) {
|
||||
this.__compScopes[scopeKey] = Comp.generateScope(this, schema);
|
||||
}
|
||||
}
|
||||
// 如果组件有设置scope,需要为组件生成一个新的scope上下文
|
||||
if (scopeKey && this.__compScopes[scopeKey]) {
|
||||
const compSelf = { ...this.__compScopes[scopeKey] };
|
||||
compSelf.__proto__ = self;
|
||||
self = compSelf;
|
||||
}
|
||||
|
||||
// 容器类组件的上下文通过props传递,避免context传递带来的嵌套问题
|
||||
const otherProps = isFileSchema(schema)
|
||||
? {
|
||||
__schema: schema,
|
||||
__appHelper: appHelper,
|
||||
__components: components,
|
||||
__componentsMap: componentsMap
|
||||
}
|
||||
: {};
|
||||
if (engine && engine.props.designMode) {
|
||||
otherProps.__designMode = engine.props.designMode;
|
||||
}
|
||||
const componentInfo = componentsMap[schema.componentName] || {};
|
||||
const props = this.__parseProps(schema.props, self, '', {
|
||||
schema,
|
||||
Comp,
|
||||
componentInfo: {
|
||||
...componentInfo,
|
||||
props: transformArrayToMap(componentInfo.props, 'name')
|
||||
}
|
||||
});
|
||||
// 对于可以获取到ref的组件做特殊处理
|
||||
if (!acceptsRef(Comp)) {
|
||||
Comp = CompWrapper(Comp);
|
||||
}
|
||||
otherProps.ref = ref => {
|
||||
const refProps = props.ref;
|
||||
if (refProps && typeof refProps === 'string') {
|
||||
this[refProps] = ref;
|
||||
}
|
||||
engine && engine.props.onCompGetRef(schema, ref);
|
||||
};
|
||||
// scope需要传入到组件上
|
||||
if (scopeKey && this.__compScopes[scopeKey]) {
|
||||
props.__scope = this.__compScopes[scopeKey];
|
||||
}
|
||||
if (schema.__ctx && schema.__ctx.lunaKey) {
|
||||
if (!isFileSchema(schema)) {
|
||||
engine && engine.props.onCompGetCtx(schema, self);
|
||||
}
|
||||
props.key = props.key || `${schema.__ctx.lunaKey}_${schema.__ctx.idx || 0}_${idx !== undefined ? idx : ''}`;
|
||||
} else if (typeof idx === 'number' && !props.key) {
|
||||
props.key = idx;
|
||||
}
|
||||
const renderComp = props => (
|
||||
<Comp {...props}>
|
||||
{(!isFileSchema(schema) &&
|
||||
!!schema.children &&
|
||||
this.__createVirtualDom(
|
||||
isJSExpression(schema.children) ? parseExpression(schema.children, self) : schema.children,
|
||||
self,
|
||||
{
|
||||
schema,
|
||||
Comp
|
||||
}
|
||||
)) ||
|
||||
null}
|
||||
</Comp>
|
||||
);
|
||||
//设计模式下的特殊处理
|
||||
if (engine && [DESIGN_MODE.EXTEND, DESIGN_MODE.BORDER].includes(engine.props.designMode)) {
|
||||
//对于overlay,dialog等组件为了使其在设计模式下显示,外层需要增加一个div容器
|
||||
if (OVERLAY_LIST.includes(schema.componentName)) {
|
||||
const { ref, ...overlayProps } = otherProps;
|
||||
return (
|
||||
<Div ref={ref} __designMode={engine.props.designMode}>
|
||||
{renderComp({ ...props, ...overlayProps })}
|
||||
</Div>
|
||||
);
|
||||
}
|
||||
// 虚拟dom显示
|
||||
if (componentInfo && componentInfo.parentRule) {
|
||||
const parentList = componentInfo.parentRule.split(',');
|
||||
const { schema: parentSchema, Comp: parentComp } = parentInfo;
|
||||
if (!parentList.includes(parentSchema.componentName) || parentComp !== components[parentSchema.componentName]) {
|
||||
props.__componentName = schema.componentName;
|
||||
Comp = VisualDom;
|
||||
} else {
|
||||
// 若虚拟dom在正常的渲染上下文中,就不显示设计模式了
|
||||
props.__disableDesignMode = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return renderComp({ ...props, ...otherProps });
|
||||
};
|
||||
|
||||
__createLoopVirtualDom = (schema, self, parentInfo, idx) => {
|
||||
if (isFileSchema(schema)) {
|
||||
console.warn('file type not support Loop');
|
||||
return null;
|
||||
}
|
||||
if (!Array.isArray(schema.loop)) return null;
|
||||
const itemArg = (schema.loopArgs && schema.loopArgs[0]) || 'item';
|
||||
const indexArg = (schema.loopArgs && schema.loopArgs[1]) || 'index';
|
||||
return schema.loop.map((item, i) => {
|
||||
const loopSelf = {
|
||||
[itemArg]: item,
|
||||
[indexArg]: i
|
||||
};
|
||||
loopSelf.__proto__ = self;
|
||||
return this.__createVirtualDom(
|
||||
{
|
||||
...schema,
|
||||
loop: undefined
|
||||
},
|
||||
loopSelf,
|
||||
parentInfo,
|
||||
idx ? `${idx}_${i}` : i
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
__createContextDom = (childCtx, currCtx) => {
|
||||
return (
|
||||
<AppContext.Consumer>
|
||||
{context => {
|
||||
this.context = context;
|
||||
this.__generateCtx(currCtx);
|
||||
this.__render();
|
||||
return (
|
||||
<AppContext.Provider
|
||||
value={{
|
||||
...this.context,
|
||||
...childCtx
|
||||
}}
|
||||
>
|
||||
{this.__createDom()}
|
||||
</AppContext.Provider>
|
||||
);
|
||||
}}
|
||||
</AppContext.Consumer>
|
||||
);
|
||||
};
|
||||
|
||||
__parseProps = (props, self, path, info) => {
|
||||
const { schema, Comp, componentInfo = {} } = info;
|
||||
const propInfo = getValue(componentInfo.props, path);
|
||||
const propType = propInfo && propInfo.extra && propInfo.extra.propType;
|
||||
const ignoreParse = schema.__ignoreParse || [];
|
||||
const checkProps = value => {
|
||||
if (!propType) return value;
|
||||
return checkPropTypes(value, path, propType, componentInfo.name) ? value : undefined;
|
||||
};
|
||||
|
||||
const parseReactNode = (data, params) => {
|
||||
if (isEmpty(params)) {
|
||||
return checkProps(this.__createVirtualDom(data, self, { schema, Comp }));
|
||||
} else {
|
||||
return checkProps(function() {
|
||||
const args = {};
|
||||
if (Array.isArray(params) && params.length) {
|
||||
params.map((item, idx) => {
|
||||
if (typeof item === 'string') {
|
||||
args[item] = arguments[idx];
|
||||
} else if (item && typeof item === 'object') {
|
||||
args[item.name] = arguments[idx];
|
||||
}
|
||||
});
|
||||
}
|
||||
args.__proto__ = self;
|
||||
return self.__createVirtualDom(data, args, { schema, Comp });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 判断是否需要解析变量
|
||||
if (
|
||||
ignoreParse.some(item => {
|
||||
if (item instanceof RegExp) {
|
||||
return item.test(path);
|
||||
}
|
||||
return item === path;
|
||||
})
|
||||
) {
|
||||
return checkProps(props);
|
||||
}
|
||||
if (isJSExpression(props)) {
|
||||
props = parseExpression(props, self);
|
||||
// 只有当变量解析出来为模型结构的时候才会继续解析
|
||||
if (!isSchema(props) && !isJSSlot(props)) return checkProps(props);
|
||||
}
|
||||
|
||||
if (isJSFunction(props)) {
|
||||
props = props.value;
|
||||
}
|
||||
if (isJSSlot(props)) {
|
||||
const { params, value } = props;
|
||||
if (!isSchema(value) || isEmpty(value)) return undefined;
|
||||
return parseReactNode(value, params);
|
||||
}
|
||||
// 兼容通过componentInfo判断的情况
|
||||
if (isSchema(props)) {
|
||||
return parseReactNode(props);
|
||||
} else if (Array.isArray(props)) {
|
||||
return checkProps(props.map((item, idx) => this.__parseProps(item, self, path ? `${path}.${idx}` : idx, info)));
|
||||
} else if (typeof props === 'function') {
|
||||
return checkProps(props.bind(self));
|
||||
} else if (props && typeof props === 'object') {
|
||||
if (props.$$typeof) return checkProps(props);
|
||||
const res = {};
|
||||
forEach(props, (val, key) => {
|
||||
if (key.startsWith('__')) {
|
||||
res[key] = val;
|
||||
return;
|
||||
}
|
||||
res[key] = this.__parseProps(val, self, path ? `${path}.${key}` : key, info);
|
||||
});
|
||||
return checkProps(res);
|
||||
} else if (typeof props === 'string') {
|
||||
return checkProps(props.trim());
|
||||
}
|
||||
return checkProps(props);
|
||||
};
|
||||
|
||||
get utils() {
|
||||
return this.appHelper && this.appHelper.utils;
|
||||
}
|
||||
get constants() {
|
||||
return this.appHelper && this.appHelper.constants;
|
||||
}
|
||||
get history() {
|
||||
return this.appHelper && this.appHelper.history;
|
||||
}
|
||||
get location() {
|
||||
return this.appHelper && this.appHelper.location;
|
||||
}
|
||||
get match() {
|
||||
return this.appHelper && this.appHelper.match;
|
||||
}
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
83
packages/rax-render/src/engine/blockEngine.jsx
Normal file
83
packages/rax-render/src/engine/blockEngine.jsx
Normal file
@ -0,0 +1,83 @@
|
||||
import { createElement } from 'rax';
|
||||
import PropTypes from 'prop-types';
|
||||
import Debug from 'debug';
|
||||
import classnames from 'classnames';
|
||||
import { isSchema, getFileCssName } from '@ali/iceluna-sdk/lib/utils';
|
||||
import BaseEngine from './base';
|
||||
|
||||
const debug = Debug('engine:block');
|
||||
|
||||
export default class BlockEngine extends BaseEngine {
|
||||
static dislayName = 'block-engine';
|
||||
static propTypes = {
|
||||
__schema: PropTypes.object
|
||||
};
|
||||
static defaultProps = {
|
||||
__schema: {}
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
debug(`block.getDerivedStateFromProps`);
|
||||
const func = props.__schema.lifeCycles && props.__schema.lifeCycles.getDerivedStateFromProps;
|
||||
if (func) {
|
||||
return func(props, state);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.__generateCtx();
|
||||
const schema = props.__schema || {};
|
||||
this.state = this.__parseData(schema.state || {});
|
||||
this.__initDataSource(props);
|
||||
this.__setLifeCycleMethods('constructor', arguments);
|
||||
debug(`block.constructor - ${schema.fileName}`);
|
||||
}
|
||||
|
||||
async getSnapshotBeforeUpdate() {
|
||||
super.getSnapshotBeforeUpdate(...arguments);
|
||||
debug(`block.getSnapshotBeforeUpdate - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
async componentDidMount() {
|
||||
super.componentDidMount(...arguments);
|
||||
debug(`block.componentDidMount - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
async componentDidUpdate() {
|
||||
super.componentDidUpdate(...arguments);
|
||||
debug(`block.componentDidUpdate - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
async componentWillUnmount() {
|
||||
super.componentWillUnmount(...arguments);
|
||||
debug(`block.componentWillUnmount - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
async componentDidCatch() {
|
||||
await super.componentDidCatch(...arguments);
|
||||
debug(`block.componentDidCatch - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { __schema } = this.props;
|
||||
|
||||
if (!isSchema(__schema, true) || __schema.componentName !== 'Block') {
|
||||
return '区块schema结构异常!';
|
||||
}
|
||||
|
||||
debug(`block.render - ${__schema.fileName}`);
|
||||
|
||||
const { id, className, style } = this.__parseData(__schema.props);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={this.__getRef}
|
||||
className={classnames('luna-block', getFileCssName(__schema.fileName), className, this.props.className)}
|
||||
id={id}
|
||||
style={style}
|
||||
>
|
||||
{this.__createContextDom({
|
||||
blockContext: this
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
103
packages/rax-render/src/engine/compEngine.jsx
Normal file
103
packages/rax-render/src/engine/compEngine.jsx
Normal file
@ -0,0 +1,103 @@
|
||||
import { createElement } from 'rax';
|
||||
import PropTypes from 'prop-types';
|
||||
import Debug from 'debug';
|
||||
import classnames from 'classnames';
|
||||
import { isSchema, getFileCssName } from '@ali/iceluna-sdk/lib/utils';
|
||||
import BaseEngine from './base';
|
||||
|
||||
const debug = Debug('engine:comp');
|
||||
|
||||
export default class CompEngine extends BaseEngine {
|
||||
static dislayName = 'comp-engine';
|
||||
static propTypes = {
|
||||
__schema: PropTypes.object
|
||||
};
|
||||
static defaultProps = {
|
||||
__schema: {}
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
debug(`comp.getDerivedStateFromProps`);
|
||||
const func = props.__schema.lifeCycles && props.__schema.lifeCycles.getDerivedStateFromProps;
|
||||
if (func) {
|
||||
return func(props, state);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.__generateCtx({
|
||||
component: this
|
||||
});
|
||||
const schema = props.__schema || {};
|
||||
this.state = this.__parseData(schema.state || {});
|
||||
this.__initDataSource(props);
|
||||
this.__setLifeCycleMethods('constructor', arguments);
|
||||
debug(`comp.constructor - ${schema.fileName}`);
|
||||
}
|
||||
|
||||
async getSnapshotBeforeUpdate() {
|
||||
super.getSnapshotBeforeUpdate(...arguments);
|
||||
debug(`comp.getSnapshotBeforeUpdate - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
async componentDidMount() {
|
||||
super.componentDidMount(...arguments);
|
||||
debug(`comp.componentDidMount - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
async componentDidUpdate() {
|
||||
super.componentDidUpdate(...arguments);
|
||||
debug(`comp.componentDidUpdate - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
async componentWillUnmount() {
|
||||
super.componentWillUnmount(...arguments);
|
||||
debug(`comp.componentWillUnmount - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
async componentDidCatch(e) {
|
||||
super.componentDidCatch(...arguments);
|
||||
debug(`comp.componentDidCatch - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { __schema } = this.props;
|
||||
|
||||
if (!isSchema(__schema, true) || __schema.componentName !== 'Component') {
|
||||
return '自定义组件schema结构异常!';
|
||||
}
|
||||
|
||||
debug(`comp.render - ${__schema.fileName}`);
|
||||
|
||||
const { id, className, style, noContainer } = this.__parseData(__schema.props);
|
||||
|
||||
if (noContainer) {
|
||||
return this.__createContextDom(
|
||||
{
|
||||
compContext: this,
|
||||
blockContext: this
|
||||
},
|
||||
{
|
||||
component: this
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={this.__getRef}
|
||||
className={classnames('luna-comp', getFileCssName(__schema.fileName), className, this.props.className)}
|
||||
id={this.props.id || id}
|
||||
style={{ ...style, ...this.props.style }}
|
||||
>
|
||||
{this.__createContextDom(
|
||||
{
|
||||
compContext: this,
|
||||
blockContext: this
|
||||
},
|
||||
{
|
||||
component: this
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
122
packages/rax-render/src/engine/index.jsx
Normal file
122
packages/rax-render/src/engine/index.jsx
Normal file
@ -0,0 +1,122 @@
|
||||
import { Component, createElement } from 'rax';
|
||||
import PropTypes from 'prop-types';
|
||||
import Debug from 'debug';
|
||||
import * as isEmpty from 'lodash.isempty';
|
||||
import findDOMNode from 'rax-find-dom-node';
|
||||
import { isFileSchema, goldlog } from '@ali/iceluna-sdk/lib/utils';
|
||||
import AppContext from '../context/appContext';
|
||||
import Page from './pageEngine';
|
||||
import CustomComp from './compEngine';
|
||||
import Block from './blockEngine';
|
||||
import Temp from './tempEngine';
|
||||
|
||||
const debug = Debug('engine:entry');
|
||||
const ENGINE_COMPS = {
|
||||
Page,
|
||||
Component: CustomComp,
|
||||
Block,
|
||||
Temp,
|
||||
};
|
||||
export default class Engine extends Component {
|
||||
static dislayName = 'engine';
|
||||
static propTypes = {
|
||||
appHelper: PropTypes.object,
|
||||
components: PropTypes.object,
|
||||
componentsMap: PropTypes.object,
|
||||
designMode: PropTypes.string,
|
||||
suspended: PropTypes.bool,
|
||||
schema: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
|
||||
onCompGetRef: PropTypes.func,
|
||||
onCompGetCtx: PropTypes.func
|
||||
};
|
||||
static defaultProps = {
|
||||
appHelper: null,
|
||||
components: {},
|
||||
componentsMap: {},
|
||||
designMode: '',
|
||||
suspended: false,
|
||||
schema: {},
|
||||
onCompGetRef: () => {},
|
||||
onCompGetCtx: () => {}
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.state = {};
|
||||
debug(`entry.constructor - ${props.schema && props.schema.componentName}`);
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
goldlog(
|
||||
'EXP',
|
||||
{
|
||||
action: 'appear',
|
||||
value: !!this.props.designMode
|
||||
},
|
||||
'engine'
|
||||
);
|
||||
debug(`entry.componentDidMount - ${this.props.schema && this.props.schema.componentName}`);
|
||||
}
|
||||
|
||||
async componentDidUpdate() {
|
||||
debug(`entry.componentDidUpdate - ${this.props.schema && this.props.schema.componentName}`);
|
||||
}
|
||||
|
||||
async componentWillUnmount() {
|
||||
debug(`entry.componentWillUnmount - ${this.props.schema && this.props.schema.componentName}`);
|
||||
}
|
||||
|
||||
async componentDidCatch(e) {
|
||||
console.warn(e);
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
return !nextProps.suspended;
|
||||
}
|
||||
|
||||
__getRef = ref => {
|
||||
this.__ref = ref;
|
||||
if (ref) {
|
||||
this.props.onCompGetRef(this.props.schema, ref, true);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { schema, designMode, appHelper, components, componentsMap } = this.props;
|
||||
if (isEmpty(schema)) {
|
||||
return null;
|
||||
}
|
||||
if (!isFileSchema(schema)) {
|
||||
return '模型结构异常';
|
||||
}
|
||||
debug('entry.render');
|
||||
const allComponents = { ...ENGINE_COMPS, ...components };
|
||||
const Comp = allComponents[schema.componentName];
|
||||
if (Comp) {
|
||||
return (
|
||||
<AppContext.Provider
|
||||
value={{
|
||||
appHelper,
|
||||
components: allComponents,
|
||||
componentsMap,
|
||||
engine: this
|
||||
}}
|
||||
>
|
||||
<Comp
|
||||
key={schema.__ctx && `${schema.__ctx.lunaKey}_${schema.__ctx.idx || '0'}`}
|
||||
ref={this.__getRef}
|
||||
__appHelper={appHelper}
|
||||
__components={allComponents}
|
||||
__componentsMap={componentsMap}
|
||||
__schema={schema}
|
||||
__designMode={designMode}
|
||||
{...this.props}
|
||||
/>
|
||||
</AppContext.Provider>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Engine.findDOMNode = findDOMNode;
|
||||
90
packages/rax-render/src/engine/pageEngine.jsx
Normal file
90
packages/rax-render/src/engine/pageEngine.jsx
Normal file
@ -0,0 +1,90 @@
|
||||
import { createElement } from 'rax';
|
||||
import PropTypes from 'prop-types';
|
||||
import Debug from 'debug';
|
||||
import classnames from 'classnames';
|
||||
import { isSchema, getFileCssName } from '@ali/iceluna-sdk/lib/utils';
|
||||
import BaseEngine from './base';
|
||||
|
||||
const debug = Debug('engine:page');
|
||||
|
||||
export default class PageEngine extends BaseEngine {
|
||||
static dislayName = 'page-engine';
|
||||
static propTypes = {
|
||||
__schema: PropTypes.object
|
||||
};
|
||||
static defaultProps = {
|
||||
__schema: {}
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
debug(`page.getDerivedStateFromProps`);
|
||||
const func = props.__schema.lifeCycles && props.__schema.lifeCycles.getDerivedStateFromProps;
|
||||
if (func) {
|
||||
return func(props, state);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.__generateCtx({
|
||||
page: this
|
||||
});
|
||||
const schema = props.__schema || {};
|
||||
this.state = this.__parseData(schema.state || {});
|
||||
this.__initDataSource(props);
|
||||
this.__setLifeCycleMethods('constructor', arguments);
|
||||
|
||||
debug(`page.constructor - ${schema.fileName}`);
|
||||
}
|
||||
|
||||
async getSnapshotBeforeUpdate() {
|
||||
super.getSnapshotBeforeUpdate(...arguments);
|
||||
debug(`page.getSnapshotBeforeUpdate - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
async componentDidMount() {
|
||||
super.componentDidMount(...arguments);
|
||||
debug(`page.componentDidMount - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
async componentDidUpdate() {
|
||||
super.componentDidUpdate(...arguments);
|
||||
debug(`page.componentDidUpdate - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
async componentWillUnmount() {
|
||||
super.componentWillUnmount(...arguments);
|
||||
debug(`page.componentWillUnmount - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
async componentDidCatch() {
|
||||
await super.componentDidCatch(...arguments);
|
||||
debug(`page.componentDidCatch - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { __schema } = this.props;
|
||||
if (!isSchema(__schema, true) || __schema.componentName !== 'Page') {
|
||||
return '页面schema结构异常!';
|
||||
}
|
||||
debug(`page.render - ${__schema.fileName}`);
|
||||
|
||||
const { id, className, style } = this.__parseData(__schema.props);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={this.__getRef}
|
||||
className={classnames('luna-page', getFileCssName(__schema.fileName), className, this.props.className)}
|
||||
id={id}
|
||||
style={style}
|
||||
>
|
||||
{this.__createContextDom(
|
||||
{
|
||||
pageContext: this,
|
||||
blockContext: this
|
||||
},
|
||||
{
|
||||
page: this
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
67
packages/rax-render/src/engine/tempEngine.jsx
Normal file
67
packages/rax-render/src/engine/tempEngine.jsx
Normal file
@ -0,0 +1,67 @@
|
||||
import { createElement } from 'rax';
|
||||
import PropTypes from 'prop-types';
|
||||
import Debug from 'debug';
|
||||
import { isSchema } from '@ali/iceluna-sdk/lib/utils';
|
||||
import AppContext from '../context/appContext';
|
||||
import BaseEngine from './base';
|
||||
|
||||
const debug = Debug('engine:temp');
|
||||
export default class TempEngine extends BaseEngine {
|
||||
static dislayName = 'temp-engine';
|
||||
static propTypes = {
|
||||
__ctx: PropTypes.object,
|
||||
__schema: PropTypes.object
|
||||
};
|
||||
static defaultProps = {
|
||||
__ctx: {},
|
||||
__schema: {}
|
||||
};
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.state = {};
|
||||
this.cacheSetState = {};
|
||||
debug(`temp.constructor - ${props.__schema.fileName}`);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const ctx = this.props.__ctx;
|
||||
if (!ctx) return;
|
||||
const setState = ctx.setState;
|
||||
this.cacheSetState = setState;
|
||||
ctx.setState = (...args) => {
|
||||
setState.call(ctx, ...args);
|
||||
setTimeout(() => this.forceUpdate(), 0);
|
||||
};
|
||||
debug(`temp.componentDidMount - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||
debug(`temp.componentDidUpdate - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
const ctx = this.props.__ctx;
|
||||
if (!ctx || !this.cacheSetState) return;
|
||||
ctx.setState = this.cacheSetState;
|
||||
delete this.cacheSetState;
|
||||
debug(`temp.componentWillUnmount - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
componentDidCatch(e) {
|
||||
console.warn(e);
|
||||
debug(`temp.componentDidCatch - ${this.props.__schema.fileName}`);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { __schema, __ctx } = this.props;
|
||||
if (!isSchema(__schema, true) || __schema.componentName !== 'Temp') {
|
||||
return '下钻编辑schema结构异常!';
|
||||
}
|
||||
|
||||
debug(`temp.render - ${__schema.fileName}`);
|
||||
|
||||
return (
|
||||
<div ref={this.__getRef} className="luna-temp">
|
||||
<AppContext.Provider value={{ ...this.context, ...__ctx }}>{this.__createDom()}</AppContext.Provider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
71
packages/rax-render/src/hoc/compFactory.js
Normal file
71
packages/rax-render/src/hoc/compFactory.js
Normal file
@ -0,0 +1,71 @@
|
||||
import { Component, createElement } from 'rax';
|
||||
import PropTypes from 'prop-types';
|
||||
import AppHelper from '@ali/iceluna-sdk/lib/utils/appHelper';
|
||||
import { forEach, isFileSchema } from '@ali/iceluna-sdk/lib/utils';
|
||||
import CompEngine from '../engine/compEngine';
|
||||
import BlockEngine from '../engine/blockEngine';
|
||||
import AppContext from '../context/appContext';
|
||||
|
||||
export default function compFactory(schema, components = {}, componentsMap = {}, config = {}) {
|
||||
// 自定义组件需要有自己独立的appHelper
|
||||
const appHelper = new AppHelper(config);
|
||||
class LNCompView extends Component {
|
||||
static dislayName = 'luna-comp-factory';
|
||||
static version = config.version || '0.0.0';
|
||||
static contextType = AppContext;
|
||||
static propTypes = {
|
||||
forwardedRef: PropTypes.func
|
||||
};
|
||||
render() {
|
||||
if (!schema || schema.componentName !== 'Component' || !isFileSchema(schema)) {
|
||||
console.warn('自定义组件模型结构异常!');
|
||||
return null;
|
||||
}
|
||||
const { forwardedRef, ...otherProps } = this.props;
|
||||
// 低代码组件透传应用上下文
|
||||
const ctx = ['utils', 'constants', 'history', 'location', 'match'];
|
||||
ctx.forEach(key => {
|
||||
if (!appHelper[key] && this.context && this.context.appHelper && this.context.appHelper[key]) {
|
||||
appHelper.set(key, this.context.appHelper[key]);
|
||||
}
|
||||
});
|
||||
// 支持通过context透传国际化配置
|
||||
const localeProps = {};
|
||||
const { locale, messages } = this.context;
|
||||
if (locale && messages && messages[schema.fileName]) {
|
||||
localeProps.locale = locale;
|
||||
localeProps.messages = messages[schema.fileName];
|
||||
}
|
||||
const props = {
|
||||
...schema.defaultProps,
|
||||
...localeProps,
|
||||
...otherProps,
|
||||
__schema: schema,
|
||||
ref: forwardedRef
|
||||
};
|
||||
|
||||
return (
|
||||
<AppContext.Consumer>
|
||||
{context => {
|
||||
this.context = context;
|
||||
return (
|
||||
<CompEngine
|
||||
{...props}
|
||||
__appHelper={appHelper}
|
||||
__components={{ ...components, Component: CompEngine, Block: BlockEngine }}
|
||||
__componentsMap={componentsMap}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</AppContext.Consumer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const ResComp = React.forwardRef((props, ref) => <LNCompView {...props} forwardedRef={ref} />);
|
||||
forEach(schema.static, (val, key) => {
|
||||
ResComp[key] = val;
|
||||
});
|
||||
ResComp.version = config.version || '0.0.0';
|
||||
return ResComp;
|
||||
}
|
||||
15
packages/rax-render/src/hoc/compWrapper.js
Normal file
15
packages/rax-render/src/hoc/compWrapper.js
Normal file
@ -0,0 +1,15 @@
|
||||
import { createElement, Component } from 'rax';
|
||||
|
||||
export default function(Comp) {
|
||||
class CompWrapper extends Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Comp {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
||||
return CompWrapper;
|
||||
}
|
||||
5
packages/rax-render/src/index.jsx
Normal file
5
packages/rax-render/src/index.jsx
Normal file
@ -0,0 +1,5 @@
|
||||
import Engine from './engine';
|
||||
|
||||
export { default as Engine } from './engine';
|
||||
export { default as CompFactory } from './hoc/compFactory';
|
||||
export default Engine;
|
||||
Loading…
x
Reference in New Issue
Block a user