Merge branch 'refactor/runtime' into 'release/1.0.0'

Refactor/runtime



See merge request !962652
This commit is contained in:
荣彬 2020-09-08 10:08:32 +08:00
commit 3c39c33694
11 changed files with 250 additions and 134 deletions

View File

@ -3,6 +3,7 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
<a name="1.0.7-0"></a> <a name="1.0.7-0"></a>
## [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) ## [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
<a name="1.0.6-0"></a> <a name="1.0.6-0"></a>
## [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) ## [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))

View File

@ -26,6 +26,7 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@ali/lowcode-runtime": "^1.0.7-0", "@ali/lowcode-runtime": "^1.0.7-0",
"@ali/lowcode-utils": "^1.0.7",
"@recore/router": "^1.0.11", "@recore/router": "^1.0.11",
"react": "^16", "react": "^16",
"react-dom": "^16" "react-dom": "^16"

View File

@ -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<IProps, IState> {
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 <ErrorView />;
}
const { getPageData, ...restProps } = this.props;
const Renderer = app.getRenderer();
const Loading = app.getLoading();
if (!Renderer || !schema) {
if (!Loading) {
return null;
}
// loading扩展点
return <Loading />;
}
return (
<Renderer
schema={schema}
loading={Loading ? <Loading /> : null}
{...restProps}
/>
)
}
}

View File

@ -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<IProps, IState> {
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 });
}
}

View File

@ -2,13 +2,14 @@ import { createElement, ReactType, ReactElement } from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { Router } from '@recore/router'; import { Router } from '@recore/router';
import { app, Provider } from '@ali/lowcode-runtime'; 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 { export default class ReactProvider extends Provider {
// 定制构造根组件的逻辑,如切换路由机制 // 定制构造根组件的逻辑,如切换路由机制
createApp() { createApp() {
const RouterView = this.getRouterView(); let RouterView = this.getRouterView();
let App; let App: any;
const layoutConfig = this.getLayoutConfig(); const layoutConfig = this.getLayoutConfig();
if (!layoutConfig || !layoutConfig.componentName) { if (!layoutConfig || !layoutConfig.componentName) {
App = (props: any) => (RouterView ? createElement(RouterView, { ...props }) : null); 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 { componentName: layoutName, props: layoutProps } = layoutConfig;
const { content: Layout, props: extraLayoutProps } = app.getLayout(layoutName) || {}; const { content: Layout, props: extraLayoutProps } = app.getLayout(layoutName) || {};
const sectionalRender = this.isSectionalRender(); const sectionalRender = this.isSectionalRender;
if (!sectionalRender && Layout) { if (!sectionalRender && Layout) {
App = (props: any) => App = (props: any) =>
createElement( createElement(
@ -31,14 +32,21 @@ export default class ReactProvider extends Provider {
} }
runApp(App: any, config: any) { runApp(App: any, config: any) {
ReactDOM.render(<App />, 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(<App />, container);
} }
// 内置实现 for 动态化渲染 // 内置实现 for 动态化渲染
getRouterView(): ReactType | null { getRouterView(): ReactType {
const routerConfig = this.getRouterConfig(); const routerConfig = this.getRouterConfig();
if (!routerConfig) { if (!routerConfig) {
return null; return () => null;
} }
const routes: Array<{ const routes: Array<{
path: string; path: string;
@ -73,11 +81,15 @@ export default class ReactProvider extends Provider {
defined: { keepAlive: true }, defined: { keepAlive: true },
}); });
} }
const appHelper = new AppHelper();
appHelper.set('utils', this.getUtils());
appHelper.set('constants', this.getConstants());
const RouterView = (props: any) => { const RouterView = (props: any) => {
return createElement(Router as any, { return createElement(Router as any, {
routes, routes,
components: this.getComponents(), components: this.getComponents(),
utils: this.getUtils(), utils: this.getUtils(),
appHelper,
componentsMap: this.getComponentsMapObj(), componentsMap: this.getComponentsMapObj(),
...props, ...props,
}); });
@ -89,15 +101,16 @@ export default class ReactProvider extends Provider {
if (!pageId) { if (!pageId) {
return null; return null;
} }
if (this.getlazyElement(pageId)) { if (this.getLazyElement(pageId)) {
return this.getlazyElement(pageId); return this.getLazyElement(pageId);
} else { } else {
const lazyElement = createElement(LazyComponent as any, { const lazyElement = createElement(LazyComponent as any, {
getPageData: async () => await this.getPageData(pageId), getPageData: async () => await this.getPageData(pageId),
key: pageId, key: pageId,
context: this,
...props, ...props,
}); });
this.setlazyElement(pageId, lazyElement); this.setLazyElement(pageId, lazyElement);
return lazyElement; return lazyElement;
} }
} }

View File

@ -3,21 +3,32 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
<a name="1.0.7-0"></a> <a name="1.0.7-0"></a>
## [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) ## [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 **Note:** Version bump only for package @ali/lowcode-runtime
<a name="1.0.6-0"></a> <a name="1.0.6-0"></a>
## [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
<a name="1.0.5-0"></a> <a name="1.0.5-0"></a>
## [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) ## [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)

View File

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

View File

@ -5,11 +5,17 @@ export interface ILayoutOptions {
props?: any; props?: any;
} }
export interface IErrorBoundaryConfig {
fallbackUI: any;
afterCatch?: (...rest: any) => any
}
export default class Container { export default class Container {
private renderer: any = null; private renderer: any = null;
private layouts: { [key: string]: { content: any; props: any } } = {}; private layouts: { [key: string]: { content: any; props: any } } = {};
private loading: any = null; private loading: any = null;
private provider: any; private errorBoundary: IErrorBoundaryConfig = { fallbackUI: () => '', afterCatch: () => {} };
private providers: { [key: string]: Provider; } = {};
registerRenderer(renderer: any): any { registerRenderer(renderer: any): any {
this.renderer = renderer; this.renderer = renderer;
@ -33,12 +39,19 @@ export default class Container {
this.loading = component; this.loading = component;
} }
registerErrorBoundary(config: IErrorBoundaryConfig) {
if (!config) {
return;
}
this.errorBoundary = config;
}
registerProvider(CustomProvider: any) { registerProvider(CustomProvider: any) {
if (Provider.isPrototypeOf(CustomProvider)) { try {
this.provider = new CustomProvider(); const p = new CustomProvider();
} else { this.providers[p.getContainerId()] = p;
const identifier = (CustomProvider && CustomProvider.name) || 'registered Provider'; } catch (error) {
throw new Error(`${identifier} is not a child class of Provider`); console.error(error.message);
} }
} }
@ -57,7 +70,19 @@ export default class Container {
return this.loading; return this.loading;
} }
getProvider() { getErrorBoundary(): any {
return this.provider; 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];
}
} }
} }

View File

@ -1,5 +1,5 @@
import Container, { ILayoutOptions } from './container'; import Container, { ILayoutOptions, IErrorBoundaryConfig } from './container';
import { IProvider } from './provider'; import Provider from './provider';
import runApp from './runApp'; import runApp from './runApp';
class App { class App {
@ -29,20 +29,28 @@ class App {
this.container.registerProvider(CustomProvider); this.container.registerProvider(CustomProvider);
} }
registerErrorBoundary(config: IErrorBoundaryConfig) {
this.container.registerErrorBoundary(config);
}
getLayout(componentName: string) { getLayout(componentName: string) {
return this.container.getLayout(componentName); return this.container.getLayout(componentName);
} }
getRenderer(): any | null { getRenderer(): any {
return this.container.getRenderer(); return this.container.getRenderer();
} }
getLoading(): any | null { getLoading(): any {
return this.container.getLoading(); return this.container.getLoading();
} }
getProvider(): IProvider { getErrorBoundary(): IErrorBoundaryConfig {
return this.container.getProvider(); return this.container.getErrorBoundary();
}
getProvider(id?: string): Provider | undefined {
return this.container.getProvider(id);
} }
} }

View File

@ -101,32 +101,20 @@ export interface I18n {
type Locale = 'zh-CN' | 'en-US'; type Locale = 'zh-CN' | 'en-US';
export interface IProvider { export default class Provider {
init(): void; emitter: EventEmitter = new EventEmitter();
ready(): void; components: IComponents = {};
onReady(cb: any): void; utils: IUtils = {};
async(): Promise<IAppConfig>; constants: IConstants = {};
getAppData(): Promise<IAppData | undefined>; routes: IRouterConfig | null = null;
getPageData(pageId: string): Promise<ComponentModel | undefined>; layout: ILayoutConfig | null = null;
getLazyComponent(pageId: string, props: any): any; componentsMap: IComponentMap[] = [];
createApp(): void; history: HistoryMode = 'hash';
runApp(App: any, config: IAppConfig): void; containerId = '';
} i18n: I18n | null = null;
homePage = '';
export default class Provider implements IProvider { lazyElementsMap: { [key: string]: any } = {};
private components: IComponents = {}; isSectionalRender = false;
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();
constructor() { constructor() {
this.init(); this.init();
@ -150,19 +138,18 @@ export default class Provider implements IProvider {
this.registerUtils(utils); this.registerUtils(utils);
this.registerContants(constants); this.registerContants(constants);
resolve({ resolve({
history, history: this.getHistory(),
components, components: this.getComponents(),
utils, utils: this.getUtils(),
containerId, containerId: this.getContainerId(),
}); });
} catch (err) { } catch (err) {
reject(err.message); reject(err);
} }
}); });
} }
init() { init() {
console.log('init');
// 默认 ready当重载了init时需手动触发 this.ready() // 默认 ready当重载了init时需手动触发 this.ready()
this.ready(); this.ready();
} }
@ -181,6 +168,50 @@ export default class Provider implements IProvider {
this.emitter.on('ready', cb); 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 { getAppData(): any {
throw new Error('Method called "getAppData" not implemented.'); throw new Error('Method called "getAppData" not implemented.');
} }
@ -244,7 +275,7 @@ export default class Provider implements IProvider {
this.routes = config; this.routes = config;
} }
setHistory(config: HistoryMode | undefined) { setHistory(config: HistoryMode | undefined): any {
if (!config) { if (!config) {
return; return;
} }
@ -265,7 +296,7 @@ export default class Provider implements IProvider {
this.i18n = i18n; this.i18n = i18n;
} }
setlazyElement(pageId: string, cache: any) { setLazyElement(pageId: string, cache: any) {
if (!pageId || !cache) { if (!pageId || !cache) {
return; return;
} }
@ -278,10 +309,6 @@ export default class Provider implements IProvider {
} }
} }
setSectionalRender() {
this.sectionalRender = true;
}
getComponents() { getComponents() {
return this.components; return this.components;
} }
@ -333,7 +360,7 @@ export default class Provider implements IProvider {
} }
getContainerId() { getContainerId() {
return this.containerId; return this.containerId || 'App';
} }
getI18n(locale?: Locale) { getI18n(locale?: Locale) {
@ -347,14 +374,10 @@ export default class Provider implements IProvider {
return this.homePage; return this.homePage;
} }
getlazyElement(pageId: string) { getLazyElement(pageId: string) {
if (!pageId) { if (!pageId) {
return; return;
} }
return this.lazyElementsMap[pageId]; return this.lazyElementsMap[pageId];
} }
isSectionalRender() {
return this.sectionalRender;
}
} }

View File

@ -13,7 +13,7 @@ export interface IUtils {
[key: string]: any; [key: string]: any;
} }
export type HistoryMode = 'browser' | 'hash'; export type HistoryMode = 'browser' | 'hash' | 'BROWSER' | 'HASH';
export interface IAppConfig { export interface IAppConfig {
history?: HistoryMode; history?: HistoryMode;
@ -36,6 +36,16 @@ export default function runApp() {
} }
const App = provider.createApp(); const App = provider.createApp();
provider.runApp(App, config); 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, {});
}); });
}); });
} }