diff --git a/packages/demo/build.json b/packages/demo/build.json index 48cdb5e94..452a15dca 100644 --- a/packages/demo/build.json +++ b/packages/demo/build.json @@ -1,7 +1,8 @@ { "entry": { - "index": "src/index.jsx", - "react-simulator-renderer": "../react-simulator-renderer/src/index.js" + "index": "src/index.ts", + "react-simulator-renderer": "../react-simulator-renderer/src/index.ts", + "preview": "src/preview.ts" }, "vendor": false, "devServer": { diff --git a/packages/demo/cloud-build.json b/packages/demo/cloud-build.json index 16a6afe1d..7b4fcd1d9 100644 --- a/packages/demo/cloud-build.json +++ b/packages/demo/cloud-build.json @@ -1,6 +1,7 @@ { "entry": { - "lowcode-demo": "src/index.jsx" + "lowcode-editor": "src/index.ts", + "lowcode-preview": "src/preview.ts" }, "vendor": false, "externals": { diff --git a/packages/demo/package.json b/packages/demo/package.json index 6e448fffe..0b5e31cb8 100644 --- a/packages/demo/package.json +++ b/packages/demo/package.json @@ -18,8 +18,11 @@ "@ali/lowcode-plugin-zh-en": "^0.8.0", "@ali/lowcode-plugin-sample-logo": "^0.8.0", "@ali/lowcode-plugin-sample-preview": "^0.8.0", + "@ali/lowcode-runtime": "^0.8.0", + "@ali/lowcode-react-renderer": "^0.8.0", "@alife/theme-lowcode-dark": "^0.1.0", "@alife/theme-lowcode-light": "^0.1.0", + "@alifd/next": "^1.19.21", "react": "^16.8.1", "react-dom": "^16.8.1" }, diff --git a/packages/demo/public/preview.html b/packages/demo/public/preview.html new file mode 100644 index 000000000..34c8cf86c --- /dev/null +++ b/packages/demo/public/preview.html @@ -0,0 +1,20 @@ + + + + + + + LowCodeEngine DEMO + + + + + + + + + + + + + diff --git a/packages/demo/src/app/config/app.js b/packages/demo/src/app/config/app.js new file mode 100644 index 000000000..c1ad5b57c --- /dev/null +++ b/packages/demo/src/app/config/app.js @@ -0,0 +1,21 @@ +export default { + sdkVersion: '1.0.3', + historyMode: 'hash', // 浏览器路由:brower 哈希路由:hash + constainerId: 'app', + layout: { + componentName: 'BasicLayout', + props: { + name: '低代码引擎预览 demo', + logo: { + src: 'https://img.alicdn.com/tfs/TB1kAfWyrY1gK0jSZTEXXXDQVXa-75-33.png', + width: 40, + height: 20, + }, + }, + }, + theme: { + package: '@alife/theme-fusion', + version: '^0.1.0', + }, + compDependencies: [], +}; diff --git a/packages/demo/src/app/config/components.js b/packages/demo/src/app/config/components.js new file mode 100644 index 000000000..91aef7993 --- /dev/null +++ b/packages/demo/src/app/config/components.js @@ -0,0 +1,12 @@ +/** + * 内置组件 + */ +import Engine from '@ali/iceluna-sdk/lib/engine'; +import Page from '@ali/iceluna-sdk/lib/engine/pageEngine' +import Div from '@ali/iceluna-comp-div'; + +export default { + Engine, + Page, + Div, +} diff --git a/packages/demo/src/app/config/componentsMap.js b/packages/demo/src/app/config/componentsMap.js new file mode 100644 index 000000000..a9bb73edb --- /dev/null +++ b/packages/demo/src/app/config/componentsMap.js @@ -0,0 +1,53 @@ +export default { + Button: { + package: '@alifd/next', + version: '1.19.18', + destructuring: true, + exportName: 'Button', + }, + 'Button.Group': { + package: '@alifd/next', + version: '1.19.18', + destructuring: true, + exportName: 'Button', + subName: 'Group', + }, + Input: { + package: '@alifd/next', + version: '1.19.18', + destructuring: true, + exportName: 'Input', + }, + Form: { + package: '@alifd/next', + version: '1.19.18', + destructuring: true, + exportName: 'Form', + }, + 'Form.Item': { + package: '@alifd/next', + version: '1.19.18', + destructuring: true, + exportName: 'Form', + subName: 'Item', + }, + NumberPicker: { + package: '@alifd/next', + version: '1.19.18', + destructuring: true, + exportName: 'NumberPicker', + }, + Select: { + package: '@alifd/next', + version: '1.19.18', + destructuring: true, + exportName: 'Select', + }, + 'Select.Option': { + package: '@alifd/next', + version: '1.19.18', + destructuring: true, + exportName: 'Select', + subName: 'Option', + }, +}; diff --git a/packages/demo/src/config/utils.js b/packages/demo/src/app/config/utils.js similarity index 100% rename from packages/demo/src/config/utils.js rename to packages/demo/src/app/config/utils.js diff --git a/packages/demo/src/app/index.ts b/packages/demo/src/app/index.ts new file mode 100644 index 000000000..2cfc99a6b --- /dev/null +++ b/packages/demo/src/app/index.ts @@ -0,0 +1,22 @@ +import { boot, run } from '@ali/lowcode-runtime'; +import Renderer from '@ali/lowcode-react-renderer'; +import FusionLoading from './plugins/loading/fusion'; +import BasicLayout from './layouts/BasicLayout'; +import provider from './plugins/provider'; + +// 注册渲染模块 +boot.registerRenderer(Renderer); + +// 注册布局组件,可注册多个 +boot.registerLayout('BasicLayout', BasicLayout); + +// 注册页面 Loading +boot.registerLoading(FusionLoading); + +const appProvider = provider.create('lowcode_demo'); // 入参为应用唯一标识 + +// 异步加载应用配置 +appProvider.then(({ App, config }) => { + // 启动应用 + run(App, config); +}); diff --git a/packages/demo/src/app/layouts/BasicLayout/index.js b/packages/demo/src/app/layouts/BasicLayout/index.js new file mode 100644 index 000000000..6a51fdc75 --- /dev/null +++ b/packages/demo/src/app/layouts/BasicLayout/index.js @@ -0,0 +1,27 @@ +import { Search, Icon, Shell } from '@alifd/next'; +import './index.less'; + +// eslint-disable-next-line react/prop-types +export default ({ name, children, logo }) => ( + + + logo + {name} + + + + + + + 用户头像 + MyName + + + {children} + + + Alibaba Fusion + @ 2019 Alibaba Piecework 版权所有 + + +); diff --git a/packages/demo/src/app/layouts/BasicLayout/index.less b/packages/demo/src/app/layouts/BasicLayout/index.less new file mode 100644 index 000000000..000abd1c8 --- /dev/null +++ b/packages/demo/src/app/layouts/BasicLayout/index.less @@ -0,0 +1,24 @@ +@header-height: 52px; + +.avatar { + width: 24px; + height: 24px; + border-radius: 50%; + vertical-align: middle; +} + +.basic-shell { + min-height: 100vh; + .next-shell-header { + height: @header-height; + } + .next-shell-main { + flex: 1; + display: flex; + flex-flow: column; + min-height: calc(100% - @header-height); + .next-shell-sub-main { + flex: 1; + } + } +} diff --git a/packages/demo/src/app/plugins/loading/deep/index.less b/packages/demo/src/app/plugins/loading/deep/index.less new file mode 100644 index 000000000..2b59ea5ef --- /dev/null +++ b/packages/demo/src/app/plugins/loading/deep/index.less @@ -0,0 +1,11 @@ +.recore-loading { + width: 48px; + height: 48px; + background: url(https://g.alicdn.com/uxcore/pic/loading.svg) center no-repeat; + background-size: contain; + position: fixed; + top: 50%; + left: 50%; + margin-top: -24px; + margin-left: -24px; +} diff --git a/packages/demo/src/app/plugins/loading/deep/index.tsx b/packages/demo/src/app/plugins/loading/deep/index.tsx new file mode 100644 index 000000000..02b2fc692 --- /dev/null +++ b/packages/demo/src/app/plugins/loading/deep/index.tsx @@ -0,0 +1,3 @@ +import './index.less'; + +export default () =>
; diff --git a/packages/demo/src/app/plugins/loading/fusion/index.less b/packages/demo/src/app/plugins/loading/fusion/index.less new file mode 100644 index 000000000..0e53165cb --- /dev/null +++ b/packages/demo/src/app/plugins/loading/fusion/index.less @@ -0,0 +1,9 @@ +.fusion-loading { + width: 48px; + height: 48px; + position: fixed; + top: 50%; + left: 50%; + margin-top: -24px; + margin-left: -24px; +} diff --git a/packages/demo/src/app/plugins/loading/fusion/index.tsx b/packages/demo/src/app/plugins/loading/fusion/index.tsx new file mode 100644 index 000000000..e61bfd9f0 --- /dev/null +++ b/packages/demo/src/app/plugins/loading/fusion/index.tsx @@ -0,0 +1,4 @@ +import { Loading } from '@alifd/next'; +import './index.less'; + +export default () => ; diff --git a/packages/demo/src/app/plugins/provider.ts b/packages/demo/src/app/plugins/provider.ts new file mode 100644 index 000000000..fc56ece03 --- /dev/null +++ b/packages/demo/src/app/plugins/provider.ts @@ -0,0 +1,114 @@ +import { createElement } from 'react'; +import { Provider, boot, Router } from '@ali/lowcode-runtime'; +import appConfig from '../config/app'; +import builtInComps from '../config/components'; +import componentsMap from '../config/componentsMap'; +import util from '../config/utils'; +import { buildComponents } from './utils'; + +// 定制加载应用配置的逻辑 +class PreviewProvider extends Provider { + // 定制获取、处理应用配置(组件、插件、路由模式、布局等)的逻辑 + async getAppData(appkey: string, restOptions?: any): Promise { + const { historyMode, layout, constainerId } = appConfig; + const appSchemaStr: any = localStorage.getItem('lce-dev-store'); + const appSchema = JSON.parse(appSchemaStr || ''); + const history = { + mode: historyMode || 'hash', + basement: '/', + }; + this.layout = layout; + const routes: any = {}; + appSchema.componentsTree.forEach((page: any, idx: number) => { + if (!page.fileName) { + return; + } + const pageId = page.fileName; + routes[pageId] = `/${pageId}`; + }); + this.routerConfig = routes; + this.componentsMap = componentsMap; + this.globalComponents = { ...builtInComps, ...buildComponents({ '@alifd/next': 'Next' }, componentsMap) }; + this.globalUtils = util; + return { + history, + globalComponents: this.globalComponents, + globalUtils: this.globalUtils, + constainerId, + }; + } + + // 定制获取、处理页面 schema 的逻辑 + async getPageData(pageId: string, restOptions?: any) { + const appSchemaStr = localStorage.getItem('lce-dev-store'); + const appSchema = JSON.parse(appSchemaStr || ''); + const idx = appSchema.componentsTree.findIndex( + (page: any, idx: number) => (page.fileName || `page${idx}`) === pageId, + ); + const schema = appSchema.componentsTree[idx]; + return schema; + } + + // 定制构造根组件的逻辑,如切换路由机制 + createApp() { + if (!this.routerConfig) { + return; + } + const routes: Array<{ path: string; children: any; exact: boolean; keepAlive: boolean }> = []; + let homePageId = ''; + Object.keys(this.routerConfig).forEach((pageId: string, idx: number) => { + if (!pageId) { + return; + } + const path = this.routerConfig[pageId]; + if (idx === 0 || path === '/') { + homePageId = pageId; + } + routes.push({ + path, + children: (props: any) => this.getLazyComponent(pageId, props), + exact: true, + keepAlive: true, + }); + }); + if (homePageId) { + routes.push({ + path: '**', + children: (props: any) => this.getLazyComponent(homePageId, { ...props }), + exact: true, + keepAlive: true, + }); + } + const RouterView = (props: any) => { + return createElement(Router as any, { + routes, + components: this.globalComponents, + utils: this.globalUtils, + componentsMap: this.componentsMap, + ...props, + }); + }; + let App; + if (!this.layout || !(this.layout as any).componentName) { + App = (props: any) => createElement(RouterView, { ...props }); + return App; + } + const { componentName: layoutName, props: layoutProps } = this.layout as any; + const Layout = boot.getLayout(layoutName); + if (Layout) { + App = (props: any) => + createElement( + Layout, + { + ...layoutProps, + }, + RouterView({ props }), + ); + } else { + App = (props: any) => createElement(RouterView, props); + } + return App; + } +} + +export default new PreviewProvider(); diff --git a/packages/demo/src/app/plugins/utils.js b/packages/demo/src/app/plugins/utils.js new file mode 100644 index 000000000..b65d4864e --- /dev/null +++ b/packages/demo/src/app/plugins/utils.js @@ -0,0 +1,74 @@ +function isESModule(obj) { + return obj && obj.__esModule; +} + +function getSubComponent(library, paths) { + const l = paths.length; + if (l < 1 || !library) { + return library; + } + let i = 0; + let component; + while (i < l) { + const key = paths[i]; + let ex; + try { + component = library[key]; + } catch (e) { + ex = e; + component = null; + } + if (i === 0 && component == null && key === 'default') { + if (ex) { + return l === 1 ? library : null; + } + component = library; + } else if (component == null) { + return null; + } + library = component; + i++; + } + return component; +} + +function accessLibrary(library) { + if (typeof library !== 'string') { + return library; + } + + return window[library]; +} + +function findComponent(libraryMap, componentName, npm) { + if (!npm) { + return accessLibrary(componentName); + } + // libraryName the key access to global + // export { exportName } from xxx exportName === global.libraryName.exportName + // export exportName from xxx exportName === global.libraryName.default || global.libraryName + // export { exportName as componentName } from package + // if exportName == null exportName === componentName; + // const componentName = exportName.subName, if exportName empty subName donot use + const exportName = npm.exportName || npm.componentName || componentName; + const libraryName = libraryMap[npm.package] || exportName; + const library = accessLibrary(libraryName); + const paths = npm.exportName && npm.subName ? npm.subName.split('.') : []; + if (npm.destructuring) { + paths.unshift(exportName); + } else if (isESModule(library)) { + paths.unshift('default'); + } + return getSubComponent(library, paths); +} + +export function buildComponents(libraryMap, componentsMap) { + const components = {}; + Object.keys(componentsMap).forEach((componentName) => { + const component = findComponent(libraryMap, componentName, componentsMap[componentName]); + if (component) { + components[componentName] = component; + } + }); + return components; +} diff --git a/packages/demo/src/config/components.js b/packages/demo/src/editor/config/components.js similarity index 100% rename from packages/demo/src/config/components.js rename to packages/demo/src/editor/config/components.js diff --git a/packages/demo/src/config/constants.js b/packages/demo/src/editor/config/constants.js similarity index 100% rename from packages/demo/src/config/constants.js rename to packages/demo/src/editor/config/constants.js diff --git a/packages/demo/src/config/skeleton.js b/packages/demo/src/editor/config/skeleton.js similarity index 100% rename from packages/demo/src/config/skeleton.js rename to packages/demo/src/editor/config/skeleton.js diff --git a/packages/demo/src/config/theme.scss b/packages/demo/src/editor/config/theme.scss similarity index 100% rename from packages/demo/src/config/theme.scss rename to packages/demo/src/editor/config/theme.scss diff --git a/packages/demo/src/editor/config/utils.js b/packages/demo/src/editor/config/utils.js new file mode 100644 index 000000000..ff8b4c563 --- /dev/null +++ b/packages/demo/src/editor/config/utils.js @@ -0,0 +1 @@ +export default {}; diff --git a/packages/demo/src/global.scss b/packages/demo/src/editor/global.scss similarity index 100% rename from packages/demo/src/global.scss rename to packages/demo/src/editor/global.scss diff --git a/packages/demo/src/index.jsx b/packages/demo/src/editor/index.tsx similarity index 96% rename from packages/demo/src/index.jsx rename to packages/demo/src/editor/index.tsx index f666fe2c5..b229bec9b 100644 --- a/packages/demo/src/index.jsx +++ b/packages/demo/src/editor/index.tsx @@ -13,6 +13,7 @@ registerSetters(); const LCE_CONTAINER = document.getElementById('lce-container'); +console.info('aeafeawef') if (!LCE_CONTAINER) { throw new Error('当前页面不存在
节点.'); } diff --git a/packages/demo/src/index.ts b/packages/demo/src/index.ts new file mode 100644 index 000000000..27a6639b2 --- /dev/null +++ b/packages/demo/src/index.ts @@ -0,0 +1 @@ +import "./editor" diff --git a/packages/demo/src/preview.js b/packages/demo/src/preview.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/demo/src/preview.ts b/packages/demo/src/preview.ts new file mode 100644 index 000000000..168ff510c --- /dev/null +++ b/packages/demo/src/preview.ts @@ -0,0 +1 @@ +import "./app"; diff --git a/packages/react-simulator-renderer/src/index.js b/packages/react-simulator-renderer/src/index.ts similarity index 67% rename from packages/react-simulator-renderer/src/index.js rename to packages/react-simulator-renderer/src/index.ts index 05be5384a..3a8872665 100644 --- a/packages/react-simulator-renderer/src/index.js +++ b/packages/react-simulator-renderer/src/index.ts @@ -1,7 +1,7 @@ import renderer from './renderer'; if (typeof window !== 'undefined') { - window.SimulatorRenderer = renderer; + (window as any).SimulatorRenderer = renderer; } export default renderer; diff --git a/packages/runtime/index.d.ts b/packages/runtime/index.d.ts index 5988aa400..6e3013290 100644 --- a/packages/runtime/index.d.ts +++ b/packages/runtime/index.d.ts @@ -1,25 +1,19 @@ import { ReactType } from 'react'; +type HistoryMode = 'browser' | 'hash'; -export as namespace LowCodeEngineRuntime; -export = LowCodeEngineRuntime; +interface ComponentsMap { + [key: string]: ReactType; +} -declare module LowCodeEngineRuntime { - type HistoryMode = 'browser' | 'hash'; +interface UtilsMap { + [key: string]: any; +} - interface ComponentsMap { - [key: string]: ReactType; - } +export interface AppConfig { + history?: HistoryMode; + globalComponents?: ComponentsMap; + globalUtils?: UtilsMap; + containerId?: string; +} - interface UtilsMap { - [key: string]: any; - } - - interface AppConfig { - history?: HistoryMode; - globalComponents?: ComponentsMap; - globalUtils?: UtilsMap; - containerId?: string; - } - - function runApp(Component: any, config?: AppConfig | (() => AppConfig), exposeModule?: boolean): any; -} \ No newline at end of file +export function run(Component: any, config?: AppConfig | (() => AppConfig)): any; diff --git a/packages/runtime/src/boot.ts b/packages/runtime/src/boot.ts index 2598b18fe..6b99987e1 100644 --- a/packages/runtime/src/boot.ts +++ b/packages/runtime/src/boot.ts @@ -7,36 +7,36 @@ class Trunk { private layouts: { [key: string]: TComponent } = {}; private loading: TComponent | null = null; - public registerRenderer(renderer: TComponent): any { + registerRenderer(renderer: TComponent): any { this.renderer = renderer; } - public registerLayout(componentName: string, Layout: TComponent): any { + registerLayout(componentName: string, Layout: TComponent): any { if (!componentName || !Layout) { return; } this.layouts[componentName] = Layout; } - public registerLoading(component: TComponent) { + registerLoading(component: TComponent) { if (!component) { return; } this.loading = component; } - public getLayout(componentName: string) { + getLayout(componentName: string) { if (!componentName) { return; } return this.layouts[componentName]; } - public getRenderer(): TComponent | null { + getRenderer(): TComponent | null { return this.renderer; } - public getLoading(): TComponent | null { + getLoading(): TComponent | null { return this.loading; } } diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index 37abfe968..dc637c4be 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -1,5 +1,5 @@ import { navigator, Router, runApp as run } from '@ali/recore'; -import Boot from './boot'; +import boot from './boot'; import Provider from './provider'; -export { run, Router, Boot, Provider, navigator }; +export { run, Router, boot, Provider, navigator }; diff --git a/packages/runtime/src/lazyComponent.tsx b/packages/runtime/src/lazyComponent.tsx index 9acd799ce..18a582241 100644 --- a/packages/runtime/src/lazyComponent.tsx +++ b/packages/runtime/src/lazyComponent.tsx @@ -1,5 +1,5 @@ import { Component, createElement } from 'react'; -import Boot from './boot'; +import boot from './boot'; interface IProps { getPageData: () => any; @@ -18,7 +18,7 @@ export default class LazyComponent extends Component { }; } - public async componentDidMount() { + async componentDidMount() { const { getPageData } = this.props; if (getPageData && !this.state.schema) { const schema = await getPageData(); @@ -26,11 +26,11 @@ export default class LazyComponent extends Component { } } - public render() { + render() { const { getPageData, ...restProps } = this.props; const { schema } = this.state; - const Renderer = Boot.getRenderer(); - const Loading = Boot.getLoading(); + const Renderer = boot.getRenderer(); + const Loading = boot.getLoading(); if (!Renderer || !schema) { if (!Loading) { return null; diff --git a/packages/runtime/src/provider.ts b/packages/runtime/src/provider.ts index b0535d89d..9949e10c3 100644 --- a/packages/runtime/src/provider.ts +++ b/packages/runtime/src/provider.ts @@ -23,18 +23,19 @@ export interface IAppConfig { containerId?: string; } -export default abstract class Provider { - public globalComponents: any = {}; - public globalUtils: any = {}; - public routerConfig: { [key: string]: string } = {}; - public layout: { componentName: string; props: any } | null = null; +export default class Provider { + globalComponents: any = {}; + globalUtils: any = {}; + routerConfig: { [key: string]: string } = {}; + layout: { componentName: string; props: any } | null = null; + componentsMap: any = null; private lazyElementsMap: { [key: string]: any } = {}; constructor() { this.init(); } - public create(appkey: string): Promise { + create(appkey: string): Promise { return new Promise(async (resolve, reject) => { try { const config = await this.getAppData(appkey); @@ -49,21 +50,21 @@ export default abstract class Provider { }); } - public async init() { + async init() { console.log('init'); } - public async getAppData(appkey: string, restOptions?: any): Promise { + async getAppData(appkey: string, restOptions?: any): Promise { console.log('getAppData'); return {}; } - public async getPageData(pageId: string, restOptions?: any): Promise { + async getPageData(pageId: string, restOptions?: any): Promise { console.log('getPageData'); return; } - public getLazyComponent(pageId: string, props: any): ReactElement | null { + getLazyComponent(pageId: string, props: any): ReactElement | null { if (!pageId) { return null; } @@ -82,7 +83,7 @@ export default abstract class Provider { } } - public createApp() { + createApp() { console.log('createApp'); return; }