diff --git a/packages/react-provider/CHANGELOG.md b/packages/react-provider/CHANGELOG.md
index 0575970db..da649feed 100644
--- a/packages/react-provider/CHANGELOG.md
+++ b/packages/react-provider/CHANGELOG.md
@@ -3,6 +3,7 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [1.0.7-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-react-provider@1.0.6-0...@ali/lowcode-react-provider@1.0.7-0) (2020-09-02)
@@ -14,6 +15,10 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
## [1.0.6-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-react-provider@1.0.5-0...@ali/lowcode-react-provider@1.0.6-0) (2020-09-02)
+### Features
+
+* 增加错误边界&pageReady钩子 ([3bb2fc1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3bb2fc1))
+
diff --git a/packages/react-provider/package.json b/packages/react-provider/package.json
index d24bf1aa2..f4441334d 100644
--- a/packages/react-provider/package.json
+++ b/packages/react-provider/package.json
@@ -26,6 +26,7 @@
"license": "MIT",
"dependencies": {
"@ali/lowcode-runtime": "^1.0.7-0",
+ "@ali/lowcode-utils": "^1.0.7",
"@recore/router": "^1.0.11",
"react": "^16",
"react-dom": "^16"
diff --git a/packages/react-provider/src/components/LazyComponent/index.tsx b/packages/react-provider/src/components/LazyComponent/index.tsx
new file mode 100644
index 000000000..4cf20a341
--- /dev/null
+++ b/packages/react-provider/src/components/LazyComponent/index.tsx
@@ -0,0 +1,81 @@
+import { Component } from 'react';
+import { app, Provider } from '@ali/lowcode-runtime';
+
+interface IProps {
+ getPageData: () => any;
+ context: Provider;
+ [key: string]: any;
+}
+
+interface IState {
+ schema: object | null;
+ hasError: boolean;
+}
+
+export default class LazyComponent extends Component {
+ constructor(props: IProps) {
+ super(props);
+ this.state = {
+ schema: null,
+ hasError: false,
+ };
+ }
+
+ static getDerivedStateFromError() {
+ return { hasError: true };
+ }
+
+ async componentDidMount() {
+ const { getPageData, context } = this.props;
+ if (getPageData && !this.state.schema) {
+ try {
+ const schema = await getPageData();
+ this.setState({ schema });
+ context.emitPageReady();
+ } catch (err) {
+ this.setState({ hasError: true });
+ this.exeAfterCatch(err.message, err.stack);
+ }
+ }
+ }
+
+ exeAfterCatch(error: any, errorInfo?: any) {
+ const { afterCatch } = app.getErrorBoundary() || {};
+ if (typeof afterCatch === 'function') {
+ afterCatch.call(this, error, errorInfo);
+ }
+ }
+
+ componentDidCatch(error: any, errorInfo: any) {
+ this.exeAfterCatch(error, errorInfo);
+ }
+
+ render() {
+ const { schema, hasError } = this.state;
+ if (hasError) {
+ const { fallbackUI: ErrorView } = app.getErrorBoundary() || {};
+ if (!ErrorView) {
+ return '';
+ }
+ return ;
+ }
+
+ const { getPageData, ...restProps } = this.props;
+ const Renderer = app.getRenderer();
+ const Loading = app.getLoading();
+ if (!Renderer || !schema) {
+ if (!Loading) {
+ return null;
+ }
+ // loading扩展点
+ return ;
+ }
+ return (
+ : null}
+ {...restProps}
+ />
+ )
+ }
+}
diff --git a/packages/react-provider/src/lazy-component.tsx b/packages/react-provider/src/lazy-component.tsx
deleted file mode 100644
index b20fd2bf8..000000000
--- a/packages/react-provider/src/lazy-component.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { Component, createElement } from 'react';
-import { app } from '@ali/lowcode-runtime';
-
-interface IProps {
- getPageData: () => any;
- [key: string]: any;
-}
-
-interface IState {
- schema: object | null;
-}
-
-export default class LazyComponent extends Component {
- constructor(props: IProps) {
- super(props);
- this.state = {
- schema: null,
- };
- }
-
- async componentDidMount() {
- const { getPageData } = this.props;
- if (getPageData && !this.state.schema) {
- const schema = await getPageData();
- this.setState({ schema });
- }
- }
-
- render() {
- const { getPageData, ...restProps } = this.props;
- const { schema } = this.state;
- const Renderer = app.getRenderer();
- const Loading = app.getLoading();
- if (!Renderer || !schema) {
- if (!Loading) {
- return null;
- }
- // loading扩展点
- return createElement(Loading);
- }
- return createElement(Renderer as any, { schema, loading: Loading ? createElement(Loading) : null, ...restProps });
- }
-}
diff --git a/packages/react-provider/src/provider.tsx b/packages/react-provider/src/provider.tsx
index de4d97352..94c37a4c9 100644
--- a/packages/react-provider/src/provider.tsx
+++ b/packages/react-provider/src/provider.tsx
@@ -2,13 +2,14 @@ import { createElement, ReactType, ReactElement } from 'react';
import ReactDOM from 'react-dom';
import { Router } from '@recore/router';
import { app, Provider } from '@ali/lowcode-runtime';
-import LazyComponent from './lazy-component';
+import { AppHelper } from '@ali/lowcode-utils';
+import LazyComponent from './components/LazyComponent';
export default class ReactProvider extends Provider {
// 定制构造根组件的逻辑,如切换路由机制
createApp() {
- const RouterView = this.getRouterView();
- let App;
+ let RouterView = this.getRouterView();
+ let App: any;
const layoutConfig = this.getLayoutConfig();
if (!layoutConfig || !layoutConfig.componentName) {
App = (props: any) => (RouterView ? createElement(RouterView, { ...props }) : null);
@@ -16,7 +17,7 @@ export default class ReactProvider extends Provider {
}
const { componentName: layoutName, props: layoutProps } = layoutConfig;
const { content: Layout, props: extraLayoutProps } = app.getLayout(layoutName) || {};
- const sectionalRender = this.isSectionalRender();
+ const sectionalRender = this.isSectionalRender;
if (!sectionalRender && Layout) {
App = (props: any) =>
createElement(
@@ -31,14 +32,21 @@ export default class ReactProvider extends Provider {
}
runApp(App: any, config: any) {
- ReactDOM.render(, document.getElementById(config?.containerId || 'App'));
+ const containerId = config?.containerId || 'App';
+ let container = document.getElementById(containerId);
+ if (!container) {
+ container = document.createElement('div');
+ document.body.appendChild(container);
+ container.id = containerId;
+ }
+ ReactDOM.render(, container);
}
// 内置实现 for 动态化渲染
- getRouterView(): ReactType | null {
+ getRouterView(): ReactType {
const routerConfig = this.getRouterConfig();
if (!routerConfig) {
- return null;
+ return () => null;
}
const routes: Array<{
path: string;
@@ -73,11 +81,15 @@ export default class ReactProvider extends Provider {
defined: { keepAlive: true },
});
}
+ const appHelper = new AppHelper();
+ appHelper.set('utils', this.getUtils());
+ appHelper.set('constants', this.getConstants());
const RouterView = (props: any) => {
return createElement(Router as any, {
routes,
components: this.getComponents(),
utils: this.getUtils(),
+ appHelper,
componentsMap: this.getComponentsMapObj(),
...props,
});
@@ -89,15 +101,16 @@ export default class ReactProvider extends Provider {
if (!pageId) {
return null;
}
- if (this.getlazyElement(pageId)) {
- return this.getlazyElement(pageId);
+ if (this.getLazyElement(pageId)) {
+ return this.getLazyElement(pageId);
} else {
const lazyElement = createElement(LazyComponent as any, {
getPageData: async () => await this.getPageData(pageId),
key: pageId,
+ context: this,
...props,
});
- this.setlazyElement(pageId, lazyElement);
+ this.setLazyElement(pageId, lazyElement);
return lazyElement;
}
}
diff --git a/packages/runtime/CHANGELOG.md b/packages/runtime/CHANGELOG.md
index 243e43945..b930929c4 100644
--- a/packages/runtime/CHANGELOG.md
+++ b/packages/runtime/CHANGELOG.md
@@ -3,21 +3,32 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+
## [1.0.7-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-runtime@1.0.6-0...@ali/lowcode-runtime@1.0.7-0) (2020-09-02)
-
-
**Note:** Version bump only for package @ali/lowcode-runtime
+
-## [1.0.6-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-runtime@1.0.5-0...@ali/lowcode-runtime@1.0.6-0) (2020-09-02)
+## [1.0.6-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-runtime@1.0.5-0...@ali/lowcode-runtime@1.0.6-0) (2020-08-31)
+### Bug Fixes
+
+* 取消Provider的校验 ([a1755f2](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/a1755f2))
+
+
+### Features
+
+* 兼容通过api更新属性的场景 ([b58d59b](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/b58d59b))
+* 增加错误边界&pageReady钩子 ([3bb2fc1](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/3bb2fc1))
+* 增加若干钩子方法 ([eb55712](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/eb55712))
+* 支持多个provider ([642c62e](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/642c62e))
+* 支持多个Provider ([e35357f](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/commit/e35357f))
-**Note:** Version bump only for package @ali/lowcode-runtime
## [1.0.5-0](https://gitlab.alibaba-inc.com/ali-lowcode/ali-lowcode-engine/compare/@ali/lowcode-runtime@1.0.4-0...@ali/lowcode-runtime@1.0.5-0) (2020-08-20)
diff --git a/packages/runtime/index.d.ts b/packages/runtime/index.d.ts
deleted file mode 100644
index 76ca8e969..000000000
--- a/packages/runtime/index.d.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-type HistoryMode = 'browser' | 'hash';
-
-interface ComponentsMap {
- [key: string]: any;
-}
-
-interface UtilsMap {
- [key: string]: any;
-}
-
-export interface AppConfig {
- history?: HistoryMode;
- globalComponents?: ComponentsMap;
- globalUtils?: UtilsMap;
- containerId?: string;
-}
-
-export function run(Component: any, config?: AppConfig | (() => AppConfig)): any;
diff --git a/packages/runtime/src/core/container.ts b/packages/runtime/src/core/container.ts
index 809394bf6..f5652e117 100644
--- a/packages/runtime/src/core/container.ts
+++ b/packages/runtime/src/core/container.ts
@@ -5,11 +5,17 @@ export interface ILayoutOptions {
props?: any;
}
+export interface IErrorBoundaryConfig {
+ fallbackUI: any;
+ afterCatch?: (...rest: any) => any
+}
+
export default class Container {
private renderer: any = null;
private layouts: { [key: string]: { content: any; props: any } } = {};
private loading: any = null;
- private provider: any;
+ private errorBoundary: IErrorBoundaryConfig = { fallbackUI: () => '', afterCatch: () => {} };
+ private providers: { [key: string]: Provider; } = {};
registerRenderer(renderer: any): any {
this.renderer = renderer;
@@ -33,12 +39,19 @@ export default class Container {
this.loading = component;
}
+ registerErrorBoundary(config: IErrorBoundaryConfig) {
+ if (!config) {
+ return;
+ }
+ this.errorBoundary = config;
+ }
+
registerProvider(CustomProvider: any) {
- if (Provider.isPrototypeOf(CustomProvider)) {
- this.provider = new CustomProvider();
- } else {
- const identifier = (CustomProvider && CustomProvider.name) || 'registered Provider';
- throw new Error(`${identifier} is not a child class of Provider`);
+ try {
+ const p = new CustomProvider();
+ this.providers[p.getContainerId()] = p;
+ } catch (error) {
+ console.error(error.message);
}
}
@@ -57,7 +70,19 @@ export default class Container {
return this.loading;
}
- getProvider() {
- return this.provider;
+ getErrorBoundary(): any {
+ return this.errorBoundary;
+ }
+
+ getProvider(id?: string) {
+ if (!id) {
+ for (const key in this.providers) {
+ if (Object.prototype.hasOwnProperty.call(this.providers, key)) {
+ return this.providers[key];
+ }
+ }
+ } else {
+ return this.providers[id];
+ }
}
}
diff --git a/packages/runtime/src/core/index.ts b/packages/runtime/src/core/index.ts
index de70f9229..720e9e471 100644
--- a/packages/runtime/src/core/index.ts
+++ b/packages/runtime/src/core/index.ts
@@ -1,5 +1,5 @@
-import Container, { ILayoutOptions } from './container';
-import { IProvider } from './provider';
+import Container, { ILayoutOptions, IErrorBoundaryConfig } from './container';
+import Provider from './provider';
import runApp from './runApp';
class App {
@@ -29,20 +29,28 @@ class App {
this.container.registerProvider(CustomProvider);
}
+ registerErrorBoundary(config: IErrorBoundaryConfig) {
+ this.container.registerErrorBoundary(config);
+ }
+
getLayout(componentName: string) {
return this.container.getLayout(componentName);
}
- getRenderer(): any | null {
+ getRenderer(): any {
return this.container.getRenderer();
}
- getLoading(): any | null {
+ getLoading(): any {
return this.container.getLoading();
}
- getProvider(): IProvider {
- return this.container.getProvider();
+ getErrorBoundary(): IErrorBoundaryConfig {
+ return this.container.getErrorBoundary();
+ }
+
+ getProvider(id?: string): Provider | undefined {
+ return this.container.getProvider(id);
}
}
diff --git a/packages/runtime/src/core/provider.ts b/packages/runtime/src/core/provider.ts
index fc116e4ef..5268797ab 100644
--- a/packages/runtime/src/core/provider.ts
+++ b/packages/runtime/src/core/provider.ts
@@ -101,32 +101,20 @@ export interface I18n {
type Locale = 'zh-CN' | 'en-US';
-export interface IProvider {
- init(): void;
- ready(): void;
- onReady(cb: any): void;
- async(): Promise;
- getAppData(): Promise;
- getPageData(pageId: string): Promise;
- getLazyComponent(pageId: string, props: any): any;
- createApp(): void;
- runApp(App: any, config: IAppConfig): void;
-}
-
-export default class Provider implements IProvider {
- private components: IComponents = {};
- private utils: IUtils = {};
- private constants: IConstants = {};
- private routes: IRouterConfig | null = null;
- private layout: ILayoutConfig | null = null;
- private componentsMap: IComponentMap[] = [];
- private history: HistoryMode = 'hash';
- private containerId = '';
- private i18n: I18n | null = null;
- private homePage = '';
- private lazyElementsMap: { [key: string]: any } = {};
- private sectionalRender = false;
- private emitter: EventEmitter = new EventEmitter();
+export default class Provider {
+ emitter: EventEmitter = new EventEmitter();
+ components: IComponents = {};
+ utils: IUtils = {};
+ constants: IConstants = {};
+ routes: IRouterConfig | null = null;
+ layout: ILayoutConfig | null = null;
+ componentsMap: IComponentMap[] = [];
+ history: HistoryMode = 'hash';
+ containerId = '';
+ i18n: I18n | null = null;
+ homePage = '';
+ lazyElementsMap: { [key: string]: any } = {};
+ isSectionalRender = false;
constructor() {
this.init();
@@ -150,19 +138,18 @@ export default class Provider implements IProvider {
this.registerUtils(utils);
this.registerContants(constants);
resolve({
- history,
- components,
- utils,
- containerId,
+ history: this.getHistory(),
+ components: this.getComponents(),
+ utils: this.getUtils(),
+ containerId: this.getContainerId(),
});
} catch (err) {
- reject(err.message);
+ reject(err);
}
});
}
init() {
- console.log('init');
// 默认 ready,当重载了init时需手动触发 this.ready()
this.ready();
}
@@ -181,6 +168,50 @@ export default class Provider implements IProvider {
this.emitter.on('ready', cb);
}
+ emitPageReady() {
+ this.emitter.emit('pageReady');
+ }
+
+ emitPageEnter() {
+ this.emitter.emit('pageEnter');
+ }
+
+ emitPageUpdate() {
+ this.emitter.emit('pageUpdate');
+ }
+
+ emitPageLeave() {
+ this.emitter.emit('pageLeave');
+ }
+
+ onPageReady(cb: (params?: any) => void) {
+ this.emitter.on('pageReady', cb);
+ return () => {
+ this.emitter.removeListener('pageReady', cb);
+ };
+ }
+
+ onPageEnter(cb: (params?: any) => void) {
+ this.emitter.on('pageEnter', cb);
+ return () => {
+ this.emitter.removeListener('pageEnter', cb);
+ };
+ }
+
+ onPageUpdate(cb: (params?: any) => void) {
+ this.emitter.on('pageUpdate', cb);
+ return () => {
+ this.emitter.removeListener('pageUpdate', cb);
+ };
+ }
+
+ onPageLeave(cb: (params?: any) => void) {
+ this.emitter.on('pageLeave', cb);
+ return () => {
+ this.emitter.removeListener('pageLeave', cb);
+ };
+ }
+
getAppData(): any {
throw new Error('Method called "getAppData" not implemented.');
}
@@ -244,7 +275,7 @@ export default class Provider implements IProvider {
this.routes = config;
}
- setHistory(config: HistoryMode | undefined) {
+ setHistory(config: HistoryMode | undefined): any {
if (!config) {
return;
}
@@ -265,7 +296,7 @@ export default class Provider implements IProvider {
this.i18n = i18n;
}
- setlazyElement(pageId: string, cache: any) {
+ setLazyElement(pageId: string, cache: any) {
if (!pageId || !cache) {
return;
}
@@ -278,10 +309,6 @@ export default class Provider implements IProvider {
}
}
- setSectionalRender() {
- this.sectionalRender = true;
- }
-
getComponents() {
return this.components;
}
@@ -333,7 +360,7 @@ export default class Provider implements IProvider {
}
getContainerId() {
- return this.containerId;
+ return this.containerId || 'App';
}
getI18n(locale?: Locale) {
@@ -347,14 +374,10 @@ export default class Provider implements IProvider {
return this.homePage;
}
- getlazyElement(pageId: string) {
+ getLazyElement(pageId: string) {
if (!pageId) {
return;
}
return this.lazyElementsMap[pageId];
}
-
- isSectionalRender() {
- return this.sectionalRender;
- }
}
diff --git a/packages/runtime/src/core/runApp.ts b/packages/runtime/src/core/runApp.ts
index 3dea9fb92..6c6cc2318 100644
--- a/packages/runtime/src/core/runApp.ts
+++ b/packages/runtime/src/core/runApp.ts
@@ -13,7 +13,7 @@ export interface IUtils {
[key: string]: any;
}
-export type HistoryMode = 'browser' | 'hash';
+export type HistoryMode = 'browser' | 'hash' | 'BROWSER' | 'HASH';
export interface IAppConfig {
history?: HistoryMode;
@@ -36,6 +36,16 @@ export default function runApp() {
}
const App = provider.createApp();
provider.runApp(App, config);
+ }).catch((err: Error) => {
+ console.error(err.message);
+ const { fallbackUI, afterCatch } = app.getErrorBoundary() || {};
+ if (typeof afterCatch === 'function') {
+ afterCatch(err.message, err.stack);
+ }
+ if (!fallbackUI) {
+ return;
+ }
+ provider.runApp(fallbackUI, {});
});
});
}