diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..4636a1bdd --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,116 @@ +name: Node CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + upload-designer-codecov: + runs-on: ubuntu-latest + # if: ${{ github.event.pull_request.head.repo.full_name == 'alibaba/lowcode-engine' }} + steps: + - name: checkout + uses: actions/checkout@v2 + + - uses: actions/setup-node@v2 + with: + node-version: '14' + + - name: install + run: npm i && npm run setup:skip-build + + - name: test designer + run: cd packages/designer && npm run test:cov && cd ../.. + + - name: Upload designer coverage to Codecov + uses: codecov/codecov-action@v3 + with: + # working-directory: packages/designer + directory: ./packages/designer/coverage + token: ${{ secrets.CODECOV_TOKEN }} + name: designer + fail_ci_if_error: true + verbose: true + + upload-renderer-core: + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v2 + + - uses: actions/setup-node@v2 + with: + node-version: '14' + + - name: install + run: npm i && npm run setup:skip-build + + - name: test renderer-core + run: cd packages/renderer-core && npm run test:cov && cd ../.. + + - name: Upload renderer-core coverage to Codecov + uses: codecov/codecov-action@v3 + with: + # working-directory: packages/designer + directory: ./packages/renderer-core/coverage + token: ${{ secrets.CODECOV_TOKEN }} + name: renderer-core + fail_ci_if_error: true + verbose: true + + upload-react-simulator-renderer: + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v2 + + - uses: actions/setup-node@v2 + with: + node-version: '14' + + - name: install + run: npm i && npm run setup:skip-build + + - name: test react-simulator-renderer + run: cd packages/react-simulator-renderer && npm run test:cov && cd ../.. + + - name: Upload react-simulator-renderer coverage to Codecov + uses: codecov/codecov-action@v3 + with: + # working-directory: packages/designer + directory: ./packages/react-simulator-renderer/coverage + token: ${{ secrets.CODECOV_TOKEN }} + name: react-simulator-renderer + fail_ci_if_error: true + verbose: true + + upload-code-generator: + runs-on: ubuntu-latest + # if: ${{ github.event.pull_request.head.repo.full_name == 'alibaba/lowcode-engine' }} + steps: + - name: checkout + uses: actions/checkout@v2 + + - uses: actions/setup-node@v2 + with: + node-version: '14' + + - name: install + run: npm i && npm run setup:skip-build + + - name: test code-generator + run: cd modules/code-generator && npm i && npm run build && npm run test:cov && cd ../.. + + - name: Upload code-generator coverage to Codecov + uses: codecov/codecov-action@v3 + with: + # working-directory: packages/designer + directory: ./modules/code-generator/coverage + token: ${{ secrets.CODECOV_TOKEN }} + name: code-generator + fail_ci_if_error: true + verbose: true \ No newline at end of file diff --git a/modules/code-generator/src/parser/SchemaParser.ts b/modules/code-generator/src/parser/SchemaParser.ts index b7b9f2b0f..2e526d88b 100644 --- a/modules/code-generator/src/parser/SchemaParser.ts +++ b/modules/code-generator/src/parser/SchemaParser.ts @@ -123,7 +123,7 @@ export class SchemaParser implements ISchemaParser { const schema = this.decodeSchema(schemaSrc); // 解析三方组件依赖 - schema.componentsMap.forEach((info) => { + schema.componentsMap.forEach((info: any) => { if (info.componentName) { compDeps[info.componentName] = { ...info, diff --git a/modules/code-generator/src/utils/expressionParser.ts b/modules/code-generator/src/utils/expressionParser.ts index 3589fc3f5..58c6e6ca8 100644 --- a/modules/code-generator/src/utils/expressionParser.ts +++ b/modules/code-generator/src/utils/expressionParser.ts @@ -181,7 +181,7 @@ export function parseExpressionGetKeywords(expr: string | null | undefined): str const fieldValue = node[fieldName as keyof typeof node]; if (typeof fieldValue === 'object') { if (Array.isArray(fieldValue)) { - fieldValue.forEach((item) => { + fieldValue.forEach((item: any) => { addIdentifierIfNeeded(item); }); } else { @@ -233,7 +233,7 @@ export function parseExpressionGetGlobalVariables( const fieldValue = node[fieldName as keyof typeof node]; if (typeof fieldValue === 'object') { if (Array.isArray(fieldValue)) { - fieldValue.forEach((item) => { + fieldValue.forEach((item: any) => { addUndeclaredIdentifierIfNeeded(item, path); }); } else { diff --git a/packages/designer/src/builtin-simulator/create-simulator.ts b/packages/designer/src/builtin-simulator/create-simulator.ts index 2ee0320d8..007286e9c 100644 --- a/packages/designer/src/builtin-simulator/create-simulator.ts +++ b/packages/designer/src/builtin-simulator/create-simulator.ts @@ -98,7 +98,7 @@ export function createSimulator( doc.close(); return new Promise((resolve) => { - const renderer = win.SimulatorRenderer || host.renderer; + const renderer = win.SimulatorRenderer; if (renderer) { return resolve(renderer); } diff --git a/packages/designer/src/builtin-simulator/host.ts b/packages/designer/src/builtin-simulator/host.ts index 4bf049f5b..db8625adb 100644 --- a/packages/designer/src/builtin-simulator/host.ts +++ b/packages/designer/src/builtin-simulator/host.ts @@ -232,6 +232,10 @@ export class BuiltinSimulatorHost implements ISimulatorHost { - return this.getClosestNodeInstance(instance, context.nodeId)?.instance === context.instance; + return this.getClosestNodeInstance(instance, context?.nodeId)?.instance === context.instance; }); } diff --git a/packages/designer/src/component-meta.ts b/packages/designer/src/component-meta.ts index 3ca04f809..4e1a74c77 100644 --- a/packages/designer/src/component-meta.ts +++ b/packages/designer/src/component-meta.ts @@ -176,12 +176,29 @@ export class ComponentMeta { } private parseMetadata(metadata: ComponentMetadata) { - const { componentName, npm } = metadata; + const { componentName, npm, ...others } = metadata; + let _metadata = metadata; + if (!npm && !Object.keys(others).length) { + // 没有注册的组件,只能删除,不支持复制、移动等操作 + _metadata = { + componentName, + configure: { + component: { + disableBehaviors: ['copy', 'move', 'lock', 'unlock'], + }, + advanced: { + callbacks: { + onMoveHook: () => false, + }, + }, + }, + }; + } this._npm = npm || this._npm; this._componentName = componentName; // 额外转换逻辑 - this._transformedMetadata = this.transformMetadata(metadata); + this._transformedMetadata = this.transformMetadata(_metadata); const { title } = this._transformedMetadata; if (title) { diff --git a/packages/designer/tests/builtin-simulator/host.test.ts b/packages/designer/tests/builtin-simulator/host.test.ts index 0329f06cb..746134c36 100644 --- a/packages/designer/tests/builtin-simulator/host.test.ts +++ b/packages/designer/tests/builtin-simulator/host.test.ts @@ -23,6 +23,7 @@ import { setShaken, } from '../../src/designer/dragon'; import { Project } from '../../src/project/project'; +import pageMetadata from '../fixtures/component-metadata/page'; import { Node } from '../../src/document/node/node'; import { Designer } from '../../src/designer/designer'; import { DocumentModel } from '../../src/document/document-model'; @@ -46,6 +47,7 @@ describe('Host 测试', () => { beforeEach(() => { designer = new Designer({ editor }); project = designer.project; + designer.createComponentMeta(pageMetadata); doc = project.createDocument(formSchema); host = new BuiltinSimulatorHost(designer.project); }); @@ -373,6 +375,14 @@ describe('Host 测试', () => { }, })).toBeNull(); }); + it('notFoundComponent', () => { + expect(host.locate({ + dragObject: { + type: DragObjectType.Node, + nodes: [doc.getNode('form')], + }, + })).toBeUndefined(); + }) it('locate', () => { host.locate({ dragObject: { diff --git a/packages/editor-core/src/config.ts b/packages/editor-core/src/config.ts index 985980673..06600fd01 100644 --- a/packages/editor-core/src/config.ts +++ b/packages/editor-core/src/config.ts @@ -137,6 +137,10 @@ const VALID_ENGINE_OPTIONS = { type: 'boolean', description: 'JSExpression 是否只支持使用 this 来访问上下文变量', }, + enableStrictNotFoundMode: { + type: 'boolean', + description: '当开启组件未找到严格模式时,渲染模块不会默认给一个容器组件', + }, }; export interface EngineOptions { /** @@ -258,6 +262,12 @@ export interface EngineOptions { * JSExpression 是否只支持使用 this 来访问上下文变量,假如需要兼容原来的 'state.xxx',则设置为 false */ thisRequiredInJSE?: boolean; + + /** + * @default false + * 当开启组件未找到严格模式时,渲染模块不会默认给一个容器组件 + */ + enableStrictNotFoundMode?: boolean; } const getStrictModeValue = (engineOptions: EngineOptions, defaultValue: boolean): boolean => { diff --git a/packages/engine/README-zh_CN.md b/packages/engine/README-zh_CN.md index 91e90ad52..5a070affb 100644 --- a/packages/engine/README-zh_CN.md +++ b/packages/engine/README-zh_CN.md @@ -12,7 +12,9 @@ [![NPM version][npm-image]][npm-url] [![NPM downloads][download-image]][download-url] - [![][issues-helper-image]][issues-helper-url] [![Issues need help][help-wanted-image]][help-wanted-url] +[![][issues-helper-image]][issues-helper-url] [![Issues need help][help-wanted-image]][help-wanted-url] + +[![codecov][codecov-image-url]][codecov-url] [npm-image]: https://img.shields.io/npm/v/@alilc/lowcode-engine.svg?style=flat-square [npm-url]: http://npmjs.org/package/@alilc/lowcode-engine @@ -25,6 +27,9 @@ [issues-helper-image]: https://img.shields.io/badge/using-issues--helper-orange?style=flat-square [issues-helper-url]: https://github.com/actions-cool/issues-helper +[codecov-image-url]: https://codecov.io/gh/alibaba/lowcode-engine/branch/main/graph/badge.svg +[codecov-url]: https://codecov.io/gh/alibaba/lowcode-engine + [![](https://img.alicdn.com/imgextra/i2/O1CN01UhoS7C1sNNhySvfWi_!!6000000005754-2-tps-2878-1588.png)](https://lowcode-engine.cn) diff --git a/packages/engine/README.md b/packages/engine/README.md index c4cc5f8ae..63e967140 100644 --- a/packages/engine/README.md +++ b/packages/engine/README.md @@ -14,6 +14,8 @@ An enterprise-class low-code technology stack with scale-out design [![][issues-helper-image]][issues-helper-url] [![Issues need help][help-wanted-image]][help-wanted-url] +[![codecov][codecov-image-url]][codecov-url] + [npm-image]: https://img.shields.io/npm/v/@alilc/lowcode-engine.svg?style=flat-square [npm-url]: http://npmjs.org/package/@alilc/lowcode-engine @@ -25,6 +27,9 @@ An enterprise-class low-code technology stack with scale-out design [issues-helper-image]: https://img.shields.io/badge/using-issues--helper-orange?style=flat-square [issues-helper-url]: https://github.com/actions-cool/issues-helper +[codecov-image-url]: https://codecov.io/gh/alibaba/lowcode-engine/branch/main/graph/badge.svg +[codecov-url]: https://codecov.io/gh/alibaba/lowcode-engine + [![](https://img.alicdn.com/imgextra/i2/O1CN01UhoS7C1sNNhySvfWi_!!6000000005754-2-tps-2878-1588.png)](http://lowcode-engine.cn) diff --git a/packages/engine/src/engine-core.ts b/packages/engine/src/engine-core.ts index 6a5ab108c..aad924324 100644 --- a/packages/engine/src/engine-core.ts +++ b/packages/engine/src/engine-core.ts @@ -1,5 +1,5 @@ import { createElement } from 'react'; -import { render } from 'react-dom'; +import { render, unmountComponentAtNode } from 'react-dom'; import { globalContext, Editor, engineConfig, EngineOptions } from '@alilc/lowcode-editor-core'; import { Designer, @@ -16,7 +16,7 @@ import { import Outline, { OutlineBackupPane, getTreeMaster } from '@alilc/lowcode-plugin-outline-pane'; import DesignerPlugin from '@alilc/lowcode-plugin-designer'; -import { Hotkey, Project, Skeleton, Setters, Material, Event } from '@alilc/lowcode-shell'; +import { Hotkey, Project, Skeleton, Setters, Material, Event, DocumentModel } from '@alilc/lowcode-shell'; import { getLogger, isPlainObject } from '@alilc/lowcode-utils'; import './modules/live-editing'; import utils from './modules/utils'; @@ -184,7 +184,8 @@ engineConfig.set('isOpenSource', isOpenSource); await plugins.register(defaultPanelRegistry); })(); -let engineInited = false; +// container which will host LowCodeEngine DOM +let engineContainer: HTMLElement; // @ts-ignore webpack Define variable export const version = VERSION_PLACEHOLDER; engineConfig.set('ENGINE_VERSION', version); @@ -193,23 +194,22 @@ export async function init( options?: EngineOptions, pluginPreference?: PluginPreference, ) { - if (engineInited) return; - engineInited = true; + await destroy(); let engineOptions = null; - let engineContainer = null; if (isPlainObject(container)) { engineOptions = container; engineContainer = document.createElement('div'); + engineContainer.id = 'engine'; document.body.appendChild(engineContainer); } else { engineOptions = options; engineContainer = container; if (!container) { engineContainer = document.createElement('div'); + engineContainer.id = 'engine'; document.body.appendChild(engineContainer); } } - engineContainer.id = 'engine'; engineConfig.setEngineOptions(engineOptions as any); await plugins.init(pluginPreference as any); @@ -222,3 +222,17 @@ export async function init( engineContainer, ); } + +export async function destroy() { + // remove all documents + const { documents } = project; + if (Array.isArray(documents) && documents.length > 0) { + documents.forEach(((doc: DocumentModel) => project.removeDocument(doc))); + } + + // TODO: delete plugins except for core plugins + + // unmount DOM container, this will trigger React componentWillUnmount lifeCycle, + // so necessary cleanups will be done. + engineContainer && unmountComponentAtNode(engineContainer); +} diff --git a/packages/plugin-outline-pane/src/main.ts b/packages/plugin-outline-pane/src/main.ts index 1f2e23ffd..85c4021f0 100644 --- a/packages/plugin-outline-pane/src/main.ts +++ b/packages/plugin-outline-pane/src/main.ts @@ -152,7 +152,8 @@ export class OutlineMain implements ISensor, ITreeBoard, IScrollable { return canMove; }); - if (!operationalNodes || operationalNodes.length === 0) { + // 如果拖拽的是 Node 才需要后面的判断,拖拽 data 不需要 + if (isDragNodeObject(dragObject) && (!operationalNodes || operationalNodes.length === 0)) { return; } diff --git a/packages/rax-renderer/src/hoc/compFactory.tsx b/packages/rax-renderer/src/hoc/compFactory.tsx index 58c7aa763..1c821ab6d 100644 --- a/packages/rax-renderer/src/hoc/compFactory.tsx +++ b/packages/rax-renderer/src/hoc/compFactory.tsx @@ -17,7 +17,7 @@ export default function compFactory(schema, components = {}, componentsMap = {}, const AppContext = contextFactory(); class LNCompView extends Component { - static dislayName = 'lce-comp-factory'; + static displayName = 'LceCompFactory'; static version = config.version || '0.0.0'; diff --git a/packages/rax-simulator-renderer/src/renderer.ts b/packages/rax-simulator-renderer/src/renderer.ts index 0e42785e7..a123dfc7e 100644 --- a/packages/rax-simulator-renderer/src/renderer.ts +++ b/packages/rax-simulator-renderer/src/renderer.ts @@ -538,6 +538,7 @@ export class SimulatorRendererContainer implements BuiltinSimulatorRenderer { // mock _leaf,减少性能开销 const _leaf = { isEmpty: () => false, + isMock: true, }; viewProps._leaf = _leaf; return createElement(Comp, viewProps, children); diff --git a/packages/react-simulator-renderer/package.json b/packages/react-simulator-renderer/package.json index 205dde40f..62f267db9 100644 --- a/packages/react-simulator-renderer/package.json +++ b/packages/react-simulator-renderer/package.json @@ -13,7 +13,8 @@ "scripts": { "test": "build-scripts test --config build.test.json", "build": "NODE_OPTIONS=--max_old_space_size=8192 build-scripts build --skip-demo", - "build:umd": "NODE_OPTIONS=--max_old_space_size=8192 build-scripts build --config build.umd.json" + "build:umd": "NODE_OPTIONS=--max_old_space_size=8192 build-scripts build --config build.umd.json", + "test:cov": "build-scripts test --config build.test.json --jest-coverage" }, "dependencies": { "@alilc/lowcode-designer": "1.0.15-beta.0", diff --git a/packages/react-simulator-renderer/src/renderer-view.tsx b/packages/react-simulator-renderer/src/renderer-view.tsx index e855d583d..e68d9c401 100644 --- a/packages/react-simulator-renderer/src/renderer-view.tsx +++ b/packages/react-simulator-renderer/src/renderer-view.tsx @@ -8,7 +8,7 @@ import { getClosestNode, isFromVC, isReactComponent } from '@alilc/lowcode-utils import { GlobalEvent } from '@alilc/lowcode-types'; import { SimulatorRendererContainer, DocumentInstance } from './renderer'; import { host } from './host'; - +import { isRendererDetached } from './utils/misc'; import './renderer.less'; // patch cloneElement avoid lost keyProps @@ -170,14 +170,12 @@ class Renderer extends Component<{ this.startTime = Date.now(); this.schemaChangedSymbol = false; - if (!container.autoRender) return null; + if (!container.autoRender || isRendererDetached()) return null; return ( { documentInstance.mountInstance(schema.id, ref); }} + enableStrictNotFoundMode={host.enableStrictNotFoundMode} /> ); } diff --git a/packages/react-simulator-renderer/src/renderer.ts b/packages/react-simulator-renderer/src/renderer.ts index 5605ba479..2dfdeccac 100644 --- a/packages/react-simulator-renderer/src/renderer.ts +++ b/packages/react-simulator-renderer/src/renderer.ts @@ -51,24 +51,6 @@ export class DocumentInstance { return this._components; } - /** - * 本次的变更数据 - */ - @obx.ref private _deltaData: any = {}; - - @computed get deltaData(): any { - return this._deltaData; - } - - /** - * 是否使用增量模式 - */ - @obx.ref private _deltaMode: boolean = false; - - @computed get deltaMode(): boolean { - return this._deltaMode; - } - // context from: utils、constants、history、location、match @obx.ref private _appContext = {}; @@ -116,7 +98,7 @@ export class DocumentInstance { return this.document.id; } - private unmountIntance(id: string, instance: ReactInstance) { + private unmountInstance(id: string, instance: ReactInstance) { const instances = this.instancesMap.get(id); if (instances) { const i = instances.indexOf(instance); @@ -144,11 +126,11 @@ export class DocumentInstance { } return; } - const unmountIntance = this.unmountIntance.bind(this); + const unmountInstance = this.unmountInstance.bind(this); const origId = (instance as any)[SYMBOL_VNID]; if (origId && origId !== id) { // 另外一个节点的 instance 在此被复用了,需要从原来地方卸载 - unmountIntance(origId, instance); + unmountInstance(origId, instance); } if (isElement(instance)) { cacheReactKey(instance); @@ -160,7 +142,7 @@ export class DocumentInstance { } // hack! delete instance from map const newUnmount = function (this: any) { - unmountIntance(id, instance); + unmountInstance(id, instance); origUnmount && origUnmount.call(this); }; (newUnmount as any).origUnmount = origUnmount; @@ -465,6 +447,7 @@ export class SimulatorRendererContainer implements BuiltinSimulatorRenderer { // mock _leaf,减少性能开销 const _leaf = { isEmpty: () => false, + isMock: true, }; viewProps._leaf = _leaf; return createElement(Comp, viewProps, children); diff --git a/packages/react-simulator-renderer/src/utils/misc.ts b/packages/react-simulator-renderer/src/utils/misc.ts index e53ac6ec1..a829a6e95 100644 --- a/packages/react-simulator-renderer/src/utils/misc.ts +++ b/packages/react-simulator-renderer/src/utils/misc.ts @@ -24,3 +24,12 @@ export function getProjectUtils(librayMap: LibrayMap, utilsMetadata: UtilsMetada }); } } + +/** + * judges if current simulator renderer deteched or not + * @returns detached or not + */ +export function isRendererDetached() { + // if current iframe detached from host document, the `window.parent` will be undefined. + return !window.parent; +} \ No newline at end of file diff --git a/packages/react-simulator-renderer/test/src/renderer/__snapshots__/demo.test.tsx.snap b/packages/react-simulator-renderer/test/src/renderer/__snapshots__/demo.test.tsx.snap index afccf6241..0ebb606da 100644 --- a/packages/react-simulator-renderer/test/src/renderer/__snapshots__/demo.test.tsx.snap +++ b/packages/react-simulator-renderer/test/src/renderer/__snapshots__/demo.test.tsx.snap @@ -11,7 +11,7 @@ exports[`Base should be render NotFoundComponent 1`] = `
- Component Not Found + Text Component Not Found
diff --git a/packages/renderer-core/package.json b/packages/renderer-core/package.json index 022f20072..7d94ca50e 100644 --- a/packages/renderer-core/package.json +++ b/packages/renderer-core/package.json @@ -32,7 +32,9 @@ }, "devDependencies": { "@alib/build-scripts": "^0.1.18", + "@alifd/next": "^1.26.0", "@alilc/lowcode-designer": "1.0.15-beta.0", + "@alilc/lowcode-designer": "1.0.14", "@alilc/lowcode-test-mate": "^1.0.1", "@babel/plugin-transform-typescript": "^7.16.8", "@testing-library/react": "^11.2.2", diff --git a/packages/renderer-core/src/adapter/index.ts b/packages/renderer-core/src/adapter/index.ts index 661b3c17f..12896b137 100644 --- a/packages/renderer-core/src/adapter/index.ts +++ b/packages/renderer-core/src/adapter/index.ts @@ -21,21 +21,21 @@ class Adapter { } initRuntime() { - const Component: IGeneralConstructor = class { + const Component: IGeneralConstructor = class { setState() {} forceUpdate() {} render() {} - state: Record; - props: Record; + state: Readonly; + props: Readonly & Readonly<{ children?: any | undefined }>; refs: Record; context: Record; }; - const PureComponent: IGeneralConstructor = class { + const PureComponent = class { setState() {} forceUpdate() {} render() {} - state: Record; - props: Record; + state: Readonly; + props: Readonly & Readonly<{ children?: any | undefined }>; refs: Record; context: Record; }; diff --git a/packages/renderer-core/src/hoc/leaf.tsx b/packages/renderer-core/src/hoc/leaf.tsx index 3c1c96b72..d4fa2afef 100644 --- a/packages/renderer-core/src/hoc/leaf.tsx +++ b/packages/renderer-core/src/hoc/leaf.tsx @@ -5,7 +5,6 @@ import { EngineOptions } from '@alilc/lowcode-editor-core'; import { debounce } from '../utils/common'; import adapter from '../adapter'; import * as types from '../types/index'; -import { parseData } from '../utils'; export interface IComponentHocInfo { schema: any; @@ -363,12 +362,12 @@ export function leafWrapper(Comp: types.IBaseRenderComponent, { }; componentWillReceiveProps(nextProps: any) { - let { _leaf, componentId } = nextProps; + let { componentId } = nextProps; if (nextProps.__tag === this.props.__tag) { return null; } - _leaf = _leaf || getNode?.(componentId); + const _leaf = getNode?.(componentId); if (_leaf && this.curEventLeaf && _leaf !== this.curEventLeaf) { this.disposeFunctions.forEach((fn) => fn()); this.disposeFunctions = []; @@ -514,7 +513,12 @@ export function leafWrapper(Comp: types.IBaseRenderComponent, { } get leaf(): Node | undefined { - return this.props._leaf || getNode?.(componentCacheId); + if (this.props._leaf?.isMock) { + // 低代码组件作为一个整体更新,其内部的组件不需要监听相关事件 + return undefined; + } + + return getNode?.(componentCacheId); } render() { diff --git a/packages/renderer-core/src/renderer/addon.tsx b/packages/renderer-core/src/renderer/addon.tsx index 964de1858..62aeddbba 100644 --- a/packages/renderer-core/src/renderer/addon.tsx +++ b/packages/renderer-core/src/renderer/addon.tsx @@ -6,7 +6,7 @@ import { IRendererAppHelper, IBaseRendererProps, IBaseRenderComponent } from '.. export default function addonRendererFactory(): IBaseRenderComponent { const BaseRenderer = baseRendererFactory(); return class AddonRenderer extends BaseRenderer { - static dislayName = 'addon-renderer'; + static displayName = 'AddonRenderer'; __namespace = 'addon'; @@ -69,7 +69,7 @@ export default function addonRendererFactory(): IBaseRenderComponent { return '插件 schema 结构异常!'; } - this.__debug(`${AddonRenderer.dislayName} render - ${__schema.fileName}`); + this.__debug(`${AddonRenderer.displayName} render - ${__schema.fileName}`); this.__generateCtx({ component: this, }); diff --git a/packages/renderer-core/src/renderer/base.tsx b/packages/renderer-core/src/renderer/base.tsx index 76e2e85dc..2d58cac76 100644 --- a/packages/renderer-core/src/renderer/base.tsx +++ b/packages/renderer-core/src/renderer/base.tsx @@ -104,13 +104,7 @@ export default function baseRendererFactory(): IBaseRenderComponent { return customBaseRenderer; } - const runtime = adapter.getRuntime(); - const Component = runtime.Component as IGeneralConstructor< - IBaseRendererProps, - Record, - any - >; - const { createElement } = runtime; + const { Component, createElement } = adapter.getRuntime(); const Div = divFactory(); const VisualDom = visualDomFactory(); const AppContext = contextFactory(); @@ -125,8 +119,8 @@ export default function baseRendererFactory(): IBaseRenderComponent { const DEFAULT_LOOP_ARG_INDEX = 'index'; let scopeIdx = 0; - return class BaseRenderer extends Component { - static displayName = 'base-renderer'; + return class BaseRenderer extends Component> { + static displayName = 'BaseRenderer'; static defaultProps = { __schema: {}, @@ -533,6 +527,10 @@ export default function baseRendererFactory(): IBaseRenderComponent { { componentName: schema.componentName, componentId: schema.id, + enableStrictNotFoundMode: engine.props.enableStrictNotFoundMode, + ref: (ref: any) => { + ref && engine.props?.onCompGetRef(schema, ref); + }, }, this.__getSchemaChildrenVirtualDom(schema, scope, Comp), ); diff --git a/packages/renderer-core/src/renderer/block.tsx b/packages/renderer-core/src/renderer/block.tsx index f87997864..560b5924b 100644 --- a/packages/renderer-core/src/renderer/block.tsx +++ b/packages/renderer-core/src/renderer/block.tsx @@ -4,7 +4,7 @@ import { IBaseRendererProps, IBaseRenderComponent } from '../types'; export default function blockRendererFactory(): IBaseRenderComponent { const BaseRenderer = baseRendererFactory(); return class BlockRenderer extends BaseRenderer { - static dislayName = 'block-renderer'; + static displayName = 'BlockRenderer'; __namespace = 'block'; @@ -23,7 +23,7 @@ export default function blockRendererFactory(): IBaseRenderComponent { return '区块 schema 结构异常!'; } - this.__debug(`${BlockRenderer.dislayName} render - ${__schema?.fileName}`); + this.__debug(`${BlockRenderer.displayName} render - ${__schema?.fileName}`); this.__generateCtx({}); this.__render(); diff --git a/packages/renderer-core/src/renderer/component.tsx b/packages/renderer-core/src/renderer/component.tsx index 85b1fd4d3..58d5c0093 100644 --- a/packages/renderer-core/src/renderer/component.tsx +++ b/packages/renderer-core/src/renderer/component.tsx @@ -4,7 +4,7 @@ import { IBaseRendererProps, IBaseRenderComponent } from '../types'; export default function componentRendererFactory(): IBaseRenderComponent { const BaseRenderer = baseRendererFactory(); return class CompRenderer extends BaseRenderer { - static dislayName = 'comp-renderer'; + static displayName = 'CompRenderer'; __namespace = 'component'; @@ -23,7 +23,7 @@ export default function componentRendererFactory(): IBaseRenderComponent { if (this.__checkSchema(__schema)) { return '自定义组件 schema 结构异常!'; } - this.__debug(`${CompRenderer.dislayName} render - ${__schema.fileName}`); + this.__debug(`${CompRenderer.displayName} render - ${__schema.fileName}`); this.__generateCtx({ component: this, diff --git a/packages/renderer-core/src/renderer/page.tsx b/packages/renderer-core/src/renderer/page.tsx index 817b9b128..ba1140c6b 100644 --- a/packages/renderer-core/src/renderer/page.tsx +++ b/packages/renderer-core/src/renderer/page.tsx @@ -4,7 +4,7 @@ import { IBaseRendererProps, IBaseRenderComponent } from '../types'; export default function pageRendererFactory(): IBaseRenderComponent { const BaseRenderer = baseRendererFactory(); return class PageRenderer extends BaseRenderer { - static dislayName = 'page-renderer'; + static displayName = 'PageRenderer'; __namespace = 'page'; @@ -34,7 +34,7 @@ export default function pageRendererFactory(): IBaseRenderComponent { if (this.__checkSchema(__schema)) { return '页面schema结构异常!'; } - this.__debug(`${PageRenderer.dislayName} render - ${__schema.fileName}`); + this.__debug(`${PageRenderer.displayName} render - ${__schema.fileName}`); this.__bindCustomMethods(this.props); this.__initDataSource(this.props); diff --git a/packages/renderer-core/src/renderer/renderer.tsx b/packages/renderer-core/src/renderer/renderer.tsx index 59d63f790..b308c251b 100644 --- a/packages/renderer-core/src/renderer/renderer.tsx +++ b/packages/renderer-core/src/renderer/renderer.tsx @@ -4,14 +4,11 @@ import contextFactory from '../context'; import { isFileSchema, isEmpty } from '../utils'; import baseRendererFactory from './base'; import divFactory from '../components/Div'; -import { IGeneralConstructor, IRenderComponent, IRendererProps, IRendererState } from '../types'; -import { RootSchema } from '@alilc/lowcode-types'; +import { IRenderComponent, IRendererProps, IRendererState } from '../types'; +import { NodeSchema, RootSchema } from '@alilc/lowcode-types'; export default function rendererFactory(): IRenderComponent { - const runtime = adapter.getRuntime(); - const Component = runtime.Component as IGeneralConstructor>; - const PureComponent = runtime.PureComponent as IGeneralConstructor>; - const { createElement, findDOMNode } = runtime; + const { PureComponent, Component, createElement, findDOMNode } = adapter.getRuntime(); const RENDERER_COMPS: any = adapter.getRenderers(); const BaseRenderer = baseRendererFactory(); const AppContext = contextFactory(); @@ -21,7 +18,7 @@ export default function rendererFactory(): IRenderComponent { const debug = Debug('renderer:entry'); - class FaultComponent extends PureComponent { + class FaultComponent extends PureComponent { render() { // FIXME: errorlog console.error('render error', this.props); @@ -35,18 +32,23 @@ export default function rendererFactory(): IRenderComponent { color: '#ff0000', border: '2px solid #ff0000', }, - }, '组件渲染异常,请查看控制台日志'); + }, `${this.props.componentName || ''} 组件渲染异常,请查看控制台日志`); } } - class NotFoundComponent extends PureComponent { + class NotFoundComponent extends PureComponent<{ + componentName: string; + } & IRendererProps> { render() { - return createElement(Div, this.props, this.props.children || 'Component Not Found'); + if (this.props.enableStrictNotFoundMode) { + return `${this.props.componentName || ''} Component Not Found`; + } + return createElement(Div, this.props, this.props.children || `${this.props.componentName || ''} Component Not Found`); } } - return class Renderer extends Component { - static dislayName = 'renderer'; + return class Renderer extends Component { + static displayName = 'Renderer'; state: Partial = {}; diff --git a/packages/renderer-core/src/renderer/temp.tsx b/packages/renderer-core/src/renderer/temp.tsx index 79d825d09..83adef7e3 100644 --- a/packages/renderer-core/src/renderer/temp.tsx +++ b/packages/renderer-core/src/renderer/temp.tsx @@ -5,7 +5,7 @@ export default function tempRendererFactory(): IBaseRenderComponent { const BaseRenderer = baseRendererFactory(); return class TempRenderer extends BaseRenderer { - static dislayName = 'temp-renderer'; + static displayName = 'TempRenderer'; __namespace = 'temp'; @@ -51,7 +51,7 @@ export default function tempRendererFactory(): IBaseRenderComponent { return '下钻编辑 schema 结构异常!'; } - this.__debug(`${TempRenderer.dislayName} render - ${__schema?.fileName}`); + this.__debug(`${TempRenderer.displayName} 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 index 7aa2f8ec4..2a4c0975e 100644 --- a/packages/renderer-core/src/types/index.ts +++ b/packages/renderer-core/src/types/index.ts @@ -21,12 +21,12 @@ interface IGeneralComponent

extends ComponentLifecycle } export type IGeneralConstructor< - P = { + T = { [key: string]: any; }, S = { [key: string]: any; - }, SS = any -> = new (props: any, context: any) => IGeneralComponent; + }, D = any +> = new (props: TT, context: any) => IGeneralComponent; /** * duck-typed History @@ -133,6 +133,11 @@ export interface IRendererProps { * JSExpression 是否只支持使用 this 来访问上下文变量 */ thisRequiredInJSE?: boolean; + /** + * @default false + * 当开启组件未找到严格模式时,渲染模块不会默认给一个容器组件 + */ + enableStrictNotFoundMode?: boolean; } export interface IRendererState { @@ -288,7 +293,7 @@ export interface IRenderComponent { getNotFoundComponent(): any; getFaultComponent(): any; }; - dislayName: string; + displayName: string; defaultProps: IRendererProps; findDOMNode: (...args: any) => any; } diff --git a/packages/renderer-core/tests/hoc/__snapshots__/leaf.test.tsx.snap b/packages/renderer-core/tests/hoc/__snapshots__/leaf.test.tsx.snap index 253a099c7..e0ddfa8c2 100644 --- a/packages/renderer-core/tests/hoc/__snapshots__/leaf.test.tsx.snap +++ b/packages/renderer-core/tests/hoc/__snapshots__/leaf.test.tsx.snap @@ -18,7 +18,9 @@ exports[`children this.props.children is array 1`] = ` exports[`lifecycle leaf change and make componentWillReceiveProps 1`] = `

content new leaf diff --git a/packages/renderer-core/tests/hoc/leaf.test.tsx b/packages/renderer-core/tests/hoc/leaf.test.tsx index 308a8b900..0e594bc5a 100644 --- a/packages/renderer-core/tests/hoc/leaf.test.tsx +++ b/packages/renderer-core/tests/hoc/leaf.test.tsx @@ -193,9 +193,10 @@ describe('lifecycle', () => { it('leaf change and make componentWillReceiveProps', () => { const newTextNodeLeaf = new Node(textSchema); + nodeMap.set(textSchema.id, newTextNodeLeaf); component.update((
- +
)); @@ -232,6 +233,9 @@ describe('mini unit render', () => { parent: MiniRenderDivNode, }); + nodeMap.set(miniRenderSchema.id, MiniRenderDivNode); + nodeMap.set(textSchema.id, TextNode); + component = renderer.create( // @ts-ignore @@ -277,6 +281,8 @@ describe('mini unit render', () => { }), }); + nodeMap.set(textSchema.id, TextNode); + renderer.create( // @ts-ignore
@@ -319,6 +325,8 @@ describe('mini unit render', () => { isRoot: true, }); + nodeMap.set(textSchema.id, TextNode); + const component = renderer.create( ); @@ -351,6 +359,8 @@ describe('mini unit render', () => { }) }); + nodeMap.set(textSchema.id, TextNode); + const component = renderer.create( ); @@ -370,7 +380,9 @@ describe('mini unit render', () => { }); it('parent is a mock leaf', () => { - const MiniRenderDivNode = {}; + const MiniRenderDivNode = { + isMock: true, + }; const component = renderer.create( // @ts-ignore @@ -409,6 +421,9 @@ describe('mini unit render', () => { hasLoop: true, }); + nodeMap.set(textSchema.id, TextNode); + nodeMap.set(miniRenderSchema.id, MiniRenderDivNode); + component = renderer.create( // @ts-ignore diff --git a/packages/shell/src/simulator-host.ts b/packages/shell/src/simulator-host.ts index d22336c3e..077564550 100644 --- a/packages/shell/src/simulator-host.ts +++ b/packages/shell/src/simulator-host.ts @@ -1,7 +1,8 @@ import { BuiltinSimulatorHost, } from '@alilc/lowcode-designer'; -import { simulatorHostSymbol } from './symbols'; +import { simulatorHostSymbol, nodeSymbol } from './symbols'; +import type Node from './node'; export default class SimulatorHost { private readonly [simulatorHostSymbol]: BuiltinSimulatorHost; @@ -51,6 +52,14 @@ export default class SimulatorHost { return this[simulatorHostSymbol].get(key); } + /** + * scroll to specific node + * @param node + */ + scrollToNode(node: Node) { + this[simulatorHostSymbol].scrollToNode(node[nodeSymbol]); + } + /** * 刷新渲染画布 */ diff --git a/specs/material-spec.md b/specs/material-spec.md index 790063e7a..c823d6646 100644 --- a/specs/material-spec.md +++ b/specs/material-spec.md @@ -617,8 +617,8 @@ component | screenshot | 组件快照 | String | 否 | | icon | 组件的小图标 | String (URL) | 是 | | tags | 组件标签 | String | 是 | -| keywards | 组件关键词,用于搜索联想 | String | 是 | -| devMode | 组件研发模式 | String  (procode,lowcode) | 是 | +| keywords | 组件关键词,用于搜索联想 | String | 是 | +| devMode | 组件研发模式 | String  (proCode,lowCode) | 是 | | npm | npm 源引入完整描述对象 | Object | 否 | | npm.package | 源码组件库名 | String | 否 | | npm.exportName | 源码组件名称 | String | 否 | @@ -857,7 +857,7 @@ props 数组下对象字段描述: // 支持条件设置 "condition": true, // 支持样式设置 - "styles": true, + "style": true, } } }