Merge branch 'develop' into release/1.0.15-beta

This commit is contained in:
liujuping 2022-09-23 15:35:21 +08:00
commit f346675e15
34 changed files with 308 additions and 96 deletions

116
.github/workflows/ci.yml vendored Normal file
View File

@ -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

View File

@ -123,7 +123,7 @@ export class SchemaParser implements ISchemaParser {
const schema = this.decodeSchema(schemaSrc); const schema = this.decodeSchema(schemaSrc);
// 解析三方组件依赖 // 解析三方组件依赖
schema.componentsMap.forEach((info) => { schema.componentsMap.forEach((info: any) => {
if (info.componentName) { if (info.componentName) {
compDeps[info.componentName] = { compDeps[info.componentName] = {
...info, ...info,

View File

@ -181,7 +181,7 @@ export function parseExpressionGetKeywords(expr: string | null | undefined): str
const fieldValue = node[fieldName as keyof typeof node]; const fieldValue = node[fieldName as keyof typeof node];
if (typeof fieldValue === 'object') { if (typeof fieldValue === 'object') {
if (Array.isArray(fieldValue)) { if (Array.isArray(fieldValue)) {
fieldValue.forEach((item) => { fieldValue.forEach((item: any) => {
addIdentifierIfNeeded(item); addIdentifierIfNeeded(item);
}); });
} else { } else {
@ -233,7 +233,7 @@ export function parseExpressionGetGlobalVariables(
const fieldValue = node[fieldName as keyof typeof node]; const fieldValue = node[fieldName as keyof typeof node];
if (typeof fieldValue === 'object') { if (typeof fieldValue === 'object') {
if (Array.isArray(fieldValue)) { if (Array.isArray(fieldValue)) {
fieldValue.forEach((item) => { fieldValue.forEach((item: any) => {
addUndeclaredIdentifierIfNeeded(item, path); addUndeclaredIdentifierIfNeeded(item, path);
}); });
} else { } else {

View File

@ -98,7 +98,7 @@ export function createSimulator(
doc.close(); doc.close();
return new Promise((resolve) => { return new Promise((resolve) => {
const renderer = win.SimulatorRenderer || host.renderer; const renderer = win.SimulatorRenderer;
if (renderer) { if (renderer) {
return resolve(renderer); return resolve(renderer);
} }

View File

@ -232,6 +232,10 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
return engineConfig.get('thisRequiredInJSE') ?? true; return engineConfig.get('thisRequiredInJSE') ?? true;
} }
get enableStrictNotFoundMode(): any {
return engineConfig.get('enableStrictNotFoundMode') ?? false;
}
@computed get componentsAsset(): Asset | undefined { @computed get componentsAsset(): Asset | undefined {
return this.get('componentsAsset'); return this.get('componentsAsset');
} }
@ -850,7 +854,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
// filter with context // filter with context
return instances.filter((instance) => { return instances.filter((instance) => {
return this.getClosestNodeInstance(instance, context.nodeId)?.instance === context.instance; return this.getClosestNodeInstance(instance, context?.nodeId)?.instance === context.instance;
}); });
} }

View File

@ -176,12 +176,29 @@ export class ComponentMeta {
} }
private parseMetadata(metadata: ComponentMetadata) { 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._npm = npm || this._npm;
this._componentName = componentName; this._componentName = componentName;
// 额外转换逻辑 // 额外转换逻辑
this._transformedMetadata = this.transformMetadata(metadata); this._transformedMetadata = this.transformMetadata(_metadata);
const { title } = this._transformedMetadata; const { title } = this._transformedMetadata;
if (title) { if (title) {

View File

@ -23,6 +23,7 @@ import {
setShaken, setShaken,
} from '../../src/designer/dragon'; } from '../../src/designer/dragon';
import { Project } from '../../src/project/project'; import { Project } from '../../src/project/project';
import pageMetadata from '../fixtures/component-metadata/page';
import { Node } from '../../src/document/node/node'; import { Node } from '../../src/document/node/node';
import { Designer } from '../../src/designer/designer'; import { Designer } from '../../src/designer/designer';
import { DocumentModel } from '../../src/document/document-model'; import { DocumentModel } from '../../src/document/document-model';
@ -46,6 +47,7 @@ describe('Host 测试', () => {
beforeEach(() => { beforeEach(() => {
designer = new Designer({ editor }); designer = new Designer({ editor });
project = designer.project; project = designer.project;
designer.createComponentMeta(pageMetadata);
doc = project.createDocument(formSchema); doc = project.createDocument(formSchema);
host = new BuiltinSimulatorHost(designer.project); host = new BuiltinSimulatorHost(designer.project);
}); });
@ -373,6 +375,14 @@ describe('Host 测试', () => {
}, },
})).toBeNull(); })).toBeNull();
}); });
it('notFoundComponent', () => {
expect(host.locate({
dragObject: {
type: DragObjectType.Node,
nodes: [doc.getNode('form')],
},
})).toBeUndefined();
})
it('locate', () => { it('locate', () => {
host.locate({ host.locate({
dragObject: { dragObject: {

View File

@ -137,6 +137,10 @@ const VALID_ENGINE_OPTIONS = {
type: 'boolean', type: 'boolean',
description: 'JSExpression 是否只支持使用 this 来访问上下文变量', description: 'JSExpression 是否只支持使用 this 来访问上下文变量',
}, },
enableStrictNotFoundMode: {
type: 'boolean',
description: '当开启组件未找到严格模式时,渲染模块不会默认给一个容器组件',
},
}; };
export interface EngineOptions { export interface EngineOptions {
/** /**
@ -258,6 +262,12 @@ export interface EngineOptions {
* JSExpression 使 this 访 'state.xxx' false * JSExpression 使 this 访 'state.xxx' false
*/ */
thisRequiredInJSE?: boolean; thisRequiredInJSE?: boolean;
/**
* @default false
*
*/
enableStrictNotFoundMode?: boolean;
} }
const getStrictModeValue = (engineOptions: EngineOptions, defaultValue: boolean): boolean => { const getStrictModeValue = (engineOptions: EngineOptions, defaultValue: boolean): boolean => {

View File

@ -12,7 +12,9 @@
[![NPM version][npm-image]][npm-url] [![NPM downloads][download-image]][download-url] [![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-image]: https://img.shields.io/npm/v/@alilc/lowcode-engine.svg?style=flat-square
[npm-url]: http://npmjs.org/package/@alilc/lowcode-engine [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-image]: https://img.shields.io/badge/using-issues--helper-orange?style=flat-square
[issues-helper-url]: https://github.com/actions-cool/issues-helper [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
</div> </div>
[![](https://img.alicdn.com/imgextra/i2/O1CN01UhoS7C1sNNhySvfWi_!!6000000005754-2-tps-2878-1588.png)](https://lowcode-engine.cn) [![](https://img.alicdn.com/imgextra/i2/O1CN01UhoS7C1sNNhySvfWi_!!6000000005754-2-tps-2878-1588.png)](https://lowcode-engine.cn)

View File

@ -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] [![][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-image]: https://img.shields.io/npm/v/@alilc/lowcode-engine.svg?style=flat-square
[npm-url]: http://npmjs.org/package/@alilc/lowcode-engine [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-image]: https://img.shields.io/badge/using-issues--helper-orange?style=flat-square
[issues-helper-url]: https://github.com/actions-cool/issues-helper [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
</div> </div>
[![](https://img.alicdn.com/imgextra/i2/O1CN01UhoS7C1sNNhySvfWi_!!6000000005754-2-tps-2878-1588.png)](http://lowcode-engine.cn) [![](https://img.alicdn.com/imgextra/i2/O1CN01UhoS7C1sNNhySvfWi_!!6000000005754-2-tps-2878-1588.png)](http://lowcode-engine.cn)

View File

@ -1,5 +1,5 @@
import { createElement } from 'react'; 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 { globalContext, Editor, engineConfig, EngineOptions } from '@alilc/lowcode-editor-core';
import { import {
Designer, Designer,
@ -16,7 +16,7 @@ import {
import Outline, { OutlineBackupPane, getTreeMaster } from '@alilc/lowcode-plugin-outline-pane'; import Outline, { OutlineBackupPane, getTreeMaster } from '@alilc/lowcode-plugin-outline-pane';
import DesignerPlugin from '@alilc/lowcode-plugin-designer'; 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 { getLogger, isPlainObject } from '@alilc/lowcode-utils';
import './modules/live-editing'; import './modules/live-editing';
import utils from './modules/utils'; import utils from './modules/utils';
@ -184,7 +184,8 @@ engineConfig.set('isOpenSource', isOpenSource);
await plugins.register(defaultPanelRegistry); await plugins.register(defaultPanelRegistry);
})(); })();
let engineInited = false; // container which will host LowCodeEngine DOM
let engineContainer: HTMLElement;
// @ts-ignore webpack Define variable // @ts-ignore webpack Define variable
export const version = VERSION_PLACEHOLDER; export const version = VERSION_PLACEHOLDER;
engineConfig.set('ENGINE_VERSION', version); engineConfig.set('ENGINE_VERSION', version);
@ -193,23 +194,22 @@ export async function init(
options?: EngineOptions, options?: EngineOptions,
pluginPreference?: PluginPreference, pluginPreference?: PluginPreference,
) { ) {
if (engineInited) return; await destroy();
engineInited = true;
let engineOptions = null; let engineOptions = null;
let engineContainer = null;
if (isPlainObject(container)) { if (isPlainObject(container)) {
engineOptions = container; engineOptions = container;
engineContainer = document.createElement('div'); engineContainer = document.createElement('div');
engineContainer.id = 'engine';
document.body.appendChild(engineContainer); document.body.appendChild(engineContainer);
} else { } else {
engineOptions = options; engineOptions = options;
engineContainer = container; engineContainer = container;
if (!container) { if (!container) {
engineContainer = document.createElement('div'); engineContainer = document.createElement('div');
engineContainer.id = 'engine';
document.body.appendChild(engineContainer); document.body.appendChild(engineContainer);
} }
} }
engineContainer.id = 'engine';
engineConfig.setEngineOptions(engineOptions as any); engineConfig.setEngineOptions(engineOptions as any);
await plugins.init(pluginPreference as any); await plugins.init(pluginPreference as any);
@ -222,3 +222,17 @@ export async function init(
engineContainer, 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);
}

View File

@ -152,7 +152,8 @@ export class OutlineMain implements ISensor, ITreeBoard, IScrollable {
return canMove; return canMove;
}); });
if (!operationalNodes || operationalNodes.length === 0) { // 如果拖拽的是 Node 才需要后面的判断,拖拽 data 不需要
if (isDragNodeObject(dragObject) && (!operationalNodes || operationalNodes.length === 0)) {
return; return;
} }

View File

@ -17,7 +17,7 @@ export default function compFactory(schema, components = {}, componentsMap = {},
const AppContext = contextFactory(); const AppContext = contextFactory();
class LNCompView extends Component { class LNCompView extends Component {
static dislayName = 'lce-comp-factory'; static displayName = 'LceCompFactory';
static version = config.version || '0.0.0'; static version = config.version || '0.0.0';

View File

@ -538,6 +538,7 @@ export class SimulatorRendererContainer implements BuiltinSimulatorRenderer {
// mock _leaf减少性能开销 // mock _leaf减少性能开销
const _leaf = { const _leaf = {
isEmpty: () => false, isEmpty: () => false,
isMock: true,
}; };
viewProps._leaf = _leaf; viewProps._leaf = _leaf;
return createElement(Comp, viewProps, children); return createElement(Comp, viewProps, children);

View File

@ -13,7 +13,8 @@
"scripts": { "scripts": {
"test": "build-scripts test --config build.test.json", "test": "build-scripts test --config build.test.json",
"build": "NODE_OPTIONS=--max_old_space_size=8192 build-scripts build --skip-demo", "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": { "dependencies": {
"@alilc/lowcode-designer": "1.0.15-beta.0", "@alilc/lowcode-designer": "1.0.15-beta.0",

View File

@ -8,7 +8,7 @@ import { getClosestNode, isFromVC, isReactComponent } from '@alilc/lowcode-utils
import { GlobalEvent } from '@alilc/lowcode-types'; import { GlobalEvent } from '@alilc/lowcode-types';
import { SimulatorRendererContainer, DocumentInstance } from './renderer'; import { SimulatorRendererContainer, DocumentInstance } from './renderer';
import { host } from './host'; import { host } from './host';
import { isRendererDetached } from './utils/misc';
import './renderer.less'; import './renderer.less';
// patch cloneElement avoid lost keyProps // patch cloneElement avoid lost keyProps
@ -170,14 +170,12 @@ class Renderer extends Component<{
this.startTime = Date.now(); this.startTime = Date.now();
this.schemaChangedSymbol = false; this.schemaChangedSymbol = false;
if (!container.autoRender) return null; if (!container.autoRender || isRendererDetached()) return null;
return ( return (
<LowCodeRenderer <LowCodeRenderer
locale={locale} locale={locale}
messages={messages} messages={messages}
schema={documentInstance.schema} schema={documentInstance.schema}
deltaData={documentInstance.deltaData}
deltaMode={documentInstance.deltaMode}
components={container.components} components={container.components}
appHelper={container.context} appHelper={container.context}
designMode={designMode} designMode={designMode}
@ -257,6 +255,7 @@ class Renderer extends Component<{
onCompGetRef={(schema: any, ref: ReactInstance | null) => { onCompGetRef={(schema: any, ref: ReactInstance | null) => {
documentInstance.mountInstance(schema.id, ref); documentInstance.mountInstance(schema.id, ref);
}} }}
enableStrictNotFoundMode={host.enableStrictNotFoundMode}
/> />
); );
} }

View File

@ -51,24 +51,6 @@ export class DocumentInstance {
return this._components; 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 // context from: utils、constants、history、location、match
@obx.ref private _appContext = {}; @obx.ref private _appContext = {};
@ -116,7 +98,7 @@ export class DocumentInstance {
return this.document.id; return this.document.id;
} }
private unmountIntance(id: string, instance: ReactInstance) { private unmountInstance(id: string, instance: ReactInstance) {
const instances = this.instancesMap.get(id); const instances = this.instancesMap.get(id);
if (instances) { if (instances) {
const i = instances.indexOf(instance); const i = instances.indexOf(instance);
@ -144,11 +126,11 @@ export class DocumentInstance {
} }
return; return;
} }
const unmountIntance = this.unmountIntance.bind(this); const unmountInstance = this.unmountInstance.bind(this);
const origId = (instance as any)[SYMBOL_VNID]; const origId = (instance as any)[SYMBOL_VNID];
if (origId && origId !== id) { if (origId && origId !== id) {
// 另外一个节点的 instance 在此被复用了,需要从原来地方卸载 // 另外一个节点的 instance 在此被复用了,需要从原来地方卸载
unmountIntance(origId, instance); unmountInstance(origId, instance);
} }
if (isElement(instance)) { if (isElement(instance)) {
cacheReactKey(instance); cacheReactKey(instance);
@ -160,7 +142,7 @@ export class DocumentInstance {
} }
// hack! delete instance from map // hack! delete instance from map
const newUnmount = function (this: any) { const newUnmount = function (this: any) {
unmountIntance(id, instance); unmountInstance(id, instance);
origUnmount && origUnmount.call(this); origUnmount && origUnmount.call(this);
}; };
(newUnmount as any).origUnmount = origUnmount; (newUnmount as any).origUnmount = origUnmount;
@ -465,6 +447,7 @@ export class SimulatorRendererContainer implements BuiltinSimulatorRenderer {
// mock _leaf减少性能开销 // mock _leaf减少性能开销
const _leaf = { const _leaf = {
isEmpty: () => false, isEmpty: () => false,
isMock: true,
}; };
viewProps._leaf = _leaf; viewProps._leaf = _leaf;
return createElement(Comp, viewProps, children); return createElement(Comp, viewProps, children);

View File

@ -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;
}

View File

@ -11,7 +11,7 @@ exports[`Base should be render NotFoundComponent 1`] = `
<div <div
componentName="Text" componentName="Text"
> >
Component Not Found Text Component Not Found
</div> </div>
</div> </div>
</div> </div>

View File

@ -32,7 +32,9 @@
}, },
"devDependencies": { "devDependencies": {
"@alib/build-scripts": "^0.1.18", "@alib/build-scripts": "^0.1.18",
"@alifd/next": "^1.26.0",
"@alilc/lowcode-designer": "1.0.15-beta.0", "@alilc/lowcode-designer": "1.0.15-beta.0",
"@alilc/lowcode-designer": "1.0.14",
"@alilc/lowcode-test-mate": "^1.0.1", "@alilc/lowcode-test-mate": "^1.0.1",
"@babel/plugin-transform-typescript": "^7.16.8", "@babel/plugin-transform-typescript": "^7.16.8",
"@testing-library/react": "^11.2.2", "@testing-library/react": "^11.2.2",

View File

@ -21,21 +21,21 @@ class Adapter {
} }
initRuntime() { initRuntime() {
const Component: IGeneralConstructor = class { const Component: IGeneralConstructor = class <T = any, S = any> {
setState() {} setState() {}
forceUpdate() {} forceUpdate() {}
render() {} render() {}
state: Record<string, unknown>; state: Readonly<S>;
props: Record<string, unknown>; props: Readonly<T> & Readonly<{ children?: any | undefined }>;
refs: Record<string, unknown>; refs: Record<string, unknown>;
context: Record<string, unknown>; context: Record<string, unknown>;
}; };
const PureComponent: IGeneralConstructor = class { const PureComponent = class <T = any, S = any> {
setState() {} setState() {}
forceUpdate() {} forceUpdate() {}
render() {} render() {}
state: Record<string, unknown>; state: Readonly<S>;
props: Record<string, unknown>; props: Readonly<T> & Readonly<{ children?: any | undefined }>;
refs: Record<string, unknown>; refs: Record<string, unknown>;
context: Record<string, unknown>; context: Record<string, unknown>;
}; };

View File

@ -5,7 +5,6 @@ import { EngineOptions } from '@alilc/lowcode-editor-core';
import { debounce } from '../utils/common'; import { debounce } from '../utils/common';
import adapter from '../adapter'; import adapter from '../adapter';
import * as types from '../types/index'; import * as types from '../types/index';
import { parseData } from '../utils';
export interface IComponentHocInfo { export interface IComponentHocInfo {
schema: any; schema: any;
@ -363,12 +362,12 @@ export function leafWrapper(Comp: types.IBaseRenderComponent, {
}; };
componentWillReceiveProps(nextProps: any) { componentWillReceiveProps(nextProps: any) {
let { _leaf, componentId } = nextProps; let { componentId } = nextProps;
if (nextProps.__tag === this.props.__tag) { if (nextProps.__tag === this.props.__tag) {
return null; return null;
} }
_leaf = _leaf || getNode?.(componentId); const _leaf = getNode?.(componentId);
if (_leaf && this.curEventLeaf && _leaf !== this.curEventLeaf) { if (_leaf && this.curEventLeaf && _leaf !== this.curEventLeaf) {
this.disposeFunctions.forEach((fn) => fn()); this.disposeFunctions.forEach((fn) => fn());
this.disposeFunctions = []; this.disposeFunctions = [];
@ -514,7 +513,12 @@ export function leafWrapper(Comp: types.IBaseRenderComponent, {
} }
get leaf(): Node | undefined { get leaf(): Node | undefined {
return this.props._leaf || getNode?.(componentCacheId); if (this.props._leaf?.isMock) {
// 低代码组件作为一个整体更新,其内部的组件不需要监听相关事件
return undefined;
}
return getNode?.(componentCacheId);
} }
render() { render() {

View File

@ -6,7 +6,7 @@ import { IRendererAppHelper, IBaseRendererProps, IBaseRenderComponent } from '..
export default function addonRendererFactory(): IBaseRenderComponent { export default function addonRendererFactory(): IBaseRenderComponent {
const BaseRenderer = baseRendererFactory(); const BaseRenderer = baseRendererFactory();
return class AddonRenderer extends BaseRenderer { return class AddonRenderer extends BaseRenderer {
static dislayName = 'addon-renderer'; static displayName = 'AddonRenderer';
__namespace = 'addon'; __namespace = 'addon';
@ -69,7 +69,7 @@ export default function addonRendererFactory(): IBaseRenderComponent {
return '插件 schema 结构异常!'; return '插件 schema 结构异常!';
} }
this.__debug(`${AddonRenderer.dislayName} render - ${__schema.fileName}`); this.__debug(`${AddonRenderer.displayName} render - ${__schema.fileName}`);
this.__generateCtx({ this.__generateCtx({
component: this, component: this,
}); });

View File

@ -104,13 +104,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
return customBaseRenderer; return customBaseRenderer;
} }
const runtime = adapter.getRuntime(); const { Component, createElement } = adapter.getRuntime();
const Component = runtime.Component as IGeneralConstructor<
IBaseRendererProps,
Record<string, any>,
any
>;
const { createElement } = runtime;
const Div = divFactory(); const Div = divFactory();
const VisualDom = visualDomFactory(); const VisualDom = visualDomFactory();
const AppContext = contextFactory(); const AppContext = contextFactory();
@ -125,8 +119,8 @@ export default function baseRendererFactory(): IBaseRenderComponent {
const DEFAULT_LOOP_ARG_INDEX = 'index'; const DEFAULT_LOOP_ARG_INDEX = 'index';
let scopeIdx = 0; let scopeIdx = 0;
return class BaseRenderer extends Component { return class BaseRenderer extends Component<IBaseRendererProps, Record<string, any>> {
static displayName = 'base-renderer'; static displayName = 'BaseRenderer';
static defaultProps = { static defaultProps = {
__schema: {}, __schema: {},
@ -533,6 +527,10 @@ export default function baseRendererFactory(): IBaseRenderComponent {
{ {
componentName: schema.componentName, componentName: schema.componentName,
componentId: schema.id, componentId: schema.id,
enableStrictNotFoundMode: engine.props.enableStrictNotFoundMode,
ref: (ref: any) => {
ref && engine.props?.onCompGetRef(schema, ref);
},
}, },
this.__getSchemaChildrenVirtualDom(schema, scope, Comp), this.__getSchemaChildrenVirtualDom(schema, scope, Comp),
); );

View File

@ -4,7 +4,7 @@ import { IBaseRendererProps, IBaseRenderComponent } from '../types';
export default function blockRendererFactory(): IBaseRenderComponent { export default function blockRendererFactory(): IBaseRenderComponent {
const BaseRenderer = baseRendererFactory(); const BaseRenderer = baseRendererFactory();
return class BlockRenderer extends BaseRenderer { return class BlockRenderer extends BaseRenderer {
static dislayName = 'block-renderer'; static displayName = 'BlockRenderer';
__namespace = 'block'; __namespace = 'block';
@ -23,7 +23,7 @@ export default function blockRendererFactory(): IBaseRenderComponent {
return '区块 schema 结构异常!'; return '区块 schema 结构异常!';
} }
this.__debug(`${BlockRenderer.dislayName} render - ${__schema?.fileName}`); this.__debug(`${BlockRenderer.displayName} render - ${__schema?.fileName}`);
this.__generateCtx({}); this.__generateCtx({});
this.__render(); this.__render();

View File

@ -4,7 +4,7 @@ import { IBaseRendererProps, IBaseRenderComponent } from '../types';
export default function componentRendererFactory(): IBaseRenderComponent { export default function componentRendererFactory(): IBaseRenderComponent {
const BaseRenderer = baseRendererFactory(); const BaseRenderer = baseRendererFactory();
return class CompRenderer extends BaseRenderer { return class CompRenderer extends BaseRenderer {
static dislayName = 'comp-renderer'; static displayName = 'CompRenderer';
__namespace = 'component'; __namespace = 'component';
@ -23,7 +23,7 @@ export default function componentRendererFactory(): IBaseRenderComponent {
if (this.__checkSchema(__schema)) { if (this.__checkSchema(__schema)) {
return '自定义组件 schema 结构异常!'; return '自定义组件 schema 结构异常!';
} }
this.__debug(`${CompRenderer.dislayName} render - ${__schema.fileName}`); this.__debug(`${CompRenderer.displayName} render - ${__schema.fileName}`);
this.__generateCtx({ this.__generateCtx({
component: this, component: this,

View File

@ -4,7 +4,7 @@ import { IBaseRendererProps, IBaseRenderComponent } from '../types';
export default function pageRendererFactory(): IBaseRenderComponent { export default function pageRendererFactory(): IBaseRenderComponent {
const BaseRenderer = baseRendererFactory(); const BaseRenderer = baseRendererFactory();
return class PageRenderer extends BaseRenderer { return class PageRenderer extends BaseRenderer {
static dislayName = 'page-renderer'; static displayName = 'PageRenderer';
__namespace = 'page'; __namespace = 'page';
@ -34,7 +34,7 @@ export default function pageRendererFactory(): IBaseRenderComponent {
if (this.__checkSchema(__schema)) { if (this.__checkSchema(__schema)) {
return '页面schema结构异常'; return '页面schema结构异常';
} }
this.__debug(`${PageRenderer.dislayName} render - ${__schema.fileName}`); this.__debug(`${PageRenderer.displayName} render - ${__schema.fileName}`);
this.__bindCustomMethods(this.props); this.__bindCustomMethods(this.props);
this.__initDataSource(this.props); this.__initDataSource(this.props);

View File

@ -4,14 +4,11 @@ import contextFactory from '../context';
import { isFileSchema, isEmpty } from '../utils'; import { isFileSchema, isEmpty } from '../utils';
import baseRendererFactory from './base'; import baseRendererFactory from './base';
import divFactory from '../components/Div'; import divFactory from '../components/Div';
import { IGeneralConstructor, IRenderComponent, IRendererProps, IRendererState } from '../types'; import { IRenderComponent, IRendererProps, IRendererState } from '../types';
import { RootSchema } from '@alilc/lowcode-types'; import { NodeSchema, RootSchema } from '@alilc/lowcode-types';
export default function rendererFactory(): IRenderComponent { export default function rendererFactory(): IRenderComponent {
const runtime = adapter.getRuntime(); const { PureComponent, Component, createElement, findDOMNode } = adapter.getRuntime();
const Component = runtime.Component as IGeneralConstructor<IRendererProps, Record<string, any>>;
const PureComponent = runtime.PureComponent as IGeneralConstructor<IRendererProps, Record<string, any>>;
const { createElement, findDOMNode } = runtime;
const RENDERER_COMPS: any = adapter.getRenderers(); const RENDERER_COMPS: any = adapter.getRenderers();
const BaseRenderer = baseRendererFactory(); const BaseRenderer = baseRendererFactory();
const AppContext = contextFactory(); const AppContext = contextFactory();
@ -21,7 +18,7 @@ export default function rendererFactory(): IRenderComponent {
const debug = Debug('renderer:entry'); const debug = Debug('renderer:entry');
class FaultComponent extends PureComponent { class FaultComponent extends PureComponent<NodeSchema> {
render() { render() {
// FIXME: errorlog // FIXME: errorlog
console.error('render error', this.props); console.error('render error', this.props);
@ -35,18 +32,23 @@ export default function rendererFactory(): IRenderComponent {
color: '#ff0000', color: '#ff0000',
border: '2px solid #ff0000', border: '2px solid #ff0000',
}, },
}, '组件渲染异常,请查看控制台日志'); }, `${this.props.componentName || ''} 组件渲染异常,请查看控制台日志`);
} }
} }
class NotFoundComponent extends PureComponent { class NotFoundComponent extends PureComponent<{
componentName: string;
} & IRendererProps> {
render() { 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 { return class Renderer extends Component<IRendererProps> {
static dislayName = 'renderer'; static displayName = 'Renderer';
state: Partial<IRendererState> = {}; state: Partial<IRendererState> = {};

View File

@ -5,7 +5,7 @@ export default function tempRendererFactory(): IBaseRenderComponent {
const BaseRenderer = baseRendererFactory(); const BaseRenderer = baseRendererFactory();
return class TempRenderer extends BaseRenderer { return class TempRenderer extends BaseRenderer {
static dislayName = 'temp-renderer'; static displayName = 'TempRenderer';
__namespace = 'temp'; __namespace = 'temp';
@ -51,7 +51,7 @@ export default function tempRendererFactory(): IBaseRenderComponent {
return '下钻编辑 schema 结构异常!'; return '下钻编辑 schema 结构异常!';
} }
this.__debug(`${TempRenderer.dislayName} render - ${__schema?.fileName}`); this.__debug(`${TempRenderer.displayName} render - ${__schema?.fileName}`);
return this.__renderContent(this.__renderContextProvider({ __ctx })); return this.__renderContent(this.__renderContextProvider({ __ctx }));
} }

View File

@ -21,12 +21,12 @@ interface IGeneralComponent<P = {}, S = {}, SS = any> extends ComponentLifecycle
} }
export type IGeneralConstructor< export type IGeneralConstructor<
P = { T = {
[key: string]: any; [key: string]: any;
}, S = { }, S = {
[key: string]: any; [key: string]: any;
}, SS = any }, D = any
> = new (props: any, context: any) => IGeneralComponent<P, S, SS>; > = new <TT = T, SS = S, DD = D>(props: TT, context: any) => IGeneralComponent<TT, SS, DD>;
/** /**
* duck-typed History * duck-typed History
@ -133,6 +133,11 @@ export interface IRendererProps {
* JSExpression 使 this 访 * JSExpression 使 this 访
*/ */
thisRequiredInJSE?: boolean; thisRequiredInJSE?: boolean;
/**
* @default false
*
*/
enableStrictNotFoundMode?: boolean;
} }
export interface IRendererState { export interface IRendererState {
@ -288,7 +293,7 @@ export interface IRenderComponent {
getNotFoundComponent(): any; getNotFoundComponent(): any;
getFaultComponent(): any; getFaultComponent(): any;
}; };
dislayName: string; displayName: string;
defaultProps: IRendererProps; defaultProps: IRendererProps;
findDOMNode: (...args: any) => any; findDOMNode: (...args: any) => any;
} }

View File

@ -18,7 +18,9 @@ exports[`children this.props.children is array 1`] = `
exports[`lifecycle leaf change and make componentWillReceiveProps 1`] = ` exports[`lifecycle leaf change and make componentWillReceiveProps 1`] = `
<div> <div>
<div <div
__id="text6"
__tag="222" __tag="222"
componentId="text6"
content="content new leaf" content="content new leaf"
> >
content new leaf content new leaf

View File

@ -193,9 +193,10 @@ describe('lifecycle', () => {
it('leaf change and make componentWillReceiveProps', () => { it('leaf change and make componentWillReceiveProps', () => {
const newTextNodeLeaf = new Node(textSchema); const newTextNodeLeaf = new Node(textSchema);
nodeMap.set(textSchema.id, newTextNodeLeaf);
component.update(( component.update((
<Div _leaf={DivNode}> <Div _leaf={DivNode}>
<Text _leaf={newTextNodeLeaf} __tag="222" content="content 123"></Text> <Text componentId={textSchema.id} __tag="222" content="content 123"></Text>
</Div> </Div>
)); ));
@ -232,6 +233,9 @@ describe('mini unit render', () => {
parent: MiniRenderDivNode, parent: MiniRenderDivNode,
}); });
nodeMap.set(miniRenderSchema.id, MiniRenderDivNode);
nodeMap.set(textSchema.id, TextNode);
component = renderer.create( component = renderer.create(
// @ts-ignore // @ts-ignore
<MiniRenderDiv _leaf={MiniRenderDivNode}> <MiniRenderDiv _leaf={MiniRenderDivNode}>
@ -277,6 +281,8 @@ describe('mini unit render', () => {
}), }),
}); });
nodeMap.set(textSchema.id, TextNode);
renderer.create( renderer.create(
// @ts-ignore // @ts-ignore
<div> <div>
@ -319,6 +325,8 @@ describe('mini unit render', () => {
isRoot: true, isRoot: true,
}); });
nodeMap.set(textSchema.id, TextNode);
const component = renderer.create( const component = renderer.create(
<Text _leaf={TextNode} content="content"></Text> <Text _leaf={TextNode} content="content"></Text>
); );
@ -351,6 +359,8 @@ describe('mini unit render', () => {
}) })
}); });
nodeMap.set(textSchema.id, TextNode);
const component = renderer.create( const component = renderer.create(
<Text _leaf={TextNode} content="content"></Text> <Text _leaf={TextNode} content="content"></Text>
); );
@ -370,7 +380,9 @@ describe('mini unit render', () => {
}); });
it('parent is a mock leaf', () => { it('parent is a mock leaf', () => {
const MiniRenderDivNode = {}; const MiniRenderDivNode = {
isMock: true,
};
const component = renderer.create( const component = renderer.create(
// @ts-ignore // @ts-ignore
@ -409,6 +421,9 @@ describe('mini unit render', () => {
hasLoop: true, hasLoop: true,
}); });
nodeMap.set(textSchema.id, TextNode);
nodeMap.set(miniRenderSchema.id, MiniRenderDivNode);
component = renderer.create( component = renderer.create(
// @ts-ignore // @ts-ignore
<MiniRenderDiv _leaf={MiniRenderDivNode}> <MiniRenderDiv _leaf={MiniRenderDivNode}>

View File

@ -1,7 +1,8 @@
import { import {
BuiltinSimulatorHost, BuiltinSimulatorHost,
} from '@alilc/lowcode-designer'; } from '@alilc/lowcode-designer';
import { simulatorHostSymbol } from './symbols'; import { simulatorHostSymbol, nodeSymbol } from './symbols';
import type Node from './node';
export default class SimulatorHost { export default class SimulatorHost {
private readonly [simulatorHostSymbol]: BuiltinSimulatorHost; private readonly [simulatorHostSymbol]: BuiltinSimulatorHost;
@ -51,6 +52,14 @@ export default class SimulatorHost {
return this[simulatorHostSymbol].get(key); return this[simulatorHostSymbol].get(key);
} }
/**
* scroll to specific node
* @param node
*/
scrollToNode(node: Node) {
this[simulatorHostSymbol].scrollToNode(node[nodeSymbol]);
}
/** /**
* *
*/ */

View File

@ -617,8 +617,8 @@ component
| screenshot | 组件快照 | String | 否 | | screenshot | 组件快照 | String | 否 |
| icon | 组件的小图标 | String (URL) | 是 | | icon | 组件的小图标 | String (URL) | 是 |
| tags | 组件标签 | String | 是 | | tags | 组件标签 | String | 是 |
| keywards | 组件关键词,用于搜索联想 | String | 是 | | keywords | 组件关键词,用于搜索联想 | String | 是 |
| devMode | 组件研发模式 | String  (procode,lowcode) | 是 | | devMode | 组件研发模式 | String  (proCode,lowCode) | 是 |
| npm | npm 源引入完整描述对象 | Object | 否 | | npm | npm 源引入完整描述对象 | Object | 否 |
| npm.package | 源码组件库名 | String | 否 | | npm.package | 源码组件库名 | String | 否 |
| npm.exportName | 源码组件名称 | String | 否 | | npm.exportName | 源码组件名称 | String | 否 |
@ -857,7 +857,7 @@ props 数组下对象字段描述:
// 支持条件设置 // 支持条件设置
"condition": true, "condition": true,
// 支持样式设置 // 支持样式设置
"styles": true, "style": true,
} }
} }
} }