From 05981a9ed82e2c2a5ad5e4e2415e1d4d350b9cc5 Mon Sep 17 00:00:00 2001 From: 1ncounter <1ncounter.100@gmail.com> Date: Mon, 1 Jul 2024 19:02:50 +0800 Subject: [PATCH] fix: some bugs fix --- packages/react-renderer/src/api/app.tsx | 3 +- packages/react-renderer/src/api/component.tsx | 19 +- packages/react-renderer/src/api/context.ts | 9 +- packages/react-renderer/src/api/types.ts | 11 + packages/react-renderer/src/app/view.tsx | 6 +- packages/react-renderer/src/index.ts | 8 +- packages/react-renderer/src/router/route.tsx | 7 +- .../react-renderer/src/runtime/context.ts | 11 - .../{schema.tsx => createComponent.tsx} | 61 +-- .../runtime/{components.tsx => elements.tsx} | 208 +++++---- packages/react-renderer/src/runtime/index.ts | 4 +- .../src/services/code-runtime/codeRuntime.ts | 128 ++++++ .../code-runtime/codeRuntimeService.ts | 127 +----- .../src/services/code-runtime/codeScope.ts | 41 +- .../src/services/code-runtime/evaluate.ts | 7 + .../src/services/code-runtime/index.ts | 1 + .../src/services/extension/boosts.ts | 12 +- .../src/services/lifeCycleService.ts | 28 +- .../src/services/model/componentTreeModel.ts | 73 ++- .../model/componentTreeModelService.ts | 36 +- .../src/services/runtimeIntlService.ts | 6 +- .../src/services/runtimeUtilService.ts | 10 +- .../src/services/widget/widget.ts | 10 +- packages/renderer-core/src/types.ts | 8 +- packages/renderer-core/src/utils/evaluate.ts | 5 - packages/shared/src/types/specs/runtime.ts | 3 +- playground/{ => engine}/package.json | 9 +- playground/engine/tsconfig.json | 6 + playground/engine/vite.config.ts | 14 + playground/renderer/index.html | 37 +- playground/renderer/package.json | 23 + playground/renderer/src/apis/index.ts | 1 + playground/renderer/src/apis/runtime.ts | 40 ++ playground/renderer/src/data.ts | 425 ++++++++++++++++++ playground/renderer/src/index.ts | 105 ++++- .../renderer/src/plugin/remote/element.ts | 158 +++++++ .../renderer/src/plugin/remote/index.ts | 47 ++ .../renderer/src/plugin/uipaas/index.ts | 95 ++++ .../src/plugin/uipaas/vuDataSource.ts | 111 +++++ .../renderer/src/plugin/uipaas/vuRouter.ts | 34 ++ playground/renderer/src/utils/axios/index.ts | 18 + .../renderer/src/utils/axios/jsonpAdapter.ts | 55 +++ playground/renderer/src/utils/index.ts | 3 + playground/renderer/src/utils/parseDeps.ts | 105 +++++ .../renderer/src/utils/parseGlogalConfig.ts | 20 + playground/renderer/src/utils/parseSchema.ts | 35 ++ playground/renderer/tsconfig.json | 6 + playground/renderer/vite.config.ts | 14 + playground/test/index.html | 16 - playground/test/package.json | 9 + playground/test/src/index.ts | 45 +- playground/tsconfig.json | 6 - playground/vite.config.ts | 23 - 53 files changed, 1828 insertions(+), 474 deletions(-) create mode 100644 packages/react-renderer/src/api/types.ts delete mode 100644 packages/react-renderer/src/runtime/context.ts rename packages/react-renderer/src/runtime/{schema.tsx => createComponent.tsx} (70%) rename packages/react-renderer/src/runtime/{components.tsx => elements.tsx} (53%) create mode 100644 packages/renderer-core/src/services/code-runtime/codeRuntime.ts create mode 100644 packages/renderer-core/src/services/code-runtime/evaluate.ts delete mode 100644 packages/renderer-core/src/utils/evaluate.ts rename playground/{ => engine}/package.json (71%) create mode 100644 playground/engine/tsconfig.json create mode 100644 playground/engine/vite.config.ts create mode 100644 playground/renderer/package.json create mode 100644 playground/renderer/src/apis/index.ts create mode 100644 playground/renderer/src/apis/runtime.ts create mode 100644 playground/renderer/src/data.ts create mode 100644 playground/renderer/src/plugin/remote/element.ts create mode 100644 playground/renderer/src/plugin/remote/index.ts create mode 100644 playground/renderer/src/plugin/uipaas/index.ts create mode 100644 playground/renderer/src/plugin/uipaas/vuDataSource.ts create mode 100644 playground/renderer/src/plugin/uipaas/vuRouter.ts create mode 100644 playground/renderer/src/utils/axios/index.ts create mode 100644 playground/renderer/src/utils/axios/jsonpAdapter.ts create mode 100644 playground/renderer/src/utils/index.ts create mode 100644 playground/renderer/src/utils/parseDeps.ts create mode 100644 playground/renderer/src/utils/parseGlogalConfig.ts create mode 100644 playground/renderer/src/utils/parseSchema.ts create mode 100644 playground/renderer/tsconfig.json create mode 100644 playground/renderer/vite.config.ts create mode 100644 playground/test/package.json delete mode 100644 playground/tsconfig.json delete mode 100644 playground/vite.config.ts diff --git a/packages/react-renderer/src/api/app.tsx b/packages/react-renderer/src/api/app.tsx index 0e1e112f0..a5c9007ac 100644 --- a/packages/react-renderer/src/api/app.tsx +++ b/packages/react-renderer/src/api/app.tsx @@ -1,7 +1,8 @@ import { createRenderer } from '@alilc/lowcode-renderer-core'; import { type Root, createRoot } from 'react-dom/client'; -import { type ReactAppOptions, RendererContext } from './context'; +import { RendererContext } from './context'; import { ApplicationView, boosts } from '../app'; +import { type ReactAppOptions } from './types'; export const createApp = async (options: ReactAppOptions) => { return createRenderer(async (context) => { diff --git a/packages/react-renderer/src/api/component.tsx b/packages/react-renderer/src/api/component.tsx index f829c2901..ca20cbd24 100644 --- a/packages/react-renderer/src/api/component.tsx +++ b/packages/react-renderer/src/api/component.tsx @@ -1,17 +1,26 @@ -import { createRenderer, type AppOptions } from '@alilc/lowcode-renderer-core'; +import { createRenderer } from '@alilc/lowcode-renderer-core'; import { FunctionComponent } from 'react'; -import { type LowCodeComponentProps, createComponentBySchema } from '../runtime/schema'; -import { RendererContext } from '../api/context'; +import { + type LowCodeComponentProps, + createComponent as createSchemaComponent, +} from '../runtime/createComponent'; +import { RendererContext } from './context'; +import { type ReactAppOptions } from './types'; interface Render { toComponent(): FunctionComponent; } -export async function createComponent(options: AppOptions) { +export async function createComponent(options: ReactAppOptions) { const creator = createRenderer((context) => { const { schema } = context; + const componentsTree = schema.get('componentsTree')[0]; + + const LowCodeComponent = createSchemaComponent(componentsTree, { + displayName: componentsTree.componentName, + ...options.component, + }); - const LowCodeComponent = createComponentBySchema(schema.get('componentsTree')[0]); const contextValue = { ...context, options }; function Component(props: LowCodeComponentProps) { diff --git a/packages/react-renderer/src/api/context.ts b/packages/react-renderer/src/api/context.ts index ccd666ee7..cfc9c2de9 100644 --- a/packages/react-renderer/src/api/context.ts +++ b/packages/react-renderer/src/api/context.ts @@ -1,9 +1,6 @@ -import { type ComponentType, createContext, useContext } from 'react'; -import { type AppOptions, type RenderContext } from '@alilc/lowcode-renderer-core'; - -export interface ReactAppOptions extends AppOptions { - faultComponent?: ComponentType; -} +import { createContext, useContext } from 'react'; +import { type RenderContext } from '@alilc/lowcode-renderer-core'; +import { type ReactAppOptions } from './types'; export const RendererContext = createContext( undefined!, diff --git a/packages/react-renderer/src/api/types.ts b/packages/react-renderer/src/api/types.ts new file mode 100644 index 000000000..2c73be30f --- /dev/null +++ b/packages/react-renderer/src/api/types.ts @@ -0,0 +1,11 @@ +import { type AppOptions } from '@alilc/lowcode-renderer-core'; +import { type ComponentType } from 'react'; +import { type ComponentOptions } from '../runtime/createComponent'; + +export interface ReactAppOptions extends AppOptions { + component?: Pick< + ComponentOptions, + 'beforeElementCreate' | 'elementCreated' | 'componentRefAttached' + >; + faultComponent?: ComponentType; +} diff --git a/packages/react-renderer/src/app/view.tsx b/packages/react-renderer/src/app/view.tsx index 687101fd4..e8731b3c8 100644 --- a/packages/react-renderer/src/app/view.tsx +++ b/packages/react-renderer/src/app/view.tsx @@ -1,10 +1,10 @@ import { useRendererContext } from '../api/context'; -import { getComponentByName } from '../runtime/schema'; +import { getComponentByName } from '../runtime/createComponent'; import { boosts } from './boosts'; export function ApplicationView() { const rendererContext = useRendererContext(); - const { schema } = rendererContext; + const { schema, options } = rendererContext; const appWrappers = boosts.getAppWrappers(); const Outlet = boosts.getOutlet(); @@ -16,7 +16,7 @@ export function ApplicationView() { if (layoutConfig) { const componentName = layoutConfig.componentName; - const Layout = getComponentByName(componentName, rendererContext); + const Layout = getComponentByName(componentName, rendererContext, options.component); if (Layout) { const layoutProps: any = layoutConfig.props ?? {}; diff --git a/packages/react-renderer/src/index.ts b/packages/react-renderer/src/index.ts index fda978f64..04f2f6bf3 100644 --- a/packages/react-renderer/src/index.ts +++ b/packages/react-renderer/src/index.ts @@ -6,5 +6,11 @@ export * from './router'; export { LifecyclePhase } from '@alilc/lowcode-renderer-core'; export type { Spec, ProCodeComponent, LowCodeComponent } from '@alilc/lowcode-shared'; -export type { PackageLoader, CodeScope, Plugin } from '@alilc/lowcode-renderer-core'; +export type { + PackageLoader, + CodeScope, + Plugin, + ModelDataSourceCreator, + ModelStateCreator, +} from '@alilc/lowcode-renderer-core'; export type { ReactRendererBoostsApi } from './app/boosts'; diff --git a/packages/react-renderer/src/router/route.tsx b/packages/react-renderer/src/router/route.tsx index 615008aef..abf345479 100644 --- a/packages/react-renderer/src/router/route.tsx +++ b/packages/react-renderer/src/router/route.tsx @@ -2,12 +2,12 @@ import { useMemo } from 'react'; import { useRendererContext } from '../api/context'; import { OutletProps } from '../app/boosts'; import { useRouteLocation } from './context'; -import { createComponentBySchema } from '../runtime/schema'; +import { createComponent } from '../runtime/createComponent'; export function RouteOutlet(props: OutletProps) { const context = useRendererContext(); const location = useRouteLocation(); - const { schema, packageManager } = context; + const { schema, packageManager, options } = context; const pageConfig = useMemo(() => { const pages = schema.get('pages') ?? []; @@ -27,11 +27,12 @@ export function RouteOutlet(props: OutletProps) { const componentsMap = schema.get('componentsMap'); packageManager.resolveComponentMaps(componentsMap); - const LowCodeComponent = createComponentBySchema(pageConfig.mappingId, { + const LowCodeComponent = createComponent(pageConfig.mappingId, { displayName: pageConfig.id, modelOptions: { metadata: pageConfig, }, + ...options.component, }); return ; diff --git a/packages/react-renderer/src/runtime/context.ts b/packages/react-renderer/src/runtime/context.ts deleted file mode 100644 index ecdc79365..000000000 --- a/packages/react-renderer/src/runtime/context.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { IComponentTreeModel } from '@alilc/lowcode-renderer-core'; -import { createContext, useContext, type ReactInstance } from 'react'; -import { type ReactComponent } from './components'; - -export const ModelContext = createContext>( - undefined!, -); - -export const useModel = () => useContext(ModelContext); - -export const ModelContextProvider = ModelContext.Provider; diff --git a/packages/react-renderer/src/runtime/schema.tsx b/packages/react-renderer/src/runtime/createComponent.tsx similarity index 70% rename from packages/react-renderer/src/runtime/schema.tsx rename to packages/react-renderer/src/runtime/createComponent.tsx index 336c11d27..06ba5dfa3 100644 --- a/packages/react-renderer/src/runtime/schema.tsx +++ b/packages/react-renderer/src/runtime/createComponent.tsx @@ -3,23 +3,23 @@ import { forwardRef, useRef, useEffect } from 'react'; import { isValidElementType } from 'react-is'; import { useRendererContext } from '../api/context'; import { reactiveStateFactory } from './reactiveState'; -import { type ReactComponent, type ReactWidget, createElementByWidget } from './components'; -import { ModelContextProvider } from './context'; +import { type ReactComponent, type ReactWidget, createElementByWidget } from './elements'; import { appendExternalStyle } from '../utils/element'; import type { RenderContext, IComponentTreeModel, - ComponentTreeModelOptions, + CreateComponentTreeModelOptions, } from '@alilc/lowcode-renderer-core'; -import type { ReactInstance, CSSProperties, ForwardedRef } from 'react'; +import type { ReactInstance, CSSProperties, ForwardedRef, ReactNode } from 'react'; export interface ComponentOptions { displayName?: string; - modelOptions?: ComponentTreeModelOptions; + modelOptions?: Pick; - widgetCreated?(widget: ReactWidget): void; - componentRefAttached?(widget: ReactWidget, instance: ReactInstance): void; + beforeElementCreate?(widget: ReactWidget): ReactWidget; + elementCreated?(widget: ReactWidget, element: ReactNode): ReactNode; + componentRefAttached?(widget: ReactWidget, instance: ReactInstance | null): void; } export interface LowCodeComponentProps { @@ -37,6 +37,7 @@ const lowCodeComponentsCache = new Map(); export function getComponentByName( name: string, { packageManager, boostsManager }: RenderContext, + componentOptions: ComponentOptions = {}, ): ReactComponent { const result = lowCodeComponentsCache.get(name) || packageManager.getComponent(name); @@ -58,7 +59,8 @@ export function getComponentByName( }); } - const lowCodeComponent = createComponentBySchema(componentsTree[0], { + const lowCodeComponent = createComponent(componentsTree[0], { + ...componentOptions, displayName: name, modelOptions: { id: metadata.id, @@ -76,40 +78,49 @@ export function getComponentByName( return result; } -export function createComponentBySchema( +export function createComponent( schema: string | Spec.ComponentTreeRoot, - { displayName = '__LowCodeComponent__', modelOptions }: ComponentOptions = {}, + componentOptions: ComponentOptions = {}, ) { + const { displayName = '__LowCodeComponent__', modelOptions } = componentOptions; + const LowCodeComponent = forwardRef(function ( props: LowCodeComponentProps, ref: ForwardedRef, ) { - const renderContext = useRendererContext(); - const { options, componentTreeModel } = renderContext; + const context = useRendererContext(); + const { options: globalOptions, componentTreeModel } = context; const modelRef = useRef>(); if (!modelRef.current) { + const finalOptions: CreateComponentTreeModelOptions = { + ...modelOptions, + codeScopeValue: { + props, + }, + stateCreator: reactiveStateFactory, + dataSourceCreator: globalOptions.dataSourceCreator, + }; + if (typeof schema === 'string') { - modelRef.current = componentTreeModel.createById(schema, modelOptions); + modelRef.current = componentTreeModel.createById(schema, finalOptions); } else { - modelRef.current = componentTreeModel.create(schema, modelOptions); + modelRef.current = componentTreeModel.create(schema, finalOptions); } + console.log( + '%c [ model ]-103', + 'font-size:13px; background:pink; color:#bf2c9f;', + modelRef.current, + ); } const model = modelRef.current!; - console.log('%c [ model ]-103', 'font-size:13px; background:pink; color:#bf2c9f;', model); const isConstructed = useRef(false); const isMounted = useRef(false); if (!isConstructed.current) { - model.initialize({ - defaultProps: props, - stateCreator: reactiveStateFactory, - dataSourceCreator: options.dataSourceCreator, - }); - model.triggerLifeCycle('constructor'); isConstructed.current = true; @@ -142,11 +153,9 @@ export function createComponentBySchema( }, []); return ( - -
- {model.widgets.map((w) => createElementByWidget(w, model.codeScope))} -
-
+
+ {model.widgets.map((w) => createElementByWidget(w, w.model.codeRuntime, componentOptions))} +
); }); diff --git a/packages/react-renderer/src/runtime/components.tsx b/packages/react-renderer/src/runtime/elements.tsx similarity index 53% rename from packages/react-renderer/src/runtime/components.tsx rename to packages/react-renderer/src/runtime/elements.tsx index 5aa45d610..702be02e8 100644 --- a/packages/react-renderer/src/runtime/components.tsx +++ b/packages/react-renderer/src/runtime/elements.tsx @@ -1,6 +1,6 @@ import { type IWidget, - type ICodeScope, + type ICodeRuntime, type NormalizedComponentNode, mapValue, } from '@alilc/lowcode-renderer-core'; @@ -15,70 +15,139 @@ import { import { type ComponentType, type ReactInstance, useMemo, createElement } from 'react'; import { useRendererContext } from '../api/context'; import { useReactiveStore } from './hooks/useReactiveStore'; -import { useModel } from './context'; -import { getComponentByName } from './schema'; +import { getComponentByName, type ComponentOptions } from './createComponent'; export type ReactComponent = ComponentType; export type ReactWidget = IWidget; interface WidgetRendererProps { widget: ReactWidget; - codeScope: ICodeScope; + codeRuntime: ICodeRuntime; + options: ComponentOptions; [key: string]: any; } export function createElementByWidget( - widget: IWidget, - codeScope: ICodeScope, + widget: ReactWidget, + codeRuntime: ICodeRuntime, + options: ComponentOptions, ) { - const { key, node } = widget; + const getElement = (widget: ReactWidget) => { + const { key, rawNode } = widget; - if (typeof node === 'string') { - return node; - } + if (typeof rawNode === 'string') { + return rawNode; + } - if (isJSExpression(node)) { - return ; - } + if (isJSExpression(rawNode)) { + return ; + } - if (isJSI18nNode(node)) { - return ; - } + if (isJSI18nNode(rawNode)) { + return ; + } + + const { condition, loop } = widget.rawNode as NormalizedComponentNode; + + // condition为 Falsy 的情况下 不渲染 + if (!condition) return null; + // loop 为数组且为空的情况下 不渲染 + if (Array.isArray(loop) && loop.length === 0) return null; + + if (isJSExpression(loop)) { + return ( + + ); + } - const { condition, loop } = widget.node as NormalizedComponentNode; + return ( + + ); + }; + + if (options.beforeElementCreate) { + widget = options.beforeElementCreate(widget); + } - // condition为 Falsy 的情况下 不渲染 - if (!condition) return null; - // loop 为数组且为空的情况下 不渲染 - if (Array.isArray(loop) && loop.length === 0) return null; + const element = getElement(widget); - if (isJSExpression(loop)) { - return ; + if (options.elementCreated) { + return options.elementCreated(widget, element); } - return ; + return element; } export function WidgetComponent(props: WidgetRendererProps) { - const { widget, codeScope, ...otherProps } = props; - const componentNode = widget.node as NormalizedComponentNode; + const { widget, codeRuntime, options, ...otherProps } = props; + const componentNode = widget.rawNode as NormalizedComponentNode; const { ref, ...componentProps } = componentNode.props; - const rendererContext = useRendererContext(); + const context = useRendererContext(); const Component = useMemo( - () => getComponentByName(componentNode.componentName, rendererContext), + () => getComponentByName(componentNode.componentName, context, options), [widget], ); + // 先将 jsslot, jsFunction 对象转换 + const processedProps = mapValue( + componentProps, + (node) => isJSFunction(node) || isJSSlot(node), + (node: Spec.JSSlot | Spec.JSFunction) => { + if (isJSSlot(node)) { + const slot = node as Spec.JSSlot; + + if (slot.value) { + const widgets = widget.model.buildWidgets( + Array.isArray(node.value) ? node.value : [node.value], + ); + + if (slot.params?.length) { + return (...args: any[]) => { + const params = slot.params!.reduce((prev, cur, idx) => { + return (prev[cur] = args[idx]); + }, {} as PlainObject); + + return widgets.map((n) => + createElementByWidget( + n, + codeRuntime.createChild({ initScopeValue: params }), + options, + ), + ); + }; + } else { + return widgets.map((n) => createElementByWidget(n, codeRuntime, options)); + } + } + } else if (isJSFunction(node)) { + return widget.model.codeRuntime.resolve(node); + } + + return null; + }, + ); + + if (process.env.NODE_ENV === 'development') { + // development 模式下 把 widget 的内容作为 prop ,便于排查问题 + processedProps.widget = widget; + } + const state = useReactiveStore({ target: { condition: componentNode.condition, - props: preprocessProps(componentProps, widget, codeScope), + props: processedProps, }, valueGetter(expr) { - return widget.model.codeRuntime.resolve(expr, { scope: codeScope }); + return codeRuntime.resolve(expr); }, }); @@ -88,6 +157,8 @@ export function WidgetComponent(props: WidgetRendererProps) { } else { if (ref) widget.model.removeComponentRef(ref); } + + options.componentRefAttached?.(widget, ins); }; if (!state.condition) { @@ -107,58 +178,15 @@ export function WidgetComponent(props: WidgetRendererProps) { key: widget.key, ref: attachRef, }, - widget.children?.map((item) => createElementByWidget(item, codeScope)) ?? [], + widget.children?.map((item) => createElementByWidget(item, codeRuntime, options)) ?? [], ); } -function preprocessProps(props: PlainObject, widget: ReactWidget, codeScope: ICodeScope) { - // 先将 jsslot, jsFunction 对象转换 - const finalProps = mapValue( - props, - (node) => isJSFunction(node) || isJSSlot(node), - (node: Spec.JSSlot | Spec.JSFunction) => { - if (isJSSlot(node)) { - const slot = node as Spec.JSSlot; - - if (slot.value) { - const widgets = widget.model.buildWidgets( - Array.isArray(node.value) ? node.value : [node.value], - ); - - if (slot.params?.length) { - return (...args: any[]) => { - const params = slot.params!.reduce((prev, cur, idx) => { - return (prev[cur] = args[idx]); - }, {} as PlainObject); - - return widgets.map((n) => createElementByWidget(n, codeScope.createChild(params))); - }; - } else { - return widgets.map((n) => createElementByWidget(n, codeScope)); - } - } - } else if (isJSFunction(node)) { - return widget.model.codeRuntime.resolve(node, { scope: codeScope }); - } - - return null; - }, - ); - - if (process.env.NODE_ENV === 'development') { - // development 模式下 把 widget 的内容作为 prop ,便于排查问题 - finalProps.widget = widget; - } - - return finalProps; -} - -function Text(props: { expr: Spec.JSExpression; codeScope: ICodeScope }) { - const model = useModel(); +function Text(props: { expr: Spec.JSExpression; codeRuntime: ICodeRuntime }) { const text: string = useReactiveStore({ target: props.expr, getter: (obj) => { - return model.codeRuntime.resolve(obj, { scope: props.codeScope }); + return props.codeRuntime.resolve(obj); }, }); @@ -167,12 +195,11 @@ function Text(props: { expr: Spec.JSExpression; codeScope: ICodeScope }) { Text.displayName = 'Text'; -function I18nText(props: { i18n: Spec.JSI18n; codeScope: ICodeScope }) { - const model = useModel(); +function I18nText(props: { i18n: Spec.JSI18n; codeRuntime: ICodeRuntime }) { const text: string = useReactiveStore({ target: props.i18n, getter: (obj) => { - return model.codeRuntime.resolve(obj, { scope: props.codeScope }); + return props.codeRuntime.resolve(obj); }, }); @@ -184,32 +211,34 @@ I18nText.displayName = 'I18nText'; function LoopWidgetRenderer({ loop, widget, - codeScope, - + codeRuntime, + options, ...otherProps }: { loop: Spec.JSExpression; widget: ReactWidget; - codeScope: ICodeScope; - + codeRuntime: ICodeRuntime; + options: ComponentOptions; [key: string]: any; }) { - const { condition, loopArgs } = widget.node as NormalizedComponentNode; + const { condition, loopArgs } = widget.rawNode as NormalizedComponentNode; const state = useReactiveStore({ target: { loop, condition, }, valueGetter(expr) { - return widget.model.codeRuntime.resolve(expr, { scope: codeScope }); + return codeRuntime.resolve(expr); }, }); if (state.condition && Array.isArray(state.loop) && state.loop.length > 0) { return state.loop.map((item: any, idx: number) => { - const childScope = codeScope.createChild({ - [loopArgs[0]]: item, - [loopArgs[1]]: idx, + const childRuntime = codeRuntime.createChild({ + initScopeValue: { + [loopArgs[0]]: item, + [loopArgs[1]]: idx, + }, }); return ( @@ -217,7 +246,8 @@ function LoopWidgetRenderer({ {...otherProps} key={`loop-${widget.key}-${idx}`} widget={widget} - codeScope={childScope} + codeRuntime={childRuntime} + options={options} /> ); }); diff --git a/packages/react-renderer/src/runtime/index.ts b/packages/react-renderer/src/runtime/index.ts index 334368f1f..c49e6c9b2 100644 --- a/packages/react-renderer/src/runtime/index.ts +++ b/packages/react-renderer/src/runtime/index.ts @@ -1,2 +1,2 @@ -export * from './schema'; -export * from './components'; +export * from './createComponent'; +export * from './elements'; diff --git a/packages/renderer-core/src/services/code-runtime/codeRuntime.ts b/packages/renderer-core/src/services/code-runtime/codeRuntime.ts new file mode 100644 index 000000000..6bc82551f --- /dev/null +++ b/packages/renderer-core/src/services/code-runtime/codeRuntime.ts @@ -0,0 +1,128 @@ +import { + type PlainObject, + type Spec, + type EventDisposable, + isJSExpression, + isJSFunction, +} from '@alilc/lowcode-shared'; +import { type ICodeScope, CodeScope } from './codeScope'; +import { isNode } from '../../utils/node'; +import { mapValue } from '../../utils/value'; +import { evaluate } from './evaluate'; + +export interface CodeRuntimeOptions { + initScopeValue?: Partial; + parentScope?: ICodeScope; + + evalCodeFunction?: EvalCodeFunction; +} + +export interface ICodeRuntime { + getScope(): ICodeScope; + + run(code: string, scope?: ICodeScope): R | undefined; + + resolve(value: PlainObject): any; + + onResolve(handler: NodeResolverHandler): EventDisposable; + + createChild( + options: Omit, 'parentScope'>, + ): ICodeRuntime; +} + +export type NodeResolverHandler = (node: Spec.JSNode) => Spec.JSNode | false | undefined; + +let onResolveHandlers: NodeResolverHandler[] = []; + +export type EvalCodeFunction = (code: string, scope: any) => any; + +export class CodeRuntime implements ICodeRuntime { + private codeScope: ICodeScope; + + private evalCodeFunction: EvalCodeFunction = evaluate; + + constructor(options: CodeRuntimeOptions = {}) { + if (options.evalCodeFunction) this.evalCodeFunction = options.evalCodeFunction; + + if (options.parentScope) { + this.codeScope = options.parentScope.createChild(options.initScopeValue ?? {}); + } else { + this.codeScope = new CodeScope(options.initScopeValue ?? {}); + } + } + + getScope() { + return this.codeScope; + } + + run(code: string): R | undefined { + if (!code) return undefined; + + try { + const result = this.evalCodeFunction(code, this.codeScope.value); + + return result as R; + } catch (err) { + // todo replace logger + console.error('eval error', code, this.codeScope.value, err); + return undefined; + } + } + + resolve(data: PlainObject): any { + if (onResolveHandlers.length > 0) { + data = mapValue(data, isNode, (node: Spec.JSNode) => { + let newNode: Spec.JSNode | false | undefined = node; + + for (const handler of onResolveHandlers) { + newNode = handler(newNode as Spec.JSNode); + if (newNode === false || typeof newNode === 'undefined') { + break; + } + } + + return newNode; + }); + } + + return mapValue( + data, + (data) => { + return isJSExpression(data) || isJSFunction(data); + }, + (node: Spec.JSExpression | Spec.JSFunction) => { + return this.resolveExprOrFunction(node); + }, + ); + } + + private resolveExprOrFunction(node: Spec.JSExpression | Spec.JSFunction) { + const v = this.run(node.value) as any; + + if (typeof v === 'undefined' && node.mock) { + return this.resolve(node.mock); + } + return v; + } + + /** + * 顺序执行 handler + */ + onResolve(handler: NodeResolverHandler): EventDisposable { + onResolveHandlers.push(handler); + return () => { + onResolveHandlers = onResolveHandlers.filter((h) => h !== handler); + }; + } + + createChild( + options?: Omit, 'parentScope'>, + ): ICodeRuntime { + return new CodeRuntime({ + initScopeValue: options?.initScopeValue, + parentScope: this.codeScope, + evalCodeFunction: options?.evalCodeFunction ?? this.evalCodeFunction, + }); + } +} diff --git a/packages/renderer-core/src/services/code-runtime/codeRuntimeService.ts b/packages/renderer-core/src/services/code-runtime/codeRuntimeService.ts index a56485fe4..321542e11 100644 --- a/packages/renderer-core/src/services/code-runtime/codeRuntimeService.ts +++ b/packages/renderer-core/src/services/code-runtime/codeRuntimeService.ts @@ -1,126 +1,33 @@ -import { - type PlainObject, - type Spec, - type EventDisposable, - createDecorator, - Provide, - isJSExpression, - isJSFunction, -} from '@alilc/lowcode-shared'; -import { type ICodeScope, CodeScope } from './codeScope'; -import { evaluate } from '../../utils/evaluate'; -import { isNode } from '../../utils/node'; -import { mapValue } from '../../utils/value'; - -export interface ResolveOptions { - scope?: ICodeScope; -} - -export type NodeResolverHandler = (node: Spec.JSNode) => Spec.JSNode | false | undefined; +import { createDecorator, invariant, Provide, type PlainObject } from '@alilc/lowcode-shared'; +import { type ICodeRuntime, type CodeRuntimeOptions, CodeRuntime } from './codeRuntime'; export interface ICodeRuntimeService { - initialize(options: CodeRuntimeInitializeOptions): void; - - getScope(): ICodeScope; + readonly rootRuntime: ICodeRuntime; - run(code: string, scope?: ICodeScope): R | undefined; + initialize(options: CodeRuntimeOptions): void; - resolve(value: PlainObject, options?: ResolveOptions): any; - - onResolve(handler: NodeResolverHandler): EventDisposable; - - createChildScope(value: PlainObject): ICodeScope; + createCodeRuntime( + options: CodeRuntimeOptions, + ): ICodeRuntime; } export const ICodeRuntimeService = createDecorator('codeRuntimeService'); -export interface CodeRuntimeInitializeOptions { - evalCodeFunction?: (code: string, scope: any) => any; -} - @Provide(ICodeRuntimeService) export class CodeRuntimeService implements ICodeRuntimeService { - private codeScope: ICodeScope = new CodeScope({}); - - private evalCodeFunction = evaluate; - - private onResolveHandlers: NodeResolverHandler[] = []; - - initialize(options: CodeRuntimeInitializeOptions) { - if (options.evalCodeFunction) this.evalCodeFunction = options.evalCodeFunction; - } - - getScope() { - return this.codeScope; - } - - run(code: string, scope: ICodeScope = this.codeScope): R | undefined { - if (!code) return undefined; + rootRuntime: ICodeRuntime; - try { - const result = this.evalCodeFunction(code, scope.value); - - return result as R; - } catch (err) { - // todo replace logger - console.error('eval error', code, scope.value, err); - return undefined; - } + initialize(options?: CodeRuntimeOptions) { + this.rootRuntime = new CodeRuntime(options); } - resolve(data: PlainObject, options: ResolveOptions = {}): any { - const handlers = this.onResolveHandlers; - - if (handlers.length > 0) { - data = mapValue(data, isNode, (node: Spec.JSNode) => { - let newNode: Spec.JSNode | false | undefined = node; - - for (const handler of handlers) { - newNode = handler(newNode as Spec.JSNode); - if (newNode === false || typeof newNode === 'undefined') { - break; - } - } - - return newNode; - }); - } - - return mapValue( - data, - (data) => { - return isJSExpression(data) || isJSFunction(data); - }, - (node: Spec.JSExpression | Spec.JSFunction) => { - return this.resolveExprOrFunction(node, options); - }, - ); - } - - private resolveExprOrFunction( - node: Spec.JSExpression | Spec.JSFunction, - options: ResolveOptions, - ) { - const scope = options.scope || this.codeScope; - const v = this.run(node.value, scope) as any; - - if (typeof v === 'undefined' && node.mock) { - return this.resolve(node.mock, options); - } - return v; - } - - /** - * 顺序执行 handler - */ - onResolve(handler: NodeResolverHandler): EventDisposable { - this.onResolveHandlers.push(handler); - return () => { - this.onResolveHandlers = this.onResolveHandlers.filter((h) => h !== handler); - }; - } + createCodeRuntime( + options: CodeRuntimeOptions = {}, + ): ICodeRuntime { + invariant(this.rootRuntime, `please initialize codeRuntimeService on renderer starting!`); - createChildScope(value: PlainObject): ICodeScope { - return this.codeScope.createChild(value); + return options.parentScope + ? new CodeRuntime(options) + : this.rootRuntime.createChild(options); } } diff --git a/packages/renderer-core/src/services/code-runtime/codeScope.ts b/packages/renderer-core/src/services/code-runtime/codeScope.ts index 104b2bf1c..6bf57bcf7 100644 --- a/packages/renderer-core/src/services/code-runtime/codeScope.ts +++ b/packages/renderer-core/src/services/code-runtime/codeScope.ts @@ -9,27 +9,28 @@ const unscopables = trustedGlobals.reduce((acc, key) => ({ ...acc, [key]: true } __proto__: null, }); -export interface ICodeScope { - readonly value: PlainObject; - set(name: string, value: any): void; - setValue(value: PlainObject, replace?: boolean): void; - createChild(initValue: PlainObject): ICodeScope; +export interface ICodeScope { + readonly value: T; + + set(name: keyof T, value: any): void; + setValue(value: Partial, replace?: boolean): void; + createChild(initValue: Partial): ICodeScope; } /** * 双链表实现父域值的获取 */ -interface IScopeNode { - parent?: IScopeNode; - current: PlainObject; +interface IScopeNode { + parent?: IScopeNode; + current: Partial; } -export class CodeScope implements ICodeScope { - __node: IScopeNode; +export class CodeScope implements ICodeScope { + __node: IScopeNode; - private proxyValue: PlainObject; + private proxyValue: T; - constructor(initValue: PlainObject) { + constructor(initValue: Partial) { this.__node = { current: initValue, }; @@ -37,15 +38,15 @@ export class CodeScope implements ICodeScope { this.proxyValue = this.createProxy(); } - get value() { + get value(): T { return this.proxyValue; } - set(name: string, value: any): void { + set(name: keyof T, value: any): void { this.__node.current[name] = value; } - setValue(value: PlainObject, replace = false) { + setValue(value: Partial, replace = false) { if (replace) { this.__node.current = { ...value }; } else { @@ -53,15 +54,15 @@ export class CodeScope implements ICodeScope { } } - createChild(initValue: PlainObject): ICodeScope { + createChild(initValue: Partial): ICodeScope { const childScope = new CodeScope(initValue); childScope.__node.parent = this.__node; return childScope; } - private createProxy(): PlainObject { - return new Proxy(Object.create(null) as PlainObject, { + private createProxy(): T { + return new Proxy(Object.create(null) as T, { set: (target, p, newValue) => { this.set(p as string, newValue); return true; @@ -74,7 +75,7 @@ export class CodeScope implements ICodeScope { private findValue(prop: PropertyKey) { if (prop === Symbol.unscopables) return unscopables; - let node: IScopeNode | undefined = this.__node; + let node: IScopeNode | undefined = this.__node; while (node) { if (Object.hasOwnProperty.call(node.current, prop)) { return node.current[prop as string]; @@ -86,7 +87,7 @@ export class CodeScope implements ICodeScope { private hasProperty(prop: PropertyKey): boolean { if (prop in unscopables) return true; - let node: IScopeNode | undefined = this.__node; + let node: IScopeNode | undefined = this.__node; while (node) { if (prop in node.current) { return true; diff --git a/packages/renderer-core/src/services/code-runtime/evaluate.ts b/packages/renderer-core/src/services/code-runtime/evaluate.ts new file mode 100644 index 000000000..465b6a2b3 --- /dev/null +++ b/packages/renderer-core/src/services/code-runtime/evaluate.ts @@ -0,0 +1,7 @@ +import { type EvalCodeFunction } from './codeRuntime'; + +export const evaluate: EvalCodeFunction = (code: string, scope: any) => { + return new Function('scope', `"use strict";return (function(){return (${code})}).bind(scope)();`)( + scope, + ); +}; diff --git a/packages/renderer-core/src/services/code-runtime/index.ts b/packages/renderer-core/src/services/code-runtime/index.ts index 74b951ea1..abebe49af 100644 --- a/packages/renderer-core/src/services/code-runtime/index.ts +++ b/packages/renderer-core/src/services/code-runtime/index.ts @@ -1,2 +1,3 @@ export * from './codeScope'; export * from './codeRuntimeService'; +export * from './codeRuntime'; diff --git a/packages/renderer-core/src/services/extension/boosts.ts b/packages/renderer-core/src/services/extension/boosts.ts index 6f8742495..92bd1392d 100644 --- a/packages/renderer-core/src/services/extension/boosts.ts +++ b/packages/renderer-core/src/services/extension/boosts.ts @@ -1,13 +1,13 @@ import { createDecorator, Provide, type PlainObject } from '@alilc/lowcode-shared'; import { isObject } from 'lodash-es'; -import { ICodeRuntimeService } from '../code-runtime'; +import { ICodeRuntime, ICodeRuntimeService } from '../code-runtime'; import { IRuntimeUtilService } from '../runtimeUtilService'; import { IRuntimeIntlService } from '../runtimeIntlService'; export type IBoosts = IBoostsApi & Extends & { [key: string]: any }; export interface IBoostsApi { - readonly codeRuntime: ICodeRuntimeService; + readonly codeRuntime: ICodeRuntime; readonly intl: Pick; @@ -39,12 +39,14 @@ export class BoostsService implements IBoostsService { private _expose: any; constructor( - @ICodeRuntimeService private codeRuntimeService: ICodeRuntimeService, + @ICodeRuntimeService codeRuntimeService: ICodeRuntimeService, @IRuntimeIntlService private runtimeIntlService: IRuntimeIntlService, @IRuntimeUtilService private runtimeUtilService: IRuntimeUtilService, ) { this.builtInApis = { - codeRuntime: this.codeRuntimeService, + get codeRuntime() { + return codeRuntimeService.rootRuntime; + }, intl: this.runtimeIntlService, util: this.runtimeUtilService, temporaryUse: (name, value) => { @@ -75,7 +77,7 @@ export class BoostsService implements IBoostsService { toExpose(): IBoosts { if (!this._expose) { - this._expose = new Proxy(Object.create(null), { + this._expose = new Proxy(this.builtInApis, { get: (_, p, receiver) => { return ( Reflect.get(this.builtInApis, p, receiver) || diff --git a/packages/renderer-core/src/services/lifeCycleService.ts b/packages/renderer-core/src/services/lifeCycleService.ts index 6378b59a3..37ce905f0 100644 --- a/packages/renderer-core/src/services/lifeCycleService.ts +++ b/packages/renderer-core/src/services/lifeCycleService.ts @@ -25,6 +25,19 @@ export interface ILifeCycleService { when(phase: LifecyclePhase, listener: () => void | Promise): EventDisposable; } +export function LifecyclePhaseToString(phase: LifecyclePhase): string { + switch (phase) { + case LifecyclePhase.Starting: + return 'Starting'; + case LifecyclePhase.OptionsResolved: + return 'OptionsResolved'; + case LifecyclePhase.Ready: + return 'Ready'; + case LifecyclePhase.Destroying: + return 'Destroying'; + } +} + export const ILifeCycleService = createDecorator('lifeCycleService'); @Provide(ILifeCycleService) @@ -55,18 +68,3 @@ export class LifeCycleService implements ILifeCycleService { return this.phaseWhen.on(LifecyclePhaseToString(phase), listener); } } - -export function LifecyclePhaseToString(phase: LifecyclePhase): string { - switch (phase) { - case LifecyclePhase.Starting: - return 'Starting'; - case LifecyclePhase.OptionsResolved: - return 'OptionsResolved'; - case LifecyclePhase.Ready: - return 'Ready'; - case LifecyclePhase.Inited: - return 'Inited'; - case LifecyclePhase.Destroying: - return 'Destroying'; - } -} diff --git a/packages/renderer-core/src/services/model/componentTreeModel.ts b/packages/renderer-core/src/services/model/componentTreeModel.ts index dc4810e3a..555c29054 100644 --- a/packages/renderer-core/src/services/model/componentTreeModel.ts +++ b/packages/renderer-core/src/services/model/componentTreeModel.ts @@ -5,7 +5,7 @@ import { invariant, uniqueId, } from '@alilc/lowcode-shared'; -import { type ICodeScope, type ICodeRuntimeService } from '../code-runtime'; +import { type ICodeRuntime } from '../code-runtime'; import { IWidget, Widget } from '../widget'; export interface NormalizedComponentNode extends Spec.ComponentNode { @@ -13,26 +13,16 @@ export interface NormalizedComponentNode extends Spec.ComponentNode { props: Spec.ComponentNodeProps; } -export interface InitializeModelOptions { - defaultProps?: PlainObject | undefined; - stateCreator: ModelScopeStateCreator; - dataSourceCreator?: ModelScopeDataSourceCreator; -} - /** * 根据低代码搭建协议的容器组件描述生成的容器模型 */ export interface IComponentTreeModel { readonly id: string; - readonly codeScope: ICodeScope; - - readonly codeRuntime: ICodeRuntimeService; + readonly codeRuntime: ICodeRuntime; readonly widgets: IWidget[]; - initialize(options: InitializeModelOptions): void; - /** * 获取协议中的 css 内容 */ @@ -56,12 +46,18 @@ export interface IComponentTreeModel { buildWidgets(nodes: Spec.NodeType[]): IWidget[]; } -export type ModelScopeStateCreator = (initalState: PlainObject) => Spec.InstanceStateApi; -export type ModelScopeDataSourceCreator = (...args: any[]) => Spec.InstanceDataSourceApi; +export type ModelStateCreator = (initalState: PlainObject) => Spec.InstanceStateApi; +export type ModelDataSourceCreator = ( + dataSourceSchema: Spec.ComponentDataSource, + codeRuntime: ICodeRuntime, +) => Spec.InstanceDataSourceApi; export interface ComponentTreeModelOptions { id?: string; metadata?: PlainObject; + + stateCreator: ModelStateCreator; + dataSourceCreator?: ModelDataSourceCreator; } export class ComponentTreeModel @@ -71,16 +67,14 @@ export class ComponentTreeModel public id: string; - public codeScope: ICodeScope; - public widgets: IWidget[] = []; public metadata: PlainObject = {}; constructor( public componentsTree: Spec.ComponentTree, - public codeRuntime: ICodeRuntimeService, - options?: ComponentTreeModelOptions, + public codeRuntime: ICodeRuntime, + options: ComponentTreeModelOptions, ) { invariant(componentsTree, 'componentsTree must to provide', 'ComponentTreeModel'); @@ -92,39 +86,28 @@ export class ComponentTreeModel if (componentsTree.children) { this.widgets = this.buildWidgets(componentsTree.children); } + + this.initialize(options); } - initialize({ defaultProps, stateCreator, dataSourceCreator }: InitializeModelOptions) { - const { - state = {}, - defaultProps: defaultSchemaProps, - props = {}, - dataSource, - methods = {}, - } = this.componentsTree; - - this.codeScope = this.codeRuntime.createChildScope({ - props: { - ...props, - ...defaultSchemaProps, - ...defaultProps, - }, - }); + private initialize({ stateCreator, dataSourceCreator }: ComponentTreeModelOptions) { + const { state = {}, defaultProps, props = {}, dataSource, methods = {} } = this.componentsTree; + const codeScope = this.codeRuntime.getScope(); - const initalProps = this.codeRuntime.resolve(props, { scope: this.codeScope }); - this.codeScope.setValue({ props: { ...defaultProps, ...initalProps } }); + const initalProps = this.codeRuntime.resolve(props); + codeScope.setValue({ props: { ...defaultProps, ...codeScope.value.props, ...initalProps } }); - const initalState = this.codeRuntime.resolve(state, { scope: this.codeScope }); + const initalState = this.codeRuntime.resolve(state); const stateApi = stateCreator(initalState); - this.codeScope.setValue(stateApi); + codeScope.setValue(stateApi); let dataSourceApi: Spec.InstanceDataSourceApi | undefined; if (dataSource && dataSourceCreator) { - const dataSourceProps = this.codeRuntime.resolve(dataSource, { scope: this.codeScope }); - dataSourceApi = dataSourceCreator(dataSourceProps, stateApi); + const dataSourceProps = this.codeRuntime.resolve(dataSource); + dataSourceApi = dataSourceCreator(dataSourceProps, this.codeRuntime); } - this.codeScope.setValue( + codeScope.setValue( Object.assign( { $: (ref: string) => { @@ -141,9 +124,9 @@ export class ComponentTreeModel ); for (const [key, fn] of Object.entries(methods)) { - const customMethod = this.codeRuntime.resolve(fn, { scope: this.codeScope }); + const customMethod = this.codeRuntime.resolve(fn); if (typeof customMethod === 'function') { - this.codeScope.set(key, customMethod); + codeScope.set(key, customMethod); } } } @@ -163,9 +146,9 @@ export class ComponentTreeModel const lifeCycleSchema = this.componentsTree.lifeCycles[lifeCycleName]; - const lifeCycleFn = this.codeRuntime.resolve(lifeCycleSchema, { scope: this.codeScope }); + const lifeCycleFn = this.codeRuntime.resolve(lifeCycleSchema); if (typeof lifeCycleFn === 'function') { - lifeCycleFn.apply(this.codeScope.value, args); + lifeCycleFn.apply(this.codeRuntime.getScope().value, args); } } diff --git a/packages/renderer-core/src/services/model/componentTreeModelService.ts b/packages/renderer-core/src/services/model/componentTreeModelService.ts index bd61900af..c5097e337 100644 --- a/packages/renderer-core/src/services/model/componentTreeModelService.ts +++ b/packages/renderer-core/src/services/model/componentTreeModelService.ts @@ -1,4 +1,10 @@ -import { createDecorator, Provide, invariant, type Spec } from '@alilc/lowcode-shared'; +import { + createDecorator, + Provide, + invariant, + type Spec, + type PlainObject, +} from '@alilc/lowcode-shared'; import { ICodeRuntimeService } from '../code-runtime'; import { type IComponentTreeModel, @@ -7,15 +13,19 @@ import { } from './componentTreeModel'; import { ISchemaService } from '../schema'; +export interface CreateComponentTreeModelOptions extends ComponentTreeModelOptions { + codeScopeValue?: PlainObject; +} + export interface IComponentTreeModelService { create( componentsTree: Spec.ComponentTree, - options?: ComponentTreeModelOptions, + options?: CreateComponentTreeModelOptions, ): IComponentTreeModel; createById( id: string, - options?: ComponentTreeModelOptions, + options?: CreateComponentTreeModelOptions, ): IComponentTreeModel; } @@ -32,20 +42,32 @@ export class ComponentTreeModelService implements IComponentTreeModelService { create( componentsTree: Spec.ComponentTree, - options?: ComponentTreeModelOptions, + options: CreateComponentTreeModelOptions, ): IComponentTreeModel { - return new ComponentTreeModel(componentsTree, this.codeRuntimeService, options); + return new ComponentTreeModel( + componentsTree, + this.codeRuntimeService.createCodeRuntime({ + initScopeValue: options?.codeScopeValue, + }), + options, + ); } createById( id: string, - options?: ComponentTreeModelOptions, + options: CreateComponentTreeModelOptions, ): IComponentTreeModel { const componentsTrees = this.schemaService.get('componentsTree'); const componentsTree = componentsTrees.find((item) => item.id === id); invariant(componentsTree, 'componentsTree not found'); - return new ComponentTreeModel(componentsTree, this.codeRuntimeService, options); + return new ComponentTreeModel( + componentsTree, + this.codeRuntimeService.createCodeRuntime({ + initScopeValue: options?.codeScopeValue, + }), + options, + ); } } diff --git a/packages/renderer-core/src/services/runtimeIntlService.ts b/packages/renderer-core/src/services/runtimeIntlService.ts index 90dc9724f..d8e1029c6 100644 --- a/packages/renderer-core/src/services/runtimeIntlService.ts +++ b/packages/renderer-core/src/services/runtimeIntlService.ts @@ -42,8 +42,6 @@ export class RuntimeIntlService implements IRuntimeIntlService { @ICodeRuntimeService private codeRuntimeService: ICodeRuntimeService, @ISchemaService private schemaService: ISchemaService, ) { - this.injectScope(); - this.lifeCycleService.when(LifecyclePhase.OptionsResolved, () => { const config = this.schemaService.get('config'); const i18nTranslations = this.schemaService.get('i18n'); @@ -56,6 +54,8 @@ export class RuntimeIntlService implements IRuntimeIntlService { this.addTranslations(key, i18nTranslations[key]); }); } + + this.injectScope(); }); } @@ -96,6 +96,6 @@ export class RuntimeIntlService implements IRuntimeIntlService { }, }; - this.codeRuntimeService.getScope().setValue(exposed); + this.codeRuntimeService.rootRuntime.getScope().setValue(exposed); } } diff --git a/packages/renderer-core/src/services/runtimeUtilService.ts b/packages/renderer-core/src/services/runtimeUtilService.ts index 9b5fcfad5..b59b6f4f3 100644 --- a/packages/renderer-core/src/services/runtimeUtilService.ts +++ b/packages/renderer-core/src/services/runtimeUtilService.ts @@ -9,6 +9,7 @@ import { isPlainObject } from 'lodash-es'; import { IPackageManagementService } from './package'; import { ICodeRuntimeService } from './code-runtime'; import { ISchemaService } from './schema'; +import { ILifeCycleService, LifecyclePhase } from './lifeCycleService'; export interface IRuntimeUtilService { add(utilItem: Spec.Util, force?: boolean): void; @@ -27,8 +28,11 @@ export class RuntimeUtilService implements IRuntimeUtilService { @ICodeRuntimeService private codeRuntimeService: ICodeRuntimeService, @IPackageManagementService private packageManagementService: IPackageManagementService, @ISchemaService private schemaService: ISchemaService, + @ILifeCycleService private lifeCycleService: ILifeCycleService, ) { - this.injectScope(); + this.lifeCycleService.when(LifecyclePhase.OptionsResolved, () => { + this.injectScope(); + }); this.schemaService.onChange('utils', (utils = []) => { for (const util of utils) { @@ -93,7 +97,7 @@ export class RuntimeUtilService implements IRuntimeUtilService { const { content } = utilItem; return { key: utilItem.name, - value: this.codeRuntimeService.run(content.value), + value: this.codeRuntimeService.rootRuntime.run(content.value), }; } else { return this.packageManagementService.getLibraryByComponentMap(utilItem.content); @@ -113,6 +117,6 @@ export class RuntimeUtilService implements IRuntimeUtilService { }, }); - this.codeRuntimeService.getScope().set('utils', exposed); + this.codeRuntimeService.rootRuntime.getScope().set('utils', exposed); } } diff --git a/packages/renderer-core/src/services/widget/widget.ts b/packages/renderer-core/src/services/widget/widget.ts index 825165e00..42fe619f3 100644 --- a/packages/renderer-core/src/services/widget/widget.ts +++ b/packages/renderer-core/src/services/widget/widget.ts @@ -1,11 +1,10 @@ import { type Spec, uniqueId } from '@alilc/lowcode-shared'; -import { clone } from 'lodash-es'; import { IComponentTreeModel } from '../model'; export interface IWidget { readonly key: string; - readonly node: Spec.NodeType; + readonly rawNode: Spec.NodeType; model: IComponentTreeModel; @@ -15,9 +14,7 @@ export interface IWidget { export class Widget implements IWidget { - public __raw: Spec.NodeType; - - public node: Spec.NodeType; + public rawNode: Spec.NodeType; public key: string; @@ -27,8 +24,7 @@ export class Widget node: Spec.NodeType, public model: IComponentTreeModel, ) { - this.node = clone(node); - this.__raw = node; + this.rawNode = node; this.key = (node as Spec.ComponentNode)?.id ?? uniqueId(); } } diff --git a/packages/renderer-core/src/types.ts b/packages/renderer-core/src/types.ts index 769dd5e55..b31505fa5 100644 --- a/packages/renderer-core/src/types.ts +++ b/packages/renderer-core/src/types.ts @@ -2,8 +2,8 @@ import { type Spec } from '@alilc/lowcode-shared'; import { type Plugin } from './services/extension'; import { type ISchemaService } from './services/schema'; import { type IPackageManagementService } from './services/package'; -import { type CodeRuntimeInitializeOptions } from './services/code-runtime'; -import { type ModelScopeDataSourceCreator } from './services/model'; +import { type CodeRuntimeOptions } from './services/code-runtime'; +import { type ModelDataSourceCreator } from './services/model'; export interface AppOptions { schema: Spec.Project; @@ -16,11 +16,11 @@ export interface AppOptions { /** * code runtime 设置选项 */ - codeRuntime?: CodeRuntimeInitializeOptions; + codeRuntime?: CodeRuntimeOptions; /** * 数据源创建工厂函数 */ - dataSourceCreator?: ModelScopeDataSourceCreator; + dataSourceCreator?: ModelDataSourceCreator; } export type RendererApplication = { diff --git a/packages/renderer-core/src/utils/evaluate.ts b/packages/renderer-core/src/utils/evaluate.ts deleted file mode 100644 index 4be366110..000000000 --- a/packages/renderer-core/src/utils/evaluate.ts +++ /dev/null @@ -1,5 +0,0 @@ -export function evaluate(code: string, scope: any) { - return new Function('scope', `"use strict";return (function(){return (${code})}).bind(scope)();`)( - scope, - ); -} diff --git a/packages/shared/src/types/specs/runtime.ts b/packages/shared/src/types/specs/runtime.ts index 6b900d9e2..0290ffae8 100644 --- a/packages/shared/src/types/specs/runtime.ts +++ b/packages/shared/src/types/specs/runtime.ts @@ -1,5 +1,4 @@ import { AnyFunction, PlainObject } from '../index'; -import { JSExpression } from './lowcode-spec'; /** * 在上述事件类型描述和变量类型描述中,在函数或 JS 表达式内,均可以通过 this 对象获取当前组件所在容器的实例化对象 @@ -62,7 +61,7 @@ export interface DataSourceMapItem { * 调用单个数据源 * @param params 替换 ComponentDataSourceItemOptions 对象描述中的 params */ - load(params: any): Promise; + load(params?: any): Promise; /** * 数据源请求的返回状态 */ diff --git a/playground/package.json b/playground/engine/package.json similarity index 71% rename from playground/package.json rename to playground/engine/package.json index 93bb53d3f..c8d3f1f0e 100644 --- a/playground/package.json +++ b/playground/engine/package.json @@ -1,5 +1,5 @@ { - "name": "playground", + "name": "playground-engine", "version": "1.0.0", "private": true, "type": "module", @@ -7,19 +7,18 @@ "dev": "vite" }, "dependencies": { - "@alilc/lowcode-shared": "workspace:*", - "@alilc/lowcode-types": "workspace:*", "@alilc/lowcode-core": "workspace:*", "@alilc/lowcode-designer": "workspace:*", "@alilc/lowcode-engine": "workspace:*", "@alilc/lowcode-react-simulator-renderer": "workspace:*", - "@alilc/lowcode-react-renderer": "workspace:*", - "@alilc/lowcode-renderer-core": "workspace:*", "@alifd/next": "^1.27.10", + "axios": "^1.7.2", + "lodash-es": "^4.17.21", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { + "@types/lodash-es": "^4.17.12", "@types/react": "^18.2.75", "@types/react-dom": "^18.2.24", "vite-plugin-external": "^4.3.0" diff --git a/playground/engine/tsconfig.json b/playground/engine/tsconfig.json new file mode 100644 index 000000000..b4e69ae1f --- /dev/null +++ b/playground/engine/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist" + } +} diff --git a/playground/engine/vite.config.ts b/playground/engine/vite.config.ts new file mode 100644 index 000000000..a7f725435 --- /dev/null +++ b/playground/engine/vite.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite'; +import createExternal from 'vite-plugin-external'; + +export default defineConfig({ + plugins: [ + createExternal({ + externals: { + react: 'React', + 'react-dom': 'ReactDOM', + '@alifd/next': 'Next', + }, + }), + ], +}); diff --git a/playground/renderer/index.html b/playground/renderer/index.html index c27791f84..a58eabe2a 100644 --- a/playground/renderer/index.html +++ b/playground/renderer/index.html @@ -1,12 +1,29 @@ - + - - - - Document - - -
- - + + + + Document + + +
+ + + + + + + + + + + + + + + + + + + diff --git a/playground/renderer/package.json b/playground/renderer/package.json new file mode 100644 index 000000000..ec90855b4 --- /dev/null +++ b/playground/renderer/package.json @@ -0,0 +1,23 @@ +{ + "name": "playground-renderer", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite" + }, + "dependencies": { + "@alilc/lowcode-react-renderer": "workspace:*", + "@alifd/next": "^1.27.10", + "axios": "^1.7.2", + "lodash-es": "^4.17.21", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/lodash-es": "^4.17.12", + "@types/react": "^18.2.75", + "@types/react-dom": "^18.2.24", + "vite-plugin-external": "^4.3.0" + } +} diff --git a/playground/renderer/src/apis/index.ts b/playground/renderer/src/apis/index.ts new file mode 100644 index 000000000..2bc3e7914 --- /dev/null +++ b/playground/renderer/src/apis/index.ts @@ -0,0 +1 @@ +export * from './runtime'; diff --git a/playground/renderer/src/apis/runtime.ts b/playground/renderer/src/apis/runtime.ts new file mode 100644 index 000000000..3840fbad6 --- /dev/null +++ b/playground/renderer/src/apis/runtime.ts @@ -0,0 +1,40 @@ +import { AxiosError } from 'axios'; +import { axiosInstance, jsonpAdapter } from '../utils'; + +export async function fetchAsset(appCode: string, version: string) { + const assetsFetchUrl = `https://uipaas-node.alibaba-inc.com/app/assets.jsonp?lccI18nCompress=y&appType=${appCode}&systemType=${version}&_=1717506796943&callback=__legaoJsonpCallback1`; + + const response = await axiosInstance.get(assetsFetchUrl, { + adapter: jsonpAdapter, + }); + + if (response.data && response.data.success) { + return response.data.content; + } + + throw new AxiosError( + 'Fetch Assest: ', + AxiosError.ERR_BAD_RESPONSE, + response.config, + undefined, + response, + ); +} + +export async function fetchSchema(appCode: string, version: string, slug: string) { + const fetchUrl = `https://aecp-builder.alibaba-inc.com/api/runtime/pageSchema?applicationCode=${appCode}&uipaasDevVersion=${version}&operator=329643&formUuid=${slug}`; + + const response = await axiosInstance.get(fetchUrl); + + if (response.data && response.data.success) { + return response.data.content; + } + + throw new AxiosError( + 'Fetch Schema: ', + AxiosError.ERR_BAD_RESPONSE, + response.config, + undefined, + response, + ); +} diff --git a/playground/renderer/src/data.ts b/playground/renderer/src/data.ts new file mode 100644 index 000000000..cc2df1030 --- /dev/null +++ b/playground/renderer/src/data.ts @@ -0,0 +1,425 @@ +export const data = [ + { + hidden: false, + navUuid: 'FORM-Z26669A1QPSVVIGC1Z9024PWU20H2H15EPOWKL', + children: [], + icon: '', + targetNew: false, + title: { + en_US: '无权限', + zh_CN: '无权限', + type: 'JSExpression', + value: '({"en_US":"无权限","zh_CN":"无权限","type":"i18n"})[this.utils.getLocale()]', + key: 'i18n-kwope4po', + extType: 'i18n', + }, + inner: true, + relateUuid: 'FORM-Z26669A1QPSVVIGC1Z9024PWU20H2H15EPOWKL', + slug: 'accessDenied', + }, + { + hidden: false, + navUuid: 'FORM-WR666MC194LV60VNZLDT813F971G2B8SOHBWK0', + children: [], + icon: '', + targetNew: false, + title: { + en_US: '系统公告', + zh_CN: '系统公告', + type: 'JSExpression', + value: '({"en_US":"系统公告","zh_CN":"系统公告","type":"i18n"})[this.utils.getLocale()]', + key: 'i18n-kwbhorvz', + extType: 'i18n', + }, + inner: true, + relateUuid: 'FORM-WR666MC194LV60VNZLDT813F971G2B8SOHBWK0', + slug: 'announcement', + }, + { + hidden: false, + navUuid: 'FORM-ODA66SB1O8GTA55B4FKMK51OADPF2RS03PJTKY2', + children: [], + icon: '', + targetNew: false, + title: { + en_US: '应用类型管理', + zh_CN: '应用类型管理', + type: 'JSExpression', + value: + '({"en_US":"应用类型管理","zh_CN":"应用类型管理","type":"i18n"})[this.utils.getLocale()]', + key: 'i18n-ktxpc8yn', + extType: 'i18n', + }, + inner: true, + relateUuid: 'FORM-ODA66SB1O8GTA55B4FKMK51OADPF2RS03PJTKY2', + slug: 'appTypeManage', + }, + { + hidden: false, + navUuid: 'FORM-YC766FB10ZQ6TIABCX62H5SVIL4S2CUSB80CL1', + children: [], + targetNew: false, + title: { + en_US: '流程管理', + zh_CN: '流程管理', + type: 'JSExpression', + value: + '({"en_US":"流程管理","zh_CN":"流程管理","type":"JSExpression","extType":"i18n"})[this.utils.getLocale()]', + extType: 'i18n', + }, + inner: true, + relateUuid: 'FORM-YC766FB10ZQ6TIABCX62H5SVIL4S2CUSB80CL1', + slug: 'bpmsProcess', + }, + { + hidden: false, + navUuid: 'FORM-CZ866GD1-WUP2JVPL9K3MR7U0TULC3-ZDE07N6L-DD', + children: [], + icon: '', + targetNew: false, + title: { + en_US: '(carbon)', + zh_CN: '配置详情', + type: 'JSExpression', + value: '({"en_US":"(carbon)","zh_CN":"配置详情","type":"i18n"})[this.utils.getLocale()]', + key: 'i18n-l6n70e2j', + extType: 'i18n', + }, + inner: true, + relateUuid: 'FORM-CZ866GD1-WUP2JVPL9K3MR7U0TULC3-ZDE07N6L-DD', + slug: 'configDetail', + }, + { + hidden: false, + navUuid: 'FORM-P8866IC1FQR4LDKPAKRN16O5K4TK3WZEEFB9L3', + children: [], + targetNew: false, + title: { + en_US: '配置对象', + zh_CN: '配置对象', + type: 'JSExpression', + value: + '({"en_US":"配置对象","zh_CN":"配置对象","type":"JSExpression","extType":"i18n"})[this.utils.getLocale()]', + extType: 'i18n', + }, + inner: true, + relateUuid: 'FORM-P8866IC1FQR4LDKPAKRN16O5K4TK3WZEEFB9L3', + slug: 'configList', + }, + { + hidden: false, + navUuid: 'FORM-HW866A91C5EVOJDA4YPNT842IU3T3080NI7WKK2', + children: [], + icon: '', + targetNew: false, + title: { + en_US: '创建物料', + zh_CN: '创建物料', + type: 'JSExpression', + value: '({"en_US":"创建物料","zh_CN":"创建物料","type":"i18n"})[this.utils.getLocale()]', + key: 'i18n-kw7imzur', + extType: 'i18n', + }, + inner: true, + relateUuid: 'FORM-HW866A91C5EVOJDA4YPNT842IU3T3080NI7WKK2', + slug: 'createMaterial', + }, + { + hidden: false, + navUuid: 'FORM-YC766FB11T26WQTVEDZBE92AJ3LI2PRNO42BL5', + children: [], + targetNew: false, + title: { + en_US: '创建中', + zh_CN: '创建中', + type: 'JSExpression', + value: + '({"en_US":"创建中","zh_CN":"创建中","type":"JSExpression","extType":"i18n"})[this.utils.getLocale()]', + extType: 'i18n', + }, + inner: true, + relateUuid: 'FORM-YC766FB11T26WQTVEDZBE92AJ3LI2PRNO42BL5', + slug: 'creating', + }, + { + hidden: false, + navUuid: 'FORM-VWA664C17R0DG40F8XAOIB63BNXO2F3NWB3LL91', + children: [], + targetNew: false, + title: { + en_US: '数据源详情', + zh_CN: '数据源详情', + type: 'JSExpression', + value: + '({"en_US":"数据源详情","zh_CN":"数据源详情","type":"JSExpression","extType":"i18n"})[this.utils.getLocale()]', + extType: 'i18n', + }, + inner: true, + relateUuid: 'FORM-VWA664C17R0DG40F8XAOIB63BNXO2F3NWB3LL91', + slug: 'dataSourceDetail', + }, + { + hidden: false, + navUuid: 'FORM-CA7662D1JYZCK1S2E9C6OCB96B6A2CNPQZRKL0', + children: [], + targetNew: false, + title: { + en_US: '数据源管理', + zh_CN: '数据源管理', + type: 'JSExpression', + value: + '({"en_US":"数据源管理","zh_CN":"数据源管理","type":"JSExpression","extType":"i18n"})[this.utils.getLocale()]', + extType: 'i18n', + }, + inner: true, + relateUuid: 'FORM-CA7662D1JYZCK1S2E9C6OCB96B6A2CNPQZRKL0', + slug: 'dataSourceManager', + }, + { + hidden: false, + navUuid: 'FORM-6F666CD11MB1OLFSFCGKM4JG9WU83DK47SH4LW', + children: [], + icon: 'uxicon-yingyong', + targetNew: false, + title: { + en_US: '应用部署', + zh_CN: '应用部署', + type: 'JSExpression', + value: '({"en_US":"应用部署","zh_CN":"应用部署","type":"i18n"})[this.utils.getLocale()]', + key: 'i18n-l4hs743f', + extType: 'i18n', + }, + inner: true, + relateUuid: 'FORM-6F666CD11MB1OLFSFCGKM4JG9WU83DK47SH4LW', + slug: 'deploy', + }, + { + hidden: false, + navUuid: 'FORM-QA666QA1PVJGO6NOE7SSZ3S04DY02ISP11FQLC', + children: [], + targetNew: false, + title: { + en_US: '单据转换', + zh_CN: '单据转换', + type: 'JSExpression', + value: + '({"en_US":"单据转换","zh_CN":"单据转换","type":"JSExpression","extType":"i18n"})[this.utils.getLocale()]', + extType: 'i18n', + }, + inner: true, + relateUuid: 'FORM-QA666QA1PVJGO6NOE7SSZ3S04DY02ISP11FQLC', + slug: 'documentConversion', + }, + { + hidden: false, + navUuid: 'FORM-3V966JC12LXGZWGP6MLS7DF0P0Y62W5LRUIQLB', + children: [], + targetNew: false, + title: { + en_US: '单据转换详情', + zh_CN: '单据转换详情', + type: 'JSExpression', + value: + '({"en_US":"单据转换详情","zh_CN":"单据转换详情","type":"JSExpression","extType":"i18n"})[this.utils.getLocale()]', + extType: 'i18n', + }, + inner: true, + relateUuid: 'FORM-3V966JC12LXGZWGP6MLS7DF0P0Y62W5LRUIQLB', + slug: 'documentConversionDetail', + }, + { + hidden: false, + navUuid: 'FORM-WR666MC13W61Z8M57DCJC5DEQAP23UM8XW64L0', + children: [], + icon: '', + targetNew: false, + title: { + en_US: '禁止访问', + zh_CN: '禁止访问', + type: 'JSExpression', + value: '({"en_US":"禁止访问","zh_CN":"禁止访问","type":"i18n"})[this.utils.getLocale()]', + key: 'i18n-l46wx8bk', + extType: 'i18n', + }, + inner: true, + relateUuid: 'FORM-WR666MC13W61Z8M57DCJC5DEQAP23UM8XW64L0', + slug: 'forbidden', + }, + { + hidden: false, + navUuid: 'FORM-6I866791425D9RBY5KF0JDZ9681H2H8ASW4LLE', + children: [], + targetNew: false, + title: { + en_US: '首页', + zh_CN: '首页', + type: 'JSExpression', + value: + '({"en_US":"首页","zh_CN":"首页","type":"JSExpression","extType":"i18n"})[this.utils.getLocale()]', + extType: 'i18n', + }, + inner: true, + relateUuid: 'home', + slug: 'home', + }, + { + hidden: false, + navUuid: 'FORM-WR666MC1OEDVMJM6WEOCH1V41SQ3355FG76WK11', + children: [], + icon: '', + targetNew: false, + title: { + en_US: '导入物料', + zh_CN: '导入物料', + type: 'JSExpression', + value: '({"en_US":"导入物料","zh_CN":"导入物料","type":"i18n"})[this.utils.getLocale()]', + key: 'i18n-kw8jb599', + extType: 'i18n', + }, + inner: true, + relateUuid: 'FORM-WR666MC1OEDVMJM6WEOCH1V41SQ3355FG76WK11', + slug: 'importMaterial', + }, + { + hidden: false, + navUuid: 'FORM-9V966AC1MGXB0JP99295O8ESQWWJ2K7HXAIJLR', + children: [], + targetNew: false, + title: { + en_US: '迭代完结或废弃', + zh_CN: '迭代完结或废弃', + type: 'JSExpression', + value: + '({"en_US":"迭代完结或废弃","zh_CN":"迭代完结或废弃","type":"JSExpression","extType":"i18n"})[this.utils.getLocale()]', + extType: 'i18n', + }, + inner: true, + relateUuid: 'FORM-9V966AC1MGXB0JP99295O8ESQWWJ2K7HXAIJLR', + slug: 'iterationFailed', + }, + { + hidden: false, + navUuid: 'FORM-WR666MC1LEDVJOB03U9XQCCBFCW52I1R143WKN2', + children: [], + icon: '', + targetNew: false, + title: { + en_US: '物料中心', + zh_CN: '物料中心', + type: 'JSExpression', + value: '({"en_US":"物料中心","zh_CN":"物料中心","type":"i18n"})[this.utils.getLocale()]', + key: 'i18n-kwaau3ky', + extType: 'i18n', + }, + inner: true, + relateUuid: 'FORM-WR666MC1LEDVJOB03U9XQCCBFCW52I1R143WKN2', + slug: 'materialCenter', + }, + { + hidden: false, + navUuid: 'FORM-N3666IB1CCHVP3GF3EUKMDTM0K5Q32KAS66WK0', + children: [], + icon: '', + targetNew: false, + title: { + en_US: '物料详情', + zh_CN: '物料详情', + type: 'JSExpression', + value: '({"en_US":"物料详情","zh_CN":"物料详情","type":"i18n"})[this.utils.getLocale()]', + key: 'i18n-kw66s9xw', + extType: 'i18n', + }, + inner: true, + relateUuid: 'FORM-N3666IB1CCHVP3GF3EUKMDTM0K5Q32KAS66WK0', + slug: 'materialDetail', + }, + { + hidden: false, + navUuid: 'FORM-AY866BC1THL8SWWT5H0SY4RAEITG2LDE5M7FLX', + children: [], + targetNew: false, + title: { + en_US: '应用监控', + zh_CN: '应用监控', + type: 'JSExpression', + value: + '({"en_US":"应用监控","zh_CN":"应用监控","type":"JSExpression","extType":"i18n"})[this.utils.getLocale()]', + extType: 'i18n', + }, + inner: true, + relateUuid: 'FORM-AY866BC1THL8SWWT5H0SY4RAEITG2LDE5M7FLX', + slug: 'monitor', + }, + { + hidden: false, + navUuid: 'FORM-WR666MC18Q71CIW86GVU6CSHPGAF3B64VZG4L6', + children: [], + icon: 'uxicon-shouye', + targetNew: false, + title: { + en_US: '应用概览', + zh_CN: '应用概览', + type: 'JSExpression', + value: '({"en_US":"应用概览","zh_CN":"应用概览","type":"i18n"})[this.utils.getLocale()]', + key: 'i18n-l4gzv3ok', + extType: 'i18n', + }, + inner: true, + relateUuid: 'FORM-WR666MC18Q71CIW86GVU6CSHPGAF3B64VZG4L6', + slug: 'overview', + }, + { + hidden: false, + navUuid: 'FORM-WR666MC12YOTROW100EG39A4E9BF2R3HX9OTKC', + children: [], + icon: '', + targetNew: false, + title: { + en_US: '权限管理', + zh_CN: '权限管理', + type: 'JSExpression', + value: '({"en_US":"权限管理","zh_CN":"权限管理","type":"i18n"})[this.utils.getLocale()]', + key: 'i18n-kto9xgws', + extType: 'i18n', + }, + inner: true, + relateUuid: 'FORM-WR666MC12YOTROW100EG39A4E9BF2R3HX9OTKC', + slug: 'permission', + }, + { + hidden: false, + navUuid: 'FORM-CZ866GD1-X4ZZM1NU4OBFD91854GX2-19FPVM3L-08', + children: [], + icon: 'uxicon-shezhi', + targetNew: false, + title: { + en_US: '(carbon)(carbon)', + zh_CN: '应用设置', + type: 'JSExpression', + value: + '({"en_US":"(carbon)(carbon)","zh_CN":"应用设置","type":"i18n"})[this.utils.getLocale()]', + key: 'i18n-l43vls50', + extType: 'i18n', + }, + inner: true, + relateUuid: 'FORM-CZ866GD1-X4ZZM1NU4OBFD91854GX2-19FPVM3L-08', + slug: 'setting', + }, + { + hidden: false, + navUuid: 'FORM-ES666MB1JUZCV011D21P1BI0EY513B8IPW4LL91', + children: [], + targetNew: false, + title: { + en_US: '工作台首页_copy', + zh_CN: '工作台', + type: 'JSExpression', + value: + '({"en_US":"工作台首页_copy","zh_CN":"工作台","type":"JSExpression","extType":"i18n"})[this.utils.getLocale()]', + extType: 'i18n', + }, + inner: true, + relateUuid: 'FORM-ES666MB1JUZCV011D21P1BI0EY513B8IPW4LL91', + slug: 'workspace', + }, +]; diff --git a/playground/renderer/src/index.ts b/playground/renderer/src/index.ts index e4c6feeda..b18333c3d 100644 --- a/playground/renderer/src/index.ts +++ b/playground/renderer/src/index.ts @@ -1,14 +1,99 @@ -import { definePlugin, createApp } from '@alilc/lowcode-react-renderer'; +import { createApp, routerPlugin, type Spec } from '@alilc/lowcode-react-renderer'; +import remotePlugin from './plugin/remote'; +import uipaasPlugin from './plugin/uipaas'; +import { version } from '../package.json'; +import { fetchAsset } from './apis'; +import { transDepsToPackages } from './utils'; +import { data } from './data'; +import { dataSourceCreator } from './plugin/uipaas/vuDataSource'; -const p = definePlugin({ - name: '', - setup(app, { boosts }) { - boosts.getAppWrappers; - }, -}); +const applicationCode = 'aecp_builder'; +const devVersion = '0.171.0'; + +async function main() { + const assetData = await fetchAsset(applicationCode, devVersion); + const packages = transDepsToPackages(assetData); + console.log('%c [ packages ]-25', 'font-size:13px; background:pink; color:#bf2c9f;', packages); + + const pageConfigs: Spec.PageConfig[] = data.map((item: any) => { + const config: Spec.PageConfig = { + id: item.navUuid, + mappingId: item.navUuid, + type: 'lowCode', + meta: { + slug: item.slug, + title: item.title, + }, + }; + + return config; + }); + + const routerConfig: Spec.RouterConfig = { + historyMode: 'browser', + baseName: '/', + routes: pageConfigs.map((item) => { + return { + path: '/' + item.meta!.slug, + name: item.id, + page: item.id, + }; + }), + }; + + const app = await createApp({ + schema: { + componentsMap: [], + componentsTree: [], + pages: pageConfigs, + router: routerConfig, + version: '1.1.0', + config: { + defaultLocale: 'zh_CN', + }, + meta: { + applicationCode, + devVersion, + }, + }, + packages: [...packages.components, ...packages.lowCodeComponents, ...packages.utils], + plugins: [routerPlugin, remotePlugin, uipaasPlugin], + component: { + beforeElementCreate(widget) { + const node = widget.rawNode as any; + if (node.props && node.props.fieldId) { + node.props.ref = node.props.fieldId; + } + return widget; + }, + }, + codeRuntime: { + evalCodeFunction: (code, scope) => { + return new Function( + '$scope', + ` + with($scope) { + return (function(){return (${code})}).call($scope) + } + `, + )(scope); + }, + }, + dataSourceCreator, + }); + + if (packages.lowCodeComponents?.length) { + for (const item of packages.lowCodeComponents) { + app.packageManager.registerComponentByName(item.componentName, item); + } + } -const app = createApp({}); + app.mount('app'); +} -app.then((app) => { - app.use(p); +main().then(() => { + console.log( + `%cRenderEngine is running at v${version}`, + 'color:#0079f2;font-weight:bold;font-size:14px;line-height: 2;', + ); }); diff --git a/playground/renderer/src/plugin/remote/element.ts b/playground/renderer/src/plugin/remote/element.ts new file mode 100644 index 000000000..1ac0942d6 --- /dev/null +++ b/playground/renderer/src/plugin/remote/element.ts @@ -0,0 +1,158 @@ +const addLeadingSlash = (path: string): string => { + return path.charAt(0) === '/' ? path : `/${path}`; +}; + +export function getElementById(id: string, tag: string = 'div') { + let el = document.getElementById(id); + if (!el) { + el = document.createElement(tag); + el.id = id; + } + + return el; +} + +const enum AssetType { + STYLE = 'style', + SCRIPT = 'script', + UNKONWN = 'unkonwn', +} + +function getAssetTypeByUrl(url: string): AssetType { + const IS_CSS_REGEX = /\.css(\?((?!\.js$).)+)?$/; + const IS_JS_REGEX = /\.[t|j]sx?(\?((?!\.css$).)+)?$/; + const IS_JSON_REGEX = /\.json$/; + + if (IS_CSS_REGEX.test(url)) { + return AssetType.STYLE; + } else if (IS_JS_REGEX.test(url) || IS_JSON_REGEX.test(url)) { + return AssetType.SCRIPT; + } + + return AssetType.UNKONWN; +} + +export async function loadPackageUrls(urls: string[]) { + const styles: string[] = []; + const scripts: string[] = []; + + for (const url of urls) { + const type = getAssetTypeByUrl(url); + + if (type === AssetType.SCRIPT) { + scripts.push(url); + } else if (type === AssetType.STYLE) { + styles.push(url); + } + } + + await Promise.all(styles.map((item) => appendExternalCss(item))); + await Promise.all(scripts.map((item) => appendExternalScript(item))); +} + +async function appendExternalScript( + url: string, + root: HTMLElement = document.body, +): Promise { + if (url) { + const el = getIfExistAssetByUrl(url, 'script'); + if (el) return el; + } + + return new Promise((resolve, reject) => { + const scriptElement = document.createElement('script'); + + // scriptElement.type = moduleType === 'module' ? 'module' : 'text/javascript'; + /** + * `async=false` is required to make sure all js resources execute sequentially. + */ + scriptElement.async = false; + scriptElement.crossOrigin = 'anonymous'; + scriptElement.src = url; + + scriptElement.addEventListener( + 'load', + () => { + resolve(scriptElement); + }, + false, + ); + scriptElement.addEventListener('error', (error) => { + if (root.contains(scriptElement)) { + root.removeChild(scriptElement); + } + reject(error); + }); + + root.appendChild(scriptElement); + }); +} + +async function appendExternalCss( + url: string, + root: HTMLElement = document.head, +): Promise { + if (url) { + const el = getIfExistAssetByUrl(url, 'link'); + if (el) return el; + } + + return new Promise((resolve, reject) => { + const el: HTMLLinkElement = document.createElement('link'); + el.rel = 'stylesheet'; + el.href = url; + + el.addEventListener( + 'load', + () => { + resolve(el); + }, + false, + ); + el.addEventListener('error', (error) => { + reject(error); + }); + + root.appendChild(el); + }); +} + +export async function appendExternalStyle( + cssText: string, + root: HTMLElement = document.head, +): Promise { + return new Promise((resolve, reject) => { + const el: HTMLStyleElement = document.createElement('style'); + el.innerText = cssText; + + el.addEventListener( + 'load', + () => { + resolve(el); + }, + false, + ); + el.addEventListener('error', (error) => { + reject(error); + }); + + root.appendChild(el); + }); +} + +function getIfExistAssetByUrl( + url: string, + tag: 'link' | 'script', +): HTMLLinkElement | HTMLScriptElement | undefined { + return Array.from(document.getElementsByTagName(tag)).find((item) => { + const elUrl = (item as HTMLLinkElement).href || (item as HTMLScriptElement).src; + + if (/^(https?:)?\/\/([\w.]+\/?)\S*/gi.test(url)) { + // if url === http://xxx.xxx + return url === elUrl; + } else { + // if url === /xx/xx/xx.xx + return `${location.origin}${addLeadingSlash(url)}` === elUrl; + } + }); +} diff --git a/playground/renderer/src/plugin/remote/index.ts b/playground/renderer/src/plugin/remote/index.ts new file mode 100644 index 000000000..43fa192b1 --- /dev/null +++ b/playground/renderer/src/plugin/remote/index.ts @@ -0,0 +1,47 @@ +import { type PackageLoader, defineRendererPlugin } from '@alilc/lowcode-react-renderer'; +import { isPlainObject } from 'lodash-es'; +import { loadPackageUrls } from './element'; + +const remoteJsLoader: PackageLoader = { + name: 'remotejs', + async load(info) { + await loadPackageUrls(info.urls!); + + const module = (window as any)[info.library!]; + const result = + module && module.__esModule + ? typeof module.default === 'undefined' + ? module + : module.default + : module; + + const registerComponent = (module: any) => { + // 组件大包以 数组形式出现 + if (Array.isArray(module)) { + module.forEach((comp: any) => { + registerComponent(comp); + }); + } + // to delete uipaas + else if (module?.displayName) { + this.registerComponentByName(module.displayName, module); + } + }; + + registerComponent(result); + + return result; + }, + active(info) { + return !!info.urls?.length; + }, +}; + +export default defineRendererPlugin({ + name: 'remotejs', + setup(context) { + const { packageManager } = context; + + packageManager.addPackageLoader(remoteJsLoader); + }, +}); diff --git a/playground/renderer/src/plugin/uipaas/index.ts b/playground/renderer/src/plugin/uipaas/index.ts new file mode 100644 index 000000000..b1f360938 --- /dev/null +++ b/playground/renderer/src/plugin/uipaas/index.ts @@ -0,0 +1,95 @@ +import { + defineRendererPlugin, + type Router, + type RouteLocationNormalized, + LifecyclePhase, +} from '@alilc/lowcode-react-renderer'; +import { fetchSchema } from '../../apis'; +import { polyfillVuRouter } from './vuRouter'; + +export default defineRendererPlugin({ + name: 'uipaas', + async setup(setupContext) { + const { boosts, schema } = setupContext; + const { codeRuntime, util, intl } = boosts; + const router = boosts.router as Router; + + util.add('getLocale', intl.getLocale.bind(intl)); + util.add('setLocale', intl.setLocale.bind(intl)); + + const vuRouter = polyfillVuRouter(router); + + util.add('router', vuRouter); + + codeRuntime.onResolve((node: any) => { + // 兼容 uipaas 旧声明 + if (node.type === 'JSExpression' && node.extType === 'function') { + return { + type: 'JSFunction', + value: node.value, + }; + } + if (node.type === 'JSSlot' && !node.value) { + return false; + } + + return node; + }); + + const componentsTree = schema.get('componentsTree'); + const pages = schema.get('pages'); + const { applicationCode, devVersion } = schema.get('meta') ?? ({} as any); + + const loadSchema = async (location: RouteLocationNormalized) => { + const matched = location.matched[location.matched.length - 1]; + + if (!matched.page) return; + + const toPage = pages?.find((page) => page.id === matched.page); + + if (toPage) { + const schemaIsloaded = componentsTree.some((item) => item.id === toPage.mappingId); + if (!schemaIsloaded) { + const fetchedSchema = await fetchSchema( + applicationCode as string, + devVersion as string, + toPage.meta!.slug as string, + ); + const pageData = fetchedSchema.pages[0]; + + const componentTree = { + ...pageData.componentsTree[0], + id: fetchedSchema.formUuid, + }; + + await schema.set('componentsTree', [...componentsTree, componentTree]); + if (pageData.utils) { + await schema.set( + 'utils', + schema.get('utils')?.concat(pageData.utils) ?? pageData.utils, + ); + } + } + } + }; + + router.beforeRouteLeave(async (to) => { + await loadSchema(to); + return true; + }); + + // first load + setupContext.whenLifeCylePhaseChange(LifecyclePhase.Ready, async () => { + await loadSchema(router.getCurrentLocation()); + util.add({ + name: 'datasourceHandler', + type: 'npm', + content: { + package: '@ali/vu-datasource-handler', + exportName: 'datasourceHandler', + }, + }); + }); + }, + dependsOn: ['rendererRouter'], +}); diff --git a/playground/renderer/src/plugin/uipaas/vuDataSource.ts b/playground/renderer/src/plugin/uipaas/vuDataSource.ts new file mode 100644 index 000000000..0544f45fb --- /dev/null +++ b/playground/renderer/src/plugin/uipaas/vuDataSource.ts @@ -0,0 +1,111 @@ +import { type ModelDataSourceCreator, type Spec } from '@alilc/lowcode-react-renderer'; + +// ref: @ali/vu-datasource-handler type Center +interface DataSourceInstance { + requestMap: Record; + isInited: boolean; + config: { + globalConfig?: Record; + [key: string]: any; + }; +} + +export const dataSourceCreator: ModelDataSourceCreator = (dataSourceSchema, codeRuntime) => { + const dataSourceConfig = codeRuntime.resolve(dataSourceSchema); + const { list } = dataSourceConfig; + const scope = codeRuntime.getScope().value; + const dataSourceInstance: DataSourceInstance = { + requestMap: {}, + isInited: false, + config: dataSourceConfig, + }; + + // init set state + const newState: any = {}; + for (const item of list) { + newState[item.id] = undefined; + } + scope.setState(newState); + + const syncItems: Spec.DataSourceMapItem[] = []; + const asyncItems: Spec.DataSourceMapItem[] = []; + + for (const item of list) { + // @ali/vu-datasource-handler + // TODO 有个默认实现,包含 fetch,JSONP,mtop + let Handler = item.requestHandler; + if (!Handler) { + continue; + } + + dataSourceInstance.requestMap[item.id] = new Handler(item, dataSourceInstance, scope); + + if (item.isInit && item.options.isSync) { + syncItems.push(dataSourceInstance.requestMap[item.id]); + } + if (item.isInit && !item.options.isSync) { + asyncItems.push(dataSourceInstance.requestMap[item.id]); + } + } + + function reload() { + dataSourceInstance.isInited = false; + const syncItemsLength = syncItems.length; + const asyncItemsLength = asyncItems.length; + if (syncItemsLength + asyncItemsLength === 0) { + dataSourceInstance.isInited = true; + return; + } + + let loadedCount = 0; + const callback = () => { + if (loadedCount >= syncItemsLength + asyncItemsLength) { + dataSourceInstance.isInited = true; + } + }; + if (syncItemsLength > 0) { + let i = 0; + let next: (c: Spec.DataSourceMapItem) => void; + const finallyHandler = () => { + i++; + loadedCount++; + if (i < syncItemsLength) { + next(syncItems[i]); + } else { + callback(); + } + }; + next = (current: Spec.DataSourceMapItem) => { + current + .load() + .catch((e) => { + console.error(`数据源 ${(current as any)?.config?.id} 加载出错`, e); + }) + .finally(() => { + finallyHandler(); + }); + }; + next(syncItems[i]); + } + if (asyncItemsLength > 0) { + asyncItems.forEach((current) => { + current + .load() + .catch((e) => { + console.error(`数据源 ${(current as any)?.config?.id} 加载出错`, e); + }) + .finally(() => { + loadedCount++; + callback(); + }); + }); + } + } + + reload(); + + return { + dataSourceMap: dataSourceInstance.requestMap, + reloadDataSource: reload, + }; +}; diff --git a/playground/renderer/src/plugin/uipaas/vuRouter.ts b/playground/renderer/src/plugin/uipaas/vuRouter.ts new file mode 100644 index 000000000..1412719d9 --- /dev/null +++ b/playground/renderer/src/plugin/uipaas/vuRouter.ts @@ -0,0 +1,34 @@ +import { type Router } from '@alilc/lowcode-react-renderer'; + +interface VuRouter { + push(path: string, query?: Record, blank?: boolean, isUrl?: boolean): void; + + replace(path: string, params?: Record, blank?: boolean, isUrl?: boolean): void; +} + +const addLeadingSlash = (path: string): string => { + return path.charAt(0) === '/' ? path : `/${path}`; +}; + +export function polyfillVuRouter(router: Router): VuRouter { + const vuRouter: VuRouter = { + push(path, query, blank, isUrl) { + router.push( + router.resolve({ + path: addLeadingSlash(path), + searchParams: query ? new URLSearchParams([...Object.entries(query)]) : undefined, + }), + ); + }, + replace(path, query, blank, isUrl) { + router.replace( + router.resolve({ + path: addLeadingSlash(path), + searchParams: query ? new URLSearchParams([...Object.entries(query)]) : undefined, + }), + ); + }, + }; + + return vuRouter; +} diff --git a/playground/renderer/src/utils/axios/index.ts b/playground/renderer/src/utils/axios/index.ts new file mode 100644 index 000000000..e97810f65 --- /dev/null +++ b/playground/renderer/src/utils/axios/index.ts @@ -0,0 +1,18 @@ +import axios from 'axios'; + +export const axiosInstance = axios.create({}); + +axiosInstance.interceptors.response.use( + (response) => { + if (response.status === 200) { + return response; + } + + return Promise.reject(response); + }, + (error) => { + return Promise.reject(error); + }, +); + +export * from './jsonpAdapter'; diff --git a/playground/renderer/src/utils/axios/jsonpAdapter.ts b/playground/renderer/src/utils/axios/jsonpAdapter.ts new file mode 100644 index 000000000..b426ecae2 --- /dev/null +++ b/playground/renderer/src/utils/axios/jsonpAdapter.ts @@ -0,0 +1,55 @@ +import { AxiosError, type AxiosAdapter, type AxiosResponse } from 'axios'; +import { isPlainObject } from 'lodash-es'; + +let cid = 1; + +export const jsonpAdapter: AxiosAdapter = (config) => { + return new Promise((resolve, reject) => { + const url = new URL(config.url!); + + if (isPlainObject(config.params)) { + for (const key of Object.keys(config.params)) { + url.searchParams.append(key, config.params[key]); + } + } + + const jsonp = url.searchParams.get('callback') ?? `__jsonpCallback${cid++}`; + + url.searchParams.append('_', `${Date.now()}`); + url.searchParams.append('callback', jsonp); + + (window as any)[jsonp] = function (data: any) { + const response: AxiosResponse = { + data, + status: 200, + statusText: '', + headers: config.headers, + config, + }; + + resolve(response); + }; + + const scriptEl = document.createElement('script'); + + scriptEl.async = true; + scriptEl.crossOrigin = 'anonymous'; + + scriptEl.src = url.toString(); + + const cleanup = () => { + scriptEl.parentElement?.removeChild(scriptEl); + }; + + scriptEl.onload = () => { + cleanup(); + }; + + scriptEl.onerror = () => { + cleanup(); + reject(new AxiosError('Network Error', AxiosError.ERR_NETWORK, config)); + }; + + document.body.append(scriptEl); + }); +}; diff --git a/playground/renderer/src/utils/index.ts b/playground/renderer/src/utils/index.ts new file mode 100644 index 000000000..fc9338fd0 --- /dev/null +++ b/playground/renderer/src/utils/index.ts @@ -0,0 +1,3 @@ +export * from './axios'; +export * from './parseDeps'; +export * from './parseSchema'; diff --git a/playground/renderer/src/utils/parseDeps.ts b/playground/renderer/src/utils/parseDeps.ts new file mode 100644 index 000000000..4d430126a --- /dev/null +++ b/playground/renderer/src/utils/parseDeps.ts @@ -0,0 +1,105 @@ +import { type Spec, type LowCodeComponent } from '@alilc/lowcode-react-renderer'; + +interface PackageDetail { + packageName: string; + version: string; + alias?: string; + urls: string[]; + library: string; + lcMetaLibrary?: string; + prototypeConfigsUrl?: string[]; + prototypeViewsUrl?: string[]; + prototypeAndViewUrls?: Record; + components?: Record[]; + prototypeUrls?: Record; +} + +interface AppDeps { + appKey: string; + systemType?: string; + externals: Record[]; + componentDependencies: PackageDetail[]; + utilsDependencies: PackageDetail[]; + otherDependencies: PackageDetail[]; + lowCodeDependencies: any[]; +} + +const ignoreVuPackages = [ + '@ali/vu-toolkit', + '@ali/vu-router', + '@ali/vu-router-spa', + '@ali/vm-router', + '@ali/vu-dataSource', +]; + +export function transDepsToPackages(appDepsData: AppDeps) { + const { componentDependencies, utilsDependencies, lowCodeDependencies } = appDepsData; + + const convertedComponentDeps = componentDependencies.map(convertPackageDetailToPackage); + const convertedUtilsDeps = utilsDependencies + .map(convertPackageDetailToPackage) + .filter((item) => item.package && !ignoreVuPackages.includes(item.package)); + const convertedLowCodeDeps = lowCodeDependencies.map(convertLowCodeDetailToPackage); + + return { + components: convertedComponentDeps, + lowCodeComponents: convertedLowCodeDeps, + utils: convertedUtilsDeps, + }; +} + +function convertPackageDetailToPackage(packageDetail: PackageDetail): Spec.Package { + const advancedUrls = urlsGroupByMode(packageDetail.urls, 'view', ['mobile']); + + return { + id: packageDetail.packageName, + package: packageDetail.packageName, + version: packageDetail.version, + type: 'proCode', + urls: advancedUrls.default, + advancedUrls, + library: packageDetail.library, + }; +} + +function convertLowCodeDetailToPackage(lowCodeDependency: any): LowCodeComponent { + return { + id: lowCodeDependency.uuid, + version: lowCodeDependency.version, + type: 'lowCode', + schema: { + ...lowCodeDependency.schema, + componentsMap: [], + }, + componentName: lowCodeDependency.name, + library: lowCodeDependency.name, + }; +} + +function urlsGroupByMode(urls: string[], type: string, devices: string[]): Spec.MultiModeUrls { + const result: Spec.MultiModeUrls = { + default: [], + }; + + for (const url of urls) { + // rax 单独处理 + if (url.includes(`${type}.mobile.rax`)) { + result.rax ??= []; + result.rax.push(url); + continue; + } + + const regExp = new RegExp(`${type}(?:.(${devices.join('|')}))?\\.`); + const r = regExp.exec(url); + + let key = 'default'; + if (r?.[1]) { + key = r[1]; + result[key] ??= []; + } + + result[key].push(url); + } + + return result; +} diff --git a/playground/renderer/src/utils/parseGlogalConfig.ts b/playground/renderer/src/utils/parseGlogalConfig.ts new file mode 100644 index 000000000..b2f5db430 --- /dev/null +++ b/playground/renderer/src/utils/parseGlogalConfig.ts @@ -0,0 +1,20 @@ +declare namespace global { + interface window { + g_config: any; + } +} + +// window.g_config = window.g_config || {}; +// window.g_config.subAppKey = window.g_config.subAppKey || '0.44.0'; +// window.g_config.currentLoadedWebJsVersion = '0.44.0'; +// window.g_config.fetchUrl = window.g_config.fetchUrl || 'https://uipaas-node.alibaba-inc.com/app/getLatestPageSchema.jsonp?appType={{appKey}}&systemType={{subAppKey}}'; +// window.g_config.assetsFetchUrl = window.g_config.assetsFetchUrl || 'https://uipaas-node.alibaba-inc.com/app/assets.jsonp?appType=test_mario_021701&systemType=0.44.0'; +// window.g_config.appKey = window.g_config.appKey || 'test_mario_021701'; + +// window.pageConfig = window.pageConfig || {}; +// window.pageConfig.appType = window.pageConfig.appType || 'test_mario_021701'; +// window.pageConfig.subAppType = window.pageConfig.subAppType || '0.44.0'; + +export function getGlobalConfig() { + return {}; +} diff --git a/playground/renderer/src/utils/parseSchema.ts b/playground/renderer/src/utils/parseSchema.ts new file mode 100644 index 000000000..3361b2afa --- /dev/null +++ b/playground/renderer/src/utils/parseSchema.ts @@ -0,0 +1,35 @@ +import { type Spec } from '@alilc/lowcode-react-renderer'; + +export function initProjectSchema(schemaData: any) { + // formUuid 为当前页面 uid + const pageConfigs: Spec.PageConfig[] = schemaData.nav.data.map((item: any) => { + const config: Spec.PageConfig = { + id: item.navUuid, + mappingId: item.navUuid, + type: 'lowCode', + meta: { + slug: item.slug, + title: item.title, + }, + }; + + return config; + }); + + const routerConfig: Spec.RouterConfig = { + historyMode: 'browser', + baseName: '/', + routes: pageConfigs.map((item) => { + return { + path: '/' + item.meta!.slug, + name: item.id, + page: item.id, + }; + }), + }; + + return { + pages: pageConfigs, + router: routerConfig, + }; +} diff --git a/playground/renderer/tsconfig.json b/playground/renderer/tsconfig.json new file mode 100644 index 000000000..b4e69ae1f --- /dev/null +++ b/playground/renderer/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist" + } +} diff --git a/playground/renderer/vite.config.ts b/playground/renderer/vite.config.ts new file mode 100644 index 000000000..a7f725435 --- /dev/null +++ b/playground/renderer/vite.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite'; +import createExternal from 'vite-plugin-external'; + +export default defineConfig({ + plugins: [ + createExternal({ + externals: { + react: 'React', + 'react-dom': 'ReactDOM', + '@alifd/next': 'Next', + }, + }), + ], +}); diff --git a/playground/test/index.html b/playground/test/index.html index 0ba5ee847..e6127f111 100644 --- a/playground/test/index.html +++ b/playground/test/index.html @@ -6,22 +6,6 @@ Document -
- - - - - - - - - - - - - - - diff --git a/playground/test/package.json b/playground/test/package.json new file mode 100644 index 000000000..fc35739a2 --- /dev/null +++ b/playground/test/package.json @@ -0,0 +1,9 @@ +{ + "name": "playground-test", + "scripts": { + "dev": "vite" + }, + "dependencies": { + "@alilc/lowcode-shared": "workspace:*" + } +} diff --git a/playground/test/src/index.ts b/playground/test/src/index.ts index 3bfbdf38d..984dbd54f 100644 --- a/playground/test/src/index.ts +++ b/playground/test/src/index.ts @@ -1,39 +1,22 @@ -import { UAParser, createSignal, effect } from '@alilc/lowcode-shared'; -import { History, Hotkey } from '@alilc/lowcode-editor-core'; +import { signal, computed, watch } from '@alilc/lowcode-shared'; -const parser = new UAParser().getResult(); -console.log('%c [ parser ]-5', 'font-size:13px; background:pink; color:#bf2c9f;', parser); - -const hotkey = new Hotkey(); -hotkey.mount(window); +const ref1 = signal({ + state: { + a: 1, + }, +}); -const signal = createSignal(0); +const computedA = computed(() => ref1.value.state.a % 2 === 0); -effect(() => { - console.log('effect ', signal.value); +watch(computedA, (newValue) => { + console.log('%c [ newValue ]-11', 'font-size:13px; background:pink; color:#bf2c9f;', newValue); }); -const history = new History(signal, (value) => { - signal.value = value; - console.log('redo', value); -}); +const button = document.createElement('button'); -const button1 = document.createElement('button'); -button1.innerHTML = 'click'; -button1.onclick = () => { - signal.value++; +button.innerHTML = 'text1111'; +button.onclick = () => { + ref1.value.state.a += 1; }; -document.getElementById('app')!.append(button1); - -hotkey.bind(['command+z'], () => { - console.log('undo'); - history.back(); - return false; -}); - -hotkey.bind(['command+shift+z'], () => { - console.log('redo'); - history.forward(); - return false; -}); +document.body.appendChild(button); diff --git a/playground/tsconfig.json b/playground/tsconfig.json deleted file mode 100644 index 60eb4078a..000000000 --- a/playground/tsconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "outDir": "dist", - } -} diff --git a/playground/vite.config.ts b/playground/vite.config.ts deleted file mode 100644 index 56a6f0e9b..000000000 --- a/playground/vite.config.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { defineConfig } from 'vite' -import { resolve } from 'node:path' -import createExternal from 'vite-plugin-external' - -export default defineConfig({ - build: { - rollupOptions: { - input: { - engine: resolve(import.meta.dirname, 'engine/index.html'), - renderer: resolve(import.meta.dirname, 'renderer/index.html') - } - } - }, - plugins: [ - createExternal({ - externals: { - react: 'React', - 'react-dom': 'ReactDOM', - '@alifd/next': 'Next' - } - }) - ] -})