From 4546dc07ed7ad0703781c31b8381d4e034ad0903 Mon Sep 17 00:00:00 2001 From: "wuyue.xht" Date: Fri, 29 Jan 2021 12:36:31 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84renderer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: 导出 compFactory --- packages/rax-renderer/package.json | 26 +- packages/rax-renderer/src/comp/Div.ts | 11 - .../rax-renderer/src/comp/visualDom/index.tsx | 24 - .../rax-renderer/src/context/appContext.ts | 4 - packages/rax-renderer/src/engine/base.tsx | 634 ------------- .../rax-renderer/src/engine/blockEngine.tsx | 90 -- .../rax-renderer/src/engine/compEngine.tsx | 144 --- packages/rax-renderer/src/engine/index.tsx | 205 ----- .../rax-renderer/src/engine/pageEngine.tsx | 158 ---- .../rax-renderer/src/engine/tempEngine.tsx | 77 -- packages/rax-renderer/src/hoc/compFactory.tsx | 17 +- packages/rax-renderer/src/hoc/compWrapper.tsx | 23 - packages/rax-renderer/src/index.ts | 52 ++ packages/rax-renderer/src/index.tsx | 5 - packages/rax-renderer/src/renderer/block.tsx | 24 + .../rax-renderer/src/renderer/component.tsx | 36 + packages/rax-renderer/src/renderer/page.tsx | 33 + packages/rax-renderer/src/utils/dataHelper.ts | 302 ------ packages/rax-renderer/src/utils/index.ts | 725 --------------- packages/rax-renderer/src/utils/request.ts | 199 ---- packages/rax-simulator-renderer/package.json | 2 +- .../src/renderer-view.tsx | 4 +- .../rax-simulator-renderer/src/renderer.ts | 136 +-- packages/react-renderer/build.test.json | 6 + packages/react-renderer/jest.config.js | 20 + packages/react-renderer/package.json | 27 +- .../react-renderer/src/components/Div.tsx | 11 - .../src/components/VisualDom.scss | 19 - .../src/components/VisualDom.tsx | 31 - .../react-renderer/src/context/appContext.ts | 5 - packages/react-renderer/src/engine/index.tsx | 230 ----- packages/react-renderer/src/index.ts | 50 + packages/react-renderer/src/index.tsx | 218 ----- .../react-renderer/src/renderer/addon.tsx | 139 --- packages/react-renderer/src/renderer/base.tsx | 620 ------------- .../react-renderer/src/renderer/block.tsx | 146 --- .../react-renderer/src/renderer/component.tsx | 129 --- packages/react-renderer/src/renderer/page.tsx | 166 ---- packages/react-renderer/src/renderer/temp.tsx | 73 -- .../tests/__snapshots__/index.test.tsx.snap | 865 ++++++++++++++++++ .../tests/fixtures/schema/basic.ts | 567 ++++++++++++ packages/react-renderer/tests/index.test.tsx | 31 + .../react-simulator-renderer/CHANGELOG.md | 16 - .../react-simulator-renderer/package.json | 8 +- .../react-simulator-renderer/src/renderer.ts | 123 +-- packages/renderer-core/.eslintrc.js | 14 + packages/renderer-core/CHANGELOG.md | 648 +++++++++++++ packages/renderer-core/README.md | 3 + packages/renderer-core/build.json | 7 + packages/renderer-core/package.json | 48 + packages/renderer-core/src/adapter/index.ts | 89 ++ packages/renderer-core/src/components/Div.tsx | 14 + .../src/components/VisualDom}/index.css | 0 .../src/components/VisualDom/index.tsx | 34 + packages/renderer-core/src/context/index.ts | 13 + packages/renderer-core/src/hoc/index.tsx | 22 + packages/renderer-core/src/index.ts | 9 + packages/renderer-core/src/module.d.ts | 3 + packages/renderer-core/src/renderer/addon.tsx | 88 ++ packages/renderer-core/src/renderer/base.tsx | 761 +++++++++++++++ packages/renderer-core/src/renderer/block.tsx | 39 + .../renderer-core/src/renderer/component.tsx | 41 + packages/renderer-core/src/renderer/index.ts | 17 + packages/renderer-core/src/renderer/page.tsx | 59 ++ .../renderer-core/src/renderer/renderer.tsx | 189 ++++ packages/renderer-core/src/renderer/temp.tsx | 55 ++ packages/renderer-core/src/types/index.ts | 122 +++ .../src/utils/common.ts} | 340 ++----- .../src/utils/dataHelper.ts | 82 +- packages/renderer-core/src/utils/index.ts | 3 + .../src/utils/request.ts | 12 +- packages/renderer-core/tsconfig.json | 7 + 72 files changed, 4186 insertions(+), 4964 deletions(-) delete mode 100644 packages/rax-renderer/src/comp/Div.ts delete mode 100644 packages/rax-renderer/src/comp/visualDom/index.tsx delete mode 100644 packages/rax-renderer/src/context/appContext.ts delete mode 100644 packages/rax-renderer/src/engine/base.tsx delete mode 100644 packages/rax-renderer/src/engine/blockEngine.tsx delete mode 100644 packages/rax-renderer/src/engine/compEngine.tsx delete mode 100644 packages/rax-renderer/src/engine/index.tsx delete mode 100644 packages/rax-renderer/src/engine/pageEngine.tsx delete mode 100644 packages/rax-renderer/src/engine/tempEngine.tsx delete mode 100644 packages/rax-renderer/src/hoc/compWrapper.tsx create mode 100644 packages/rax-renderer/src/index.ts delete mode 100644 packages/rax-renderer/src/index.tsx create mode 100644 packages/rax-renderer/src/renderer/block.tsx create mode 100644 packages/rax-renderer/src/renderer/component.tsx create mode 100644 packages/rax-renderer/src/renderer/page.tsx delete mode 100644 packages/rax-renderer/src/utils/dataHelper.ts delete mode 100644 packages/rax-renderer/src/utils/index.ts delete mode 100644 packages/rax-renderer/src/utils/request.ts create mode 100644 packages/react-renderer/build.test.json create mode 100644 packages/react-renderer/jest.config.js delete mode 100644 packages/react-renderer/src/components/Div.tsx delete mode 100644 packages/react-renderer/src/components/VisualDom.scss delete mode 100644 packages/react-renderer/src/components/VisualDom.tsx delete mode 100644 packages/react-renderer/src/context/appContext.ts delete mode 100644 packages/react-renderer/src/engine/index.tsx create mode 100644 packages/react-renderer/src/index.ts delete mode 100644 packages/react-renderer/src/index.tsx delete mode 100644 packages/react-renderer/src/renderer/addon.tsx delete mode 100644 packages/react-renderer/src/renderer/base.tsx delete mode 100644 packages/react-renderer/src/renderer/block.tsx delete mode 100644 packages/react-renderer/src/renderer/component.tsx delete mode 100644 packages/react-renderer/src/renderer/page.tsx delete mode 100644 packages/react-renderer/src/renderer/temp.tsx create mode 100644 packages/react-renderer/tests/__snapshots__/index.test.tsx.snap create mode 100644 packages/react-renderer/tests/fixtures/schema/basic.ts create mode 100644 packages/react-renderer/tests/index.test.tsx create mode 100644 packages/renderer-core/.eslintrc.js create mode 100644 packages/renderer-core/CHANGELOG.md create mode 100644 packages/renderer-core/README.md create mode 100644 packages/renderer-core/build.json create mode 100644 packages/renderer-core/package.json create mode 100644 packages/renderer-core/src/adapter/index.ts create mode 100644 packages/renderer-core/src/components/Div.tsx rename packages/{rax-renderer/src/comp/visualDom => renderer-core/src/components/VisualDom}/index.css (100%) create mode 100644 packages/renderer-core/src/components/VisualDom/index.tsx create mode 100644 packages/renderer-core/src/context/index.ts create mode 100644 packages/renderer-core/src/hoc/index.tsx create mode 100644 packages/renderer-core/src/index.ts create mode 100644 packages/renderer-core/src/module.d.ts create mode 100644 packages/renderer-core/src/renderer/addon.tsx create mode 100644 packages/renderer-core/src/renderer/base.tsx create mode 100644 packages/renderer-core/src/renderer/block.tsx create mode 100644 packages/renderer-core/src/renderer/component.tsx create mode 100644 packages/renderer-core/src/renderer/index.ts create mode 100644 packages/renderer-core/src/renderer/page.tsx create mode 100644 packages/renderer-core/src/renderer/renderer.tsx create mode 100644 packages/renderer-core/src/renderer/temp.tsx create mode 100644 packages/renderer-core/src/types/index.ts rename packages/{react-renderer/src/utils/index.ts => renderer-core/src/utils/common.ts} (52%) rename packages/{react-renderer => renderer-core}/src/utils/dataHelper.ts (82%) create mode 100644 packages/renderer-core/src/utils/index.ts rename packages/{react-renderer => renderer-core}/src/utils/request.ts (89%) create mode 100644 packages/renderer-core/tsconfig.json diff --git a/packages/rax-renderer/package.json b/packages/rax-renderer/package.json index 30e5859b1..7f29c46b2 100644 --- a/packages/rax-renderer/package.json +++ b/packages/rax-renderer/package.json @@ -11,8 +11,7 @@ "files": [ "dist", "es", - "lib", - "src" + "lib" ], "keywords": [ "low-code", @@ -32,28 +31,9 @@ "build": "build-scripts build" }, "dependencies": { - "@ali/b3-one": "^0.0.17", - "@ali/bzb-request": "2.6.1", - "@ali/lib-mtop": "^2.5.1", - "@ali/lowcode-datasource-engine": "^1.0.22", + "@ali/lowcode-renderer-core": "^1.0.33", "@ali/lowcode-utils": "^1.0.33", - "@ali/ui-table": "^1.0.1-beta.6", - "classnames": "^2.2.6", - "debug": "^4.1.1", - "events": "^3.0.0", - "fetch-jsonp": "^1.1.3", - "fs-extra": "^9.0.1", - "intl-messageformat": "^9.3.1", - "jsonuri": "^2.1.2", - "keymaster": "^1.6.2", - "lodash": "^4.17.11", - "moment": "^2.24.0", - "rax-find-dom-node": "^1.0.1", - "rax-text": "^1.1.6", - "rax-view": "^1.0.0", - "react-is": "^16.10.1", - "serialize-javascript": "^1.7.0", - "whatwg-fetch": "^3.0.0" + "rax-find-dom-node": "^1.0.1" }, "devDependencies": { "@alib/build-scripts": "^0.1.0", diff --git a/packages/rax-renderer/src/comp/Div.ts b/packages/rax-renderer/src/comp/Div.ts deleted file mode 100644 index 8db0dbd15..000000000 --- a/packages/rax-renderer/src/comp/Div.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { createElement, PureComponent } from 'rax'; - -export default class DivView extends PureComponent { - static displayName = 'Div'; - - static version = '0.0.0'; - - render(): any { - return createElement('div', this.props); - } -} diff --git a/packages/rax-renderer/src/comp/visualDom/index.tsx b/packages/rax-renderer/src/comp/visualDom/index.tsx deleted file mode 100644 index d67eb067d..000000000 --- a/packages/rax-renderer/src/comp/visualDom/index.tsx +++ /dev/null @@ -1,24 +0,0 @@ -// @ts-nocheck -import { Component } from 'rax'; -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 ( -
-
- {title || label || text || __componentName} -
{children}
-
-
- ); - } -} diff --git a/packages/rax-renderer/src/context/appContext.ts b/packages/rax-renderer/src/context/appContext.ts deleted file mode 100644 index b1fce29c4..000000000 --- a/packages/rax-renderer/src/context/appContext.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { createContext } from 'rax'; - -const context = createContext({}); -export default context; diff --git a/packages/rax-renderer/src/engine/base.tsx b/packages/rax-renderer/src/engine/base.tsx deleted file mode 100644 index c87a688da..000000000 --- a/packages/rax-renderer/src/engine/base.tsx +++ /dev/null @@ -1,634 +0,0 @@ -// @ts-nocheck -import { Component, createElement } from 'rax'; -import PropTypes from 'prop-types'; -import Debug from 'debug'; -import classnames from 'classnames'; -import { create as createDataSourceEngine } from '@ali/lowcode-datasource-engine/interpret'; -import DataHelper from '../utils/dataHelper'; -import { - forEach, - getValue, - parseData, - parseExpression, - isEmpty, - isSchema, - isFileSchema, - isJSExpression, - isJSSlot, - isJSFunction, - transformArrayToMap, - checkPropTypes, - generateI18n, - acceptsRef, - getFileCssName, -} from '../utils'; -import VisualDom from '../comp/visualDom'; -import Div from '../comp/Div'; -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, - // eslint-disable-next-line react/no-unused-prop-types - __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 = {}; - this.__instanceMap = {}; - 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 = () => 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 dataSource = (schema && schema.dataSource) || {}; - // requestHandlersMap 存在才走数据源引擎方案 - if (props?.__appHelper?.requestHandlersMap) { - const { dataSourceMap, reloadDataSource } = createDataSourceEngine(dataSource, this, { - requestHandlersMap: props.__appHelper.requestHandlersMap, - }); - this.dataSourceMap = dataSourceMap; - this.reloadDataSource = () => new Promise((resolve, reject) => { - debug('reload data source'); - // this.__showPlaceholder = true; - reloadDataSource().then(() => { - // this.__showPlaceholder = false; - // @TODO 是否需要 forceUpate - resolve(); - }); - }); - } else { - const appHelper = props.__appHelper; - this.__dataHelper = new DataHelper(this, dataSource, appHelper, (config) => this.__parseData(config)); - this.dataSourceMap = this.__dataHelper.dataSourceMap; - this.reloadDataSource = () => 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); - }); - }); - } - // 设置容器组件占位,若设置占位则在初始异步请求完成之前用loading占位且不渲染容器组件内部内容 - // @TODO __showPlaceholder 的逻辑一旦开启就关不掉,先注释掉了 - /* if (this.__parseData(schema.props && schema.props.autoLoading)) { - this.__showPlaceholder = (dataSource.list || []).some((item) => !!this.__parseData(item.isInit)); - } */ - }; - - __render = () => { - const schema = this.props.__schema; - this.__setLifeCycleMethods('render'); - - const { engine } = this.context; - 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) => { - const { engine } = this.context; - const { __schema } = this.props; - engine && engine.props.onCompGetRef(__schema, 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) => { - const { engine } = this.context || {}; - try { - if (!schema) return null; - if (schema.componentName === 'Text') { // 这个是不是不应该在这里处理 - if (typeof schema.props.text === 'string') { - schema = { ...schema }; - schema.children = [schema.props.text]; - } - } - - const { __appHelper: appHelper, __components: components = {} } = this.props || {}; - - if (isJSExpression(schema)) { - return parseExpression(schema, self); - } - if (isJSSlot(schema)) { - return this.__createVirtualDom(schema.value, self, parentInfo); - } - 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, idy) => this.__createVirtualDom(item, self, parentInfo, item && item.__ctx && item.__ctx.lunaKey ? '' : idy)); - } - // FIXME - const _children = this.getSchemaChildren(schema); - // 解析占位组件 - if (schema.componentName === 'Flagment' && _children) { - const tarChildren = isJSExpression(_children) ? parseExpression(_children, self) : _children; - return this.__createVirtualDom(tarChildren, self, parentInfo); - } - - if (schema.$$typeof) { - return schema; - } - if (!isSchema(schema)) return null; - let Comp = components[schema.componentName] || engine.getNotFoundComponent(); - - if (schema.hidden) { - return null; - } - - if (schema.loop != null) { - const loop = parseData(schema.loop, self); - if ((Array.isArray(loop) && loop.length > 0) || isJSExpression(loop)) { - return this.__createLoopVirtualDom( - { - ...schema, - loop, - }, - self, - parentInfo, - idx, - ); - } - } - const condition = schema.condition == null ? 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, - } - : {}; - if (engine && engine.props.designMode) { - otherProps.__designMode = engine.props.designMode; - } - const componentInfo = {}; - const props = - this.__parseProps(schema.props, self, '', { - schema, - Comp, - componentInfo: { - ...componentInfo, - props: transformArrayToMap(componentInfo.props, 'name'), - }, - }) || {}; - // 对于可以获取到ref的组件做特殊处理 - if (!acceptsRef(Comp)) { - Comp = compWrapper(Comp); - } - // if (acceptsRef(Comp)) { - otherProps.ref = (ref) => { - this.$(props.fieldId, ref); // 收集ref - const refProps = props.ref; - if (refProps && typeof refProps === 'string') { - this[refProps] = ref; - } - ref && engine && engine.props.onCompGetRef(schema, ref); - }; - // } - // scope需要传入到组件上 - if (scopeKey && this.__compScopes[scopeKey]) { - props.__scope = this.__compScopes[scopeKey]; - } - // FIXME 这里清除 key 是为了避免循环渲染中更改 key 导致的渲染重复 - props.key = ''; - 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; - } - - props.__id = schema.id; - if (!props.key) { - props.key = props.__id; - } - - let child = null; - if (/*!isFileSchema(schema) && */!!_children) { - child = this.__createVirtualDom( - isJSExpression(_children) ? parseExpression(_children, self) : _children, - self, - { - schema, - Comp, - }, - ); - } - const renderComp = (props) => engine.createElement(Comp, props, child); - // 设计模式下的特殊处理 - 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 ( -
- {renderComp({ ...props, ...overlayProps })} -
- ); - } - // 虚拟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 }); - } catch (e) { - return engine.createElement(engine.getFaultComponent(), { - error: e, - schema, - self, - parentInfo, - idx, - }); - } - }; - - getSchemaChildren = (schema) => { - if (!schema || !schema.props) { - return schema?.children; - } - if (!schema.children) return schema.props.children; - if (!schema.props.children) return schema.children; - let _children = [].concat(schema.children); - if (Array.isArray(schema.props.children)) { - _children = _children.concat(schema.props.children); - } else { - _children.push(schema.props.children); - } - return _children; - }; - - __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, props) => ( - - {(context) => { - this.context = context; - this.__generateCtx(currCtx); - this.__render(); - return ( - - {context.engine.createElement( - props.__components.Page, - { - ...props, - ref: this.__getRef, - className: classnames(getFileCssName(props.__schema.fileName), props.className), - __id: props.__schema.id, - }, - this.__createDom(), - )} - - ); - }} - - ); - - __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 })); - } - return checkProps(function () { - const args = {}; - if (Array.isArray(params) && params.length) { - params.forEach((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); - } - if (Array.isArray(props)) { - return checkProps(props.map((item, idx) => this.__parseProps(item, self, path ? `${path}.${idx}` : idx, info))); - } - if (typeof props === 'function') { - return checkProps(props.bind(self)); - } - 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); - } - if (typeof props === 'string') { - return checkProps(props.trim()); - } - return checkProps(props); - }; - - $(filedId, instance) { - this.__instanceMap = this.__instanceMap || {}; - if (!filedId) { - return this.__instanceMap; - } - if (instance) { - this.__instanceMap[filedId] = instance; - } - return this.__instanceMap[filedId]; - } - - 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; - } -} diff --git a/packages/rax-renderer/src/engine/blockEngine.tsx b/packages/rax-renderer/src/engine/blockEngine.tsx deleted file mode 100644 index 5aefc730a..000000000 --- a/packages/rax-renderer/src/engine/blockEngine.tsx +++ /dev/null @@ -1,90 +0,0 @@ -// @ts-nocheck -import { createElement } from 'rax'; -import PropTypes from 'prop-types'; -import Debug from 'debug'; -import classnames from 'classnames'; -import { isSchema, getFileCssName } from '../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 ( -
- {this.__createContextDom({ - blockContext: this, - })} -
- ); - } -} diff --git a/packages/rax-renderer/src/engine/compEngine.tsx b/packages/rax-renderer/src/engine/compEngine.tsx deleted file mode 100644 index 3e3320d26..000000000 --- a/packages/rax-renderer/src/engine/compEngine.tsx +++ /dev/null @@ -1,144 +0,0 @@ -// @ts-nocheck - -import { createElement } from 'rax'; -import PropTypes from 'prop-types'; -import Debug from 'debug'; -import classnames from 'classnames'; -import { isSchema, getFileCssName } from '../utils'; -import BaseEngine from './base'; -import AppContext from '../context/appContext'; - -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() { - super.componentDidCatch(...arguments); - debug(`comp.componentDidCatch - ${this.props.__schema.fileName}`); - } - - __createContextDom = (childCtx, currCtx, props) => ( - - {(context) => { - this.context = context; - this.__generateCtx(currCtx); - this.__render(); - return ( - - {context.engine.createElement( - props.__components.Component, - { - ...props, - ref: this.__getRef, - className: classnames(getFileCssName(props.__schema.fileName), props.className), - __id: props.__schema.id, - }, - this.__createDom(), - )} - - ); - }} - - ); - - 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 ( -
- {this.__createContextDom( - { - compContext: this, - blockContext: this, - }, - { - component: this, - }, - this.props, - )} -
- ); - } -} diff --git a/packages/rax-renderer/src/engine/index.tsx b/packages/rax-renderer/src/engine/index.tsx deleted file mode 100644 index c704fc59c..000000000 --- a/packages/rax-renderer/src/engine/index.tsx +++ /dev/null @@ -1,205 +0,0 @@ -// @ts-nocheck -/* eslint-disable */ -import { Component, createElement } from 'rax'; -import PropTypes from 'prop-types'; -import Debug from 'debug'; -import isEmpty from 'lodash/isEmpty'; -import findDOMNode from 'rax-find-dom-node'; -import { isFileSchema, goldlog } from '../utils'; -import AppContext from '../context/appContext'; -import PageEngine from './pageEngine'; -import ComponentEngine from './compEngine'; -import BlockEngine from './blockEngine'; -import TempEngine from './tempEngine'; -import BaseEngine from './base'; -import compWrapper from '../hoc/compWrapper'; - -const debug = Debug('engine:entry'); -const ENGINE_COMPS = { - PageEngine, - ComponentEngine, - BlockEngine, - TempEngine, -}; - -const raxCreateElement = createElement; - -class FaultComponent extends Component { - render() { - // FIXME: errorlog - console.error('render error', this.props); - return
RenderError
; - } -} - -class NotFoundComponent extends Component { - render() { - console.error('component not found', this.props); - return
; - } -} - -export default class Engine extends Component { - static dislayName = 'engine'; - - static propTypes = { - appHelper: PropTypes.object, - components: PropTypes.object, - componentsMap: PropTypes.object, - // 数据源请求处理 - requestHandlersMap: PropTypes.object, - designMode: PropTypes.string, - suspended: PropTypes.bool, - schema: PropTypes.oneOfType([PropTypes.array, PropTypes.object]), - onCompGetRef: PropTypes.func, - onCompGetCtx: PropTypes.func, - customCreateElement: PropTypes.func, - notFoundComponent: PropTypes.element, - faultComponent: PropTypes.element, - }; - - static defaultProps = { - appHelper: null, - components: {}, - componentsMap: {}, - // 数据源请求处理 - requestHandlersMap: null, - 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; - } - - getNotFoundComponent() { - const { notFoundComponent } = this.props; - return notFoundComponent || NotFoundComponent; - } - - getFaultComponent() { - const { faultComponent } = this.props; - return faultComponent || FaultComponent; - } - - __getRef = (ref) => { - const { schema, onCompGetRef } = this.props; - this.__ref = ref; - if (ref) { - onCompGetRef(schema, ref, true); - } - }; - - patchDidCatch(Comp) { - if (Comp.patchedCatch) { - return; - } - Comp.patchedCatch = true;// eslint-disable-line - Comp.getDerivedStateFromError = (error) => ({ engineRenderError: true, error });// eslint-disable-line - // const engine = this; - const originRender = Comp.prototype.render; - Comp.prototype.render = function() { - if (this.state && this.state.engineRenderError) { - this.state.engineRenderError = false; - return engine.createElement(engine.getFaultComponent(), { - ...this.props, - error: this.state.error, - }); - } - return originRender.call(this); - }; - const originShouldComponentUpdate = Comp.prototype.shouldComponentUpdate; - Comp.prototype.shouldComponentUpdate = (nextProps, nextState) => {// eslint-disable-line - if (nextState && nextState.engineRenderError) { - return true; - } - return originShouldComponentUpdate ? originShouldComponentUpdate.call(this, nextProps, nextState) : true;// eslint-disable-line - }; - } - - createElement(Comp, props, children) { - const { customCreateElement } = this.props; - // TODO: enable in runtime mode? - this.patchDidCatch(Component); - return (customCreateElement || raxCreateElement)(Comp, props, children); - } - - render() { - const { schema, designMode, appHelper, components } = this.props; - if (isEmpty(schema)) { - return null; - } - if (!isFileSchema(schema)) { - return '模型结构异常'; - } - debug('entry.render'); - const allComponents = { ...ENGINE_COMPS, ...components }; - const { componentName } = schema; - // const Comp = allComponents[schema.componentName]; - let Comp = allComponents[componentName] || ENGINE_COMPS[`${componentName}Engine`]; - if (Comp && Comp.prototype) { - // const proto = Comp.prototype; - if (!(Comp.prototype instanceof BaseEngine)) { - Comp = ENGINE_COMPS[`${componentName}Engine`]; - } - } - - return ( - Comp - ? - - - : null - ); - } -} - -Engine.findDOMNode = findDOMNode; diff --git a/packages/rax-renderer/src/engine/pageEngine.tsx b/packages/rax-renderer/src/engine/pageEngine.tsx deleted file mode 100644 index 82b1cb22e..000000000 --- a/packages/rax-renderer/src/engine/pageEngine.tsx +++ /dev/null @@ -1,158 +0,0 @@ -// @ts-nocheck -import { createElement } from 'rax'; -import PropTypes from 'prop-types'; -import Debug from 'debug'; -import classnames from 'classnames'; -import AppContext from '../context/appContext'; -import { isSchema, getFileCssName } from '../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 contextType = AppContext; - - 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, __components } = this.props; - if (!isSchema(__schema, true) || __schema.componentName !== 'Page') { - return '页面schema结构异常!'; - } - debug(`page.render - ${__schema.fileName}`); - - const { - id, className, style, - } = this.__parseData(__schema.props); - - const { Page } = __components; - if (Page) { - // const { engine } = this.context || {}; - return ( - - {(context) => { - this.context = context; - this.__render(); - return ( - - {this.context.engine.createElement( - Page, - { - ...this.props, - ref: this.__getRef, - className: classnames(getFileCssName(__schema.fileName), className, this.props.className), - __id: __schema.id, - }, - this.__createDom(), - )} - - ); - }} - - ); - } - - const renderContent = () => ( - - {this.__createDom()} - - ); - - // if (autoLoading || loading !== undefined) { - // return ( - // - // {!this.__showPlaceholder && renderContent()} - // - // ); - // } - - return ( -
- {renderContent()} -
- ); - } -} diff --git a/packages/rax-renderer/src/engine/tempEngine.tsx b/packages/rax-renderer/src/engine/tempEngine.tsx deleted file mode 100644 index 031c35f9e..000000000 --- a/packages/rax-renderer/src/engine/tempEngine.tsx +++ /dev/null @@ -1,77 +0,0 @@ -// @ts-nocheck - -import { createElement } from 'rax'; -import PropTypes from 'prop-types'; -import Debug from 'debug'; -import { isSchema } from '../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; - this.cacheSetState = setState; - ctx.setState = (...args) => { - setState.call(ctx, ...args); - setTimeout(() => this.forceUpdate(), 0); - }; - debug(`temp.componentDidMount - ${this.props.__schema.fileName}`); - } - - componentDidUpdate() { - 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 ( -
- {this.__createDom()} -
- ); - } -} diff --git a/packages/rax-renderer/src/hoc/compFactory.tsx b/packages/rax-renderer/src/hoc/compFactory.tsx index 48ae7a6ce..9fcf98078 100644 --- a/packages/rax-renderer/src/hoc/compFactory.tsx +++ b/packages/rax-renderer/src/hoc/compFactory.tsx @@ -3,16 +3,21 @@ import { Component, createElement, forwardRef } from 'rax'; import PropTypes from 'prop-types'; import { AppHelper } from '@ali/lowcode-utils'; -import { forEach, isFileSchema } from '../utils'; -import CompEngine from '../engine/compEngine'; -import BlockEngine from '../engine/blockEngine'; -import AppContext from '../context/appContext'; +import { utils, contextFactory } from '@ali/lowcode-renderer-core'; +import componentRendererFactory from '../renderer/component'; +import blockRendererFactory from '../renderer/block'; + +const { forEach, isFileSchema } = utils; export default function compFactory(schema, components = {}, componentsMap = {}, config = {}) { // 自定义组件需要有自己独立的appHelper const appHelper = new AppHelper(config); + const CompRenderer = componentRendererFactory(); + const BlockRenderer = blockRendererFactory(); + const AppContext = contextFactory(); + class LNCompView extends Component { - static dislayName = 'luna-comp-factory'; + static dislayName = 'lce-comp-factory'; static version = config.version || '0.0.0'; @@ -58,7 +63,7 @@ export default function compFactory(schema, components = {}, componentsMap = {}, ); diff --git a/packages/rax-renderer/src/hoc/compWrapper.tsx b/packages/rax-renderer/src/hoc/compWrapper.tsx deleted file mode 100644 index e90eaabf4..000000000 --- a/packages/rax-renderer/src/hoc/compWrapper.tsx +++ /dev/null @@ -1,23 +0,0 @@ -// @ts-nocheck -import { createElement, Component, forwardRef } from 'rax'; - -export default function (Comp) { - class compWrapper extends Component { - // eslint-disable-next-line no-useless-constructor - constructor(props, context) { - super(props, context); - } - - render() { - const { forwardRef } = this.props; - return createElement(Comp, { - ...this.props, - ref: forwardRef, - }); - } - } - - return forwardRef((props, ref) => { - return createElement(compWrapper, { ...props, forwardRef: ref }); - }); -} diff --git a/packages/rax-renderer/src/index.ts b/packages/rax-renderer/src/index.ts new file mode 100644 index 000000000..d89326bc5 --- /dev/null +++ b/packages/rax-renderer/src/index.ts @@ -0,0 +1,52 @@ +import { Component, PureComponent, createElement, createContext, forwardRef } from 'rax'; +import findDOMNode from 'rax-find-dom-node'; +import { + adapter, + addonRendererFactory, + tempRendererFactory, + rendererFactory +} from '@ali/lowcode-renderer-core'; +import pageRendererFactory from './renderer/page'; +import componentRendererFactory from './renderer/component'; +import blockRendererFactory from './renderer/block'; +import CompFactory from './hoc/compFactory'; + +adapter.setRuntime({ + Component, + PureComponent, + createContext, + createElement, + forwardRef, + findDOMNode, +}); + +adapter.setRenderers({ + PageRenderer: pageRendererFactory(), + ComponentRenderer: componentRendererFactory(), + BlockRenderer: blockRendererFactory(), + AddonRenderer: addonRendererFactory(), + TempRenderer: tempRendererFactory(), +}); + +function factory() { + const Renderer = rendererFactory(); + return class extends Renderer { + constructor(props: any, context: any) { + super(props, context); + } + + isValidComponent(obj: any) { + return obj?.prototype?.setState || obj?.prototype instanceof Component; + } + } +} + +const RaxRenderer = factory(); +const Engine = RaxRenderer; + +export { + Engine, + CompFactory, +} + +export default RaxRenderer; diff --git a/packages/rax-renderer/src/index.tsx b/packages/rax-renderer/src/index.tsx deleted file mode 100644 index e7b4ba078..000000000 --- a/packages/rax-renderer/src/index.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import Engine from './engine'; - -export { default as Engine } from './engine'; -export { default as CompFactory } from './hoc/compFactory'; -export default Engine; diff --git a/packages/rax-renderer/src/renderer/block.tsx b/packages/rax-renderer/src/renderer/block.tsx new file mode 100644 index 000000000..6cf51903d --- /dev/null +++ b/packages/rax-renderer/src/renderer/block.tsx @@ -0,0 +1,24 @@ +import { blockRendererFactory, types } from '@ali/lowcode-renderer-core'; + +export default function raxBlockRendererFactory() { + const OriginBlock = blockRendererFactory(); + return class BlockRenderer extends OriginBlock { + render() { + // @ts-ignore + const that: types.IRenderer = this; + const { __schema, __components } = that.props; + if (that.__checkSchema(__schema)) { + return '区块 schema 结构异常!'; + } + that.__debug(`render - ${__schema.fileName}`); + + const children = ((context) => { + that.context = context; + that.__generateCtx({}); + that.__render(); + return that.__renderComp((__components as any)?.Block, { blockContext: that }); + }); + return that.__renderContextConsumer(children); + } + } +} diff --git a/packages/rax-renderer/src/renderer/component.tsx b/packages/rax-renderer/src/renderer/component.tsx new file mode 100644 index 000000000..f779d748f --- /dev/null +++ b/packages/rax-renderer/src/renderer/component.tsx @@ -0,0 +1,36 @@ +import { componentRendererFactory, types } from '@ali/lowcode-renderer-core'; + +export default function raxComponentRendererFactory() { + const OriginComponent = componentRendererFactory(); + return class ComponentRenderer extends OriginComponent { + render() { + // @ts-ignore + const that: types.IRenderer = this; + const { __schema, __components } = that.props; + if (that.__checkSchema(__schema)) { + return '自定义组件 schema 结构异常!'; + } + that.__debug(`render - ${__schema.fileName}`); + + const { noContainer } = that.__parseData(__schema.props); + + const children = ((context) => { + that.context = context; + that.__generateCtx({ component: that }); + that.__render(); + // 传 null,使用内置的 div 来渲染,解决在页面中渲染 vc-component 报错的问题 + return that.__renderComp(null, { + compContext: that, + blockContext: that, + }); + }); + const content = that.__renderContextConsumer(children); + + if (noContainer) { + return content; + } + + return that.__renderContent(content); + } + } +} diff --git a/packages/rax-renderer/src/renderer/page.tsx b/packages/rax-renderer/src/renderer/page.tsx new file mode 100644 index 000000000..79cd55ca9 --- /dev/null +++ b/packages/rax-renderer/src/renderer/page.tsx @@ -0,0 +1,33 @@ +import { pageRendererFactory, types } from '@ali/lowcode-renderer-core'; + +export default function raxPageRendererFactory() { + const OriginPage = pageRendererFactory(); + return class PageRenderer extends OriginPage { + async componentDidUpdate() { + // @ts-ignore + super.componentDidUpdate(...arguments); + } + + render() { + // @ts-ignore + const that: types.IRenderer = this; + const { __schema, __components } = that.props; + if (that.__checkSchema(__schema)) { + return '页面 schema 结构异常!'; + } + that.__debug(`render - ${__schema?.fileName}`); + + const { Page } = __components as any; + if (Page) { + const children = ((context) => { + that.context = context; + that.__render(); + return that.__renderComp(Page, { pageContext: that }); + }); + return that.__renderContextConsumer(children); + } + + return that.__renderContent(that.__renderContextProvider({ pageContext: that })); + } + } +} diff --git a/packages/rax-renderer/src/utils/dataHelper.ts b/packages/rax-renderer/src/utils/dataHelper.ts deleted file mode 100644 index 7cd6b44d9..000000000 --- a/packages/rax-renderer/src/utils/dataHelper.ts +++ /dev/null @@ -1,302 +0,0 @@ -/* eslint-disable object-curly-newline */ -// @ts-nocheck -import { transformArrayToMap, isJSFunction, transformStringToFunction, clone, comboSkeletonConfig } from './index'; -import { jsonp, mtop, request, get, post, bzb, webTableProxy } from './request'; - -const DS_STATUS = { - INIT: 'init', - LOADING: 'loading', - LOADED: 'loaded', - ERROR: 'error', -}; - -export default class DataHelper { - constructor(comp, config = {}, appHelper, parser) { - this.host = comp; - this.config = config; - this.parser = parser; - this.ajaxList = (config && config.list) || []; - this.ajaxMap = transformArrayToMap(this.ajaxList, 'id'); - this.dataSourceMap = this.generateDataSourceMap(); - this.appHelper = appHelper; - } - - // 重置config,dataSourceMap状态会被重置; - resetConfig(config = {}) { - this.config = config; - this.ajaxList = (config && config.list) || []; - this.ajaxMap = transformArrayToMap(this.ajaxList, 'id'); - this.dataSourceMap = this.generateDataSourceMap(); - return this.dataSourceMap; - } - - // 更新config,只会更新配置,状态保存; - updateConfig(config = {}) { - this.config = config; - this.ajaxList = (config && config.list) || []; - const ajaxMap = transformArrayToMap(this.ajaxList, 'id'); - // 删除已经移除的接口 - Object.keys(this.ajaxMap).forEach((key) => { - if (!ajaxMap[key]) { - delete this.dataSourceMap[key]; - } - }); - this.ajaxMap = ajaxMap; - // 添加未加入到dataSourceMap中的接口 - this.ajaxList.forEach((item) => { - if (!this.dataSourceMap[item.id]) { - this.dataSourceMap[item.id] = { - status: DS_STATUS.INIT, - load: (...args) => this.getDataSource(item.id, ...args), - }; - } - }); - return this.dataSourceMap; - } - - generateDataSourceMap() { - const res = {}; - this.ajaxList.forEach((item) => { - item.id = item.id || item.name; - res[item.id] = { - status: DS_STATUS.INIT, - load: (...args) => this.getDataSource(item.id, ...args), - }; - }); - return res; - } - - updateDataSourceMap(id, data, error) { - this.dataSourceMap[id].error = error || undefined; - this.dataSourceMap[id].data = data; - this.dataSourceMap[id].status = error ? DS_STATUS.ERROR : DS_STATUS.LOADED; - } - - getInitData() { - const initSyncData = this.parser(this.ajaxList).filter((item) => { - if (item.isInit) { - this.dataSourceMap[item.id].status = DS_STATUS.LOADING; - return true; - } - return false; - }); - return this.asyncDataHandler(initSyncData).then((res) => { - let { dataHandler } = this.config; - if (isJSFunction(dataHandler)) { - dataHandler = transformStringToFunction(dataHandler.value); - } - if (!dataHandler || typeof dataHandler !== 'function') return res; - try { - return dataHandler.call(this.host, res); - } catch (e) { - console.error('请求数据处理函数运行出错', e); - } - }); - } - - getDataSource(id, params, otherOptions, callback) { - console.log('load',id); - const req = this.parser(this.ajaxMap[id]); - const options = req.options || {}; - if (typeof otherOptions === 'function') { - callback = otherOptions; - otherOptions = {}; - } - const { headers, ...otherProps } = otherOptions || {}; - if (!req) { - console.warn(`getDataSource API named ${id} not exist`); - return; - } - return this.asyncDataHandler([ - { - ...req, - options: { - ...options, - // 支持参数为array的情况,当参数为array时,不做参数合并 - params: - Array.isArray(options.params) || Array.isArray(params) - ? params || options.params - : { - ...options.params, - ...params, - }, - headers: { - ...options.headers, - ...headers, - }, - ...otherProps, - }, - }, - ]) - .then(res => { - try { - callback && callback(res && res[id]); - } catch (e) { - console.error('load请求回调函数报错', e); - } - - return res && res[id]; - }) - .catch(err => { - try { - callback && callback(null, err); - } catch (e) { - console.error('load请求回调函数报错', e); - } - - return err; - }); - } - - asyncDataHandler(asyncDataList) { - return new Promise((resolve, reject) => { - const allReq = []; - const doserReq = []; - const doserList = []; - const beforeRequest = this.appHelper && this.appHelper.utils && this.appHelper.utils.beforeRequest; - const afterRequest = this.appHelper && this.appHelper.utils && this.appHelper.utils.afterRequest; - const csrfInput = document.getElementById('_csrf_token'); - const _tb_token_ = csrfInput && csrfInput.value; - asyncDataList.forEach(req => { - const { id, type, options } = req; - if (!id || !type) return; - if (type === 'doServer') { - const { uri, params } = options || {}; - if (!uri) return; - doserList.push(id); - doserReq.push({ name: uri, package: 'cms', params }); - } else { - allReq.push(req); - } - }); - - if (doserReq.length > 0) { - allReq.push({ - type: 'doServer', - options: { - uri: '/nrsService.do', - cors: true, - method: 'POST', - params: { - data: JSON.stringify(doserReq), - _tb_token_, - }, - }, - }); - } - if (allReq.length === 0) resolve({}); - const res = {}; - Promise.all( - allReq.map( - (item) => - new Promise((resolve) => { - const { type, id, dataHandler, options } = item; - const fetchHandler = async (data, error) => { - if (type === 'doServer') { - if (!Array.isArray(data)) { - data = [data]; - } - doserList.forEach(async (id, idx) => { - const req = this.ajaxMap[id]; - if (req) { - res[id] = await this.dataHandler(id, req.dataHandler, data && data[idx], error); - this.updateDataSourceMap(id, res[id], error); - } - }); - } else { - res[id] = await this.dataHandler(id, dataHandler, data, error); - this.updateDataSourceMap(id, res[id], error); - } - resolve(); - }; - const doFetch = (type, req) => { - this.fetchOne(type, req) - .then(async (data) => { - console.log(data) - if (afterRequest) { - this.appHelper.utils.afterRequest(item, data, undefined, async (data, error) => { - await fetchHandler(data, error); - }); - } else { - await fetchHandler(data, undefined); - } - }) - .catch(async (err) => { - if (afterRequest) { - // 必须要这么调用,否则beforeRequest中的this会丢失 - this.appHelper.utils.afterRequest(item, undefined, err, async (data, error) => { - await fetchHandler(data, error); - }); - } else { - await fetchHandler(undefined, err); - } - }); - }; - if (type === 'doServer') { - doserList.forEach(item => { - this.dataSourceMap[item].status = DS_STATUS.LOADING; - }); - } else { - this.dataSourceMap[id].status = DS_STATUS.LOADING; - } - // 请求切片 - if (beforeRequest) { - // 必须要这么调用,否则beforeRequest中的this会丢失 - this.appHelper.utils.beforeRequest(item, clone(options), () => doFetch(type, item)); - } else { - doFetch(type, item); - } - }) - ), - ).then(() => { - resolve(res); - }) - .catch((e) => { - reject(e); - }); - }); - } - - async dataHandler(id, dataHandler, data, error) { - if (isJSFunction(dataHandler)) { - dataHandler = transformStringToFunction(dataHandler.value); - } - if (!dataHandler || typeof dataHandler !== 'function') return data; - try { - return await dataHandler.call(this.host, data, error); - } catch (e) { - console.error(`[${id}]单个请求数据处理函数运行出错`, e); - } - } - - fetchOne(type, req) { - const { options } = req; - // eslint-disable-next-line prefer-const - let { uri, method = 'GET', headers, params, ...otherProps } = options; - otherProps = otherProps || {}; - switch (type) { - case 'mtop': - method && (otherProps.method = method); - return mtop(uri, params, otherProps); - case 'jsonp': - return jsonp(uri, params, otherProps); - case 'bzb': - return bzb(uri, params, { - method, - headers, - ...otherProps, - }); - case 'legao': - return webTableProxy(req); - default: - method = method.toUpperCase(); - if (method === 'GET') { - return get(uri || otherProps.url, params, headers, otherProps); - } - if (method === 'POST') { - return post(uri, params, headers, otherProps); - } - return request(uri, method, params, headers, otherProps); - } - } -} diff --git a/packages/rax-renderer/src/utils/index.ts b/packages/rax-renderer/src/utils/index.ts deleted file mode 100644 index ec4f2c450..000000000 --- a/packages/rax-renderer/src/utils/index.ts +++ /dev/null @@ -1,725 +0,0 @@ -// @ts-nocheck - -import Debug from 'debug'; -import _keymaster from 'keymaster'; -import { forEach as _forEach, shallowEqual as _shallowEqual } from '@ali/b3-one/lib/obj'; -import { serialize as serializeParams } from '@ali/b3-one/lib/url'; -import _moment from 'moment'; -import 'moment/locale/zh-cn'; -import _pick from 'lodash/pick'; -import _deepEqual from 'lodash/isEqualWith'; -import _clone from 'lodash/cloneDeep'; -import _isEmpty from 'lodash/isEmpty'; -import _throttle from 'lodash/throttle'; -import _debounce from 'lodash/debounce'; -import _serialize from 'serialize-javascript'; -import * as _jsonuri from 'jsonuri'; -import IntlMessageFormat from 'intl-messageformat'; - -const sdkVersion = (require('../../package.json')).version; - -window.sdkVersion = sdkVersion; - -export const moment = _moment; -moment.locale('zh-cn'); -export const forEach = _forEach; -export const shallowEqual = _shallowEqual; -export const keymaster = _keymaster; -export const pick = _pick; -export const deepEqual = _deepEqual; -export const clone = _clone; -export const isEmpty = _isEmpty; -export const throttle = _throttle; -export const debounce = _debounce; -export const serialize = _serialize; -export const jsonuri = _jsonuri; -export { - get, post, jsonp, mtop, request, -} from './request'; - -const ReactIs = require('react-is'); - -const ReactPropTypesSecret = require('prop-types/lib/ReactPropTypesSecret'); - -const factoryWithTypeCheckers = require('prop-types/factoryWithTypeCheckers'); - -const PropTypes2 = factoryWithTypeCheckers(ReactIs.isElement, true); - -const EXPRESSION_TYPE = { - JSEXPRESSION: 'JSExpression', - JSFUNCTION: 'JSFunction', - JSSLOT: 'JSSlot', -}; -const EXPRESSION_REG = /^\{\{(\{.*\}|.*?)\}\}$/; -const debug = Debug('utils:index'); - -const ENV = { - TBE: 'TBE', - WEBIDE: 'WEB-IDE', - VSCODE: 'VSCODE', - WEB: 'WEB', -}; - -/** - * @name isSchema - * @description 判断是否是模型结构 - */ -export function isSchema(schema, ignoreArr) { - if (isEmpty(schema)) return false; - if (!ignoreArr && Array.isArray(schema)) return schema.every((item) => isSchema(item)); - return !!(schema.componentName && schema.props && (typeof schema.props === 'object' || isJSExpression(schema.props))); -} - -export function isFileSchema(schema) { - if (isEmpty(schema)) return false; - return ['Page', 'Block', 'Component', 'Addon', 'Temp'].includes(schema.componentName); -} - -// 判断当前页面是否被嵌入到同域的页面中 -export function inSameDomain() { - try { - return window.parent !== window && window.parent.location.host === window.location.host; - } catch (e) { - return false; - } -} - -export function getFileCssName(fileName) { - if (!fileName) return; - const name = fileName.replace(/([A-Z])/g, '-$1').toLowerCase(); - return `luna-${name}` - .split('-') - .filter((p) => !!p) - .join('-'); -} - -export function isJSSlot(obj) { - return obj && typeof obj === 'object' && EXPRESSION_TYPE.JSSLOT === obj.type; -} -export function isJSFunction(obj) { - return obj && typeof obj === 'object' && EXPRESSION_TYPE.JSFUNCTION === obj.type; -} -export function isJSExpression(obj) { - // 兼容两种写法,有js构造表达式的情况 - const isJSExpressionObj = obj && typeof obj === 'object' && EXPRESSION_TYPE.JSEXPRESSION === obj.type && typeof obj.value === 'string'; - const isJSExpressionStr = typeof obj === 'string' && EXPRESSION_REG.test(obj.trim()); - return isJSExpressionObj || isJSExpressionStr; -} - -/** - * @name wait - * @description 等待函数 - */ -export function wait(ms) { - return new Promise((resolve) => setTimeout(() => resolve(true), ms)); -} - -export function curry(Comp, hocs = []) { - return hocs.reverse().reduce((pre, cur) => cur(pre), Comp); -} - -/** - * 获取parent window上的值,需要做同域判断 - * @param {string} key - */ -export function getParentWinValue(key) { - if (inSameDomain()) { - return window.parent && window.parent[key]; - } -} - -export function getValue(obj, path, defaultValue) { - if (isEmpty(obj) || typeof obj !== 'object') return defaultValue; - const res = path.split('.').reduce((pre, cur) => pre && pre[cur], obj); - if (res === undefined) return defaultValue; - return res; -} - -export function parseObj(schemaStr) { - if (typeof schemaStr !== 'string') return schemaStr; - // 默认调用顶层窗口的parseObj,保障new Function的window对象是顶层的window对象 - try { - if (inSameDomain() && window.parent.__newFunc) { - return window.parent.__newFunc(`"use strict"; return ${schemaStr}`)(); - } - return new Function(`"use strict"; return ${schemaStr}`)(); - } catch (err) { - return undefined; - } -} - -export function parseSearch(search) { - if (!search || typeof search !== 'string') { - return {}; - } - const str = search.replace(/^\?/, ''); - const paramStr = str.split('&'); - const res = {}; - paramStr.forEach((item) => { - const regRes = item.split('='); - if (regRes[0] && regRes[1]) { - res[regRes[0]] = decodeURIComponent(regRes[1]); - } - }); - return res; -} - -export function fastClone(obj) { - return parseObj(serialize(obj, { unsafe: true })); -} - -// 更新obj的内容但不改变obj的指针 -export function fillObj(receiver = {}, ...suppliers) { - Object.keys(receiver).forEach((item) => { - delete receiver[item]; - }); - Object.assign(receiver, ...suppliers); - return receiver; -} - -// 中划线转驼峰 -export function toHump(name) { - // eslint-disable-next-line no-useless-escape - return name.replace(/\-(\w)/g, (all, letter) => letter.toUpperCase()); -} -// 驼峰转中划线 -export function toLine(name) { - return name.replace(/([A-Z])/g, '-$1').toLowerCase(); -} - -// 获取当前环境 -export function getEnv() { - const { userAgent } = navigator; - const isVscode = /Electron\//.test(userAgent); - if (isVscode) return ENV.VSCODE; - const isTheia = window.is_theia === true; - if (isTheia) return ENV.WEBIDE; - return ENV.WEB; -} - -/** - * 合并skeleton配置 - * @param {*} 骨架默认配置 - * @param {*} 用户自定义配置 - */ -export function comboSkeletonConfig(defaultConfig = {}, customConfig) { - const { - skeleton, theme, addons, hooks, shortCuts, extensions, constants, utils, i18n, - } = customConfig || {}; - - if (skeleton && skeleton.handler && typeof skeleton.handler === 'function') { - return skeleton.handler({ - skeleton, - ...defaultConfig, - }); - } - - const defaultShortCuts = transformArrayToMap(defaultConfig.shortCuts || [], 'keyboard'); - const customShortCuts = transformArrayToMap(shortCuts || [], 'keyboard'); - const localeList = ['zh-CN', 'zh-TW', 'en-US', 'ja-JP']; - const i18nConfig = {}; - localeList.forEach((key) => { - i18nConfig[key] = { - ...(defaultConfig.i18n && defaultConfig.i18n[key]), - ...(i18n && i18n[key]), - }; - }); - return { - skeleton, - theme: { - ...defaultConfig.theme, - ...theme, - }, - addons: { - ...defaultConfig.addons, - ...addons, - }, - hooks: [...(defaultConfig.hooks || []), ...(hooks || [])], - shortCuts: Object.values({ - ...defaultShortCuts, - ...customShortCuts, - }), - extensions: { - ...defaultConfig.extensions, - ...extensions, - }, - constants: { - ...defaultConfig.constants, - ...constants, - }, - utils: [...(defaultConfig.utils || []), ...(utils || [])], - i18n: i18nConfig, - }; -} - -/** - * 用于构造国际化字符串处理函数 - * @param {*} locale 国际化标识,例如 zh-CN、en-US - * @param {*} messages 国际化语言包 - */ -export function generateI18n(locale = 'zh-CN', messages = {}) { - return (key, values = {}) => { - if (!messages || !messages[key]) return ''; - const formater = new IntlMessageFormat(messages[key], locale); - return formater.format(values); - }; -} - -/** - * 判断当前组件是否能够设置ref - * @param {*} Comp 需要判断的组件 - */ -export function acceptsRef(Comp) { - return Comp && Comp.prototype && Comp.prototype.setState; -} - -/** - * 黄金令箭埋点 - * @param {String} gmKey 为黄金令箭业务类型 - * @param {Object} params 参数 - * @param {String} logKey 属性串 - */ -export function goldlog(gmKey, params = {}, logKey = 'other') { - // vscode 黄金令箭API - const sendIDEMessage = window.sendIDEMessage || getParentWinValue('sendIDEMessage'); - const goKey = serializeParams({ - sdkVersion, - env: getEnv(), - ...params, - }); - if (sendIDEMessage) { - sendIDEMessage({ - action: 'goldlog', - data: { - logKey: `/iceluna.core.${logKey}`, - gmKey, - goKey, - }, - }); - } - window.goldlog && window.goldlog.record(`/iceluna.core.${logKey}`, gmKey, goKey, 'POST'); -} - -// utils为编辑器打包生成的utils文件内容,utilsConfig为数据库存放的utils配置 -export function generateUtils(utils, utilsConfig) { - if (!Array.isArray(utilsConfig)) return { ...utils }; - const res = {}; - utilsConfig.forEach((item) => { - if (!item.name || !item.type || !item.content) return; - if (item.type === 'function' && typeof item.content === 'function') { - res[item.name] = item.content; - } else if (item.type === 'npm' && utils[item.name]) { - res[item.name] = utils[item.name]; - } - }); - return res; -} -// 复制到粘贴板 -export function setClipboardData(str) { - return new Promise((resolve, reject) => { - if (typeof str !== 'string') reject('不支持拷贝'); - if (navigator.clipboard) { - navigator.clipboard - .writeText(str) - .then(() => { - resolve(); - }) - .catch((err) => { - reject('复制失败,请重试!', err); - }); - } else { - const textArea = document.createElement('textarea'); - textArea.value = str; - document.body.appendChild(textArea); - textArea.focus(); - textArea.select(); - try { - const successful = document.execCommand('copy'); - if (successful) { - document.body.removeChild(textArea); - resolve(); - } - } catch (err) { - document.body.removeChild(textArea); - reject('复制失败,请重试!', err); - } - } - }); -} -// 获取粘贴板数据 -export function getClipboardData() { - return new Promise((resolve, reject) => { - if (window.clipboardData) { - resolve(window.clipboardData.getData('text')); - } else if (navigator.clipboard) { - return navigator.clipboard - .readText() - .then((res) => { - resolve(res); - }) - .catch((err) => { - reject('粘贴板获取失败', err); - }); - } else { - reject('粘贴板获取失败'); - } - }); -} -// 将函数返回结果转成promise形式,如果函数有返回值则根据返回值的bool类型判断是reject还是resolve,若函数无返回值默认执行resolve -export function transformToPromise(input) { - if (input instanceof Promise) return input; - return new Promise((resolve, reject) => { - if (input || input === undefined) { - resolve(); - } else { - reject(); - } - }); -} - -export function moveArrayItem(arr, sourceIdx, distIdx, direction) { - if ( - !Array.isArray(arr) - || sourceIdx === distIdx - || sourceIdx < 0 - || sourceIdx >= arr.length - || distIdx < 0 - || distIdx >= arr.length - ) { - return arr; - } - const item = arr[sourceIdx]; - if (direction === 'after') { - arr.splice(distIdx + 1, 0, item); - } else { - arr.splice(distIdx, 0, item); - } - if (sourceIdx < distIdx) { - arr.splice(sourceIdx, 1); - } else { - arr.splice(sourceIdx + 1, 1); - } - return arr; -} - -export function transformArrayToMap(arr, key, overwrite = true) { - if (isEmpty(arr) || !Array.isArray(arr)) return {}; - const res = {}; - arr.forEach((item) => { - const curKey = item[key]; - if (item[key] === undefined) return; - if (res[curKey] && !overwrite) return; - res[curKey] = item; - }); - return res; -} - -export function checkPropTypes(value, name, rule, componentName) { - if (typeof rule === 'string') { - rule = new Function(`"use strict"; const PropTypes = arguments[0]; return ${rule}`)(PropTypes2); - } - if (!rule || typeof rule !== 'function') { - console.warn('checkPropTypes should have a function type rule argument'); - return true; - } - const err = rule( - { - [name]: value, - }, - name, - componentName, - 'prop', - null, - ReactPropTypesSecret, - ); - if (err) { - console.warn(err); - } - return !err; -} - -export function transformSchemaToPure(obj) { - const pureObj = (obj) => { - if (Array.isArray(obj)) { - return obj.map((item) => pureObj(item)); - } - if (typeof obj === 'object') { - // 对于undefined及null直接返回 - if (!obj) return obj; - const res = {}; - forEach(obj, (val, key) => { - if (key.startsWith('__') && key !== '__ignoreParse') return; - res[key] = pureObj(val); - }); - return res; - } - return obj; - }; - return pureObj(obj); -} - -export function transformSchemaToStandard(obj) { - const standardObj = (obj) => { - if (Array.isArray(obj)) { - return obj.map((item) => standardObj(item)); - } - if (typeof obj === 'object') { - // 对于undefined及null直接返回 - if (!obj) return obj; - const res = {}; - forEach(obj, (val, key) => { - if (key.startsWith('__') && key !== '__ignoreParse') return; - if (isSchema(val) && key !== 'children' && obj.type !== 'JSSlot') { - res[key] = { - type: 'JSSlot', - value: standardObj(val), - }; - // table特殊处理 - if (key === 'cell') { - res[key].params = ['value', 'index', 'record']; - } - } else { - res[key] = standardObj(val); - } - }); - return res; - } - if (typeof obj === 'function') { - return { - type: 'JSFunction', - value: obj.toString(), - }; - } - if (typeof obj === 'string' && EXPRESSION_REG.test(obj.trim())) { - const regRes = obj.trim().match(EXPRESSION_REG); - return { - type: 'JSExpression', - value: (regRes && regRes[1]) || '', - }; - } - return obj; - }; - return standardObj(obj, false); -} - -export function transformStringToFunction(str) { - if (typeof str !== 'string') return str; - if (inSameDomain() && window.parent.__newFunc) { - return window.parent.__newFunc(`"use strict"; return ${str}`)(); - } - return new Function(`"use strict"; return ${str}`)(); -} - -export function addCssTag(id, content) { - let styleTag = document.getElementById(id); - if (styleTag) { - styleTag.innerHTML = content; - return; - } - styleTag = document.createElement('style'); - styleTag.id = id; - styleTag.class = 'luna-style'; - styleTag.innerHTML = content; - document.head.appendChild(styleTag); -} - -// 注册快捷 -export function registShortCuts(config, appHelper) { - keymaster.filter = (event) => { - const eTarget = event.target || event.srcElement; - const { tagName } = eTarget; - const isInput = !!(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA'); - const isContenteditable = (target) => { - while (target) { - if (target.contentEditable === 'true') return true; - target = target.parentNode; - } - return false; - }; - if (isInput || isContenteditable(eTarget)) { - if (event.metaKey === true && [70, 83].includes(event.keyCode)) event.preventDefault(); // 禁止触发chrome原生的页面保存或查找 - return false; - } - return true; - }; - const keyboardFilter = keymaster.filter; - - const ideMessage = appHelper.utils && appHelper.utils.ideMessage; - - // 复制 - if (!document.copyListener) { - document.copyListener = (e) => { - if (!keyboardFilter(e) || appHelper.isCopying) return; - const schema = appHelper.schemaHelper && appHelper.schemaHelper.schemaMap[appHelper.activeKey]; - if (!schema || !isSchema(schema)) return; - appHelper.isCopying = true; - const schemaStr = serialize(transformSchemaToPure(schema), { - unsafe: true, - }); - setClipboardData(schemaStr) - .then(() => { - ideMessage && ideMessage('success', '当前内容已复制到剪贴板,请使用快捷键Command+v进行粘贴'); - appHelper.emit('schema.copy', schemaStr, schema); - appHelper.isCopying = false; - }) - .catch((errMsg) => { - ideMessage && ideMessage('error', errMsg); - appHelper.isCopying = false; - }); - }; - document.addEventListener('copy', document.copyListener); - if (getParentWinValue('vscode')) { - keymaster('command+c', document.copyListener); - } - } - - // 粘贴 - if (!document.pasteListener) { - const doPaste = (e, text) => { - if (!keyboardFilter(e) || appHelper.isPasting) return; - const { schemaHelper } = appHelper; - let targetKey = appHelper.activeKey; - let direction = 'after'; - const topKey = schemaHelper.schema && schemaHelper.schema.__ctx && schemaHelper.schema.__ctx.lunaKey; - if (!targetKey || topKey === targetKey) { - const { schemaHelper } = appHelper; - const topKey = schemaHelper.schema && schemaHelper.schema.__ctx && schemaHelper.schema.__ctx.lunaKey; - if (!topKey) return; - targetKey = topKey; - direction = 'in'; - } - appHelper.isPasting = true; - const schema = parseObj(text); - if (!isSchema(schema)) { - appHelper.emit('illegalSchema.paste', text); - // ideMessage && ideMessage('error', '当前内容不是模型结构,不能粘贴进来!'); - console.warn('paste schema illegal'); - appHelper.isPasting = false; - return; - } - appHelper.emit('material.add', { - schema, - targetKey, - direction, - }); - appHelper.isPasting = false; - appHelper.emit('schema.paste', schema); - }; - document.pasteListener = (e) => { - const clipboardData = e.clipboardData || window.clipboardData; - const text = clipboardData && clipboardData.getData('text'); - doPaste(e, text); - }; - document.addEventListener('paste', document.pasteListener); - if (getParentWinValue('vscode')) { - keymaster('command+v', (e) => { - const sendIDEMessage = getParentWinValue('sendIDEMessage'); - sendIDEMessage - && sendIDEMessage({ - action: 'readClipboard', - }) - .then((text) => { - doPaste(e, text); - }) - .catch((err) => { - console.warn(err); - }); - }); - } - } - - (config || []).forEach((item) => { - keymaster(item.keyboard, (ev) => { - ev.preventDefault(); - item.handler(ev, appHelper, keymaster); - }); - }); -} - -// 取消注册快捷 -export function unRegistShortCuts(config) { - (config || []).forEach((item) => { - keymaster.unbind(item.keyboard); - }); - if (getParentWinValue('vscode')) { - keymaster.unbind('command+c'); - keymaster.unbind('command+v'); - } - if (document.copyListener) { - document.removeEventListener('copy', document.copyListener); - delete document.copyListener; - } - if (document.pasteListener) { - document.removeEventListener('paste', document.pasteListener); - delete document.pasteListener; - } -} - -export function parseData(schema, self) { - if (isJSExpression(schema)) { - return parseExpression(schema, self); - } - if (typeof schema === 'string') { - return schema.trim(); - } - if (Array.isArray(schema)) { - return schema.map((item) => parseData(item, self)); - } - if (typeof schema === 'function') { - return schema.bind(self); - } - if (typeof schema === 'object') { - // 对于undefined及null直接返回 - if (!schema) return schema; - const res = {}; - forEach(schema, (val, key) => { - if (key.startsWith('__')) return; - res[key] = parseData(val, self); - }); - return res; - } - return schema; -} - -/* 全匹配{{开头,}}结尾的变量表达式,或者对象类型JSExpression,且均不支持省略this */ -// todo: -export function parseExpression(str, self) { - try { - const contextArr = ['"use strict";', 'var __self = arguments[0];']; - contextArr.push('return '); - let tarStr; - // 向前兼容,支持标准协议新格式 - if (typeof str === 'string') { - const regRes = str.trim().match(EXPRESSION_REG); - tarStr = regRes[1]; - } else { - tarStr = (str.value || '').trim(); - } - tarStr = tarStr.replace(/this(\W|$)/g, (a, b) => `__self${b}`); - tarStr = contextArr.join('\n') + tarStr; - // 默认调用顶层窗口的parseObj,保障new Function的window对象是顶层的window对象 - if (inSameDomain() && window.parent.__newFunc) { - return window.parent.__newFunc(tarStr)(self); - } - return new Function(tarStr)(self); - } catch (err) { - debug('parseExpression.error', err, str, self); - return undefined; - } -} - -/** - * 判断组件配置中是否有reactNode且type为function的props - * @param {*} componentInfo - */ -export function hasReactNodeFuncProps(componentInfo) { - const isReactNodeFuncProps = (config) => { - if (config.type === 'ReactNode') { - return config.props && config.props.type === 'function'; - } - if (config.type === 'Mixin') { - return config.props && config.props.reactNodeProps && config.props.reactNodeProps.type === 'function'; - } - }; - return componentInfo && (componentInfo.props || []).some((item) => isReactNodeFuncProps(item)); -} diff --git a/packages/rax-renderer/src/utils/request.ts b/packages/rax-renderer/src/utils/request.ts deleted file mode 100644 index 0d47bd0c9..000000000 --- a/packages/rax-renderer/src/utils/request.ts +++ /dev/null @@ -1,199 +0,0 @@ -// @ts-nocheck - -import 'whatwg-fetch'; -import fetchMtop from '@ali/lib-mtop'; -import fetchJsonp from 'fetch-jsonp'; -import bzbRequest from '@ali/bzb-request'; -import { serialize, buildUrl, parseUrl } from '@ali/b3-one/lib/url'; - -export function get(dataAPI, params = {}, headers = {}, otherProps = {}) { - headers = { - Accept: 'application/json', - ...headers, - }; - dataAPI = buildUrl(dataAPI, params); - return request(dataAPI, 'GET', null, headers, otherProps); -} - -export function post(dataAPI, params = {}, headers = {}, otherProps = {}) { - headers = { - Accept: 'application/json', - 'Content-Type': 'application/x-www-form-urlencoded', - ...headers, - }; - return request( - dataAPI, - 'POST', - headers['Content-Type'].indexOf('application/json') > -1 || Array.isArray(params) - ? JSON.stringify(params) - : serialize(params), - headers, - otherProps, - ); -} - -export function request(dataAPI, method = 'GET', data, headers = {}, otherProps = {}) { - switch (method) { - case 'PUT': - case 'DELETE': - headers = { - Accept: 'application/json', - 'Content-Type': 'application/json', - ...headers, - }; - data = JSON.stringify(data || {}); - break; - } - return new Promise((resolve, reject) => { - if (otherProps.timeout) { - setTimeout(() => { - reject(new Error('timeout')); - }, otherProps.timeout); - } - fetch(dataAPI, { - method, - credentials: 'include', - headers, - body: data, - ...otherProps, - }) - .then(response => { - switch (response.status) { - case 200: - case 201: - case 202: - return response.json(); - case 204: - if (method === 'DELETE') { - return { - success: true, - }; - } else { - return { - __success: false, - code: response.status, - }; - } - case 400: - case 401: - case 403: - case 404: - case 406: - case 410: - case 422: - case 500: - return response - .json() - .then(res => { - return { - __success: false, - code: response.status, - data: res, - }; - }) - .catch(() => { - return { - __success: false, - code: response.status, - }; - }); - } - return null; - }) - .then(json => { - if (json && json.__success !== false) { - resolve(json); - } else { - delete json.__success; - reject(json); - } - }) - .catch(err => { - reject(err); - }); - }); -} - -export function jsonp(dataAPI, params = {}, otherProps = {}) { - return new Promise((resolve, reject) => { - otherProps = { - timeout: 5000, - ...otherProps, - }; - fetchJsonp(buildUrl(dataAPI, params), otherProps) - .then(response => response.json()) - .then(json => { - if (json) { - resolve(json); - } else { - reject(); - } - }) - .catch(err => { - reject(err); - }); - }); -} - -export function mtop(dataAPI, params, otherProps = {}) { - fetchMtop.config.subDomain = otherProps.subDomain || 'm'; - return fetchMtop.request({ - api: dataAPI, - v: '1.0', - data: params, - ecode: otherProps.ecode || 0, - type: otherProps.method || 'GET', - dataType: otherProps.dataType || 'jsonp', - AntiFlood: true, // 防刷 - timeout: otherProps.timeout || 20000, - }); -} - -export function bzb(apiCode, params, otherProps = {}) { - // 通过url参数设置小二工作台请求环境 - const getUrlEnv = () => { - try { - if (window.parent && window.parent.location.host === window.location.host) { - const urlInfo = parseUrl(window.parent && window.parent.location.href); - return urlInfo && urlInfo.params && urlInfo.params._env; - } - const urlInfo = parseUrl(window.location.href); - return urlInfo && urlInfo.params && urlInfo.params._env; - } catch (e) { - return null; - } - }; - - otherProps.method = otherProps.method || 'GET'; - otherProps.env = getUrlEnv() || otherProps.env || 'prod'; - return bzbRequest(apiCode, { - data: params, - ...otherProps, - }); -} - -export async function webTableProxy(req) { - const { _table } = window.parent; - const { VisualEngine } = window; - const { Bus } = VisualEngine; - if (_table) { - const { options } = req; - const { params, oneAPIConfig } = options; - const { code } = oneAPIConfig; - const sheetId = oneAPIConfig['x-model']; - const sheet = await _table.find({ id: sheetId }); - const result = await sheet.instance.fetch({ code }, params); - return result; - } - return new Promise((resolve, reject) => { - Bus.emitter.on('table.ready', async (table) => { - const { options } = req; - const { params, oneAPIConfig } = options; - const { code } = oneAPIConfig; - const sheetId = oneAPIConfig['x-model']; - const sheet = await table.find({ id: sheetId }); - const result = await sheet.instance.fetch({ code }, params); - resolve(result); - }); - }); -} diff --git a/packages/rax-simulator-renderer/package.json b/packages/rax-simulator-renderer/package.json index e4b720aa4..ac52387e1 100644 --- a/packages/rax-simulator-renderer/package.json +++ b/packages/rax-simulator-renderer/package.json @@ -57,5 +57,5 @@ "publishConfig": { "registry": "https://registry.npm.alibaba-inc.com" }, - "homepage": "https://unpkg.alibaba-inc.com/@ali/lowcode-rax-simulator-renderer@1.0.32/build/index.html" + "homepage": "https://unpkg.alibaba-inc.com/@ali/lowcode-rax-simulator-renderer@1.0.33/build/index.html" } diff --git a/packages/rax-simulator-renderer/src/renderer-view.tsx b/packages/rax-simulator-renderer/src/renderer-view.tsx index eb8440d83..cd4248bff 100644 --- a/packages/rax-simulator-renderer/src/renderer-view.tsx +++ b/packages/rax-simulator-renderer/src/renderer-view.tsx @@ -1,4 +1,4 @@ -import RaxEngine from '@ali/lowcode-rax-renderer/lib/index'; +import RaxRenderer from '@ali/lowcode-rax-renderer'; import { History } from 'history'; import { Component, createElement, Fragment } from 'rax'; import { useRouter } from './rax-use-router'; @@ -175,7 +175,7 @@ class Renderer extends Component<{ const { designMode, device } = container; const { rendererContainer: renderer } = this.props; return ( - { + const componentMeta = host.currentDocument?.getComponentMeta(Comp.displayName); + if (componentMeta?.isModal) { + return null; + } + + const { __id, __designMode, ...viewProps } = props; + // mock _leaf,减少性能开销 + const _leaf = { + isEmpty: () => false, + }; + viewProps._leaf = _leaf; + return createElement(Comp, viewProps, children); + } + }); + } + } + + return LowCodeComp; } private _running = false; @@ -599,101 +622,4 @@ function findComponent(libraryMap: LibraryMap, componentName: string, npm?: NpmI return getSubComponent(library, paths); } -const processPropsSchema = (propsSchema: any, propsMap: any, componentsMap: any): any => { - if (!propsSchema) { - return {}; - } - - const result = { ...propsSchema }; - const reg = /^(?:this\.props|props)\.(\S+)$/; - Object.keys(result).map((key: string) => { - if (result[key]?.type === 'JSExpression') { - const { value } = result[key]; - const matched = reg.exec(value); - if (matched) { - const propName = matched[1]; - result[key] = propsMap[propName]; - } - } else if (result[key]?.type === 'JSSlot') { - const schema = result[key].value; - result[key] = createElement(ComponentCreator, { schema, propsMap: {}, componentsMap }); - } - }); - - return result; -}; - -function findComponentCreator(instance: any) { - let isComponentCreator = false; - while (instance && !isComponentCreator) { - instance = instance[INTERNAL]?.__parentInstance; - isComponentCreator = instance instanceof ComponentCreator; - } - return isComponentCreator; -} - -class ComponentCreator extends Rax.Component<{ schema: any; propsMap: any, componentsMap: any }> { - private isModal: boolean; - - constructor(props: any) { - super(props); - const componentMeta = host.currentDocument?.getComponentMeta(props.schema.componentName); - if (componentMeta?.prototype?.isModal()) { - this.isModal = true; - } - } - - render() { - if (this.isModal) { - return null; - } - const { schema, propsMap, componentsMap } = this.props; - const ComponentClass = componentsMap[schema.componentName]; - if (!ComponentClass) { - return null; - } - let children = null; - if (schema.children && schema.children.length > 0) { - children = schema.children.map((item: any, index: number) => createElement(ComponentCreator, { schema: item, propsMap, componentsMap, key: item?.id || index })); - } - const props = processPropsSchema(schema.props, propsMap, componentsMap); - const _leaf = host.currentDocument?.createNode(schema); - - return createElement(ComponentClass, { ...props, _leaf }, children); - } -} - -function getComponentController(schema: NodeSchema, componentsMap: any) { - class ComponentController extends Rax.Component<{ schema: any }> { - renderSchema: any; - - constructor(props: any) { - super(props); - const node = host.currentDocument?.createNode(schema); - this.renderSchema = node?.export(TransformStage.Render) || {}; - } - - // TODO: 暂时解决性能问题 - shouldComponentUpdate() { - return false; - } - - render() { - const { renderSchema } = this; - const { componentName } = renderSchema; - if (componentName === 'Component') { - let children = [] as any; - const propsMap = this.props || {}; - if (renderSchema.children && Array.isArray(renderSchema.children)) { - children = renderSchema.children.map((item: any, index: number) => createElement(ComponentCreator, { schema: item, propsMap, componentsMap, key: item?.id || index })); - } - return createElement('div', {}, children); - } else { - return createElement(ComponentCreator, { schema, propsMap: {}, componentsMap }); - } - } - } - return ComponentController; -} - export default new SimulatorRendererContainer(); diff --git a/packages/react-renderer/build.test.json b/packages/react-renderer/build.test.json new file mode 100644 index 000000000..228c07e7f --- /dev/null +++ b/packages/react-renderer/build.test.json @@ -0,0 +1,6 @@ +{ + "plugins": [ + "build-plugin-component", + "@ali/lowcode-test-mate/plugin/index.ts" + ] +} diff --git a/packages/react-renderer/jest.config.js b/packages/react-renderer/jest.config.js new file mode 100644 index 000000000..7a5f208ff --- /dev/null +++ b/packages/react-renderer/jest.config.js @@ -0,0 +1,20 @@ +const esModules = ['@recore/obx-react'].join('|'); + +module.exports = { + // transform: { + // '^.+\\.[jt]sx?$': 'babel-jest', + // // '^.+\\.(ts|tsx)$': 'ts-jest', + // // '^.+\\.(js|jsx)$': 'babel-jest', + // }, + // testMatch: ['(/tests?/.*(test))\\.[jt]s$'], + transformIgnorePatterns: [ + `/node_modules/(?!${esModules})/`, + ], + moduleFileExtensions: ['ts', 'tsx', 'js', 'json'], + collectCoverage: true, + collectCoverageFrom: [ + 'src/**/*.{ts,tsx}', + '!**/node_modules/**', + '!**/vendor/**', + ], +}; \ No newline at end of file diff --git a/packages/react-renderer/package.json b/packages/react-renderer/package.json index 9a9b10806..9fe3e0306 100644 --- a/packages/react-renderer/package.json +++ b/packages/react-renderer/package.json @@ -9,7 +9,7 @@ "es" ], "scripts": { - "test": "echo \"Error: run tests from root\" && exit 1", + "test": "build-scripts test --config build.test.json", "start": "build-scripts start", "build": "build-scripts build --skip-demo", "prepublishOnly": "npm run build" @@ -24,32 +24,19 @@ "react" ], "dependencies": { - "@ali/b3-one": "^0.0.17", - "@ali/bzb-request": "^2.6.0-beta.13", - "@ali/lib-mtop": "^2.5.1", - "@ali/lowcode-datasource-engine": "^1.0.22", - "@alifd/next": "^1.19.17", - "debug": "^4.1.1", - "events": "^3.0.0", - "fetch-jsonp": "^1.1.3", - "intl-messageformat": "^9.3.1", - "jsonuri": "^2.1.2", - "keymaster": "^1.6.2", - "lodash": "^4.17.11", - "moment": "^2.24.0", - "react-is": "^16.10.1", - "serialize-javascript": "^1.7.0", - "socket.io-client": "^2.2.0", - "whatwg-fetch": "^3.0.0" + "@ali/lowcode-renderer-core": "^1.0.33", + "@alifd/next": "^1.21.16" }, "devDependencies": { + "@alifd/next": "^1.19.17", + "@ali/lowcode-test-mate": "^1.0.1", "@alib/build-scripts": "^0.1.18", "build-plugin-component": "^0.2.10", "build-plugin-fusion": "^0.1.0", "build-plugin-moment-locales": "^0.1.0", - "moment": "^2.24.0", "react": "^16.4.1", - "react-dom": "^16.4.1" + "react-dom": "^16.4.1", + "react-test-renderer": "^16" }, "publishConfig": { "registry": "http://registry.npm.alibaba-inc.com" diff --git a/packages/react-renderer/src/components/Div.tsx b/packages/react-renderer/src/components/Div.tsx deleted file mode 100644 index a392b2a03..000000000 --- a/packages/react-renderer/src/components/Div.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React, { PureComponent } from 'react'; - -export default class DivView extends PureComponent { - static displayName = 'Div'; - - static version = '0.0.0'; - - render() { - return
; - } -} diff --git a/packages/react-renderer/src/components/VisualDom.scss b/packages/react-renderer/src/components/VisualDom.scss deleted file mode 100644 index 074b9dad8..000000000 --- a/packages/react-renderer/src/components/VisualDom.scss +++ /dev/null @@ -1,19 +0,0 @@ -.visual-dom { - .panel-container { - box-sizing: border-box; - border: 1px solid #e9e9e9; - .title { - display: block; - font-size: 12px; - color: #333; - background-color: #ebecf0; - line-height: 28px; - padding: 0 12px; - border-bottom: 1px solid #e9e9e9; - } - .content { - min-height: 20px; - padding: 5px; - } - } -} diff --git a/packages/react-renderer/src/components/VisualDom.tsx b/packages/react-renderer/src/components/VisualDom.tsx deleted file mode 100644 index aee8d45cc..000000000 --- a/packages/react-renderer/src/components/VisualDom.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; -import './VisualDom.scss'; - -export default class VisualDom extends PureComponent { - static displayName = 'VisualDom'; - - static propTypes = { - children: PropTypes.oneOfType([PropTypes.element, PropTypes.arrayOf(PropTypes.element)]), - }; - - static defaultProps = { - children: null, - }; - - render() { - const { children, cell, title, label, text, __componentName } = this.props; - let mainContent = children; - if (cell && typeof cell === 'function') { - mainContent = cell(); - } - return ( -
-
- {title || label || text || __componentName} -
{mainContent}
-
-
- ); - } -} diff --git a/packages/react-renderer/src/context/appContext.ts b/packages/react-renderer/src/context/appContext.ts deleted file mode 100644 index 266cd6bec..000000000 --- a/packages/react-renderer/src/context/appContext.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { createContext } from 'react'; - -const context = createContext({}); -window.__appContext = context; -export default context; diff --git a/packages/react-renderer/src/engine/index.tsx b/packages/react-renderer/src/engine/index.tsx deleted file mode 100644 index d7f8b3fcf..000000000 --- a/packages/react-renderer/src/engine/index.tsx +++ /dev/null @@ -1,230 +0,0 @@ -import React, { Component, PureComponent, createElement as reactCreateElement } from 'react'; -import ReactDOM from 'react-dom'; -import PropTypes from 'prop-types'; -import Debug from 'debug'; -import { isEmpty } from '@ali/b3-one/lib/obj'; -import Div from '@ali/iceluna-comp-div'; -import AppContext from '../context/appContext'; -import { isFileSchema, goldlog } from '../utils'; -import PageEngine from './pageEngine'; -import ComponentEngine from './compEngine'; -import BlockEngine from './blockEngine'; -import AddonEngine from './addonEngine'; -import TempEngine from './tempEngine'; -import BaseEngine from './base'; - -window.React = React; -window.ReactDom = ReactDOM; - -const debug = Debug('engine:entry'); -const ENGINE_COMPS = { - PageEngine, - ComponentEngine, - BlockEngine, - AddonEngine, - TempEngine, - DivEngine: BlockEngine, -}; - -class FaultComponent extends PureComponent { - render() { - // FIXME: errorlog - console.error('render error', this.props); - const { _componentName: componentName } = this.props; - return ( -
- 组件 {componentName} 渲染错误,请打开控制台排查 -
- ); - } -} - -class NotFoundComponent extends PureComponent { - render() { - console.error('component not found:', this.props); - const { _componentName: componentName } = this.props; - return ( -
- 组件 {componentName} 无视图,请打开控制台排查 -
- ); - } -} - -function isReactClass(obj) { - return obj && obj.prototype && (obj.prototype.isReactComponent || obj.prototype instanceof Component); -} - -// eslint-disable-next-line react/no-redundant-should-component-update -export default class Engine extends PureComponent { - static dislayName = 'engine'; - - static propTypes = { - appHelper: PropTypes.object, - components: PropTypes.object, - designMode: PropTypes.string, - suspended: PropTypes.bool, - schema: PropTypes.oneOfType([PropTypes.array, PropTypes.object]), - onCompGetRef: PropTypes.func, - onCompGetCtx: PropTypes.func, - customCreateElement: PropTypes.func, - }; - - static defaultProps = { - appHelper: null, - components: {}, - 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); - } - }; - - patchDidCatch(SetComponent) { - if (!isReactClass(SetComponent)) { - return; - } - if (SetComponent.patchedCatch) { - return; - } - SetComponent.patchedCatch = true; - SetComponent.getDerivedStateFromError = (error) => ({ engineRenderError: true, error }); - const engine = this; - const originRender = SetComponent.prototype.render; - SetComponent.prototype.render = function () { - if (this.state && this.state.engineRenderError) { - this.state.engineRenderError = false; - return engine.createElement(engine.getFaultComponent(), { - ...this.props, - error: this.state.error, - }); - } - return originRender.call(this); - }; - const originShouldComponentUpdate = SetComponent.prototype.shouldComponentUpdate; - SetComponent.prototype.shouldComponentUpdate = function (nextProps, nextState) { - if (nextState && nextState.engineRenderError) { - return true; - } - return originShouldComponentUpdate ? originShouldComponentUpdate.call(this, nextProps, nextState) : true; - }; - } - - createElement(SetComponent, props, children) { - // TODO: enable in runtime mode? - this.patchDidCatch(SetComponent); - return (this.props.customCreateElement || reactCreateElement)(SetComponent, props, children); - } - - getNotFoundComponent() { - return this.props.notFoundComponent || NotFoundComponent; - } - - getFaultComponent() { - return this.props.faultComponent || FaultComponent; - } - - render() { - const { - schema, designMode, appHelper, components, - } = this.props; - if (isEmpty(schema)) { - return null; - } - // 兼容乐高区块模板 - if (schema.componentName !== 'Div' && !isFileSchema(schema)) { - return '模型结构异常'; - } - debug('entry.render'); - const { componentName } = schema; - const allComponents = { ...ENGINE_COMPS, ...components }; - let Comp = allComponents[componentName] || ENGINE_COMPS[`${componentName}Engine`]; - if (Comp && Comp.prototype) { - if (!(Comp.prototype instanceof BaseEngine)) { - Comp = ENGINE_COMPS[`${componentName}Engine`]; - } - } - if (Comp) { - return ( - - - - ); - } - return null; - } -} - -Engine.findDOMNode = ReactDOM.findDOMNode; diff --git a/packages/react-renderer/src/index.ts b/packages/react-renderer/src/index.ts new file mode 100644 index 000000000..b2985db59 --- /dev/null +++ b/packages/react-renderer/src/index.ts @@ -0,0 +1,50 @@ +import React, { Component, PureComponent, createElement, createContext, forwardRef } from 'react'; +import ReactDOM from 'react-dom'; +import { + adapter, + pageRendererFactory, + componentRendererFactory, + blockRendererFactory, + addonRendererFactory, + tempRendererFactory, + rendererFactory +} from '@ali/lowcode-renderer-core'; +import ConfigProvider from '@alifd/next/lib/config-provider'; + +window.React = React; +(window as any).ReactDom = ReactDOM; + +adapter.setRuntime({ + Component, + PureComponent, + createContext, + createElement, + forwardRef, + findDOMNode: ReactDOM.findDOMNode, +}); + +adapter.setRenderers({ + PageRenderer: pageRendererFactory(), + ComponentRenderer: componentRendererFactory(), + BlockRenderer: blockRendererFactory(), + AddonRenderer: addonRendererFactory(), + TempRenderer: tempRendererFactory(), + DivRenderer: blockRendererFactory(), +}); + +adapter.setConfigProvider(ConfigProvider); + +function factory() { + const Renderer = rendererFactory(); + return class ReactRenderer extends Renderer { + constructor(props: any, context: any) { + super(props, context); + } + + isValidComponent(obj: any) { + return obj?.prototype?.isReactComponent || obj?.prototype instanceof Component; + } + } +} + +export default factory(); diff --git a/packages/react-renderer/src/index.tsx b/packages/react-renderer/src/index.tsx deleted file mode 100644 index 53708652f..000000000 --- a/packages/react-renderer/src/index.tsx +++ /dev/null @@ -1,218 +0,0 @@ -import React, { Component, PureComponent, createElement as reactCreateElement } from 'react'; -import ReactDOM from 'react-dom'; -import PropTypes from 'prop-types'; -import Debug from 'debug'; -import ConfigProvider from '@alifd/next/lib/config-provider'; -import { isEmpty } from '@ali/b3-one/lib/obj'; -import AppContext from './context/appContext'; -import { isFileSchema, goldlog } from './utils'; -import PageEngine from './renderer/page'; -import ComponentEngine from './renderer/component'; -import BlockEngine from './renderer/block'; -import AddonEngine from './renderer/addon'; -import TempEngine from './renderer/temp'; -import BaseEngine from './renderer/base'; -import Div from './components/Div'; - -window.React = React; -window.ReactDom = ReactDOM; - -const debug = Debug('renderer:entry'); -const ENGINE_COMPS = { - PageEngine, - ComponentEngine, - BlockEngine, - AddonEngine, - TempEngine, - DivEngine: BlockEngine, -}; - -class FaultComponent extends PureComponent { - render() { - // FIXME: errorlog - console.error('render error', this.props); - return ( -
- 组件渲染异常,请查看控制台日志 -
- ); - } -} - -class NotFoundComponent extends PureComponent { - render() { - console.error('component not found', this.props); - return
{this.props.children || 'Component Not Found'}
; - } -} - -function isReactClass(obj) { - return obj && obj.prototype && (obj.prototype.isReactComponent || obj.prototype instanceof Component); -} - -export default class Renderer extends Component { - static dislayName = 'renderer'; - - static propTypes = { - appHelper: PropTypes.object, - components: PropTypes.object, - designMode: PropTypes.string, - suspended: PropTypes.bool, - schema: PropTypes.oneOfType([PropTypes.array, PropTypes.object]), - onCompGetRef: PropTypes.func, - onCompGetCtx: PropTypes.func, - customCreateElement: PropTypes.func, - }; - - static defaultProps = { - appHelper: null, - components: {}, - 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); - } - }; - - patchDidCatch(SetComponent) { - if (!isReactClass(SetComponent)) { - return; - } - if (SetComponent.patchedCatch) { - return; - } - SetComponent.patchedCatch = true; - SetComponent.getDerivedStateFromError = (error) => { - return { engineRenderError: true, error }; - }; - const engine = this; - const originRender = SetComponent.prototype.render; - SetComponent.prototype.render = function () { - if (this.state && this.state.engineRenderError) { - this.state.engineRenderError = false; - return engine.createElement(engine.getFaultComponent(), { - ...this.props, - error: this.state.error, - }); - } - return originRender.call(this); - }; - const originShouldComponentUpdate = SetComponent.prototype.shouldComponentUpdate; - SetComponent.prototype.shouldComponentUpdate = function (nextProps, nextState) { - if (nextState && nextState.engineRenderError) { - return true; - } - return originShouldComponentUpdate ? originShouldComponentUpdate.call(this, nextProps, nextState) : true; - }; - } - - createElement(SetComponent, props, children) { - // TODO: enable in runtime mode? - this.patchDidCatch(SetComponent); - return (this.props.customCreateElement || reactCreateElement)(SetComponent, props, children); - } - - getNotFoundComponent() { - return this.props.notFoundComponent || NotFoundComponent; - } - - getFaultComponent() { - return this.props.faultComponent || FaultComponent; - } - - render() { - const { schema, designMode, appHelper, components } = this.props; - if (isEmpty(schema)) { - return null; - } - // 兼容乐高区块模板 - if (schema.componentName !== 'Div' && !isFileSchema(schema)) { - return '模型结构异常'; - } - debug('entry.render'); - const { componentName } = schema; - const allComponents = { ...ENGINE_COMPS, ...components }; - let Comp = allComponents[componentName] || ENGINE_COMPS[`${componentName}Engine`]; - if (Comp && Comp.prototype) { - if (!(Comp.prototype instanceof BaseEngine)) { - Comp = ENGINE_COMPS[`${componentName}Engine`]; - } - } - if (Comp) { - return ( - - - - - - ); - } - return null; - } -} - -Renderer.findDOMNode = ReactDOM.findDOMNode; diff --git a/packages/react-renderer/src/renderer/addon.tsx b/packages/react-renderer/src/renderer/addon.tsx deleted file mode 100644 index 8e2448dd6..000000000 --- a/packages/react-renderer/src/renderer/addon.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import Debug from 'debug'; -import classnames from 'classnames'; -import AppContext from '../context/appContext'; -import BaseRenderer from './base'; -import { isSchema, getFileCssName, isEmpty, goldlog } from '../utils'; - -const debug = Debug('renderer:addon'); - -export default class AddonRenderer extends BaseRenderer { - static dislayName = 'addon-renderer'; - - static propTypes = { - config: PropTypes.object, - __schema: PropTypes.object, - }; - - static defaultProps = { - config: {}, - __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 || {}); - if (isEmpty(props.config) || !props.config.addonKey) { - console.warn('luna addon has wrong config'); - this.state.__hasError = true; - return; - } - // 注册插件 - this.addonKey = props.config.addonKey; - this.appHelper.addons = this.appHelper.addons || {}; - this.appHelper.addons[this.addonKey] = this; - this.__initDataSource(props); - this.open = this.open || (() => {}); - this.close = this.close || (() => {}); - this.__setLifeCycleMethods('constructor', arguments); - debug(`addon.constructor - ${schema.fileName}`); - } - - async getSnapshotBeforeUpdate() { - super.getSnapshotBeforeUpdate(...arguments); - debug(`addon.getSnapshotBeforeUpdate - ${this.props.__schema.fileName}`); - } - - async componentDidMount() { - super.componentDidMount(...arguments); - debug(`addon.componentDidMount - ${this.props.__schema.fileName}`); - } - - async componentDidUpdate() { - super.componentDidUpdate(...arguments); - debug(`addon.componentDidUpdate - ${this.props.__schema.fileName}`); - } - - async componentWillUnmount() { - super.componentWillUnmount(...arguments); - // 注销插件 - const config = this.props.config || {}; - if (config && this.appHelper.addons) { - delete this.appHelper.addons[config.addonKey]; - } - debug(`addon.componentWillUnmount - ${this.props.__schema.fileName}`); - } - - async componentDidCatch() { - super.componentDidCatch(...arguments); - debug(`addon.componentDidCatch - ${this.props.__schema.fileName}`); - } - - goldlog = (goKey, params) => { - const { addonKey, addonConfig = {} } = this.props.config || {}; - goldlog( - goKey, - { - addonKey, - package: addonConfig.package, - version: addonConfig.version, - ...this.appHelper.logParams, - ...params, - }, - 'addon', - ); - }; - - get utils() { - const { utils = {} } = this.context.config || {}; - return { ...this.appHelper.utils, ...utils }; - } - - render() { - const { __schema } = this.props; - - if (!isSchema(__schema, true) || __schema.componentName !== 'Addon') { - return '插件schema结构异常!'; - } - - debug(`addon.render - ${__schema.fileName}`); - this.__generateCtx({ - component: this, - }); - this.__render(); - - const { id, className, style } = this.__parseData(__schema.props); - return ( -
- - {this.__createDom()} - -
- ); - } -} diff --git a/packages/react-renderer/src/renderer/base.tsx b/packages/react-renderer/src/renderer/base.tsx deleted file mode 100644 index 45d30f1c1..000000000 --- a/packages/react-renderer/src/renderer/base.tsx +++ /dev/null @@ -1,620 +0,0 @@ -/* eslint-disable no-proto */ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; -import Debug from 'debug'; -import { create as createDataSourceEngine } from '@ali/lowcode-datasource-engine/interpret'; -import Div from '../components/Div'; -import VisualDom from '../components/VisualDom'; -import AppContext from '../context/appContext'; -import DataHelper from '../utils/dataHelper'; -import { - forEach, - getValue, - parseData, - parseExpression, - isEmpty, - isSchema, - isFileSchema, - isJSExpression, - isJSSlot, - isJSFunction, - transformArrayToMap, - transformStringToFunction, - checkPropTypes, - generateI18n, - acceptsRef, -} from '../utils'; - -const debug = Debug('renderer:base'); -const DESIGN_MODE = { - EXTEND: 'extend', - BORDER: 'border', - PREVIEW: 'preview', -}; -const OVERLAY_LIST = ['Dialog', 'Overlay', 'Animate', 'ConfigProvider']; -let scopeIdx = 0; - -export default class BaseRender extends PureComponent { - static dislayName = 'base-renderer'; - - static propTypes = { - locale: PropTypes.string, - messages: PropTypes.object, - __appHelper: PropTypes.object, - __components: 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 = () => 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', {}); - let fn = lifeCycleMethods[method]; - if (fn) { - // TODO, cache - if (isJSExpression(fn) || isJSFunction(fn)) { - fn = parseExpression(fn, this); - } - if (typeof fn !== 'function') { - console.error(`生命周期${method}类型不符`, fn); - return; - } - try { - return fn.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) => { - if (isJSExpression(val) || isJSFunction(val)) { - val = parseExpression(val, this); - } - if (typeof val !== 'function') { - console.error(`自定义函数${key}类型不符`, val); - return; - } - 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 dataSource = (schema && schema.dataSource) || {}; - // requestHandlersMap 存在才走数据源引擎方案 - if (props?.__appHelper?.requestHandlersMap) { - const { dataSourceMap, reloadDataSource } = createDataSourceEngine(dataSource, this, { - requestHandlersMap: props.__appHelper.requestHandlersMap, - }); - this.dataSourceMap = dataSourceMap; - this.reloadDataSource = () => new Promise((resolve) => { - debug('reload data source'); - // this.__showPlaceholder = true; - reloadDataSource().then(() => { - // this.__showPlaceholder = false; - // @TODO 是否需要 forceUpate - // this.forceUpdate(); - resolve(); - }); - }); - } else { - const appHelper = props.__appHelper; - this.__dataHelper = new DataHelper(this, dataSource, appHelper, (config) => this.__parseData(config)); - this.dataSourceMap = this.__dataHelper.dataSourceMap; - this.reloadDataSource = () => 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); - }); - }); - } - // 设置容器组件占位,若设置占位则在初始异步请求完成之前用loading占位且不渲染容器组件内部内容 - // @TODO __showPlaceholder 的逻辑一旦开启就关不掉,先注释掉了 - /* 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; - 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; - }; - - getSchemaChildren = (schema) => { - if (!schema || !schema.props) { - return schema?.children; - } - if (!schema.children) return schema.props.children; - if (!schema.props.children) return schema.children; - let _children = [].concat(schema.children); - if (Array.isArray(schema.props.children)) { - _children = _children.concat(schema.props.children); - } else { - _children.push(schema.props.children); - } - return _children; - }; - - __createDom = () => { - const { __schema, __ctx, __components = {} } = this.props; - const self = {}; - self.__proto__ = __ctx || this; - const _children = this.getSchemaChildren(__schema); - return this.__createVirtualDom(_children, self, { - schema: __schema, - Comp: __components[__schema.componentName], - }); - }; - - // 将模型结构转换成react Element - // schema 模型结构 - // self 为每个渲染组件构造的上下文,self是自上而下继承的 - // parentInfo 父组件的信息,包含schema和Comp - // idx 若为循环渲染的循环Index - __createVirtualDom = (schema, self, parentInfo, idx) => { - const { engine } = this.context || {}; - try { - if (!schema) return null; - const { __appHelper: appHelper, __components: components = {} } = this.props || {}; - - if (isJSExpression(schema)) { - return parseExpression(schema, self); - } - if (isJSSlot(schema)) { - return this.__createVirtualDom(schema.value, self, parentInfo); - } - 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, idy) => this.__createVirtualDom(item, self, parentInfo, item && item.__ctx && item.__ctx.lunaKey ? '' : idy)); - } - // FIXME - const _children = this.getSchemaChildren(schema); - // 解析占位组件 - if (schema.componentName === 'Flagment' && _children) { - const tarChildren = isJSExpression(_children) ? parseExpression(_children, self) : _children; - return this.__createVirtualDom(tarChildren, self, parentInfo); - } - - if (schema.$$typeof) { - return schema; - } - if (!isSchema(schema)) return null; - let Comp = components[schema.componentName] || engine.getNotFoundComponent(); - - if (schema.hidden) { - return null; - } - - if (schema.loop != null) { - const loop = parseData(schema.loop, self); - if ((Array.isArray(loop) && loop.length > 0) || isJSExpression(loop)) { - return this.__createLoopVirtualDom( - { - ...schema, - loop, - }, - self, - parentInfo, - idx, - ); - } - } - const condition = schema.condition == null ? 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, - } - : {}; - if (engine && engine.props.designMode) { - otherProps.__designMode = engine.props.designMode; - } - const componentInfo = {}; - const props = - this.__parseProps(schema.props, self, '', { - schema, - Comp, - componentInfo: { - ...componentInfo, - props: transformArrayToMap(componentInfo.props, 'name'), - }, - }) || {}; - // 对于可以获取到ref的组件做特殊处理 - if (acceptsRef(Comp)) { - otherProps.ref = (ref) => { - const refProps = props.ref; - if (refProps && typeof refProps === 'string') { - this[refProps] = ref; - } - ref && engine && engine.props.onCompGetRef(schema, ref); - }; - } - // scope需要传入到组件上 - if (scopeKey && this.__compScopes[scopeKey]) { - props.__scope = this.__compScopes[scopeKey]; - } - // FIXME 这里清除 key 是为了避免循环渲染中更改 key 导致的渲染重复 - props.key = ''; - 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; - } - - props.__id = schema.id; - if (!props.key) { - props.key = props.__id; - } - - let child = null; - if (/*!isFileSchema(schema) && */!!_children) { - child = this.__createVirtualDom( - isJSExpression(_children) ? parseExpression(_children, self) : _children, - self, - { - schema, - Comp, - }, - ); - } - const renderComp = (props) => engine.createElement(Comp, props, child); - // 设计模式下的特殊处理 - 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 ( -
- {renderComp({ ...props, ...overlayProps })} -
- ); - } - // 虚拟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 }); - } catch (e) { - return engine.createElement(engine.getFaultComponent(), { - error: e, - schema, - self, - parentInfo, - idx, - }); - } - }; - - __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, - ); - }); - }; - - __parseProps = (props, self, path, info) => { - const { schema, Comp, componentInfo = {} } = info; - const propInfo = getValue(componentInfo.props, path); - // FIXME! 将这行逻辑外置,解耦,线上环境不要验证参数,调试环境可以有,通过传参自定义 - 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 })); - } - return checkProps(function () { - const args = {}; - if (Array.isArray(params) && params.length) { - params.forEach((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 = transformStringToFunction(props.value); - } - if (isJSSlot(props)) { - const { params, value } = props; - if (!isSchema(value) || isEmpty(value)) return undefined; - return parseReactNode(value, params); - } - // 兼容通过componentInfo判断的情况 - if (isSchema(props)) { - const isReactNodeFunction = !!( - propInfo - && propInfo.type === 'ReactNode' - && propInfo.props - && propInfo.props.type === 'function' - ); - - const isMixinReactNodeFunction = !!( - propInfo - && propInfo.type === 'Mixin' - && propInfo.props - && propInfo.props.types - && propInfo.props.types.indexOf('ReactNode') > -1 - && propInfo.props.reactNodeProps - && propInfo.props.reactNodeProps.type === 'function' - ); - return parseReactNode( - props, - isReactNodeFunction - ? propInfo.props.params - : isMixinReactNodeFunction - ? propInfo.props.reactNodeProps.params - : null, - ); - } - if (Array.isArray(props)) { - return checkProps(props.map((item, idx) => this.__parseProps(item, self, path ? `${path}.${idx}` : idx, info))); - } - if (typeof props === 'function') { - return checkProps(props.bind(self)); - } - 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); - } - if (typeof props === 'string') { - return checkProps(props.trim()); - } - return checkProps(props); - }; - - get requestHandlersMap() { - return this.appHelper && this.appHelper.requestHandlersMap; - } - - 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; - } -} diff --git a/packages/react-renderer/src/renderer/block.tsx b/packages/react-renderer/src/renderer/block.tsx deleted file mode 100644 index 5dcc9757c..000000000 --- a/packages/react-renderer/src/renderer/block.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import Debug from 'debug'; -import classnames from 'classnames'; -// import Loading from '@alifd/next/lib/loading'; -// import '@alifd/next/lib/loading/style'; -import BaseRenderer from './base'; -import AppContext from '../context/appContext'; -import { isSchema, getFileCssName } from '../utils'; - -const debug = Debug('renderer:block'); - -export default class BlockRenderer extends BaseRenderer { - static dislayName = 'block-renderer'; - - 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, __components } = this.props; - - if (!isSchema(__schema, true) || (__schema.componentName !== 'Block' && __schema.componentName !== 'Div')) { - return '区块schema结构异常!'; - } - - debug(`block.render - ${__schema.fileName}`); - this.__generateCtx(); - this.__render(); - - const props = this.__parseData(__schema.props); - const { id, className, style, autoLoading, defaultHeight = 300, loading } = props; - - const { Block } = __components; - if (Block) { - const { engine } = this.context || {}; - return ( - - {engine.createElement( - Block, - { - ...props, - ref: this.__getRef, - className: classnames(getFileCssName(__schema.fileName), className, this.props.className), - __id: __schema.id, - }, - this.__createDom(), - )} - - ); - } - - const renderContent = () => ( - - {this.__createDom()} - - ); - - // if (autoLoading || loading !== undefined) { - // return ( - // - // {!this.__showPlaceholder && renderContent()} - // - // ); - // } - - return ( -
- {renderContent()} -
- ); - } -} diff --git a/packages/react-renderer/src/renderer/component.tsx b/packages/react-renderer/src/renderer/component.tsx deleted file mode 100644 index 66e67ddbd..000000000 --- a/packages/react-renderer/src/renderer/component.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import Debug from 'debug'; -import classnames from 'classnames'; -// import Loading from '@alifd/next/lib/loading'; -// import '@alifd/next/lib/loading/style'; -import AppContext from '../context/appContext'; -import BaseRenderer from './base'; -import { isSchema, getFileCssName } from '../utils'; - -const debug = Debug('renderer:comp'); - -export default class CompRenderer extends BaseRenderer { - static dislayName = 'comp-renderer'; - - 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() { - 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}`); - this.__generateCtx({ - component: this, - }); - this.__render(); - - const { id, className, style, noContainer, autoLoading, defaultHeight = 300, loading } = this.__parseData( - __schema.props, - ); - const renderContent = () => ( - - {this.__createDom()} - - ); - - if (noContainer) { - return renderContent(); - } - // if (autoLoading || loading !== undefined) { - // return ( - // - // {!this.__showPlaceholder && renderContent()} - // - // ); - // } - return ( -
- {renderContent()} -
- ); - } -} diff --git a/packages/react-renderer/src/renderer/page.tsx b/packages/react-renderer/src/renderer/page.tsx deleted file mode 100644 index 7581e4731..000000000 --- a/packages/react-renderer/src/renderer/page.tsx +++ /dev/null @@ -1,166 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import Debug from 'debug'; -import classnames from 'classnames'; -// import Loading from '@alifd/next/lib/loading'; -// import '@alifd/next/lib/loading/style'; -import AppContext from '../context/appContext'; -import BaseRenderer from './base'; -import { isSchema, getFileCssName, parseData } from '../utils'; - -const debug = Debug('renderer:page'); - -export default class PageRenderer extends BaseRenderer { - static dislayName = 'page-renderer'; - 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(prevProps) { - const { __ctx } = this.props; - const prevState = parseData(prevProps.__schema.state, __ctx); - const newState = parseData(this.props.__schema.state, __ctx); - // 当编排的时候修改schema.state值,需要将最新schema.state值setState - if (JSON.stringify(newState) != JSON.stringify(prevState)) { - this.setState(newState); - } - - 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, __components } = this.props; - if (!isSchema(__schema, true) || __schema.componentName !== 'Page') { - return '页面schema结构异常!'; - } - debug(`page.render - ${__schema.fileName}`); - - this.__bindCustomMethods(this.props); - this.__initDataSource(this.props); - - // this.__setLifeCycleMethods('constructor', arguments); - - this.__generateCtx({ - page: this, - }); - this.__render(); - - const props = this.__parseData(__schema.props); - const { id, className, style, autoLoading, defaultHeight = 300, loading, - } = props; - - const { Page } = __components; - if (Page) { - const { engine } = this.context || {}; - return ( - - {engine.createElement( - Page, - { - ...props, - ref: this.__getRef, - className: classnames(getFileCssName(__schema.fileName), className, this.props.className), - __id: __schema.id, - }, - this.__createDom(), - )} - - ); - } - - const renderContent = () => ( - - {this.__createDom()} - - ); - - // if (autoLoading || loading !== undefined) { - // return ( - // - // {!this.__showPlaceholder && renderContent()} - // - // ); - // } - - return ( -
- {renderContent()} -
- ); - } -} diff --git a/packages/react-renderer/src/renderer/temp.tsx b/packages/react-renderer/src/renderer/temp.tsx deleted file mode 100644 index 016f90159..000000000 --- a/packages/react-renderer/src/renderer/temp.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import Debug from 'debug'; -import AppContext from '../context/appContext'; -import BaseRenderer from './base'; -import { isSchema } from '../utils'; - -const debug = Debug('renderer:temp'); - -export default class TempRenderer extends BaseRenderer { - static dislayName = 'temp-renderer'; - - 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; - this.cacheSetState = setState; - ctx.setState = (...args) => { - setState.call(ctx, ...args); - setTimeout(() => this.forceUpdate(), 0); - }; - debug(`temp.componentDidMount - ${this.props.__schema.fileName}`); - } - - componentDidUpdate() { - 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 ( -
- {this.__createDom()} -
- ); - } -} diff --git a/packages/react-renderer/tests/__snapshots__/index.test.tsx.snap b/packages/react-renderer/tests/__snapshots__/index.test.tsx.snap new file mode 100644 index 000000000..90d2f6a5d --- /dev/null +++ b/packages/react-renderer/tests/__snapshots__/index.test.tsx.snap @@ -0,0 +1,865 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`React Renderer render basic case 1`] = ` +
+
+
+ +
+
+
+
+
+ +
+
+ + + + + + + + 请选择 + + +   + + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+ + + + + + + + 请选择 + + +   + + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+ + + + + +
+
+
+ + +
+ +
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + +
+
+ 姓名 +
+
+
+ 年龄 +
+
+
+ 邮箱 +
+
+
+ 没有数据 +
+
+
+
+
+
+ +
+ + + + + + +
+ + + + 1 + + / + 10 + + + 到第 + + + + + + 页 + + +
+
+
+
+
+
+ Component Not Found +
+
+`; diff --git a/packages/react-renderer/tests/fixtures/schema/basic.ts b/packages/react-renderer/tests/fixtures/schema/basic.ts new file mode 100644 index 000000000..43b92f041 --- /dev/null +++ b/packages/react-renderer/tests/fixtures/schema/basic.ts @@ -0,0 +1,567 @@ +export default{ + "componentName": "Page", + "id": "node_dockcviv8fo1", + "props": { + "ref": "outterView", + "autoLoading": true, + "style": { + "padding": "0 5px 0 5px" + } + }, + "fileName": "test", + "dataSource": { + "list": [] + }, + "state": { + "text": "outter", + "isShowDialog": false + }, + "css": "body {font-size: 12px;} .botton{width:100px;color:#ff00ff}", + "lifeCycles": { + "componentDidMount": { + "type": "JSFunction", + "value": "function() {\n console.log('did mount');\n }" + }, + "componentWillUnmount": { + "type": "JSFunction", + "value": "function() {\n console.log('will umount');\n }" + } + }, + "methods": { + "testFunc": { + "type": "JSFunction", + "value": "function() {\n console.log('test func');\n }" + }, + "onClick": { + "type": "JSFunction", + "value": "function() {\n this.setState({\n isShowDialog: true\n })\n }" + }, + "closeDialog": { + "type": "JSFunction", + "value": "function() {\n this.setState({\n isShowDialog: false\n })\n }" + } + }, + "children": [ + { + "componentName": "Box", + "id": "node_dockcy8n9xed", + "props": { + "style": { + "backgroundColor": "rgba(31,56,88,0.1)", + "padding": "12px 12px 12px 12px" + } + }, + "children": [ + { + "componentName": "Box", + "id": "node_dockcy8n9xee", + "props": { + "style": { + "padding": "12px 12px 12px 12px", + "backgroundColor": "#ffffff" + } + }, + "children": [ + { + "componentName": "Breadcrumb", + "id": "node_dockcy8n9xef", + "props": { + "prefix": "next-", + "maxNode": 100, + "component": "nav" + }, + "children": [ + { + "componentName": "Breadcrumb.Item", + "id": "node_dockcy8n9xeg", + "props": { + "prefix": "next-", + "children": "首页" + } + }, + { + "componentName": "Breadcrumb.Item", + "id": "node_dockcy8n9xei", + "props": { + "prefix": "next-", + "children": "品质中台" + } + }, + { + "componentName": "Breadcrumb.Item", + "id": "node_dockcy8n9xek", + "props": { + "prefix": "next-", + "children": "商家品质页面管理" + } + }, + { + "componentName": "Breadcrumb.Item", + "id": "node_dockcy8n9xem", + "props": { + "prefix": "next-", + "children": "质检知识条配置" + } + } + ] + } + ] + }, + { + "componentName": "Box", + "id": "node_dockcy8n9xeo", + "props": { + "style": { + "marginTop": "12px", + "backgroundColor": "#ffffff" + } + }, + "children": [ + { + "componentName": "Form", + "id": "node_dockcy8n9xep", + "props": { + "inline": true, + "style": { + "marginTop": "12px", + "marginRight": "12px", + "marginLeft": "12px" + }, + "__events": [] + }, + "children": [ + { + "componentName": "Form.Item", + "id": "node_dockcy8n9xeq", + "props": { + "style": { + "marginBottom": "0" + }, + "label": "类目名:" + }, + "children": [ + { + "componentName": "Select", + "id": "node_dockcy8n9xer", + "props": { + "mode": "single", + "hasArrow": true, + "cacheValue": true, + "style": { + "width": "150px" + } + } + } + ] + }, + { + "componentName": "Form.Item", + "id": "node_dockcy8n9xes", + "props": { + "style": { + "marginBottom": "0" + }, + "label": "项目类型:" + }, + "children": [ + { + "componentName": "Select", + "id": "node_dockcy8n9xet", + "props": { + "mode": "single", + "hasArrow": true, + "cacheValue": true, + "style": { + "width": "200px" + } + } + } + ] + }, + { + "componentName": "Form.Item", + "id": "node_dockcy8n9xeu", + "props": { + "style": { + "marginBottom": "0" + }, + "label": "项目 ID:" + }, + "children": [ + { + "componentName": "Input", + "id": "node_dockcy8n9xev", + "props": { + "hasBorder": true, + "size": "medium", + "autoComplete": "off", + "style": { + "width": "200px" + } + } + } + ] + }, + { + "componentName": "Button.Group", + "id": "node_dockcy8n9xew", + "props": {}, + "children": [ + { + "componentName": "Button", + "id": "node_dockcy8n9xex", + "props": { + "type": "primary", + "style": { + "margin": "0 5px 0 5px" + }, + "htmlType": "submit", + "children": "搜索" + } + }, + { + "componentName": "Button", + "id": "node_dockcy8n9xe10", + "props": { + "type": "normal", + "style": { + "margin": "0 5px 0 5px" + }, + "htmlType": "reset", + "children": "清空" + } + } + ] + } + ] + } + ] + }, + { + "componentName": "Box", + "id": "node_dockcy8n9xe1f", + "props": { + "style": { + "backgroundColor": "#ffffff", + "paddingBottom": "24px", + "display": "flex", + "flexDirection": "row", + "justifyContent": "flex-end" + } + }, + "children": [ + { + "componentName": "Button", + "id": "node_dockd5nrh9p4", + "props": { + "type": "primary", + "size": "medium", + "htmlType": "button", + "component": "button", + "children": "新建配置", + "style": {}, + "__events": [ + { + "type": "componentEvent", + "name": "onClick", + "relatedEventName": "onClick" + } + ], + "onClick": { + "type": "JSFunction", + "value": "function(){ this.onClick() }" + } + } + } + ] + }, + { + "componentName": "Box", + "id": "node_dockd5nrh9p5", + "props": {}, + "children": [ + { + "componentName": "Table", + "id": "node_dockjielosj1", + "props": { + "showMiniPager": true, + "showActionBar": true, + "actionBar": [ + { + "title": "新增", + "type": "primary" + }, + { + "title": "编辑" + } + ], + "columns": [ + { + "dataKey": "name", + "width": 200, + "align": "center", + "title": "姓名", + "editType": "text" + }, + { + "dataKey": "age", + "width": 200, + "align": "center", + "title": "年龄" + }, + { + "dataKey": "email", + "width": 200, + "align": "center", + "title": "邮箱" + } + ], + "data": [ + { + "name": "王小", + "id": "1", + "age": 15000, + "email": "aaa@abc.com" + }, + { + "name": "王中", + "id": "2", + "age": 25000, + "email": "bbb@abc.com" + }, + { + "name": "王大", + "id": "3", + "age": 35000, + "email": "ccc@abc.com" + } + ], + "actionTitle": "操作", + "actionWidth": 180, + "actionType": "link", + "actionFixed": "right", + "actionHidden": false, + "maxWebShownActionCount": 2, + "actionColumn": [ + { + "title": "编辑", + "callback": { + "type": "JSFunction", + "value": "(rowData, action, table) => {\n return table.editRow(rowData).then((row) => {\n console.log(row);\n });\n }" + }, + "device": [ + "desktop" + ] + }, + { + "title": "保存", + "callback": { + "type": "JSFunction", + "value": "(rowData, action, table) => { \nreturn table.saveRow(rowData).then((row) => { \nconsole.log(row); \n}); \n}" + }, + "mode": "EDIT" + } + ] + } + }, + { + "componentName": "Box", + "id": "node_dockd5nrh9pg", + "props": { + "style": { + "display": "flex", + "flexDirection": "row", + "justifyContent": "flex-end" + } + }, + "children": [ + { + "componentName": "Pagination", + "id": "node_dockd5nrh9pf", + "props": { + "prefix": "next-", + "type": "normal", + "shape": "normal", + "size": "medium", + "defaultCurrent": 1, + "total": 100, + "pageShowCount": 5, + "pageSize": 10, + "pageSizePosition": "start", + "showJump": true, + "style": {} + } + } + ] + } + ] + } + ] + }, + { + "componentName": "Dialog", + "id": "node_dockcy8n9xe1h", + "props": { + "prefix": "next-", + "footerAlign": "right", + "footerActions": [ + "ok", + "cancel" + ], + "closeable": "esc,close", + "hasMask": true, + "align": "cc cc", + "minMargin": 40, + "visible": { + "type": "JSExpression", + "value": "this.state.isShowDialog" + }, + "title": "标题", + "events": [], + "__events": [ + { + "type": "componentEvent", + "name": "onCancel", + "relatedEventName": "closeDialog" + }, + { + "type": "componentEvent", + "name": "onClose", + "relatedEventName": "closeDialog" + }, + { + "type": "componentEvent", + "name": "onOk", + "relatedEventName": "testFunc" + } + ], + "onCancel": { + "type": "JSFunction", + "value": "function(){ this.closeDialog() }" + }, + "onClose": { + "type": "JSFunction", + "value": "function(){ this.closeDialog() }" + }, + "onOk": { + "type": "JSFunction", + "value": "function(){ this.testFunc() }" + } + }, + "children": [ + { + "componentName": "Form", + "id": "node_dockd5nrh9pi", + "props": { + "inline": false, + "labelAlign": "top", + "labelTextAlign": "right", + "size": "medium" + }, + "children": [ + { + "componentName": "Form.Item", + "id": "node_dockd5nrh9pj", + "props": { + "style": { + "marginBottom": "0", + "minWidth": "200px", + "minHeight": "28px" + }, + "label": "商品类目" + }, + "children": [ + { + "componentName": "Select", + "id": "node_dockd5nrh9pk", + "props": { + "mode": "single", + "hasArrow": true, + "cacheValue": true + } + } + ] + }, + { + "componentName": "Form.Item", + "id": "node_dockd5nrh9pl", + "props": { + "style": { + "marginBottom": "0", + "minWidth": "200px", + "minHeight": "28px" + }, + "label": "商品类目" + }, + "children": [ + { + "componentName": "Select", + "id": "node_dockd5nrh9pm", + "props": { + "mode": "single", + "hasArrow": true, + "cacheValue": true + } + } + ] + }, + { + "componentName": "Form.Item", + "id": "node_dockd5nrh9pn", + "props": { + "style": { + "marginBottom": "0", + "minWidth": "200px", + "minHeight": "28px" + }, + "label": "商品类目", + "asterisk": true + }, + "children": [ + { + "componentName": "Select", + "id": "node_dockd5nrh9po", + "props": { + "mode": "single", + "hasArrow": true, + "cacheValue": true + } + } + ] + }, + { + "componentName": "Form.Item", + "id": "node_dockd5nrh9pp", + "props": { + "style": { + "marginBottom": "0", + "minWidth": "200px", + "minHeight": "28px" + }, + "label": "商品类目" + }, + "children": [ + { + "componentName": "Input", + "id": "node_dockd5nrh9pr", + "props": { + "hasBorder": true, + "size": "medium", + "autoComplete": "off" + } + }, + ] + }, + ] + }, + ] + }, + { + "componentName": "ErrorComponent", + "id": "node_dockd5nrh9pr", + "props": { + "name": "error" + } + } + ] +} \ No newline at end of file diff --git a/packages/react-renderer/tests/index.test.tsx b/packages/react-renderer/tests/index.test.tsx new file mode 100644 index 000000000..72bc04a1a --- /dev/null +++ b/packages/react-renderer/tests/index.test.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import renderer from 'react-test-renderer'; +import { Box, Breadcrumb, Form, Select, Input, Button, Table, Pagination, Dialog } from '@alifd/next'; +import ReactRenderer from '../src'; +import schema from './fixtures/schema/basic'; + +describe('React Renderer', () => { + it('render basic case', () => { + const components = { + Box, + Breadcrumb, + 'Breadcrumb.Item': Breadcrumb.Item, + Form, + 'Form.Item': Form.Item, + Select, + Input, + Button, + 'Button.Group': Button.Group, + Table, + Pagination, + Dialog, + }; + const content = ( + ); + const tree = renderer.create(content).toJSON(); + expect(tree).toMatchSnapshot(); + }); +}); diff --git a/packages/react-simulator-renderer/CHANGELOG.md b/packages/react-simulator-renderer/CHANGELOG.md index 659e51423..3623f0fc5 100644 --- a/packages/react-simulator-renderer/CHANGELOG.md +++ b/packages/react-simulator-renderer/CHANGELOG.md @@ -3,22 +3,6 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. - -## [1.0.33](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.33-beta.1...v1.0.33) (2021-01-29) - - - - -**Note:** Version bump only for package @ali/lowcode-react-simulator-renderer - - -## [1.0.33-beta.1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.33-beta.0...v1.0.33-beta.1) (2021-01-28) - - - - -**Note:** Version bump only for package @ali/lowcode-react-simulator-renderer - ## [1.0.33-beta.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.32...v1.0.33-beta.0) (2021-01-28) diff --git a/packages/react-simulator-renderer/package.json b/packages/react-simulator-renderer/package.json index fde4d6376..b3cfae666 100644 --- a/packages/react-simulator-renderer/package.json +++ b/packages/react-simulator-renderer/package.json @@ -16,10 +16,10 @@ "build": "build-scripts build --skip-demo" }, "dependencies": { - "@ali/lowcode-designer": "^1.0.33", - "@ali/lowcode-react-renderer": "^1.0.33", - "@ali/lowcode-types": "^1.0.33", - "@ali/lowcode-utils": "^1.0.33", + "@ali/lowcode-designer": "^1.0.33-beta.0", + "@ali/lowcode-react-renderer": "^1.0.33-beta.0", + "@ali/lowcode-types": "^1.0.33-beta.0", + "@ali/lowcode-utils": "^1.0.33-beta.0", "@ali/vu-css-style": "^1.0.2", "@recore/obx": "^1.0.8", "@recore/obx-react": "^1.0.7", diff --git a/packages/react-simulator-renderer/src/renderer.ts b/packages/react-simulator-renderer/src/renderer.ts index 7bf47106e..ddd1dd9e9 100644 --- a/packages/react-simulator-renderer/src/renderer.ts +++ b/packages/react-simulator-renderer/src/renderer.ts @@ -18,6 +18,7 @@ import { RootSchema, ComponentSchema, TransformStage, NodeSchema } from '@ali/lo // import { RootSchema, NpmInfo, ComponentSchema, TransformStage, NodeSchema } from '@ali/lowcode-types'; // just use types import { BuiltinSimulatorRenderer, NodeInstance, Component, DocumentModel } from '@ali/lowcode-designer'; +import LowCodeRenderer from '@ali/lowcode-react-renderer'; import { createMemoryHistory, MemoryHistory } from 'history'; import Slot from './builtin-components/slot'; import Leaf from './builtin-components/leaf'; @@ -392,12 +393,37 @@ export class SimulatorRendererContainer implements BuiltinSimulatorRenderer { doc.getElementsByTagName('head')[0].appendChild(s); } - // const node = host.currentDocument?.createNode(_schema); - // _schema = node?.export(TransformStage.Render) || {}; - const renderer = this; - const { componentsMap } = renderer; - return getComponentController(schema, componentsMap); + const { componentsMap: components } = renderer; + + class LowCodeComp extends React.Component { + render() { + // @ts-ignore + return createElement(LowCodeRenderer, { + schema: _schema, + components, + designMode: renderer.designMode, + device: renderer.device, + appHelper: renderer.context, + customCreateElement: (Comp: any, props: any, children: any) => { + const componentMeta = host.currentDocument?.getComponentMeta(Comp.displayName); + if (componentMeta?.isModal) { + return null; + } + + const { __id, __designMode, ...viewProps } = props; + // mock _leaf,减少性能开销 + const _leaf = { + isEmpty: () => false, + }; + viewProps._leaf = _leaf; + return createElement(Comp, viewProps, children); + } + }); + } + } + + return LowCodeComp; } private _running = false; @@ -500,91 +526,4 @@ function checkInstanceMounted(instance: any): boolean { return true; } -const processPropsSchema = (propsSchema: any, propsMap: any, componentsMap: any): any => { - if (!propsSchema) { - return {}; - } - - const result = { ...propsSchema }; - const reg = /^(?:this\.props|props)\.(\S+)$/; - Object.keys(result).map((key: string) => { - if (result[key]?.type === 'JSExpression') { - const { value } = result[key]; - const matched = reg.exec(value); - if (matched) { - const propName = matched[1]; - result[key] = propsMap[propName]; - } - } else if (result[key]?.type === 'JSSlot') { - const schema = result[key].value; - result[key] = createElement(ComponentCreator, { schema, propsMap: {}, componentsMap }); - } - }); - - return result; -}; - -class ComponentCreator extends React.Component<{ schema: any; propsMap: any, componentsMap: any }> { - private isModal: boolean; - - constructor(props: any) { - super(props); - const componentMeta = host.currentDocument?.getComponentMeta(props.schema.componentName); - if (componentMeta?.isModal) { - this.isModal = true; - } - } - - render() { - if (this.isModal) { - return null; - } - const { schema, propsMap, componentsMap } = this.props; - const ComponentClass = componentsMap[schema.componentName]; - if (!ComponentClass) { - return null; - } - let children = null; - if (schema.children && schema.children.length > 0) { - children = schema.children.map((item: any) => createElement(ComponentCreator, { schema: item, propsMap, componentsMap })); - } - const props = processPropsSchema(schema.props, propsMap, componentsMap); - const _leaf = host.currentDocument?.createNode(schema); - return createElement(ComponentClass, { ...props, _leaf }, children); - } -} - -function getComponentController(schema: NodeSchema, componentsMap: any) { - class ComponentController extends React.Component<{ schema: any }> { - renderSchema: any; - - constructor(props: any) { - super(props); - const node = host.currentDocument?.createNode(schema); - this.renderSchema = node?.export(TransformStage.Render) || {}; - } - - // TODO: 暂时解决性能问题 - shouldComponentUpdate() { - return false; - } - - render() { - const { renderSchema } = this; - const { componentName } = renderSchema; - if (componentName === 'Component') { - let children = [] as any; - const propsMap = this.props || {}; - if (renderSchema.children && Array.isArray(renderSchema.children)) { - children = renderSchema.children.map((item: any) => createElement(ComponentCreator, { schema: item, propsMap, componentsMap })); - } - return createElement('div', {}, children); - } else { - return createElement(ComponentCreator, { schema, propsMap: {}, componentsMap }); - } - } - } - return ComponentController; -} - export default new SimulatorRendererContainer(); diff --git a/packages/renderer-core/.eslintrc.js b/packages/renderer-core/.eslintrc.js new file mode 100644 index 000000000..f8bcac9a2 --- /dev/null +++ b/packages/renderer-core/.eslintrc.js @@ -0,0 +1,14 @@ +module.exports = { + extends: 'eslint-config-ali/typescript/react', + rules: { + 'react/no-multi-comp': 1, + 'no-unused-expressions': 1, + 'implicit-arrow-linebreak': 1, + 'no-nested-ternary': 1, + 'no-mixed-operators': 1, + '@typescript-eslint/no-parameter-properties': 1, + '@typescript-eslint/ban-types': 1, + 'no-shadow': 1, + 'no-prototype-builtins': 1, + } +} \ No newline at end of file diff --git a/packages/renderer-core/CHANGELOG.md b/packages/renderer-core/CHANGELOG.md new file mode 100644 index 000000000..276edfc5f --- /dev/null +++ b/packages/renderer-core/CHANGELOG.md @@ -0,0 +1,648 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + + +## [1.0.27](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.27-beta.2...v1.0.27) (2020-12-24) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.27-beta.2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.27-beta.1...v1.0.27-beta.2) (2020-12-23) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.27-beta.1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.27-beta.0...v1.0.27-beta.1) (2020-12-23) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.27-beta.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.26...v1.0.27-beta.0) (2020-12-23) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.26](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.26-beta.1...v1.0.26) (2020-12-22) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.26-beta.1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.26-beta.0...v1.0.26-beta.1) (2020-12-22) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.26-beta.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.25-beta.1...v1.0.26-beta.0) (2020-12-22) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.25-beta.1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.4...v1.0.25-beta.1) (2020-12-15) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.24-beta.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.3...v1.0.24-beta.4) (2020-12-14) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.24-beta.3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.2...v1.0.24-beta.3) (2020-12-11) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.24-beta.2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.1...v1.0.24-beta.2) (2020-12-10) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.24-beta.1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.24-beta.0...v1.0.24-beta.1) (2020-12-09) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.24-beta.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.23...v1.0.24-beta.0) (2020-12-09) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.23](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.23-beta.2...v1.0.23) (2020-12-08) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.23-beta.5](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.23-beta.4...v1.0.23-beta.5) (2020-12-08) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.23-beta.4](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.23-beta.3...v1.0.23-beta.4) (2020-12-08) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.23-beta.3](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.23-beta.2...v1.0.23-beta.3) (2020-12-08) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.23-beta.2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v1.0.23-beta.1...v1.0.23-beta.2) (2020-12-08) + + +### Bug Fixes + +* editor-core 统一版本 ([edd4129](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/edd4129)) + + +### Features + +* 增加 plugin-designer ([8bff207](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8bff207)) + + + + + +## [1.0.23-beta.1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-29...v1.0.23-beta.1) (2020-12-07) + + +### Bug Fixes + +* 修复setter设置defaultValue不生效的问题 ([0cf47da](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0cf47da)) + + +### Features + +* 合入 trunk-vision 代码 ([ea6bc7a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ea6bc7a)) + + + + + +## [1.0.23-beta.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/v0.13.1-29...v1.0.23-beta.0) (2020-12-07) + + +### Bug Fixes + +* 修复setter设置defaultValue不生效的问题 ([0cf47da](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0cf47da)) + + +### Features + +* 合入 trunk-vision 代码 ([ea6bc7a](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/ea6bc7a)) + + + + + +## [1.0.22](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@1.0.21...@ali/lowcode-editor-core@1.0.22) (2020-11-16) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.21](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@1.0.20...@ali/lowcode-editor-core@1.0.21) (2020-11-10) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.20](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@1.0.19...@ali/lowcode-editor-core@1.0.20) (2020-11-10) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.19](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@1.0.18...@ali/lowcode-editor-core@1.0.19) (2020-11-05) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.18](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@1.0.17...@ali/lowcode-editor-core@1.0.18) (2020-11-05) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.17](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@1.0.16...@ali/lowcode-editor-core@1.0.17) (2020-11-05) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.16](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@1.0.15...@ali/lowcode-editor-core@1.0.16) (2020-11-04) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.15](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@1.0.13...@ali/lowcode-editor-core@1.0.15) (2020-11-04) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.14](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@1.0.13...@ali/lowcode-editor-core@1.0.14) (2020-11-04) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.13](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@1.0.12...@ali/lowcode-editor-core@1.0.13) (2020-11-02) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@1.0.11...@ali/lowcode-editor-core@1.0.12) (2020-10-20) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.11](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@1.0.10...@ali/lowcode-editor-core@1.0.11) (2020-10-19) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@1.0.9...@ali/lowcode-editor-core@1.0.10) (2020-09-29) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@1.0.8...@ali/lowcode-editor-core@1.0.9) (2020-09-28) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@1.0.8-0...@ali/lowcode-editor-core@1.0.8) (2020-09-28) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.8-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.36...@ali/lowcode-editor-core@1.0.8-0) (2020-09-09) + + +### Bug Fixes + +* 合并master分支 ([bd2c6ad](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bd2c6ad)) +* 清理代码依赖及版本 ([0b15d30](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0b15d30)) + + + + + +## [1.0.7-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@1.0.6-0...@ali/lowcode-editor-core@1.0.7-0) (2020-09-02) + +**Note:** Version bump only for package @ali/lowcode-editor-core + +## [1.0.6-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.34...@ali/lowcode-editor-core@1.0.6-0) (2020-09-02) + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [0.8.36](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.35...@ali/lowcode-editor-core@0.8.36) (2020-09-03) + + +## [0.8.35](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.34...@ali/lowcode-editor-core@0.8.35) (2020-09-03) + + + +### Bug Fixes + +* 合并master分支 ([bd2c6ad](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/bd2c6ad)) +* 清理代码依赖及版本 ([0b15d30](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/0b15d30)) + + + + + +## [1.0.5-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@1.0.4-0...@ali/lowcode-editor-core@1.0.5-0) (2020-08-20) + + +## [0.8.34](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.33...@ali/lowcode-editor-core@0.8.34) (2020-08-27) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [0.8.33](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.32...@ali/lowcode-editor-core@0.8.33) (2020-08-24) + + +### Features + +* 编辑器 hooks 能力实现 ([f3ac23b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f3ac23b)) + + + + + +## [0.8.32](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.30...@ali/lowcode-editor-core@0.8.32) (2020-08-20) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.4-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@1.0.3-0...@ali/lowcode-editor-core@1.0.4-0) (2020-08-20) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.3-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@1.0.2-0...@ali/lowcode-editor-core@1.0.3-0) (2020-08-20) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.2-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@1.0.1-0...@ali/lowcode-editor-core@1.0.2-0) (2020-08-20) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [1.0.1-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.30...@ali/lowcode-editor-core@1.0.1-0) (2020-08-20) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +# [1.0.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.13.0...@ali/lowcode-editor-core@1.0.0) (2020-08-17) + +## [0.8.30](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.29...@ali/lowcode-editor-core@0.8.30) (2020-08-19) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +# [0.13.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.12.0...@ali/lowcode-editor-core@0.13.0) (2020-08-17) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +# [0.12.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.10.0...@ali/lowcode-editor-core@0.12.0) (2020-08-17) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +# [0.11.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.10.0...@ali/lowcode-editor-core@0.11.0) (2020-08-17) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +# [0.10.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.9.0...@ali/lowcode-editor-core@0.10.0) (2020-08-16) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +# [0.9.0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.28...@ali/lowcode-editor-core@0.9.0) (2020-08-14) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [0.8.28](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.27...@ali/lowcode-editor-core@0.8.28) (2020-08-04) + + +### Bug Fixes + +* 增加try catch ([6f5d11c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/6f5d11c)) + + + + + +## [0.8.27](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.25...@ali/lowcode-editor-core@0.8.27) (2020-08-04) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [0.8.26](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.25...@ali/lowcode-editor-core@0.8.26) (2020-08-04) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [0.8.25](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.24...@ali/lowcode-editor-core@0.8.25) (2020-07-28) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [0.8.24](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.23...@ali/lowcode-editor-core@0.8.24) (2020-07-22) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [0.8.23](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.22...@ali/lowcode-editor-core@0.8.23) (2020-07-21) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [0.8.22](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.21...@ali/lowcode-editor-core@0.8.22) (2020-07-21) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [0.8.21](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.20...@ali/lowcode-editor-core@0.8.21) (2020-07-13) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [0.8.20](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.19...@ali/lowcode-editor-core@0.8.20) (2020-07-12) + + +### Bug Fixes + +* 修复删除时,当前组件信息丢失问题 ([3bd1248](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3bd1248)) + + + + + +## [0.8.19](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.17...@ali/lowcode-editor-core@0.8.19) (2020-06-23) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [0.8.17](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.16...@ali/lowcode-editor-core@0.8.17) (2020-06-23) + + +### Features + +* export Monitor ([51025f0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/51025f0)) +* 引擎层埋点 ([69de533](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/69de533)) + + + + + +## [0.8.16](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.15...@ali/lowcode-editor-core@0.8.16) (2020-06-15) + + +### Features + +* add Monitor ([f915d19](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f915d19)) +* add URL link for setter titles ([4678408](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/4678408)) +* ve事件埋点 ([700e5b0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/700e5b0)) + + + + + +## [0.8.15](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.14...@ali/lowcode-editor-core@0.8.15) (2020-05-20) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [0.8.14](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.13...@ali/lowcode-editor-core@0.8.14) (2020-05-18) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [0.8.13](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.12...@ali/lowcode-editor-core@0.8.13) (2020-05-15) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [0.8.12](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.11...@ali/lowcode-editor-core@0.8.12) (2020-05-13) + + +### Bug Fixes + +* supports ([371b84c](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/371b84c)) +* tip direction ([f51d496](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/f51d496)) + + + + + +## [0.8.11](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.10...@ali/lowcode-editor-core@0.8.11) (2020-05-08) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [0.8.10](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.9...@ali/lowcode-editor-core@0.8.10) (2020-05-07) + + +### Bug Fixes + +* intl ([8a061ab](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/8a061ab)) + + + + + +## [0.8.9](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.8...@ali/lowcode-editor-core@0.8.9) (2020-04-27) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [0.8.8](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.7...@ali/lowcode-editor-core@0.8.8) (2020-04-27) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core + + +## [0.8.7](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-editor-core@0.8.6...@ali/lowcode-editor-core@0.8.7) (2020-04-27) + + + + +**Note:** Version bump only for package @ali/lowcode-editor-core diff --git a/packages/renderer-core/README.md b/packages/renderer-core/README.md new file mode 100644 index 000000000..30c3e7d7d --- /dev/null +++ b/packages/renderer-core/README.md @@ -0,0 +1,3 @@ +shared globals + + 发 CDN diff --git a/packages/renderer-core/build.json b/packages/renderer-core/build.json new file mode 100644 index 000000000..49a393b6b --- /dev/null +++ b/packages/renderer-core/build.json @@ -0,0 +1,7 @@ +{ + "plugins": [ + [ + "build-plugin-component" + ] + ] +} diff --git a/packages/renderer-core/package.json b/packages/renderer-core/package.json new file mode 100644 index 000000000..450e1f07e --- /dev/null +++ b/packages/renderer-core/package.json @@ -0,0 +1,48 @@ +{ + "name": "@ali/lowcode-renderer-core", + "version": "1.0.33", + "description": "renderer core", + "license": "MIT", + "main": "lib/index.js", + "module": "es/index.js", + "files": [ + "lib", + "es" + ], + "scripts": { + "build": "build-scripts build --skip-demo", + "cloud-build": "build-scripts build --skip-demo" + }, + "dependencies": { + "@ali/b3-one": "^0.0.17", + "@ali/bzb-request": "^2.6.0-beta.13", + "@ali/lib-mtop": "^2.5.1", + "@ali/lowcode-datasource-engine": "^1.0.22", + "classnames": "^2.2.6", + "prop-types": "^15.7.2", + "debug": "^4.1.1", + "events": "^3.0.0", + "fetch-jsonp": "^1.1.3", + "intl-messageformat": "^9.3.1", + "jsonuri": "^2.1.2", + "lodash": "^4.17.11", + "moment": "^2.24.0", + "react-is": "^16.10.1", + "serialize-javascript": "^1.7.0", + "socket.io-client": "^2.2.0", + "whatwg-fetch": "^3.0.0" + }, + "devDependencies": { + "@alib/build-scripts": "^0.1.18", + "@types/classnames": "^2.2.11", + "@types/debug": "^4.1.5", + "@types/lodash": "^4.14.167", + "@types/node": "^13.7.1", + "@types/prop-types": "^15.7.3", + "@types/serialize-javascript": "^5.0.0", + "build-plugin-component": "^0.2.11" + }, + "publishConfig": { + "registry": "https://registry.npm.alibaba-inc.com" + } +} diff --git a/packages/renderer-core/src/adapter/index.ts b/packages/renderer-core/src/adapter/index.ts new file mode 100644 index 000000000..cafd9bd06 --- /dev/null +++ b/packages/renderer-core/src/adapter/index.ts @@ -0,0 +1,89 @@ +import { IRuntime, IRendererModules } from '../types'; + +export enum Env { + React = 'react', + Rax = 'rax', +} + +class Adapter { + runtime: IRuntime; + builtinModules = ['Component', 'PureComponent', 'createElement', 'createContext', 'forwardRef', 'findDOMNode']; + env: Env; + renderers: IRendererModules; + configProvider: any; + + constructor() { + this.initRuntime(); + } + + initRuntime() { + const Component = class {}; + const PureComponent = class {}; + const createElement = () => {}; + const createContext = () => {}; + const forwardRef = () => {}; + const findDOMNode = () => {}; + this.runtime = { + Component, + PureComponent, + createElement, + createContext, + forwardRef, + findDOMNode, + }; + } + + setRuntime(runtime: IRuntime) { + if (this.isValidRuntime(runtime)) { + this.runtime = runtime; + } + } + + isValidRuntime(runtime: IRuntime) { + if (typeof runtime !== 'object' || Array.isArray(runtime)) { + return false; + } + + return this.builtinModules.every(m => { + const flag = !!this.runtime[m]; + if (!flag) { + throw new Error(`runtime is inValid, module '${m}' is not existed`); + } + return flag; + }); + } + + getRuntime() { + return this.runtime; + } + + setEnv(env: Env) { + this.env = env; + } + + isReact() { + return this.env === Env.React; + } + + isRax() { + return this.env === Env.Rax; + } + + setRenderers(renderers: IRendererModules) { + this.renderers = renderers; + } + + getRenderers() { + return this.renderers || {}; + } + + setConfigProvider(Comp: any) { + this.configProvider = Comp; + } + + getConfigProvider() { + return this.configProvider; + } +} + +export default new Adapter(); diff --git a/packages/renderer-core/src/components/Div.tsx b/packages/renderer-core/src/components/Div.tsx new file mode 100644 index 000000000..2897bd31f --- /dev/null +++ b/packages/renderer-core/src/components/Div.tsx @@ -0,0 +1,14 @@ +import adapter from '../adapter'; + +export default function divFactory() { + const { PureComponent, createElement } = adapter.getRuntime(); + return class Div extends PureComponent { + static displayName = 'Div'; + + static version = '0.0.0'; + + render() { + return createElement('div', this.props); + } + }; +} diff --git a/packages/rax-renderer/src/comp/visualDom/index.css b/packages/renderer-core/src/components/VisualDom/index.css similarity index 100% rename from packages/rax-renderer/src/comp/visualDom/index.css rename to packages/renderer-core/src/components/VisualDom/index.css diff --git a/packages/renderer-core/src/components/VisualDom/index.tsx b/packages/renderer-core/src/components/VisualDom/index.tsx new file mode 100644 index 000000000..69ce2add9 --- /dev/null +++ b/packages/renderer-core/src/components/VisualDom/index.tsx @@ -0,0 +1,34 @@ +import PropTypes from 'prop-types'; +import adapter from '../../adapter'; +import './index.css'; + +export default function visualDomFactory() { + const { PureComponent, createElement } = adapter.getRuntime(); + return class VisualDom extends PureComponent { + static displayName = 'VisualDom'; + + static propTypes = { + children: PropTypes.oneOfType([PropTypes.element, PropTypes.arrayOf(PropTypes.element)]), + }; + + static defaultProps = { + children: null, + }; + + render() { + const { children, cell, title, label, text, __componentName } = this.props; + let mainContent = children; + if (cell && typeof cell === 'function') { + mainContent = cell(); + } + return createElement('div', { className: 'visual-dom' }, + createElement('div', { className: 'panel-container' }, + [ + createElement('span', { className: 'title' }, title || label || text || __componentName), + createElement('div', { className: 'content' }, mainContent), + ] + ) + ); + } + }; +} diff --git a/packages/renderer-core/src/context/index.ts b/packages/renderer-core/src/context/index.ts new file mode 100644 index 000000000..38335f6b4 --- /dev/null +++ b/packages/renderer-core/src/context/index.ts @@ -0,0 +1,13 @@ +import adapter from '../adapter'; + + +export default function contextFactory() { + const { createContext } = adapter.getRuntime(); + + let context = (window as any).__appContext; + if (!context) { + context = createContext({}); + (window as any).__appContext = context; + } + return context; +} diff --git a/packages/renderer-core/src/hoc/index.tsx b/packages/renderer-core/src/hoc/index.tsx new file mode 100644 index 000000000..5e1714a63 --- /dev/null +++ b/packages/renderer-core/src/hoc/index.tsx @@ -0,0 +1,22 @@ +import adapter from '../adapter'; + +export function compWrapper(Comp: any) { + const { createElement, Component, forwardRef } = adapter.getRuntime(); + class Wrapper extends Component { + constructor(props: any, context: any) { + super(props, context) + } + + render() { + const { forwardRef } = this.props; + return createElement(Comp, { + ...this.props, + ref: forwardRef, + }); + } + } + + return forwardRef((props: any, ref: any) => { + return createElement(Wrapper, { ...props, forwardRef: ref }); + }); +} diff --git a/packages/renderer-core/src/index.ts b/packages/renderer-core/src/index.ts new file mode 100644 index 000000000..0609a0fdf --- /dev/null +++ b/packages/renderer-core/src/index.ts @@ -0,0 +1,9 @@ +import adapter from './adapter'; +import contextFactory from './context'; + +export { adapter, contextFactory }; + +export * from './renderer'; +export * as types from './types'; +export * as utils from './utils'; +export * from './hoc'; diff --git a/packages/renderer-core/src/module.d.ts b/packages/renderer-core/src/module.d.ts new file mode 100644 index 000000000..654068e05 --- /dev/null +++ b/packages/renderer-core/src/module.d.ts @@ -0,0 +1,3 @@ +declare module '@ali/b3-one/lib/obj'; +declare module '@ali/b3-one/lib/url'; +declare module '@ali/lib-mtop'; diff --git a/packages/renderer-core/src/renderer/addon.tsx b/packages/renderer-core/src/renderer/addon.tsx new file mode 100644 index 000000000..eca712b74 --- /dev/null +++ b/packages/renderer-core/src/renderer/addon.tsx @@ -0,0 +1,88 @@ +import PropTypes from 'prop-types'; +import baseRendererFactory from './base'; +import { isEmpty, goldlog } from '../utils'; +import { IRendererProps } from '../types'; + +export default function addonRendererFactory() { + const BaseRenderer = baseRendererFactory(); + return class AddonRenderer extends BaseRenderer { + static dislayName = 'addon-renderer'; + __namespace = 'addon'; + + static propTypes = { + config: PropTypes.object, + __schema: PropTypes.object, + }; + + static defaultProps = { + config: {}, + __schema: {}, + }; + + __afterInit(props: IRendererProps) { + this.__generateCtx({ + component: this, + }); + const schema = props.__schema || {}; + this.state = this.__parseData(schema.state || {}); + if (isEmpty(props.config) || !props.config.addonKey) { + console.warn('luna addon has wrong config'); + this.state.__hasError = true; + return; + } + // 注册插件 + this.addonKey = props.config.addonKey; + this.appHelper.addons = this.appHelper.addons || {}; + this.appHelper.addons[this.addonKey] = this; + this.__initDataSource(props); + this.open = this.open || (() => { }); + this.close = this.close || (() => { }); + this.__setLifeCycleMethods('constructor', arguments); + } + + async componentWillUnmount() { + super.componentWillUnmount(...arguments); + // 注销插件 + const config = this.props.config || {}; + if (config && this.appHelper.addons) { + delete this.appHelper.addons[config.addonKey]; + } + } + + goldlog = (goKey: string, params: any) => { + const { addonKey, addonConfig = {} } = this.props.config || {}; + goldlog( + goKey, + { + addonKey, + package: addonConfig.package, + version: addonConfig.version, + ...this.appHelper.logParams, + ...params, + }, + 'addon', + ); + }; + + get utils() { + const { utils = {} } = this.context.config || {}; + return { ...this.appHelper.utils, ...utils }; + } + + render() { + const { __schema } = this.props; + + if (this.__checkSchema(__schema)) { + return '插件 schema 结构异常!'; + } + + this.__debug(`render - ${__schema.fileName}`); + this.__generateCtx({ + component: this, + }); + this.__render(); + + return this.__renderContent(this.__renderContextProvider({ compContext: this })); + } + }; +} diff --git a/packages/renderer-core/src/renderer/base.tsx b/packages/renderer-core/src/renderer/base.tsx new file mode 100644 index 000000000..3135b4b5c --- /dev/null +++ b/packages/renderer-core/src/renderer/base.tsx @@ -0,0 +1,761 @@ +import classnames from 'classnames'; +import Debug from 'debug'; +import { create as createDataSourceEngine } from '@ali/lowcode-datasource-engine/interpret'; +import adapter from '../adapter'; +import divFactory from '../components/Div'; +import visualDomFactory from '../components/VisualDom'; +import contextFactory from '../context'; +import { + forEach, + getValue, + parseData, + parseExpression, + isEmpty, + isSchema, + isFileSchema, + isJSExpression, + isJSSlot, + isJSFunction, + transformArrayToMap, + transformStringToFunction, + checkPropTypes, + generateI18n, + acceptsRef, + getFileCssName, + capitalizeFirstLetter, + DataHelper, + isI18n, + isVariable +} from '../utils'; +import { IRendererProps, ISchema, IInfo, ComponentModel, IRenderer } from '../types'; +import { compWrapper } from '../hoc'; + +export default function baseRenererFactory() { + const { BaseRenderer: customBaseRenderer } = adapter.getRenderers(); + + if (customBaseRenderer) { + return customBaseRenderer; + } + + const { Component, createElement } = adapter.getRuntime(); + const Div = divFactory(); + const VisualDom = visualDomFactory(); + const AppContext = contextFactory(); + + const debug = Debug('renderer:base'); + const DESIGN_MODE = { + EXTEND: 'extend', + BORDER: 'border', + PREVIEW: 'preview', + }; + const OVERLAY_LIST = ['Dialog', 'Overlay', 'Animate', 'ConfigProvider']; + let scopeIdx = 0; + + return class BaseRenderer extends Component implements IRenderer { + static dislayName = 'base-renderer'; + + static defaultProps = { + __schema: {}, + }; + + static contextType = AppContext; + + __namespace = 'base'; + + constructor(props: IRendererProps, context: any) { + super(props, context); + this.__beforeInit(props); + this.__init(props); + this.__afterInit(props); + this.__initDebug(); + this.__debug(`constructor - ${props?.__schema?.fileName}`); + } + + __beforeInit(props: IRendererProps) { } + + __init(props: IRendererProps) { + this.appHelper = props.__appHelper; + this.__compScopes = {}; + this.__instanceMap = {}; + const { locale, messages } = props; + this.i18n = generateI18n(locale, messages); + this.__bindCustomMethods(props); + } + + __afterInit(props: IRendererProps) { } + + static getDerivedStateFromProps(props: IRendererProps, state: any) { + debug('getDerivedStateFromProps'); + const func = props?.__schema?.lifeCycles?.getDerivedStateFromProps; + + if (func) { + return func(props, state); + } + return null; + } + + async getSnapshotBeforeUpdate() { + this.__setLifeCycleMethods('getSnapshotBeforeUpdate', arguments); + this.__debug(`getSnapshotBeforeUpdate - ${this.props?.__schema?.fileName}`); + } + + async componentDidMount() { + this.reloadDataSource(); + this.__setLifeCycleMethods('componentDidMount', arguments); + this.__debug(`componentDidMount - ${this.props?.__schema?.fileName}`); + } + + async componentDidUpdate(...args: any) { + this.__setLifeCycleMethods('componentDidUpdate', args); + this.__debug(`componentDidUpdate - ${this.props.__schema.fileName}`); + } + + async componentWillUnmount(...args: any) { + this.__setLifeCycleMethods('componentWillUnmount', args); + this.__debug(`componentWillUnmount - ${this.props?.__schema?.fileName}`); + } + + async componentDidCatch(e: any) { + this.__setLifeCycleMethods('componentDidCatch', arguments); + console.warn(e); + } + + reloadDataSource = () => new Promise((resolve, reject) => { + this.__debug('reload data source'); + if (!this.__dataHelper) { + this.__showPlaceholder = false; + return resolve({}); + } + this.__dataHelper + .getInitData() + .then((res: any) => { + this.__showPlaceholder = false; + if (isEmpty(res)) { + this.forceUpdate(); + return resolve({}); + } + this.setState(res, resolve); + }) + .catch((err: Error) => { + if (this.__showPlaceholder) { + this.__showPlaceholder = false; + this.forceUpdate(); + } + reject(err); + }); + }); + + __setLifeCycleMethods = (method: string, args?: any) => { + const lifeCycleMethods = getValue(this.props.__schema, 'lifeCycles', {}); + let fn = lifeCycleMethods[method]; + if (fn) { + // TODO, cache + if (isJSExpression(fn) || isJSFunction(fn)) { + fn = parseExpression(fn, this); + } + if (typeof fn !== 'function') { + console.error(`生命周期${method}类型不符`, fn); + return; + } + try { + return fn.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: any) => { + if (!customMethodsList.includes(item)) { + delete this[item]; + } + }); + this.__customMethodsList = customMethodsList; + forEach(__schema.methods, (val: any, key: string) => { + if (isJSExpression(val) || isJSFunction(val)) { + val = parseExpression(val, this); + } + if (typeof val !== 'function') { + console.error(`自定义函数${key}类型不符`, val); + return; + } + this[key] = val.bind(this); + }); + }; + + __generateCtx = (ctx: object) => { + const { pageContext, compContext } = this.context; + const obj = { + page: pageContext, + component: compContext, + ...ctx, + }; + forEach(obj, (val: any, key: string) => { + this[key] = val; + }); + }; + + __parseData = (data: any, ctx?: object) => { + const { __ctx } = this.props; + return parseData(data, ctx || __ctx || this); + }; + + __initDataSource = (props = this.props) => { + const schema = props.__schema || {}; + const dataSource = (schema && schema.dataSource) || {}; + // requestHandlersMap 存在才走数据源引擎方案 + if (props?.__appHelper?.requestHandlersMap) { + const { dataSourceMap, reloadDataSource } = createDataSourceEngine(dataSource, (this as any), { + requestHandlersMap: props.__appHelper.requestHandlersMap, + }); + this.dataSourceMap = dataSourceMap; + this.reloadDataSource = () => new Promise((resolve) => { + this.__debug('reload data source'); + // this.__showPlaceholder = true; + reloadDataSource().then(() => { + // this.__showPlaceholder = false; + // @TODO 是否需要 forceUpate + // this.forceUpdate(); + resolve({}); + }); + }); + } else { + const appHelper = props.__appHelper; + this.__dataHelper = new DataHelper(this, dataSource, appHelper, (config: any) => this.__parseData(config)); + this.dataSourceMap = this.__dataHelper.dataSourceMap; + this.reloadDataSource = () => new Promise((resolve, reject) => { + this.__debug('reload data source'); + if (!this.__dataHelper) { + // this.__showPlaceholder = false; + return resolve({}); + } + this.__dataHelper + .getInitData() + .then((res: any) => { + // this.__showPlaceholder = false; + if (isEmpty(res)) { + this.forceUpdate(); + return resolve({}); + } + this.setState(res, resolve); + }) + .catch((err: Error) => { + if (this.__showPlaceholder) { + this.__showPlaceholder = false; + this.forceUpdate(); + } + reject(err); + }); + }); + } + // 设置容器组件占位,若设置占位则在初始异步请求完成之前用loading占位且不渲染容器组件内部内容 + // @TODO __showPlaceholder 的逻辑一旦开启就关不掉,先注释掉了 + /* 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; + 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: any) => { + const { engine } = this.context; + const { __schema } = this.props; + ref && engine?.props?.onCompGetRef(__schema, ref); + this.__ref = ref; + }; + + getSchemaChildren = (schema: ISchema) => { + if (!schema || !schema.props) { + return schema?.children; + } + if (!schema.children) return schema.props.children; + if (!schema.props.children) return schema.children; + let _children = ([] as ComponentModel[]).concat(schema.children); + if (Array.isArray(schema.props.children)) { + _children = _children.concat(schema.props.children); + } else { + _children.push(schema.props.children); + } + return _children; + }; + + __createDom = () => { + const { __schema, __ctx, __components = {} } = this.props; + const self: any = {}; + self.__proto__ = __ctx || this; + const _children = this.getSchemaChildren(__schema); + return this.__createVirtualDom(_children, self, ({ + schema: __schema, + Comp: __components[__schema.componentName], + } as IInfo)); + }; + + // 将模型结构转换成react Element + // schema 模型结构 + // self 为每个渲染组件构造的上下文,self是自上而下继承的 + // parentInfo 父组件的信息,包含schema和Comp + // idx 若为循环渲染的循环Index + __createVirtualDom = (schema: any, self: any, parentInfo: IInfo, idx: string | number = ''): any => { + const { engine } = this.context || {}; + try { + if (!schema) return null; + + // FIXME + if (schema.componentName === 'Text' && typeof schema.props.text === 'string') { + schema = { ...schema }; + schema.children = [schema.props.text]; + } + + const { __appHelper: appHelper, __components: components = {} } = this.props || {}; + + if (isJSExpression(schema)) { + return parseExpression(schema, self); + } + if (isJSSlot(schema)) { + return this.__createVirtualDom(schema.value, self, parentInfo); + } + 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, idy) => this.__createVirtualDom(item, self, parentInfo, item?.__ctx?.lceKey ? '' : idy)); + } + // FIXME + const _children = this.getSchemaChildren(schema); + // 解析占位组件 + if (schema.componentName === 'Flagment' && _children) { + const tarChildren = isJSExpression(_children) ? parseExpression(_children, self) : _children; + return this.__createVirtualDom(tarChildren, self, parentInfo); + } + + if (schema.$$typeof) { + return schema; + } + if (!isSchema(schema)) return null; + let Comp = components[schema.componentName] || engine.getNotFoundComponent(); + + if (schema.hidden) { + return null; + } + + if (schema.loop != null) { + const loop = parseData(schema.loop, self); + if ((Array.isArray(loop) && loop.length > 0) || isJSExpression(loop)) { + return this.__createLoopVirtualDom( + { + ...schema, + loop, + }, + self, + parentInfo, + idx, + ); + } + } + const condition = schema.condition == null ? 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上下文,需要手动生成一个lceKey + schema.__ctx = { + lceKey: `lce${++scopeIdx}`, + }; + scopeKey = schema.__ctx.lceKey; + } else { + // 需要判断循环的情况 + scopeKey = schema.__ctx.lceKey + (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: any = isFileSchema(schema) + ? { + __schema: schema, + __appHelper: appHelper, + __components: components, + } + : {}; + if (engine?.props?.designMode) { + otherProps.__designMode = engine.props.designMode; + } + const componentInfo: any = {}; + const props: any = + this.__parseProps(schema.props, self, '', { + schema, + Comp, + componentInfo: { + ...componentInfo, + props: transformArrayToMap(componentInfo.props, 'name'), + }, + }) || {}; + + // 对于可以获取到ref的组件做特殊处理 + if (!acceptsRef(Comp)) { + Comp = compWrapper(Comp); + } + otherProps.ref = (ref: any) => { + this.$(props.fieldId, ref); // 收集ref + const refProps = props.ref; + if (refProps && typeof refProps === 'string') { + this[refProps] = ref; + } + ref && engine?.props?.onCompGetRef(schema, ref); + }; + + // scope需要传入到组件上 + if (scopeKey && this.__compScopes[scopeKey]) { + props.__scope = this.__compScopes[scopeKey]; + } + // FIXME 这里清除 key 是为了避免循环渲染中更改 key 导致的渲染重复 + props.key = ''; + if (schema?.__ctx?.lceKey) { + if (!isFileSchema(schema)) { + engine?.props?.onCompGetCtx(schema, self); + } + props.key = props.key || `${schema.__ctx.lceKey}_${schema.__ctx.idx || 0}_${idx !== undefined ? idx : ''}`; + } else if (typeof idx === 'number' && !props.key) { + props.key = idx; + } + + props.__id = schema.id; + if (!props.key) { + props.key = props.__id; + } + + let child: any = null; + if (/*!isFileSchema(schema) && */!!_children) { + child = this.__createVirtualDom( + isJSExpression(_children) ? parseExpression(_children, self) : _children, + self, + { + schema, + Comp, + }, + ); + } + const renderComp = (props: any) => engine.createElement(Comp, props, child); + // 设计模式下的特殊处理 + 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 createElement(Div, { + ref, + __designMode: engine.props.designMode, + }, renderComp({ ...props, ...overlayProps })); + } + // 虚拟dom显示 + if (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 }); + } catch (e) { + return engine.createElement(engine.getFaultComponent(), { + error: e, + schema, + self, + parentInfo, + idx, + }); + } + }; + + __createLoopVirtualDom = (schema: any, self: any, parentInfo: IInfo, idx: number | string) => { + 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: Array, i: number) => { + const loopSelf: any = { + [itemArg]: item, + [indexArg]: i, + }; + loopSelf.__proto__ = self; + return this.__createVirtualDom( + { + ...schema, + loop: undefined, + }, + loopSelf, + parentInfo, + idx ? `${idx}_${i}` : i, + ); + }); + }; + + __parseProps = (props: any, self: any, path: string, info: IInfo): any => { + const { schema, Comp, componentInfo = {} } = info; + const propInfo = getValue(componentInfo.props, path); + // FIXME! 将这行逻辑外置,解耦,线上环境不要验证参数,调试环境可以有,通过传参自定义 + const propType = propInfo?.extra?.propType; + const ignoreParse = schema.__ignoreParse || []; + const checkProps = (value: any) => { + if (!propType) return value; + return checkPropTypes(value, path, propType, componentInfo.name) ? value : undefined; + }; + + const parseReactNode = (data: any, params: any) => { + if (isEmpty(params)) { + return checkProps(this.__createVirtualDom(data, self, ({ schema, Comp } as IInfo))); + } + return checkProps(function () { + const args: any = {}; + if (Array.isArray(params) && params.length) { + params.forEach((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: any) => { + 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); + } + + const handleI18n = (props: any) => props[props.use || 'zh_CN']; + + // 兼容乐高设计态 i18n 数据 + if (isI18n(props)) { + props = handleI18n(props); + } + + // 兼容乐高设计态的变量绑定 + if (isVariable(props)) { + props = props.value; + if (isI18n(props)) { + props = handleI18n(props); + } + } + + if (isJSFunction(props)) { + props = transformStringToFunction(props.value); + } + if (isJSSlot(props)) { + const { params, value } = props; + if (!isSchema(value) || isEmpty(value)) return undefined; + return parseReactNode(value, params); + } + // 兼容通过componentInfo判断的情况 + if (isSchema(props)) { + const isReactNodeFunction = !!( + propInfo?.type === 'ReactNode' + && propInfo?.props?.type === 'function' + ); + + const isMixinReactNodeFunction = !!( + propInfo?.type === 'Mixin' + && propInfo?.props?.types?.indexOf('ReactNode') > -1 + && propInfo?.props?.reactNodeProps?.type === 'function' + ); + return parseReactNode( + props, + isReactNodeFunction + ? propInfo.props.params + : isMixinReactNodeFunction + ? propInfo.props.reactNodeProps.params + : null, + ); + } + if (Array.isArray(props)) { + return checkProps(props.map((item, idx) => this.__parseProps(item, self, path ? `${path}.${idx}` : `${idx}`, info))); + } + if (typeof props === 'function') { + return checkProps(props.bind(self)); + } + if (props && typeof props === 'object') { + if (props.$$typeof) return checkProps(props); + const res: any = {}; + forEach(props, (val: any, key: string) => { + if (key.startsWith('__')) { + res[key] = val; + return; + } + res[key] = this.__parseProps(val, self, path ? `${path}.${key}` : key, info); + }); + return checkProps(res); + } + if (typeof props === 'string') { + return checkProps(props.trim()); + } + return checkProps(props); + }; + + $(filedId: string, instance?: any) { + this.__instanceMap = this.__instanceMap || {}; + if (!filedId) { + return this.__instanceMap; + } + if (instance) { + this.__instanceMap[filedId] = instance; + } + return this.__instanceMap[filedId]; + } + + __initDebug = () => { + this.__logger = Debug(`renderer:${this.__namespace || 'base'}`); + } + + __debug = (msg: string = '') => { + if (this.__logger) { + this.__logger(`${this.__namespace}.${msg}`); + } + } + + __renderContextProvider = (customProps?: object, children?: any) => { + customProps = customProps || {}; + children = children || this.__createDom(); + return createElement(AppContext.Provider, { + value: { + ...this.context, + blockContext: this, + ...customProps, + }, children + }); + } + + __renderContextConsumer = (children: any) => { + return createElement(AppContext.Consumer, {}, children); + } + + __renderComp(Comp: any, ctxProps: object) { + const { __schema } = this.props; + const data = this.__parseData(__schema?.props); + const { className } = data; + const { engine } = this.context || {}; + if (!engine) { + return null; + } + const child = engine.createElement( + Comp || Div, + { + ...data, + ...this.props, + ref: this.__getRef, + className: classnames(getFileCssName(__schema?.fileName), className, this.props.className), + __id: __schema?.id, + }, + this.__createDom(), + ); + return this.__renderContextProvider(ctxProps, child); + } + + __renderContent(children: any) { + const { __schema } = this.props; + const props = this.__parseData(__schema.props); + const { id, className, style = {} } = props; + return createElement('div', { + ref: this.__getRef, + className: classnames(`lce-${this.__namespace}`, getFileCssName(__schema.fileName), className, this.props.className), + id: this.props.id || id, + style: { ...style, ...(typeof this.props.style === 'object' ? this.props.style : {}) }, + }, children); + } + + __checkSchema = (schema: ISchema, extraComponents: string | string[] = []) => { + if (typeof extraComponents === 'string') { + extraComponents = [extraComponents]; + } + + const buitin = capitalizeFirstLetter(this.__namespace); + const componentNames = [buitin, ...extraComponents]; + return !isSchema(schema, true) || !componentNames.includes(schema.componentName) + } + + get requestHandlersMap() { + return this.appHelper?.requestHandlersMap; + } + + get utils() { + return this.appHelper?.utils; + } + + get constants() { + return this.appHelper?.constants; + } + + get history() { + return this.appHelper?.history; + } + + get location() { + return this.appHelper?.location; + } + + get match() { + return this.appHelper?.match; + } + + render() { + return null; + } + }; +} diff --git a/packages/renderer-core/src/renderer/block.tsx b/packages/renderer-core/src/renderer/block.tsx new file mode 100644 index 000000000..2579a0d84 --- /dev/null +++ b/packages/renderer-core/src/renderer/block.tsx @@ -0,0 +1,39 @@ +import classnames from 'classnames'; +import baseRendererFactory from './base'; +import { IRendererProps } from '../types'; +import { getFileCssName } from '../utils'; + +export default function blockRendererFactory() { + const BaseRenderer = baseRendererFactory(); + return class BlockRenderer extends BaseRenderer { + static dislayName = 'block-renderer'; + __namespace = 'block'; + + __afterInit(props: IRendererProps) { + this.__generateCtx({}); + const schema = props.__schema || {}; + this.state = this.__parseData(schema.state || {}); + this.__initDataSource(props); + this.__setLifeCycleMethods('constructor', arguments); + } + + render() { + const { __schema, __components } = this.props; + + if (this.__checkSchema(__schema, 'Div')) { + return '区块 schema 结构异常!'; + } + + this.__debug(`render - ${__schema.fileName}`); + this.__generateCtx({}); + this.__render(); + + const { Block } = __components; + if (Block) { + return this.__renderComp(Block, {}); + } + + return this.__renderContent(this.__renderContextProvider()); + } + }; +} diff --git a/packages/renderer-core/src/renderer/component.tsx b/packages/renderer-core/src/renderer/component.tsx new file mode 100644 index 000000000..f9cb0fd21 --- /dev/null +++ b/packages/renderer-core/src/renderer/component.tsx @@ -0,0 +1,41 @@ +import baseRendererFactory from './base'; +import { IRendererProps } from '../types'; + +export default function componentRendererFactory() { + const BaseRenderer = baseRendererFactory(); + return class CompRenderer extends BaseRenderer { + static dislayName = 'comp-renderer'; + __namespace = 'component'; + + __afterInit(props: IRendererProps) { + this.__generateCtx({ + component: this, + }); + const schema = props.__schema || {}; + this.state = this.__parseData(schema.state || {}); + this.__initDataSource(props); + this.__setLifeCycleMethods('constructor', arguments); + } + + render() { + const { __schema } = this.props; + if (this.__checkSchema(__schema)) { + return '自定义组件 schema 结构异常!'; + } + this.__debug(`render - ${__schema.fileName}`); + + this.__generateCtx({ + component: this, + }); + this.__render(); + + const { noContainer } = this.__parseData(__schema.props); + + if (noContainer) { + return this.__renderContextProvider({ compContext: this }); + } + + return this.__renderContent(this.__renderContextProvider({ compContext: this })); + } + }; +} diff --git a/packages/renderer-core/src/renderer/index.ts b/packages/renderer-core/src/renderer/index.ts new file mode 100644 index 000000000..a4fee1d48 --- /dev/null +++ b/packages/renderer-core/src/renderer/index.ts @@ -0,0 +1,17 @@ +import baseRendererFactory from './base'; +import pageRendererFactory from './page'; +import componentRendererFactory from './component'; +import blockRendererFactory from './block'; +import addonRendererFactory from './addon'; +import tempRendererFactory from './temp'; +import rendererFactory from './renderer'; + +export { + baseRendererFactory, + pageRendererFactory, + componentRendererFactory, + blockRendererFactory, + addonRendererFactory, + tempRendererFactory, + rendererFactory, +}; diff --git a/packages/renderer-core/src/renderer/page.tsx b/packages/renderer-core/src/renderer/page.tsx new file mode 100644 index 000000000..354576425 --- /dev/null +++ b/packages/renderer-core/src/renderer/page.tsx @@ -0,0 +1,59 @@ +import baseRendererFactory from './base'; +import { parseData } from '../utils'; +import { IRendererProps } from '../types'; + +export default function pageRendererFactory() { + const BaseRenderer = baseRendererFactory(); + return class PageRenderer extends BaseRenderer { + static dislayName = 'page-renderer'; + __namespace = 'page'; + + __afterInit(props: IRendererProps) { + this.__generateCtx({ + page: this, + }); + const schema = props.__schema || {}; + this.state = this.__parseData(schema.state || {}); + this.__initDataSource(props); + this.__setLifeCycleMethods('constructor', arguments); + } + + async componentDidUpdate(prevProps: any) { + const { __ctx } = this.props; + const prevState = parseData(prevProps.__schema.state, __ctx); + const newState = parseData(this.props.__schema.state, __ctx); + // 当编排的时候修改schema.state值,需要将最新schema.state值setState + if (JSON.stringify(newState) != JSON.stringify(prevState)) { + this.setState(newState); + } + + super.componentDidUpdate(...arguments); + } + + render() { + const { __schema, __components } = this.props; + if (this.__checkSchema(__schema)) { + return '页面schema结构异常!'; + } + this.__debug(`render - ${__schema.fileName}`); + + this.__bindCustomMethods(this.props); + this.__initDataSource(this.props); + + // this.__setLifeCycleMethods('constructor', arguments); + + this.__generateCtx({ + page: this, + }); + this.__render(); + + + const { Page } = __components; + if (Page) { + return this.__renderComp(Page, { pageContext: this }); + } + + return this.__renderContent(this.__renderContextProvider({ pageContext: this })); + } + }; +} diff --git a/packages/renderer-core/src/renderer/renderer.tsx b/packages/renderer-core/src/renderer/renderer.tsx new file mode 100644 index 000000000..6cba11f89 --- /dev/null +++ b/packages/renderer-core/src/renderer/renderer.tsx @@ -0,0 +1,189 @@ +import Debug from 'debug'; +import { isEmpty } from '@ali/b3-one/lib/obj'; +import adapter from '../adapter'; +import contextFactory from '../context'; +import { isFileSchema, goldlog } from '../utils'; +import baseRendererFactory from './base'; +import divFactory from '../components/Div'; +import { IProps } from '../types'; + +export default function rendererFactory() { + const { createElement, Component, PureComponent, findDOMNode } = adapter.getRuntime(); + const RENDERER_COMPS: any = adapter.getRenderers(); + const BaseRenderer = baseRendererFactory(); + const AppContext = contextFactory(); + const Div = divFactory(); + + const ConfigProvider = adapter.getConfigProvider() || Div; + + const debug = Debug('renderer:entry'); + + class FaultComponent extends PureComponent { + render() { + // FIXME: errorlog + console.error('render error', this.props); + return createElement(Div, { + style: { + width: '100%', + height: '50px', + lineHeight: '50px', + textAlign: 'center', + fontSize: '15px', + color: '#ff0000', + border: '2px solid #ff0000', + }, + }, '组件渲染异常,请查看控制台日志'); + } + } + + class NotFoundComponent extends PureComponent { + render() { + console.error('component not found', this.props); + return createElement(Div, this.props, this.props.children || 'Component Not Found'); + } + } + + return class Renderer extends Component { + static dislayName = 'renderer'; + + static defaultProps = { + appHelper: null, + components: {}, + designMode: '', + suspended: false, + schema: {}, + onCompGetRef: () => { }, + onCompGetCtx: () => { }, + }; + + static findDOMNode = findDOMNode; + + constructor(props: IProps, context: any) { + super(props, context); + this.state = {}; + debug(`entry.constructor - ${props?.schema?.componentName}`); + } + + async componentDidMount() { + goldlog( + 'EXP', + { + action: 'appear', + value: !!this.props.designMode, + }, + 'renderer', + ); + debug(`entry.componentDidMount - ${this.props.schema && this.props.schema.componentName}`); + } + + async componentDidUpdate() { + debug(`entry.componentDidUpdate - ${this.props?.schema?.componentName}`); + } + + async componentWillUnmount() { + debug(`entry.componentWillUnmount - ${this.props?.schema?.componentName}`); + } + + async componentDidCatch(e: any) { + console.warn(e); + } + + shouldComponentUpdate(nextProps: IProps) { + return !nextProps.suspended; + } + + __getRef = (ref: any) => { + this.__ref = ref; + if (ref) { + this.props.onCompGetRef(this.props.schema, ref, true); + } + }; + + isValidComponent(SetComponent: any) { + return SetComponent; + } + + patchDidCatch(SetComponent: any) { + if (!this.isValidComponent(SetComponent)) { + return; + } + if (SetComponent.patchedCatch) { + return; + } + SetComponent.patchedCatch = true; + SetComponent.getDerivedStateFromError = (error: Error) => { + return { engineRenderError: true, error }; + }; + const engine = this; + const originRender = SetComponent.prototype.render; + SetComponent.prototype.render = function () { + if (this.state && this.state.engineRenderError) { + this.state.engineRenderError = false; + return engine.createElement(engine.getFaultComponent(), { + ...this.props, + error: this.state.error, + }); + } + return originRender.call(this); + }; + const originShouldComponentUpdate = SetComponent.prototype.shouldComponentUpdate; + SetComponent.prototype.shouldComponentUpdate = function (nextProps: IProps, nextState: any) { + if (nextState && nextState.engineRenderError) { + return true; + } + return originShouldComponentUpdate ? originShouldComponentUpdate.call(this, nextProps, nextState) : true; + }; + } + + createElement(SetComponent: any, props: any, children?: any) { + // TODO: enable in runtime mode? + this.patchDidCatch(SetComponent); + return (this.props.customCreateElement || createElement)(SetComponent, props, children); + } + + getNotFoundComponent() { + return this.props.notFoundComponent || NotFoundComponent; + } + + getFaultComponent() { + return this.props.faultComponent || FaultComponent; + } + + render() { + const { schema, designMode, appHelper, components } = this.props; + if (isEmpty(schema)) { + return null; + } + // 兼容乐高区块模板 + if (schema.componentName !== 'Div' && !isFileSchema(schema)) { + return '模型结构异常'; + } + debug('entry.render'); + const { componentName } = schema; + const allComponents = { ...RENDERER_COMPS, ...components }; + let Comp = allComponents[componentName] || RENDERER_COMPS[`${componentName}Renderer`]; + if (Comp && Comp.prototype) { + if (!(Comp.prototype instanceof BaseRenderer)) { + Comp = RENDERER_COMPS[`${componentName}Renderer`]; + } + } + + if (Comp) { + return createElement(AppContext.Provider, { value: { + appHelper, + components: allComponents, + engine: this, + }}, createElement(ConfigProvider, {device: this.props.device}, createElement(Comp, { + key: schema.__ctx && `${schema.__ctx.lceKey}_${schema.__ctx.idx || '0'}`, + ref: this.__getRef, + __appHelper: appHelper, + __components: allComponents, + __schema: schema, + __designMode: designMode, + ...this.props, + }))); + } + return null; + } + }; +} diff --git a/packages/renderer-core/src/renderer/temp.tsx b/packages/renderer-core/src/renderer/temp.tsx new file mode 100644 index 000000000..0bcd3e6d6 --- /dev/null +++ b/packages/renderer-core/src/renderer/temp.tsx @@ -0,0 +1,55 @@ +import baseRendererFactory from './base'; + +export default function tempRendererFactory() { + const BaseRenderer = baseRendererFactory(); + + return class TempRenderer extends BaseRenderer { + static dislayName = 'temp-renderer'; + __namespace = 'temp'; + + __init() { + this.state = {}; + this.cacheSetState = {}; + } + + async componentDidMount() { + const ctx = this.props.__ctx; + if (!ctx) return; + const { setState } = ctx; + this.cacheSetState = setState; + ctx.setState = (...args: any) => { + setState.call(ctx, ...args); + setTimeout(() => this.forceUpdate(), 0); + }; + this.__debug(`componentDidMount - ${this.props.__schema.fileName}`); + } + + async componentDidUpdate() { + this.__debug(`componentDidUpdate - ${this.props.__schema.fileName}`); + } + + async componentWillUnmount() { + const ctx = this.props.__ctx; + if (!ctx || !this.cacheSetState) return; + ctx.setState = this.cacheSetState; + delete this.cacheSetState; + this.__debug(`componentWillUnmount - ${this.props.__schema.fileName}`); + } + + async componentDidCatch(e: any) { + console.warn(e); + this.__debug(`componentDidCatch - ${this.props.__schema.fileName}`); + } + + render() { + const { __schema, __ctx } = this.props; + if (this.__checkSchema(__schema)) { + return '下钻编辑 schema 结构异常!'; + } + + this.__debug(`render - ${__schema.fileName}`); + + return this.__renderContent(this.__renderContextProvider({ __ctx })); + } + }; +} diff --git a/packages/renderer-core/src/types/index.ts b/packages/renderer-core/src/types/index.ts new file mode 100644 index 000000000..aac251dd3 --- /dev/null +++ b/packages/renderer-core/src/types/index.ts @@ -0,0 +1,122 @@ +export interface IProps { + appHelper: any; + components: { [key: string]: any }; + componentsMap: { [key: string]: any }; + designMode: string; + suspended: boolean; + schema: ISchema; + onCompGetRef: (schema: ISchema, ref: any) => void; + onCompGetCtx: (schema: ISchema, ref: any) => void; + customCreateElement: (...args: any) => any; + notFoundComponent: any; + faultComponent: any; +} + +export interface IRendererProps { + locale: string; + messages: object; + __appHelper: object; + __components: object; + __ctx: object; + __schema: ISchema; + [key: string]: any; +} + +export interface ComponentModel { + componentName: string; + props: any; + children: ComponentModel[]; +} + +export interface ISchema { + componentName: string; + props: any; + children: ComponentModel[] + dataSource?: any; + methods?: any; + lifeCycles?: any; + [key: string]: any; +} + +export interface IInfo { + schema: any; + Comp: any; + componentInfo?: any; +} + +export interface JSExpression { + type: string; + value: string; +} + +export interface DataSourceItem { + id: string; + isInit: boolean; + type: string; + options: { + uri: string; + params: object; + method: string; + shouldFetch?: string; + willFetch?: string; + fit?: string; + didFetch?: string; + }; + dataHandler: JSExpression; +} + +export interface DataSource { + list: DataSourceItem[]; + dataHandler: JSExpression; +} + +type Constructor = new(...args: any) => any; + +export interface IRuntime { + Component: Constructor; + PureComponent: Constructor; + createElement: (...args: any) => any; + createContext: (...args: any) => any; + forwardRef: (...args: any) => any; + findDOMNode: (...args: any) => any; + [key: string]: any; +} + +export interface IRendererModules { + BaseRenderer?: any; + PageRenderer: any; + ComponentRenderer: any; + BlockRenderer?: any, + AddonRenderer?: any, + TempRenderer?: any, + DivRenderer?: any; +} + +export interface IRenderer { + props?: IRendererProps; + context?: any; + __beforeInit: (props: IRendererProps) => any; + __init: (props: IRendererProps) => any; + __afterInit: (props: IRendererProps) => any; + reloadDataSource: () => Promise; + __setLifeCycleMethods: (method: string, args?: any) => any; + __bindCustomMethods: (props: IRendererProps) => any; + __generateCtx: (ctx: object) => any; + __parseData: (data: any, ctx?: object) => any; + __initDataSource: (props: IRendererProps) => any; + __render: () => any; + __getRef: (ref: any) => any; + getSchemaChildren: (schema: ISchema) => any; + __createDom: () => any; + __createVirtualDom: (schema: any, self: any, parentInfo: IInfo, idx: string | number) => any; + __createLoopVirtualDom: (schema: any, self: any, parentInfo: IInfo, idx: number | string) => any; + __parseProps: (props: any, self: any, path: string, info: IInfo) => any; + __initDebug: () => void; + __debug: (msg: string) => void; + __renderContextProvider: (customProps?: object, children?: any) => any; + __renderContextConsumer: (children: any) => any; + __renderContent: (children: any) => any; + __checkSchema: (schema: ISchema, extraComponents?: string | string[]) => any; + __renderComp: (Comp: any, ctxProps: object) => any; + $: (filedId: string, instance?: any) => any; +} diff --git a/packages/react-renderer/src/utils/index.ts b/packages/renderer-core/src/utils/common.ts similarity index 52% rename from packages/react-renderer/src/utils/index.ts rename to packages/renderer-core/src/utils/common.ts index 38497b836..c8f9f63d8 100644 --- a/packages/react-renderer/src/utils/index.ts +++ b/packages/renderer-core/src/utils/common.ts @@ -1,6 +1,5 @@ /* eslint-disable no-new-func */ import Debug from 'debug'; -import _keymaster from 'keymaster'; import { forEach as _forEach, shallowEqual as _shallowEqual } from '@ali/b3-one/lib/obj'; import { serialize as serializeParams } from '@ali/b3-one/lib/url'; // moment对象配置 @@ -20,12 +19,13 @@ import * as _jsonuri from 'jsonuri'; import IntlMessageFormat from 'intl-messageformat'; -export const keymaster = _keymaster; +import { ISchema } from '../types'; + export const forEach = _forEach; export const shallowEqual = _shallowEqual; export const moment = _moment; moment.locale('zh-cn'); -window.sdkVersion = pkg.version; +(window as any).sdkVersion = pkg.version; export const pick = _pick; export const deepEqual = _deepEqual; @@ -35,7 +35,6 @@ export const throttle = _throttle; export const debounce = _debounce; export const serialize = _serialize; export const jsonuri = _jsonuri; -export { get, post, jsonp, mtop, request } from './request'; const ReactIs = require('react-is'); const ReactPropTypesSecret = require('prop-types/lib/ReactPropTypesSecret'); @@ -47,6 +46,7 @@ const EXPRESSION_TYPE = { JSEXPRESSION: 'JSExpression', JSFUNCTION: 'JSFunction', JSSLOT: 'JSSlot', + JSBLOCK: 'JSBlock' }; const EXPRESSION_REG = /^\{\{(\{.*\}|.*?)\}\}$/; const hasSymbol = typeof Symbol === 'function' && Symbol.for; @@ -64,7 +64,7 @@ const ENV = { * @name isSchema * @description 判断是否是模型结构 */ -export function isSchema(schema, ignoreArr) { +export function isSchema(schema: ISchema, ignoreArr = false): boolean { if (isEmpty(schema)) return false; // Leaf 组件也返回 true if (schema.componentName === 'Leaf' || schema.componentName === 'Slot') return true; @@ -72,7 +72,7 @@ export function isSchema(schema, ignoreArr) { return !!(schema.componentName && schema.props && (typeof schema.props === 'object' || isJSExpression(schema.props))); } -export function isFileSchema(schema) { +export function isFileSchema(schema: ISchema) { if (isEmpty(schema)) return false; return ['Page', 'Block', 'Component', 'Addon', 'Temp'].includes(schema.componentName); } @@ -86,22 +86,25 @@ export function inSameDomain() { } } -export function getFileCssName(fileName) { +export function getFileCssName(fileName: string) { if (!fileName) return; const name = fileName.replace(/([A-Z])/g, '-$1').toLowerCase(); - return (`luna-${ name}`) + return (`luna-${name}`) .split('-') .filter((p) => !!p) .join('-'); } -export function isJSSlot(obj) { - return obj && typeof obj === 'object' && EXPRESSION_TYPE.JSSLOT === obj.type; +// 兼容乐高设计态 JSBlock 的老协议 +export function isJSSlot(obj: any) { + return obj && typeof obj === 'object' && ([EXPRESSION_TYPE.JSSLOT, EXPRESSION_TYPE.JSBLOCK].includes(obj.type)); } -export function isJSFunction(obj) { + +export function isJSFunction(obj: any) { return obj && typeof obj === 'object' && EXPRESSION_TYPE.JSFUNCTION === obj.type; } -export function isJSExpression(obj) { + +export function isJSExpression(obj: any) { // 兼容两种写法,有js构造表达式的情况 const isJSExpressionObj = obj && typeof obj === 'object' && EXPRESSION_TYPE.JSEXPRESSION === obj.type && typeof obj.value === 'string'; @@ -113,17 +116,17 @@ export function isJSExpression(obj) { * @name wait * @description 等待函数 */ -export function wait(ms) { +export function wait(ms: number) { return new Promise((resolve) => setTimeout(() => resolve(true), ms)); } -export function curry(Comp, hocs = []) { - return hocs.reverse().reduce((pre, cur) => { +export function curry(Comp: any, hocs = []) { + return hocs.reverse().reduce((pre, cur: (pre: any) => any) => { return cur(pre); }, Comp); } -export function getValue(obj, path, defaultValue) { +export function getValue(obj: any, path: string, defaultValue = {}) { if (isEmpty(obj) || typeof obj !== 'object') return defaultValue; const res = path.split('.').reduce((pre, cur) => { return pre && pre[cur]; @@ -132,25 +135,8 @@ export function getValue(obj, path, defaultValue) { return res; } -export function parseObj(schemaStr) { - if (typeof schemaStr !== 'string') return schemaStr; - // 默认调用顶层窗口的parseObj,保障new Function的window对象是顶层的window对象 - try { - if (inSameDomain() && window.parent.__newFunc) { - return window.parent.__newFunc(`"use strict"; return ${schemaStr}`)(); - } - return new Function(`"use strict"; return ${schemaStr}`)(); - } catch (err) { - return undefined; - } -} - -export function fastClone(obj) { - return parseObj(serialize(obj, { unsafe: true })); -} - // 更新obj的内容但不改变obj的指针 -export function fillObj(receiver = {}, ...suppliers) { +export function fillObj(receiver: any = {}, ...suppliers: any) { Object.keys(receiver).forEach((item) => { delete receiver[item]; }); @@ -159,14 +145,15 @@ export function fillObj(receiver = {}, ...suppliers) { } // 中划线转驼峰 -export function toHump(name) { +export function toHump(name: string) { // eslint-disable-next-line no-useless-escape - return name.replace(/\-(\w)/g, (all, letter) => { + return name.replace(/\-(\w)/g, (_: any, letter: string) => { return letter.toUpperCase(); }); } + // 驼峰转中划线 -export function toLine(name) { +export function toLine(name: string) { return name.replace(/([A-Z])/g, '-$1').toLowerCase(); } @@ -175,7 +162,7 @@ export function getEnv() { const { userAgent } = navigator; const isVscode = /Electron\//.test(userAgent); if (isVscode) return ENV.VSCODE; - const isTheia = window.is_theia === true; + const isTheia = (window as any).is_theia === true; if (isTheia) return ENV.WEBIDE; return ENV.WEB; } @@ -185,8 +172,8 @@ export function getEnv() { * @param {*} locale 国际化标识,例如 zh-CN、en-US * @param {*} messages 国际化语言包 */ -export function generateI18n(locale = 'zh-CN', messages = {}) { - return (key, values = {}) => { +export function generateI18n(locale = 'zh-CN', messages: any = {}) { + return (key: string, values = {}) => { if (!messages || !messages[key]) return ''; const formater = new IntlMessageFormat(messages[key], locale); return formater.format(values); @@ -197,10 +184,8 @@ export function generateI18n(locale = 'zh-CN', messages = {}) { * 判断当前组件是否能够设置ref * @param {*} Comp 需要判断的组件 */ -export function acceptsRef(Comp) { - return ( - (Comp.$$typeof && Comp.$$typeof === REACT_FORWARD_REF_TYPE) || (Comp.prototype && Comp.prototype.isReactComponent) - ); +export function acceptsRef(Comp: any) { + return Comp?.$$typeof === REACT_FORWARD_REF_TYPE || Comp?.prototype?.isReactComponent || Comp?.prototype?.setState; } /** @@ -209,9 +194,9 @@ export function acceptsRef(Comp) { * @param {Object} params 参数 * @param {String} logKey 属性串 */ -export function goldlog(gmKey, params = {}, logKey = 'other') { +export function goldlog(gmKey: string, params = {}, logKey = 'other') { // vscode 黄金令箭API - const sendIDEMessage = window.sendIDEMessage || (inSameDomain() && window.parent.sendIDEMessage); + const sendIDEMessage = (window as any).sendIDEMessage || (inSameDomain() && (window.parent as any).sendIDEMessage); const goKey = serializeParams({ sdkVersion: pkg.version, env: getEnv(), @@ -221,19 +206,19 @@ export function goldlog(gmKey, params = {}, logKey = 'other') { sendIDEMessage({ action: 'goldlog', data: { - logKey: `/iceluna.core.${logKey}`, + logKey: `/lce.core.${logKey}`, gmKey, goKey, }, }); } - window.goldlog && window.goldlog.record(`/iceluna.core.${logKey}`, gmKey, goKey, 'POST'); + (window as any)?.goldlog?.record(`/lce.core.${logKey}`, gmKey, goKey, 'POST'); } // utils为编辑器打包生成的utils文件内容,utilsConfig为数据库存放的utils配置 -export function generateUtils(utils, utilsConfig) { +export function generateUtils(utils: any, utilsConfig: { name: string; type: string; content: any }[]) { if (!Array.isArray(utilsConfig)) return { ...utils }; - const res = {}; + const res: any = {}; utilsConfig.forEach((item) => { if (!item.name || !item.type || !item.content) return; if (item.type === 'function' && typeof item.content === 'function') { @@ -244,70 +229,20 @@ export function generateUtils(utils, utilsConfig) { }); return res; } -// 复制到粘贴板 -export function setClipboardData(str) { - return new Promise((resolve, reject) => { - if (typeof str !== 'string') reject('不支持拷贝'); - if (navigator.clipboard) { - navigator.clipboard - .writeText(str) - .then(() => { - resolve(); - }) - .catch((err) => { - reject('复制失败,请重试!', err); - }); - } else { - const textArea = document.createElement('textarea'); - textArea.value = str; - document.body.appendChild(textArea); - textArea.focus(); - textArea.select(); - try { - const successful = document.execCommand('copy'); - if (successful) { - document.body.removeChild(textArea); - resolve(); - } - } catch (err) { - document.body.removeChild(textArea); - reject('复制失败,请重试!', err); - } - } - }); -} -// 获取粘贴板数据 -export function getClipboardData() { - return new Promise((resolve, reject) => { - if (window.clipboardData) { - resolve(window.clipboardData.getData('text')); - } else if (navigator.clipboard) { - return navigator.clipboard - .readText() - .then((res) => { - resolve(res); - }) - .catch((err) => { - reject('粘贴板获取失败', err); - }); - } else { - reject('粘贴板获取失败'); - } - }); -} + // 将函数返回结果转成promise形式,如果函数有返回值则根据返回值的bool类型判断是reject还是resolve,若函数无返回值默认执行resolve -export function transformToPromise(input) { +export function transformToPromise(input: any) { if (input instanceof Promise) return input; return new Promise((resolve, reject) => { if (input || input === undefined) { - resolve(); + resolve({}); } else { reject(); } }); } -export function moveArrayItem(arr, sourceIdx, distIdx, direction) { +export function moveArrayItem(arr: any[], sourceIdx: number, distIdx: number, direction: 'after' | 'before') { if ( !Array.isArray(arr) || sourceIdx === distIdx || @@ -330,9 +265,9 @@ export function moveArrayItem(arr, sourceIdx, distIdx, direction) { return arr; } -export function transformArrayToMap(arr, key, overwrite = true) { +export function transformArrayToMap(arr: any[], key: string, overwrite = true) { if (isEmpty(arr) || !Array.isArray(arr)) return {}; - const res = {}; + const res: any = {}; arr.forEach((item) => { const curKey = item[key]; if (item[key] === undefined) return; @@ -342,7 +277,7 @@ export function transformArrayToMap(arr, key, overwrite = true) { return res; } -export function checkPropTypes(value, name, rule, componentName) { +export function checkPropTypes(value: any, name: string, rule: any, componentName: string) { if (typeof rule === 'string') { rule = new Function(`"use strict"; const PropTypes = arguments[0]; return ${rule}`)(PropTypes2); } @@ -366,15 +301,15 @@ export function checkPropTypes(value, name, rule, componentName) { return !err; } -export function transformSchemaToPure(obj) { - const pureObj = (obj) => { +export function transformSchemaToPure(obj: any) { + const pureObj = (obj: any): any => { if (Array.isArray(obj)) { return obj.map((item) => pureObj(item)); } else if (typeof obj === 'object') { // 对于undefined及null直接返回 if (!obj) return obj; - const res = {}; - forEach(obj, (val, key) => { + const res: any = {}; + forEach(obj, (val: any, key: string) => { if (key.startsWith('__') && key !== '__ignoreParse') return; res[key] = pureObj(val); }); @@ -385,15 +320,15 @@ export function transformSchemaToPure(obj) { return pureObj(obj); } -export function transformSchemaToStandard(obj) { - const standardObj = (obj) => { +export function transformSchemaToStandard(obj: any) { + const standardObj = (obj: any): any => { if (Array.isArray(obj)) { return obj.map((item) => standardObj(item)); } else if (typeof obj === 'object') { // 对于undefined及null直接返回 if (!obj) return obj; - const res = {}; - forEach(obj, (val, key) => { + const res: any = {}; + forEach(obj, (val: any, key: string) => { if (key.startsWith('__') && key !== '__ignoreParse') return; if (isSchema(val) && key !== 'children' && obj.type !== 'JSSlot') { res[key] = { @@ -423,159 +358,19 @@ export function transformSchemaToStandard(obj) { } return obj; }; - return standardObj(obj, false); + return standardObj(obj); } -export function transformStringToFunction(str) { +export function transformStringToFunction(str: string) { if (typeof str !== 'string') return str; - if (inSameDomain() && window.parent.__newFunc) { - return window.parent.__newFunc(`"use strict"; return ${str}`)(); + if (inSameDomain() && (window.parent as any).__newFunc) { + return (window.parent as any).__newFunc(`"use strict"; return ${str}`)(); } else { return new Function(`"use strict"; return ${str}`)(); } } -export function addCssTag(id, content) { - let styleTag = document.getElementById(id); - if (styleTag) { - styleTag.innerHTML = content; - return; - } - styleTag = document.createElement('style'); - styleTag.id = id; - styleTag.class = 'luna-style'; - styleTag.innerHTML = content; - document.head.appendChild(styleTag); -} - -// 注册快捷 -export function registShortCuts(config, appHelper) { - const keyboardFilter = (event) => { - const eTarget = event.target || event.srcElement; - const { tagName } = eTarget; - const isInput = !!(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA'); - const isContenteditable = !!eTarget.getAttribute('contenteditable'); - if (isInput || isContenteditable) { - if (event.metaKey === true && [70, 83].includes(event.keyCode)) event.preventDefault(); // 禁止触发chrome原生的页面保存或查找 - return false; - } else { - return true; - } - }; - keymaster.filter = keyboardFilter; - - const ideMessage = appHelper.utils && appHelper.utils.ideMessage; - - // 复制 - if (!document.copyListener) { - document.copyListener = (e) => { - if (!keyboardFilter(e) || appHelper.isCopying) return; - const schema = appHelper.schemaHelper && appHelper.schemaHelper.schemaMap[appHelper.activeKey]; - if (!schema || !isSchema(schema)) return; - appHelper.isCopying = true; - const schemaStr = serialize(transformSchemaToPure(schema), { - unsafe: true, - }); - setClipboardData(schemaStr) - .then(() => { - ideMessage && ideMessage('success', '当前内容已复制到剪贴板,请使用快捷键Command+v进行粘贴'); - appHelper.emit('schema.copy', schemaStr, schema); - appHelper.isCopying = false; - }) - .catch((errMsg) => { - ideMessage && ideMessage('error', errMsg); - appHelper.isCopying = false; - }); - }; - document.addEventListener('copy', document.copyListener); - if (window.parent.vscode) { - keymaster('command+c', document.copyListener); - } - } - - // 粘贴 - if (!document.pasteListener) { - const doPaste = (e, text) => { - if (!keyboardFilter(e) || appHelper.isPasting) return; - const { schemaHelper } = appHelper; - let targetKey = appHelper.activeKey; - let direction = 'after'; - const topKey = schemaHelper.schema && schemaHelper.schema.__ctx && schemaHelper.schema.__ctx.lunaKey; - if (!targetKey || topKey === targetKey) { - const { schemaHelper } = appHelper; - const topKey = schemaHelper.schema && schemaHelper.schema.__ctx && schemaHelper.schema.__ctx.lunaKey; - if (!topKey) return; - targetKey = topKey; - direction = 'in'; - } - appHelper.isPasting = true; - const schema = parseObj(text); - if (!isSchema(schema)) { - appHelper.emit('illegalSchema.paste', text); - // ideMessage && ideMessage('error', '当前内容不是模型结构,不能粘贴进来!'); - console.warn('paste schema illegal'); - appHelper.isPasting = false; - return; - } - appHelper.emit('material.add', { - schema, - targetKey, - direction, - }); - appHelper.isPasting = false; - appHelper.emit('schema.paste', schema); - }; - document.pasteListener = (e) => { - const clipboardData = e.clipboardData || window.clipboardData; - const text = clipboardData && clipboardData.getData('text'); - doPaste(e, text); - }; - document.addEventListener('paste', document.pasteListener); - if (window.parent.vscode) { - keymaster('command+v', (e) => { - const { sendIDEMessage } = window.parent; - sendIDEMessage && - sendIDEMessage({ - action: 'readClipboard', - }) - .then((text) => { - doPaste(e, text); - }) - .catch((err) => { - console.warn(err); - }); - }); - } - } - - (config || []).forEach((item) => { - keymaster(item.keyboard, (ev) => { - ev.preventDefault(); - item.handler(ev, appHelper, keymaster); - }); - }); -} - -// 取消注册快捷 -export function unRegistShortCuts(config) { - (config || []).forEach((item) => { - keymaster.unbind(item.keyboard); - }); - if (window.parent.vscode) { - keymaster.unbind('command+c'); - keymaster.unbind('command+v'); - } - if (document.copyListener) { - document.removeEventListener('copy', document.copyListener); - delete document.copyListener; - } - if (document.pasteListener) { - document.removeEventListener('paste', document.pasteListener); - delete document.pasteListener; - } -} - -export function parseData(schema, self) { +export function parseData(schema: any, self: any): any { if (isJSExpression(schema)) { return parseExpression(schema, self); } else if (typeof schema === 'string') { @@ -587,8 +382,8 @@ export function parseData(schema, self) { } else if (typeof schema === 'object') { // 对于undefined及null直接返回 if (!schema) return schema; - const res = {}; - forEach(schema, (val, key) => { + const res: any = {}; + forEach(schema, (val: any, key: string) => { if (key.startsWith('__')) return; res[key] = parseData(val, self); }); @@ -598,14 +393,14 @@ export function parseData(schema, self) { } /* 全匹配{{开头,}}结尾的变量表达式,或者对象类型JSExpression,且均不支持省略this */ -export function parseExpression(str, self) { +export function parseExpression(str: any, self: any) { try { const contextArr = ['"use strict";', 'var __self = arguments[0];']; contextArr.push('return '); let tarStr; // 向前兼容,支持标准协议新格式 if (typeof str === 'string') { - const regRes = str.trim().match(EXPRESSION_REG); + const regRes: any = str.trim().match(EXPRESSION_REG); tarStr = regRes[1]; } else { tarStr = (str.value || '').trim(); @@ -613,8 +408,8 @@ export function parseExpression(str, self) { tarStr = tarStr.replace(/this(\W|$)/g, (a, b) => `__self${b}`); tarStr = contextArr.join('\n') + tarStr; // 默认调用顶层窗口的parseObj,保障new Function的window对象是顶层的window对象 - if (inSameDomain() && window.parent.__newFunc) { - return window.parent.__newFunc(tarStr)(self); + if (inSameDomain() && (window.parent as any).__newFunc) { + return (window.parent as any).__newFunc(tarStr)(self); } return new Function(tarStr)(self); } catch (err) { @@ -622,3 +417,16 @@ export function parseExpression(str, self) { return undefined; } } + +// 首字母大写 +export function capitalizeFirstLetter(word: string) { + return word[0].toUpperCase() + word.slice(1); +} + +export function isI18n(obj: any) { + return obj && typeof obj === 'object' && obj?.type === 'i18n'; +} + +export function isVariable(obj: any) { + return obj && typeof obj === 'object' && obj?.type === 'variable'; +} diff --git a/packages/react-renderer/src/utils/dataHelper.ts b/packages/renderer-core/src/utils/dataHelper.ts similarity index 82% rename from packages/react-renderer/src/utils/dataHelper.ts rename to packages/renderer-core/src/utils/dataHelper.ts index 7503b92da..a47f18f24 100644 --- a/packages/react-renderer/src/utils/dataHelper.ts +++ b/packages/renderer-core/src/utils/dataHelper.ts @@ -1,6 +1,7 @@ /* eslint-disable object-curly-newline */ -import { transformArrayToMap, isJSFunction, transformStringToFunction, clone } from './index'; +import { transformArrayToMap, isJSFunction, transformStringToFunction, clone } from './common'; import { jsonp, mtop, request, get, post, bzb } from './request'; +import { DataSource, DataSourceItem } from '../types'; const DS_STATUS = { INIT: 'init', @@ -8,12 +9,21 @@ const DS_STATUS = { LOADED: 'loaded', ERROR: 'error', }; -export default class DataHelper { - constructor(comp, config = {}, appHelper, parser) { + +export class DataHelper { + host: any; + config: DataSource; + parser: any; + ajaxList: any[]; + ajaxMap: any; + dataSourceMap: any; + appHelper: any; + + constructor(comp: any, config: DataSource, appHelper: any, parser: any) { this.host = comp; - this.config = config; + this.config = config || {}; this.parser = parser; - this.ajaxList = (config && config.list) || []; + this.ajaxList = config?.list || []; this.ajaxMap = transformArrayToMap(this.ajaxList, 'id'); this.dataSourceMap = this.generateDataSourceMap(); this.appHelper = appHelper; @@ -21,8 +31,8 @@ export default class DataHelper { // 重置config,dataSourceMap状态会被重置; resetConfig(config = {}) { - this.config = config; - this.ajaxList = (config && config.list) || []; + this.config = config as DataSource; + this.ajaxList = (config as DataSource)?.list || []; this.ajaxMap = transformArrayToMap(this.ajaxList, 'id'); this.dataSourceMap = this.generateDataSourceMap(); return this.dataSourceMap; @@ -30,9 +40,9 @@ export default class DataHelper { // 更新config,只会更新配置,状态保存; updateConfig(config = {}) { - this.config = config; - this.ajaxList = (config && config.list) || []; - const ajaxMap = transformArrayToMap(this.ajaxList, 'id'); + this.config = config as DataSource; + this.ajaxList = (config as DataSource)?.list || []; + const ajaxMap: any = transformArrayToMap(this.ajaxList, 'id'); // 删除已经移除的接口 Object.keys(this.ajaxMap).forEach((key) => { if (!ajaxMap[key]) { @@ -45,7 +55,8 @@ export default class DataHelper { if (!this.dataSourceMap[item.id]) { this.dataSourceMap[item.id] = { status: DS_STATUS.INIT, - load: (...args) => { + load: (...args: any) => { + // @ts-ignore return this.getDataSource(item.id, ...args); }, }; @@ -55,11 +66,12 @@ export default class DataHelper { } generateDataSourceMap() { - const res = {}; + const res: any = {}; this.ajaxList.forEach((item) => { res[item.id] = { status: DS_STATUS.INIT, - load: (...args) => { + load: (...args: any) => { + // @ts-ignore return this.getDataSource(item.id, ...args); }, }; @@ -67,14 +79,14 @@ export default class DataHelper { return res; } - updateDataSourceMap(id, data, error) { + updateDataSourceMap(id: string, data: any, error: any) { this.dataSourceMap[id].error = error || undefined; this.dataSourceMap[id].data = data; this.dataSourceMap[id].status = error ? DS_STATUS.ERROR : DS_STATUS.LOADED; } getInitData() { - const initSyncData = this.parser(this.ajaxList).filter((item) => { + const initSyncData = this.parser(this.ajaxList).filter((item: DataSourceItem) => { if (item.isInit) { this.dataSourceMap[item.id].status = DS_STATUS.LOADING; return true; @@ -89,14 +101,14 @@ export default class DataHelper { } if (!dataHandler || typeof dataHandler !== 'function') return res; try { - return dataHandler.call(this.host, res); + return (dataHandler as any).call(this.host, res); } catch (e) { console.error('请求数据处理函数运行出错', e); } }); } - getDataSource(id, params, otherOptions, callback) { + getDataSource(id: string, params: any, otherOptions: any, callback: any) { const req = this.parser(this.ajaxMap[id]); const options = req.options || {}; if (typeof otherOptions === 'function') { @@ -129,7 +141,7 @@ export default class DataHelper { }, }, ]) - .then((res) => { + .then((res: any) => { try { callback && callback(res && res[id]); } catch (e) { @@ -149,15 +161,15 @@ export default class DataHelper { }); } - asyncDataHandler(asyncDataList) { + asyncDataHandler(asyncDataList: any[]) { return new Promise((resolve, reject) => { const allReq = []; - const doserReq = []; - const doserList = []; + const doserReq: {name: string; package: string; params: any }[] = []; + const doserList: string[] = []; const beforeRequest = this.appHelper && this.appHelper.utils && this.appHelper.utils.beforeRequest; const afterRequest = this.appHelper && this.appHelper.utils && this.appHelper.utils.afterRequest; const csrfInput = document.getElementById('_csrf_token'); - const _tb_token_ = csrfInput && csrfInput.value; + const _tb_token_ = (csrfInput as any)?.value; asyncDataList.forEach((req) => { const { id, type, options } = req; if (!id || !type || type === 'legao') return; @@ -186,27 +198,27 @@ export default class DataHelper { }); } if (allReq.length === 0) resolve({}); - const res = {}; + const res: any = {}; // todo: Promise.all( - allReq.map((item) => { + allReq.map((item: any) => { return new Promise((resolve) => { const { type, id, dataHandler, options } = item; - const doFetch = (type, options) => { + const doFetch = (type: string, options: any) => { this.fetchOne(type, options) - .then((data) => { + .then((data: any) => { if (afterRequest) { - this.appHelper.utils.afterRequest(item, data, undefined, (data, error) => { + this.appHelper.utils.afterRequest(item, data, undefined, (data: any, error: any) => { fetchHandler(data, error); }); } else { fetchHandler(data, undefined); } }) - .catch((err) => { + .catch((err: Error) => { if (afterRequest) { // 必须要这么调用,否则beforeRequest中的this会丢失 - this.appHelper.utils.afterRequest(item, undefined, err, (data, error) => { + this.appHelper.utils.afterRequest(item, undefined, err, (data: any, error: any) => { fetchHandler(data, error); }); } else { @@ -214,13 +226,13 @@ export default class DataHelper { } }); }; - const fetchHandler = (data, error) => { + const fetchHandler = (data: any, error: any) => { if (type === 'doServer') { if (!Array.isArray(data)) { data = [data]; } doserList.forEach((id, idx) => { - const req = this.ajaxMap[id]; + const req: any = this.ajaxMap[id]; if (req) { res[id] = this.dataHandler(id, req.dataHandler, data && data[idx], error); this.updateDataSourceMap(id, res[id], error); @@ -230,7 +242,7 @@ export default class DataHelper { res[id] = this.dataHandler(id, dataHandler, data, error); this.updateDataSourceMap(id, res[id], error); } - resolve(); + resolve({}); }; if (type === 'doServer') { @@ -243,7 +255,7 @@ export default class DataHelper { // 请求切片 if (beforeRequest) { // 必须要这么调用,否则beforeRequest中的this会丢失 - this.appHelper.utils.beforeRequest(item, clone(options), (options) => doFetch(type, options)); + this.appHelper.utils.beforeRequest(item, clone(options), (options: any) => doFetch(type, options)); } else { doFetch(type, options); } @@ -260,7 +272,7 @@ export default class DataHelper { } // dataHandler todo: - dataHandler(id, dataHandler, data, error) { + dataHandler(id: string, dataHandler: any, data: any, error: any) { if (isJSFunction(dataHandler)) { dataHandler = transformStringToFunction(dataHandler.value); } @@ -272,7 +284,7 @@ export default class DataHelper { } } - fetchOne(type, options) { + fetchOne(type: string, options: any) { // eslint-disable-next-line prefer-const let { uri, url, method = 'GET', headers, params, ...otherProps } = options; otherProps = otherProps || {}; diff --git a/packages/renderer-core/src/utils/index.ts b/packages/renderer-core/src/utils/index.ts new file mode 100644 index 000000000..27d5e016f --- /dev/null +++ b/packages/renderer-core/src/utils/index.ts @@ -0,0 +1,3 @@ +export * from './common'; +export * from './dataHelper'; +export * from './request'; diff --git a/packages/react-renderer/src/utils/request.ts b/packages/renderer-core/src/utils/request.ts similarity index 89% rename from packages/react-renderer/src/utils/request.ts rename to packages/renderer-core/src/utils/request.ts index 6b2ae46a3..ced0726fc 100644 --- a/packages/react-renderer/src/utils/request.ts +++ b/packages/renderer-core/src/utils/request.ts @@ -4,7 +4,7 @@ import fetchJsonp from 'fetch-jsonp'; import bzbRequest from '@ali/bzb-request'; import { serialize, buildUrl, parseUrl } from '@ali/b3-one/lib/url'; -export function get(dataAPI, params = {}, headers = {}, otherProps = {}) { +export function get(dataAPI: any, params = {}, headers = {}, otherProps = {}) { headers = { Accept: 'application/json', ...headers, @@ -13,7 +13,7 @@ export function get(dataAPI, params = {}, headers = {}, otherProps = {}) { return request(dataAPI, 'GET', null, headers, otherProps); } -export function post(dataAPI, params = {}, headers = {}, otherProps = {}) { +export function post(dataAPI: any, params = {}, headers: any = {}, otherProps = {}) { headers = { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded', @@ -30,7 +30,7 @@ export function post(dataAPI, params = {}, headers = {}, otherProps = {}) { ); } -export function request(dataAPI, method = 'GET', data, headers = {}, otherProps = {}) { +export function request(dataAPI: any, method = 'GET', data: any, headers = {}, otherProps: any = {}) { switch (method) { case 'PUT': case 'DELETE': @@ -112,7 +112,7 @@ export function request(dataAPI, method = 'GET', data, headers = {}, otherProps }); } -export function jsonp(dataAPI, params = {}, otherProps = {}) { +export function jsonp(dataAPI: any, params = {}, otherProps = {}) { return new Promise((resolve, reject) => { otherProps = { timeout: 5000, @@ -133,7 +133,7 @@ export function jsonp(dataAPI, params = {}, otherProps = {}) { }); } -export function mtop(dataAPI, params, otherProps = {}) { +export function mtop(dataAPI: any, params: any, otherProps: any = {}) { fetchMtop.config.subDomain = otherProps.subDomain || 'm'; return fetchMtop.request({ api: dataAPI, @@ -147,7 +147,7 @@ export function mtop(dataAPI, params, otherProps = {}) { }); } -export function bzb(apiCode, params, otherProps = {}) { +export function bzb(apiCode: string, params: any, otherProps: any = {}) { // 通过url参数设置小二工作台请求环境 const getUrlEnv = () => { try { diff --git a/packages/renderer-core/tsconfig.json b/packages/renderer-core/tsconfig.json new file mode 100644 index 000000000..91c180bdd --- /dev/null +++ b/packages/renderer-core/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["./src/"], +}