mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-11 18:42:56 +00:00
fix: fix renderer some bugs
This commit is contained in:
parent
ac8aa2c5a4
commit
a855c05d67
@ -34,13 +34,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@alilc/lowcode-shared": "workspace:*",
|
||||
"@alilc/lowcode-types": "workspace:*",
|
||||
"@alilc/lowcode-utils": "workspace:*",
|
||||
"@formatjs/intl": "^2.10.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"events": "^3.3.0"
|
||||
"lodash-es": "^4.17.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
@ -49,8 +43,6 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@alilc/lowcode-shared": "workspace:*",
|
||||
"@alilc/lowcode-types": "workspace:*",
|
||||
"@alilc/lowcode-utils": "workspace:*",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
|
||||
@ -1,4 +1,2 @@
|
||||
export * from './preference';
|
||||
export * from './hotkey';
|
||||
export * from './intl';
|
||||
export * from './instantiation';
|
||||
|
||||
@ -1,154 +0,0 @@
|
||||
import {
|
||||
signal,
|
||||
computed,
|
||||
effect,
|
||||
createLogger,
|
||||
type Spec,
|
||||
type Signal,
|
||||
type ComputedSignal,
|
||||
type PlainObject,
|
||||
} from '@alilc/lowcode-shared';
|
||||
import { createIntl, createIntlCache, type IntlShape as IntlFormatter } from '@formatjs/intl';
|
||||
import { mapKeys } from 'lodash-es';
|
||||
|
||||
export { IntlFormatter };
|
||||
|
||||
const logger = createLogger({ level: 'warn', bizName: 'globalLocale' });
|
||||
|
||||
/**
|
||||
* todo: key 需要被统一
|
||||
*/
|
||||
const STORED_LOCALE_KEY = 'ali-lowcode-config';
|
||||
|
||||
export type Locale = string;
|
||||
export type IntlMessage = Spec.I18nMap[Locale];
|
||||
export type IntlMessageRecord = Spec.I18nMap;
|
||||
|
||||
export class Intl {
|
||||
#locale: Signal<Locale>;
|
||||
#messageStore: Signal<IntlMessageRecord>;
|
||||
#currentMessage: ComputedSignal<IntlMessage>;
|
||||
#intlShape: IntlFormatter;
|
||||
|
||||
constructor(defaultLocale?: string, messages: IntlMessageRecord = {}) {
|
||||
if (defaultLocale) {
|
||||
defaultLocale = nomarlizeLocale(defaultLocale);
|
||||
} else {
|
||||
defaultLocale = 'zh-CN';
|
||||
}
|
||||
|
||||
const messageStore = mapKeys(messages, (_, key) => {
|
||||
return nomarlizeLocale(key);
|
||||
});
|
||||
|
||||
this.#locale = signal(defaultLocale);
|
||||
this.#messageStore = signal(messageStore);
|
||||
this.#currentMessage = computed(() => {
|
||||
return this.#messageStore.value[this.#locale.value] ?? {};
|
||||
});
|
||||
|
||||
effect(() => {
|
||||
const cache = createIntlCache();
|
||||
this.#intlShape = createIntl(
|
||||
{
|
||||
locale: this.#locale.value,
|
||||
messages: this.#currentMessage.value,
|
||||
},
|
||||
cache,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
getLocale() {
|
||||
return this.#locale.value;
|
||||
}
|
||||
|
||||
setLocale(locale: Locale) {
|
||||
const nomarlizedLocale = nomarlizeLocale(locale);
|
||||
this.#locale.value = nomarlizedLocale;
|
||||
}
|
||||
|
||||
addMessages(locale: Locale, messages: IntlMessage) {
|
||||
locale = nomarlizeLocale(locale);
|
||||
const original = this.#messageStore.value[locale];
|
||||
|
||||
this.#messageStore.value[locale] = Object.assign(original, messages);
|
||||
}
|
||||
|
||||
getFormatter(): IntlFormatter {
|
||||
return this.#intlShape;
|
||||
}
|
||||
}
|
||||
|
||||
function initializeLocale() {
|
||||
let result: Locale | undefined;
|
||||
|
||||
let config: PlainObject = {};
|
||||
try {
|
||||
// store 1: config from storage
|
||||
config = JSON.parse(localStorage.getItem(STORED_LOCALE_KEY) || '');
|
||||
} catch {
|
||||
// ignore;
|
||||
}
|
||||
if (config?.locale) {
|
||||
result = (config.locale || '').replace('_', '-');
|
||||
logger.debug(`getting locale from localStorage: ${result}`);
|
||||
}
|
||||
|
||||
if (!result && navigator.language) {
|
||||
// store 2: config from system
|
||||
result = nomarlizeLocale(navigator.language);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
logger.warn(
|
||||
'something when wrong when trying to get locale, use zh-CN as default, please check it out!',
|
||||
);
|
||||
result = 'zh-CN';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const navigatorLanguageMapping: Record<string, string> = {
|
||||
en: 'en-US',
|
||||
zh: 'zh-CN',
|
||||
zt: 'zh-TW',
|
||||
es: 'es-ES',
|
||||
pt: 'pt-PT',
|
||||
fr: 'fr-FR',
|
||||
de: 'de-DE',
|
||||
it: 'it-IT',
|
||||
ru: 'ru-RU',
|
||||
ja: 'ja-JP',
|
||||
ko: 'ko-KR',
|
||||
ar: 'ar-SA',
|
||||
tr: 'tr-TR',
|
||||
th: 'th-TH',
|
||||
vi: 'vi-VN',
|
||||
nl: 'nl-NL',
|
||||
he: 'iw-IL',
|
||||
id: 'in-ID',
|
||||
pl: 'pl-PL',
|
||||
hi: 'hi-IN',
|
||||
uk: 'uk-UA',
|
||||
ms: 'ms-MY',
|
||||
tl: 'tl-PH',
|
||||
};
|
||||
|
||||
/**
|
||||
* nomarlize navigator.language or user input's locale
|
||||
* eg: zh -> zh-CN, zh_CN -> zh-CN, zh-cn -> zh-CN
|
||||
* @param target
|
||||
*/
|
||||
function nomarlizeLocale(target: Locale) {
|
||||
if (navigatorLanguageMapping[target]) {
|
||||
return navigatorLanguageMapping[target];
|
||||
}
|
||||
|
||||
const replaced = target.replace('_', '-');
|
||||
const splited = replaced.split('-').slice(0, 2);
|
||||
splited[1] = splited[1].toUpperCase();
|
||||
|
||||
return splited.join('-');
|
||||
}
|
||||
@ -1,22 +1,12 @@
|
||||
import { createRenderer, type AppOptions } from '@alilc/lowcode-renderer-core';
|
||||
import { type ComponentType } from 'react';
|
||||
import { createRenderer } from '@alilc/lowcode-renderer-core';
|
||||
import { type Root, createRoot } from 'react-dom/client';
|
||||
import { ApplicationView, RendererContext, boosts } from '../app';
|
||||
|
||||
export interface ReactAppOptions extends AppOptions {
|
||||
faultComponent?: ComponentType<any>;
|
||||
}
|
||||
import { type ReactAppOptions, RendererContext } from './context';
|
||||
import { ApplicationView, boosts } from '../app';
|
||||
|
||||
export const createApp = async (options: ReactAppOptions) => {
|
||||
return createRenderer(async (context) => {
|
||||
const { schema, boostsManager } = context;
|
||||
|
||||
// set config
|
||||
// if (options.faultComponent) {
|
||||
// context.config.set('faultComponent', options.faultComponent);
|
||||
// }
|
||||
|
||||
// extends boosts
|
||||
boostsManager.extend(boosts.toExpose());
|
||||
|
||||
let root: Root | undefined;
|
||||
@ -27,10 +17,11 @@ export const createApp = async (options: ReactAppOptions) => {
|
||||
|
||||
const defaultId = schema.get('config')?.targetRootID ?? 'app';
|
||||
const rootElement = normalizeContainer(containerOrId, defaultId);
|
||||
const contextValue = { ...context, options };
|
||||
|
||||
root = createRoot(rootElement);
|
||||
root.render(
|
||||
<RendererContext.Provider value={context}>
|
||||
<RendererContext.Provider value={contextValue}>
|
||||
<ApplicationView />
|
||||
</RendererContext.Provider>,
|
||||
);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { createRenderer, type AppOptions } from '@alilc/lowcode-renderer-core';
|
||||
import { FunctionComponent } from 'react';
|
||||
import { type LowCodeComponentProps, createComponentBySchema } from '../runtime/schema';
|
||||
import { RendererContext } from '../app/context';
|
||||
import { RendererContext } from '../api/context';
|
||||
|
||||
interface Render {
|
||||
toComponent(): FunctionComponent<LowCodeComponentProps>;
|
||||
@ -12,10 +12,11 @@ export async function createComponent(options: AppOptions) {
|
||||
const { schema } = context;
|
||||
|
||||
const LowCodeComponent = createComponentBySchema(schema.get('componentsTree')[0]);
|
||||
const contextValue = { ...context, options };
|
||||
|
||||
function Component(props: LowCodeComponentProps) {
|
||||
return (
|
||||
<RendererContext.Provider value={context}>
|
||||
<RendererContext.Provider value={contextValue}>
|
||||
<LowCodeComponent {...props} />
|
||||
</RendererContext.Provider>
|
||||
);
|
||||
|
||||
14
packages/react-renderer/src/api/context.ts
Normal file
14
packages/react-renderer/src/api/context.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { type ComponentType, createContext, useContext } from 'react';
|
||||
import { type AppOptions, type RenderContext } from '@alilc/lowcode-renderer-core';
|
||||
|
||||
export interface ReactAppOptions extends AppOptions {
|
||||
faultComponent?: ComponentType<any>;
|
||||
}
|
||||
|
||||
export const RendererContext = createContext<RenderContext & { options: ReactAppOptions }>(
|
||||
undefined!,
|
||||
);
|
||||
|
||||
RendererContext.displayName = 'RendererContext';
|
||||
|
||||
export const useRendererContext = () => useContext(RendererContext);
|
||||
@ -1,8 +0,0 @@
|
||||
import { createContext, useContext } from 'react';
|
||||
import { type RenderContext } from '@alilc/lowcode-renderer-core';
|
||||
|
||||
export const RendererContext = createContext<RenderContext>(undefined!);
|
||||
|
||||
RendererContext.displayName = 'RendererContext';
|
||||
|
||||
export const useRenderContext = () => useContext(RendererContext);
|
||||
@ -1,3 +1,2 @@
|
||||
export * from './context';
|
||||
export * from './boosts';
|
||||
export * from './view';
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { useRenderContext } from './context';
|
||||
import { useRendererContext } from '../api/context';
|
||||
import { getComponentByName } from '../runtime/schema';
|
||||
import { boosts } from './boosts';
|
||||
|
||||
export function ApplicationView() {
|
||||
const renderContext = useRenderContext();
|
||||
const { schema } = renderContext;
|
||||
const rendererContext = useRendererContext();
|
||||
const { schema } = 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, renderContext);
|
||||
const Layout = getComponentByName(componentName, rendererContext);
|
||||
|
||||
if (Layout) {
|
||||
const layoutProps: any = layoutConfig.props ?? {};
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
export * from './api/app';
|
||||
export * from './api/component';
|
||||
export { useRenderContext, defineRendererPlugin } from './app';
|
||||
export * from './api/context';
|
||||
export { defineRendererPlugin } from './app';
|
||||
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';
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
export * from './context';
|
||||
export * from './plugin';
|
||||
export type { Router, RouterHistory } from '@alilc/lowcode-renderer-router';
|
||||
export type * from '@alilc/lowcode-renderer-router';
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { defineRendererPlugin } from '../app/boosts';
|
||||
import { LifecyclePhase } from '@alilc/lowcode-renderer-core';
|
||||
import { createRouter, type RouterOptions } from '@alilc/lowcode-renderer-router';
|
||||
import { createRouterView } from './routerView';
|
||||
import { RouteOutlet } from './route';
|
||||
@ -13,7 +12,7 @@ const defaultRouterOptions: RouterOptions = {
|
||||
export const routerPlugin = defineRendererPlugin({
|
||||
name: 'rendererRouter',
|
||||
async setup(context) {
|
||||
const { whenLifeCylePhaseChange, schema, boosts } = context;
|
||||
const { schema, boosts } = context;
|
||||
|
||||
let routerConfig = defaultRouterOptions;
|
||||
|
||||
@ -27,17 +26,14 @@ export const routerPlugin = defineRendererPlugin({
|
||||
}
|
||||
|
||||
const router = createRouter(routerConfig);
|
||||
|
||||
boosts.codeRuntime.getScope().set('router', router);
|
||||
boosts.temporaryUse('router', router);
|
||||
|
||||
const RouterView = createRouterView(router);
|
||||
|
||||
boosts.addAppWrapper(RouterView);
|
||||
boosts.setOutlet(RouteOutlet);
|
||||
|
||||
whenLifeCylePhaseChange(LifecyclePhase.AfterInitPackageLoad).then(() => {
|
||||
return router.isReady();
|
||||
});
|
||||
boosts.codeRuntime.getScope().set('router', router);
|
||||
boosts.temporaryUse('router', router);
|
||||
|
||||
await router.isReady();
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useRenderContext } from '../app/context';
|
||||
import { useRendererContext } from '../api/context';
|
||||
import { OutletProps } from '../app/boosts';
|
||||
import { useRouteLocation } from './context';
|
||||
import { createComponentBySchema } from '../runtime/schema';
|
||||
|
||||
export function RouteOutlet(props: OutletProps) {
|
||||
const context = useRenderContext();
|
||||
const context = useRendererContext();
|
||||
const location = useRouteLocation();
|
||||
const { schema, packageManager } = context;
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ import {
|
||||
type Spec,
|
||||
} from '@alilc/lowcode-shared';
|
||||
import { type ComponentType, type ReactInstance, useMemo, createElement } from 'react';
|
||||
import { useRenderContext } from '../app/context';
|
||||
import { useRendererContext } from '../api/context';
|
||||
import { useReactiveStore } from './hooks/useReactiveStore';
|
||||
import { useModel } from './context';
|
||||
import { getComponentByName } from './schema';
|
||||
@ -65,10 +65,10 @@ export function WidgetComponent(props: WidgetRendererProps) {
|
||||
const componentNode = widget.node as NormalizedComponentNode;
|
||||
const { ref, ...componentProps } = componentNode.props;
|
||||
|
||||
const renderContext = useRenderContext();
|
||||
const rendererContext = useRendererContext();
|
||||
|
||||
const Component = useMemo(
|
||||
() => getComponentByName(componentNode.componentName, renderContext),
|
||||
() => getComponentByName(componentNode.componentName, rendererContext),
|
||||
[widget],
|
||||
);
|
||||
|
||||
@ -94,11 +94,16 @@ export function WidgetComponent(props: WidgetRendererProps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const finalProps = {
|
||||
...otherProps,
|
||||
...state.props,
|
||||
};
|
||||
|
||||
return createElement(
|
||||
Component,
|
||||
{
|
||||
...otherProps,
|
||||
...state.props,
|
||||
...finalProps,
|
||||
id: finalProps.id ? finalProps.id : undefined,
|
||||
key: widget.key,
|
||||
ref: attachRef,
|
||||
},
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
export const dataSourceCreator = () =>
|
||||
({
|
||||
dataSourceMap: {},
|
||||
reloadDataSource: () => {},
|
||||
}) as any;
|
||||
@ -1,9 +1,8 @@
|
||||
import { invariant, isLowCodeComponentPackage, type Spec } from '@alilc/lowcode-shared';
|
||||
import { forwardRef, useRef, useEffect } from 'react';
|
||||
import { isValidElementType } from 'react-is';
|
||||
import { useRenderContext } from '../app/context';
|
||||
import { useRendererContext } from '../api/context';
|
||||
import { reactiveStateFactory } from './reactiveState';
|
||||
import { dataSourceCreator } from './dataSource';
|
||||
import { type ReactComponent, type ReactWidget, createElementByWidget } from './components';
|
||||
import { ModelContextProvider } from './context';
|
||||
import { appendExternalStyle } from '../utils/element';
|
||||
@ -39,9 +38,7 @@ export function getComponentByName(
|
||||
name: string,
|
||||
{ packageManager, boostsManager }: RenderContext,
|
||||
): ReactComponent {
|
||||
const componentsRecord = packageManager.getComponentsNameRecord<ReactComponent>();
|
||||
// read cache first
|
||||
const result = lowCodeComponentsCache.get(name) || componentsRecord[name];
|
||||
const result = lowCodeComponentsCache.get(name) || packageManager.getComponent(name);
|
||||
|
||||
if (isLowCodeComponentPackage(result)) {
|
||||
const { schema, ...metadata } = result;
|
||||
@ -87,8 +84,8 @@ export function createComponentBySchema(
|
||||
props: LowCodeComponentProps,
|
||||
ref: ForwardedRef<any>,
|
||||
) {
|
||||
const renderContext = useRenderContext();
|
||||
const { componentTreeModel } = renderContext;
|
||||
const renderContext = useRendererContext();
|
||||
const { options, componentTreeModel } = renderContext;
|
||||
|
||||
const modelRef = useRef<IComponentTreeModel<ReactComponent, ReactInstance>>();
|
||||
|
||||
@ -110,7 +107,7 @@ export function createComponentBySchema(
|
||||
model.initialize({
|
||||
defaultProps: props,
|
||||
stateCreator: reactiveStateFactory,
|
||||
dataSourceCreator,
|
||||
dataSourceCreator: options.dataSourceCreator,
|
||||
});
|
||||
|
||||
model.triggerLifeCycle('constructor');
|
||||
@ -123,11 +120,6 @@ export function createComponentBySchema(
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const scopeValue = model.codeScope.value;
|
||||
|
||||
// init dataSource
|
||||
scopeValue.reloadDataSource?.();
|
||||
|
||||
// trigger lifeCycles
|
||||
// componentDidMount?.();
|
||||
model.triggerLifeCycle('componentDidMount');
|
||||
|
||||
@ -1,7 +1,3 @@
|
||||
export const addLeadingSlash = (path: string): string => {
|
||||
return path.charAt(0) === '/' ? path : `/${path}`;
|
||||
};
|
||||
|
||||
export interface ExternalElementOptions {
|
||||
id?: string;
|
||||
root?: HTMLElement;
|
||||
|
||||
@ -14,10 +14,10 @@ describe('LifeCycleService', () => {
|
||||
lifeCycle.when(LifecyclePhase.Ready).finally(() => {
|
||||
result += '2';
|
||||
});
|
||||
lifeCycle.when(LifecyclePhase.AfterInitPackageLoad).then(() => {
|
||||
lifeCycle.when(LifecyclePhase.Inited).then(() => {
|
||||
result += '3';
|
||||
});
|
||||
lifeCycle.when(LifecyclePhase.AfterInitPackageLoad).finally(() => {
|
||||
lifeCycle.when(LifecyclePhase.Inited).finally(() => {
|
||||
result += '4';
|
||||
});
|
||||
|
||||
@ -27,7 +27,7 @@ describe('LifeCycleService', () => {
|
||||
|
||||
expect(result).toEqual('12');
|
||||
|
||||
lifeCycle.phase = LifecyclePhase.AfterInitPackageLoad;
|
||||
lifeCycle.phase = LifecyclePhase.Inited;
|
||||
|
||||
await sleep();
|
||||
|
||||
|
||||
@ -30,21 +30,7 @@ export class RendererMain<RenderObject> {
|
||||
@IComponentTreeModelService private componentTreeModelService: IComponentTreeModelService,
|
||||
@IBoostsService private boostsService: IBoostsService,
|
||||
@ILifeCycleService private lifeCycleService: ILifeCycleService,
|
||||
) {
|
||||
this.lifeCycleService.when(LifecyclePhase.OptionsResolved).then(async () => {
|
||||
const renderContext = {
|
||||
schema: this.schemaService,
|
||||
packageManager: this.packageManagementService,
|
||||
boostsManager: this.boostsService,
|
||||
componentTreeModel: this.componentTreeModelService,
|
||||
lifeCycle: this.lifeCycleService,
|
||||
};
|
||||
|
||||
this.renderObject = await this.adapter(renderContext);
|
||||
|
||||
this.lifeCycleService.phase = LifecyclePhase.Ready;
|
||||
});
|
||||
}
|
||||
) {}
|
||||
|
||||
async main(options: AppOptions, adapter: RenderAdapter<RenderObject>) {
|
||||
const { schema, mode, plugins = [] } = options;
|
||||
@ -58,20 +44,26 @@ export class RendererMain<RenderObject> {
|
||||
|
||||
this.codeRuntimeService.initialize(options.codeRuntime ?? {});
|
||||
|
||||
this.lifeCycleService.phase = LifecyclePhase.OptionsResolved;
|
||||
await this.lifeCycleService.setPhase(LifecyclePhase.OptionsResolved);
|
||||
|
||||
await this.lifeCycleService.when(LifecyclePhase.Ready);
|
||||
const renderContext = {
|
||||
schema: this.schemaService,
|
||||
packageManager: this.packageManagementService,
|
||||
boostsManager: this.boostsService,
|
||||
componentTreeModel: this.componentTreeModelService,
|
||||
lifeCycle: this.lifeCycleService,
|
||||
};
|
||||
|
||||
this.renderObject = await this.adapter(renderContext);
|
||||
|
||||
await this.extensionHostService.registerPlugin(plugins);
|
||||
|
||||
// 先加载插件提供 package loader
|
||||
await this.packageManagementService.loadPackages(this.initOptions.packages ?? []);
|
||||
|
||||
this.lifeCycleService.phase = LifecyclePhase.AfterInitPackageLoad;
|
||||
await this.lifeCycleService.setPhase(LifecyclePhase.Ready);
|
||||
}
|
||||
|
||||
async getApp(): Promise<RendererApplication<RenderObject>> {
|
||||
await this.lifeCycleService.when(LifecyclePhase.AfterInitPackageLoad);
|
||||
|
||||
getApp(): RendererApplication<RenderObject> {
|
||||
// construct application
|
||||
return Object.freeze<RendererApplication<RenderObject>>({
|
||||
// develop use
|
||||
@ -85,6 +77,9 @@ export class RendererMain<RenderObject> {
|
||||
use: (plugin) => {
|
||||
return this.extensionHostService.registerPlugin(plugin);
|
||||
},
|
||||
destroy: async () => {
|
||||
return this.lifeCycleService.setPhase(LifecyclePhase.Destroying);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,11 +58,7 @@ export class CodeRuntimeService implements ICodeRuntimeService {
|
||||
if (!code) return undefined;
|
||||
|
||||
try {
|
||||
let result = this.evalCodeFunction(code, scope.value);
|
||||
|
||||
if (typeof result === 'function') {
|
||||
result = result.bind(scope.value);
|
||||
}
|
||||
const result = this.evalCodeFunction(code, scope.value);
|
||||
|
||||
return result as R;
|
||||
} catch (err) {
|
||||
@ -105,7 +101,8 @@ export class CodeRuntimeService implements ICodeRuntimeService {
|
||||
node: Spec.JSExpression | Spec.JSFunction,
|
||||
options: ResolveOptions,
|
||||
) {
|
||||
const v = this.run(node.value, options.scope || this.codeScope);
|
||||
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);
|
||||
|
||||
@ -63,7 +63,7 @@ export class CodeScope implements ICodeScope {
|
||||
private createProxy(): PlainObject {
|
||||
return new Proxy(Object.create(null) as PlainObject, {
|
||||
set: (target, p, newValue) => {
|
||||
this.set(p as string, newValue, true);
|
||||
this.set(p as string, newValue);
|
||||
return true;
|
||||
},
|
||||
get: (_, p) => this.findValue(p) ?? undefined,
|
||||
|
||||
@ -3,7 +3,7 @@ import { type Plugin, type PluginContext } from './plugin';
|
||||
import { IBoostsService } from './boosts';
|
||||
import { IPackageManagementService } from '../package';
|
||||
import { ISchemaService } from '../schema';
|
||||
import { ILifeCycleService, LifecyclePhase } from '../lifeCycleService';
|
||||
import { ILifeCycleService } from '../lifeCycleService';
|
||||
|
||||
interface IPluginRuntime extends Plugin {
|
||||
status: 'setup' | 'ready';
|
||||
@ -42,8 +42,8 @@ export class ExtensionHostService implements IExtensionHostService {
|
||||
schema: this.schemaService,
|
||||
packageManager: this.packageManagementService,
|
||||
|
||||
whenLifeCylePhaseChange: (phase) => {
|
||||
return this.lifeCycleService.when(phase);
|
||||
whenLifeCylePhaseChange: (phase, listener) => {
|
||||
return this.lifeCycleService.when(phase, listener);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { type EventEmitter, type IStore, type PlainObject } from '@alilc/lowcode-shared';
|
||||
import { type IBoosts } from './boosts';
|
||||
import { LifecyclePhase } from '../lifeCycleService';
|
||||
import { ILifeCycleService } from '../lifeCycleService';
|
||||
import { type ISchemaService } from '../schema';
|
||||
import { type IPackageManagementService } from '../package';
|
||||
|
||||
@ -8,12 +8,12 @@ export interface PluginContext<BoostsExtends = object> {
|
||||
eventEmitter: EventEmitter;
|
||||
globalState: IStore<PlainObject, string>;
|
||||
boosts: IBoosts<BoostsExtends>;
|
||||
schema: ISchemaService;
|
||||
schema: Pick<ISchemaService, 'get' | 'set'>;
|
||||
packageManager: IPackageManagementService;
|
||||
/**
|
||||
* 生命周期变更事件
|
||||
*/
|
||||
whenLifeCylePhaseChange(phase: LifecyclePhase): Promise<void>;
|
||||
whenLifeCylePhaseChange: ILifeCycleService['when'];
|
||||
}
|
||||
|
||||
export interface Plugin<BoostsExtends = object> {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Provide, createDecorator, Barrier } from '@alilc/lowcode-shared';
|
||||
import { Provide, createDecorator, EventEmitter, EventDisposable } from '@alilc/lowcode-shared';
|
||||
|
||||
export const enum LifecyclePhase {
|
||||
Starting = 1,
|
||||
@ -7,7 +7,7 @@ export const enum LifecyclePhase {
|
||||
|
||||
Ready = 3,
|
||||
|
||||
AfterInitPackageLoad = 4,
|
||||
Destroying = 4,
|
||||
}
|
||||
|
||||
export interface ILifeCycleService {
|
||||
@ -16,18 +16,20 @@ export interface ILifeCycleService {
|
||||
*/
|
||||
phase: LifecyclePhase;
|
||||
|
||||
setPhase(phase: LifecyclePhase): Promise<void>;
|
||||
|
||||
/**
|
||||
* Returns a promise that resolves when a certain lifecycle phase
|
||||
* has started.
|
||||
*/
|
||||
when(phase: LifecyclePhase): Promise<void>;
|
||||
when(phase: LifecyclePhase, listener: () => void | Promise<void>): EventDisposable;
|
||||
}
|
||||
|
||||
export const ILifeCycleService = createDecorator<ILifeCycleService>('lifeCycleService');
|
||||
|
||||
@Provide(ILifeCycleService)
|
||||
export class LifeCycleService implements ILifeCycleService {
|
||||
private readonly phaseWhen = new Map<LifecyclePhase, Barrier>();
|
||||
private readonly phaseWhen = new EventEmitter();
|
||||
|
||||
private _phase = LifecyclePhase.Starting;
|
||||
|
||||
@ -35,7 +37,7 @@ export class LifeCycleService implements ILifeCycleService {
|
||||
return this._phase;
|
||||
}
|
||||
|
||||
set phase(value: LifecyclePhase) {
|
||||
async setPhase(value: LifecyclePhase) {
|
||||
if (value < this._phase) {
|
||||
throw new Error('Lifecycle cannot go backwards');
|
||||
}
|
||||
@ -44,28 +46,27 @@ export class LifeCycleService implements ILifeCycleService {
|
||||
return;
|
||||
}
|
||||
|
||||
// this.logService.trace(`lifecycle: phase changed (value: ${value})`);
|
||||
|
||||
this._phase = value;
|
||||
|
||||
const barrier = this.phaseWhen.get(this._phase);
|
||||
if (barrier) {
|
||||
barrier.open();
|
||||
this.phaseWhen.delete(this._phase);
|
||||
}
|
||||
await this.phaseWhen.emit(LifecyclePhaseToString(value));
|
||||
}
|
||||
|
||||
async when(phase: LifecyclePhase): Promise<void> {
|
||||
if (phase <= this._phase) {
|
||||
return;
|
||||
}
|
||||
|
||||
let barrier = this.phaseWhen.get(phase);
|
||||
if (!barrier) {
|
||||
barrier = new Barrier();
|
||||
this.phaseWhen.set(phase, barrier);
|
||||
}
|
||||
|
||||
await barrier.wait();
|
||||
when(phase: LifecyclePhase, listener: () => void | Promise<void>) {
|
||||
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';
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,9 +15,8 @@ export interface NormalizedComponentNode extends Spec.ComponentNode {
|
||||
|
||||
export interface InitializeModelOptions {
|
||||
defaultProps?: PlainObject | undefined;
|
||||
|
||||
stateCreator: ModelScopeStateCreator;
|
||||
dataSourceCreator: ModelScopeDataSourceCreator;
|
||||
dataSourceCreator?: ModelScopeDataSourceCreator;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -60,14 +59,6 @@ export interface IComponentTreeModel<Component, ComponentInstance = unknown> {
|
||||
export type ModelScopeStateCreator = (initalState: PlainObject) => Spec.InstanceStateApi;
|
||||
export type ModelScopeDataSourceCreator = (...args: any[]) => Spec.InstanceDataSourceApi;
|
||||
|
||||
const defaultDataSourceSchema: Spec.ComponentDataSource = {
|
||||
list: [],
|
||||
dataHandler: {
|
||||
type: 'JSFunction',
|
||||
value: '() => {}',
|
||||
},
|
||||
};
|
||||
|
||||
export interface ComponentTreeModelOptions {
|
||||
id?: string;
|
||||
metadata?: PlainObject;
|
||||
@ -108,7 +99,7 @@ export class ComponentTreeModel<Component, ComponentInstance = unknown>
|
||||
state = {},
|
||||
defaultProps: defaultSchemaProps,
|
||||
props = {},
|
||||
dataSource = defaultDataSourceSchema,
|
||||
dataSource,
|
||||
methods = {},
|
||||
} = this.componentsTree;
|
||||
|
||||
@ -120,16 +111,22 @@ export class ComponentTreeModel<Component, ComponentInstance = unknown>
|
||||
},
|
||||
});
|
||||
|
||||
const initalState = this.codeRuntime.resolve(state, { scope: this.codeScope });
|
||||
const initalProps = this.codeRuntime.resolve(props, { scope: this.codeScope });
|
||||
this.codeScope.setValue({ props: { ...defaultProps, ...initalProps } });
|
||||
|
||||
const initalState = this.codeRuntime.resolve(state, { scope: this.codeScope });
|
||||
const stateApi = stateCreator(initalState);
|
||||
const dataSourceApi = dataSourceCreator(dataSource, stateApi);
|
||||
this.codeScope.setValue(stateApi);
|
||||
|
||||
let dataSourceApi: Spec.InstanceDataSourceApi | undefined;
|
||||
if (dataSource && dataSourceCreator) {
|
||||
const dataSourceProps = this.codeRuntime.resolve(dataSource, { scope: this.codeScope });
|
||||
dataSourceApi = dataSourceCreator(dataSourceProps, stateApi);
|
||||
}
|
||||
|
||||
this.codeScope.setValue(
|
||||
Object.assign(
|
||||
{
|
||||
props: { ...defaultProps, ...initalProps },
|
||||
$: (ref: string) => {
|
||||
const insArr = this.instanceMap.get(ref);
|
||||
if (!insArr) return undefined;
|
||||
@ -139,7 +136,6 @@ export class ComponentTreeModel<Component, ComponentInstance = unknown>
|
||||
return this.instanceMap.get(ref) ?? [];
|
||||
},
|
||||
},
|
||||
stateApi,
|
||||
dataSourceApi,
|
||||
),
|
||||
);
|
||||
|
||||
@ -10,7 +10,7 @@ import {
|
||||
import { get as lodashGet } from 'lodash-es';
|
||||
import { PackageLoader } from './loader';
|
||||
import { ISchemaService } from '../schema';
|
||||
import { ILifeCycleService, LifecyclePhase } from '../lifeCycleService';
|
||||
import { ILifeCycleService } from '../lifeCycleService';
|
||||
|
||||
export interface NormalizedPackage {
|
||||
id: string;
|
||||
@ -33,16 +33,13 @@ export interface IPackageManagementService {
|
||||
|
||||
setLibraryByPackageName(packageName: string, library: any): void;
|
||||
|
||||
getLibraryByComponentMap(componentMap: Spec.ComponentMap): any;
|
||||
getLibraryByComponentMap(
|
||||
componentMap: Spec.ComponentMap,
|
||||
): { key: string; value: any } | undefined;
|
||||
|
||||
/** 解析组件映射 */
|
||||
resolveComponentMaps(componentMaps: Spec.ComponentMap[]): void;
|
||||
|
||||
/** 获取组件映射对象,key = componentName value = component */
|
||||
getComponentsNameRecord<C = unknown>(
|
||||
componentMaps?: Spec.ComponentMap[],
|
||||
): Record<string, C | LowCodeComponent>;
|
||||
|
||||
/** 通过组件名获取对应的组件 */
|
||||
getComponent<C = unknown>(componentName: string): C | LowCodeComponent | undefined;
|
||||
/** 注册组件 */
|
||||
@ -72,8 +69,7 @@ export class PackageManagementService implements IPackageManagementService {
|
||||
@ISchemaService private schemaService: ISchemaService,
|
||||
@ILifeCycleService private lifeCycleService: ILifeCycleService,
|
||||
) {
|
||||
this.lifeCycleService.when(LifecyclePhase.AfterInitPackageLoad).then(() => {
|
||||
const componentsMaps = this.schemaService.get('componentsMap');
|
||||
this.schemaService.onChange('componentsMap', (componentsMaps) => {
|
||||
this.resolveComponentMaps(componentsMaps);
|
||||
});
|
||||
}
|
||||
@ -109,19 +105,23 @@ export class PackageManagementService implements IPackageManagementService {
|
||||
this.packageStore.set(packageName, library);
|
||||
}
|
||||
|
||||
getLibraryByComponentMap(componentMap: Spec.ComponentMap) {
|
||||
if (this.packageStore.has(componentMap.package!)) {
|
||||
getLibraryByComponentMap(
|
||||
componentMap: Spec.ComponentMap,
|
||||
): { key: string; value: any } | undefined {
|
||||
if (!componentMap.componentName && !componentMap.exportName) return;
|
||||
|
||||
if (this.packageStore.has(componentMap.package)) {
|
||||
const library = this.packageStore.get(componentMap.package!);
|
||||
// export { exportName } from xxx exportName === global.libraryName.exportName
|
||||
// export exportName from xxx exportName === global.libraryName.default || global.libraryName
|
||||
// export { exportName as componentName } from package
|
||||
// if exportName == null exportName === componentName;
|
||||
// const componentName = exportName.subName, if exportName empty subName donot use
|
||||
const paths =
|
||||
componentMap.exportName && componentMap.subName ? componentMap.subName.split('.') : [];
|
||||
|
||||
// if exportName === nil, exportName === componentName;
|
||||
const exportName = componentMap.exportName ?? componentMap.componentName;
|
||||
|
||||
if (componentMap.destructuring) {
|
||||
if (exportName && componentMap.destructuring) {
|
||||
paths.unshift(exportName);
|
||||
}
|
||||
|
||||
@ -130,10 +130,12 @@ export class PackageManagementService implements IPackageManagementService {
|
||||
result = result[path] || result;
|
||||
}
|
||||
|
||||
return result;
|
||||
// export { exportName as componentName } from package
|
||||
return {
|
||||
key: componentMap.componentName ?? componentMap.exportName!,
|
||||
value: result,
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
resolveComponentMaps(componentMaps: Spec.ComponentMap[]) {
|
||||
@ -146,22 +148,15 @@ export class PackageManagementService implements IPackageManagementService {
|
||||
}
|
||||
} else {
|
||||
const result = this.getLibraryByComponentMap(map);
|
||||
if (map.componentName && result) this.componentsRecord[map.componentName] = result;
|
||||
if (result) {
|
||||
this.componentsRecord[result.key] = result.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getComponentsNameRecord(componentMaps?: Spec.ComponentMap[]) {
|
||||
if (componentMaps) {
|
||||
const newMaps = componentMaps.filter((item) => !this.componentsRecord[item.componentName]);
|
||||
this.resolveComponentMaps(newMaps);
|
||||
}
|
||||
|
||||
return { ...this.componentsRecord };
|
||||
}
|
||||
|
||||
getComponent(componentName: string) {
|
||||
return this.componentsRecord[componentName];
|
||||
return lodashGet(this.componentsRecord, componentName);
|
||||
}
|
||||
|
||||
registerComponentByName(componentName: string, Component: unknown) {
|
||||
|
||||
@ -42,7 +42,9 @@ export class RuntimeIntlService implements IRuntimeIntlService {
|
||||
@ICodeRuntimeService private codeRuntimeService: ICodeRuntimeService,
|
||||
@ISchemaService private schemaService: ISchemaService,
|
||||
) {
|
||||
this.lifeCycleService.when(LifecyclePhase.OptionsResolved).then(() => {
|
||||
this.injectScope();
|
||||
|
||||
this.lifeCycleService.when(LifecyclePhase.OptionsResolved, () => {
|
||||
const config = this.schemaService.get('config');
|
||||
const i18nTranslations = this.schemaService.get('i18n');
|
||||
|
||||
@ -55,10 +57,6 @@ export class RuntimeIntlService implements IRuntimeIntlService {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.lifeCycleService.when(LifecyclePhase.Ready).then(() => {
|
||||
this.toExpose();
|
||||
});
|
||||
}
|
||||
|
||||
t(descriptor: MessageDescriptor): string {
|
||||
@ -85,7 +83,7 @@ export class RuntimeIntlService implements IRuntimeIntlService {
|
||||
this.intl.addTranslations(locale, translations);
|
||||
}
|
||||
|
||||
private toExpose(): void {
|
||||
private injectScope(): void {
|
||||
const exposed: Spec.IntlApi = {
|
||||
i18n: (key, params) => {
|
||||
return this.t({ key, params });
|
||||
|
||||
@ -8,12 +8,11 @@ import {
|
||||
import { isPlainObject } from 'lodash-es';
|
||||
import { IPackageManagementService } from './package';
|
||||
import { ICodeRuntimeService } from './code-runtime';
|
||||
import { ILifeCycleService, LifecyclePhase } from './lifeCycleService';
|
||||
import { ISchemaService } from './schema';
|
||||
|
||||
export interface IRuntimeUtilService {
|
||||
add(utilItem: Spec.Util): void;
|
||||
add(name: string, target: AnyFunction | PlainObject): void;
|
||||
add(utilItem: Spec.Util, force?: boolean): void;
|
||||
add(name: string, target: AnyFunction | PlainObject, force?: boolean): void;
|
||||
|
||||
remove(name: string): void;
|
||||
}
|
||||
@ -27,38 +26,61 @@ export class RuntimeUtilService implements IRuntimeUtilService {
|
||||
constructor(
|
||||
@ICodeRuntimeService private codeRuntimeService: ICodeRuntimeService,
|
||||
@IPackageManagementService private packageManagementService: IPackageManagementService,
|
||||
@ILifeCycleService private lifeCycleService: ILifeCycleService,
|
||||
@ISchemaService private schemaService: ISchemaService,
|
||||
) {
|
||||
this.lifeCycleService.when(LifecyclePhase.AfterInitPackageLoad).then(() => {
|
||||
const utils = this.schemaService.get('utils') ?? [];
|
||||
this.injectScope();
|
||||
|
||||
this.schemaService.onChange('utils', (utils = []) => {
|
||||
for (const util of utils) {
|
||||
this.add(util);
|
||||
}
|
||||
this.toExpose();
|
||||
});
|
||||
}
|
||||
|
||||
add(utilItem: Spec.Util): void;
|
||||
add(name: string, fn: AnyFunction | PlainObject): void;
|
||||
add(name: Spec.Util | string, fn?: AnyFunction | PlainObject): void {
|
||||
if (typeof name === 'string') {
|
||||
if (fn) {
|
||||
if (isPlainObject(fn)) {
|
||||
if ((fn as PlainObject).destructuring) {
|
||||
for (const key of Object.keys(fn)) {
|
||||
this.add(key, (fn as PlainObject)[key]);
|
||||
}
|
||||
} else {
|
||||
this.utilsMap.set(name, fn);
|
||||
}
|
||||
} else if (typeof fn === 'function') {
|
||||
this.utilsMap.set(name, fn);
|
||||
}
|
||||
}
|
||||
add(utilItem: Spec.Util, force?: boolean): void;
|
||||
add(name: string, fn: AnyFunction | PlainObject, force?: boolean): void;
|
||||
add(util: Spec.Util | string, fn?: AnyFunction | PlainObject | boolean, force?: boolean): void {
|
||||
let name: string;
|
||||
let utilObj: AnyFunction | PlainObject | Spec.Util;
|
||||
|
||||
if (typeof util === 'string') {
|
||||
if (!fn) return;
|
||||
|
||||
name = util;
|
||||
utilObj = fn as AnyFunction | PlainObject;
|
||||
} else {
|
||||
const util = this.parseUtil(name);
|
||||
if (util) this.add(name.name, util);
|
||||
if (!util) return;
|
||||
|
||||
name = util.name;
|
||||
utilObj = util;
|
||||
force = fn as boolean;
|
||||
}
|
||||
|
||||
this.addUtilByName(name, utilObj, force);
|
||||
}
|
||||
|
||||
private addUtilByName(
|
||||
name: string,
|
||||
fn: AnyFunction | PlainObject | Spec.Util,
|
||||
force?: boolean,
|
||||
): void {
|
||||
if (this.utilsMap.has(name) && !force) return;
|
||||
|
||||
if (isPlainObject(fn)) {
|
||||
if ((fn as Spec.Util).type === 'function' || (fn as Spec.Util).type === 'npm') {
|
||||
const utilFn = this.parseUtil(fn as Spec.Util);
|
||||
if (utilFn) {
|
||||
this.addUtilByName(utilFn.key, utilFn.value, force);
|
||||
}
|
||||
} else if ((fn as PlainObject).destructuring) {
|
||||
for (const key of Object.keys(fn)) {
|
||||
this.addUtilByName(key, (fn as PlainObject)[key], force);
|
||||
}
|
||||
} else {
|
||||
this.utilsMap.set(name, fn);
|
||||
}
|
||||
} else if (typeof fn === 'function') {
|
||||
this.utilsMap.set(name, fn);
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,13 +91,16 @@ export class RuntimeUtilService implements IRuntimeUtilService {
|
||||
private parseUtil(utilItem: Spec.Util) {
|
||||
if (utilItem.type === 'function') {
|
||||
const { content } = utilItem;
|
||||
return this.codeRuntimeService.run(content.value);
|
||||
return {
|
||||
key: utilItem.name,
|
||||
value: this.codeRuntimeService.run(content.value),
|
||||
};
|
||||
} else {
|
||||
return this.packageManagementService.getLibraryByComponentMap(utilItem.content);
|
||||
}
|
||||
}
|
||||
|
||||
private toExpose(): void {
|
||||
private injectScope(): void {
|
||||
const exposed = new Proxy(Object.create(null), {
|
||||
get: (_, p: string) => {
|
||||
return this.utilsMap.get(p);
|
||||
|
||||
@ -4,6 +4,8 @@ import {
|
||||
Provide,
|
||||
type IStore,
|
||||
KeyValueStore,
|
||||
EventEmitter,
|
||||
type EventDisposable,
|
||||
} from '@alilc/lowcode-shared';
|
||||
import { isObject } from 'lodash-es';
|
||||
import { schemaValidation } from './validation';
|
||||
@ -19,7 +21,12 @@ export interface ISchemaService {
|
||||
|
||||
get<K extends NormalizedSchemaKey>(key: K): NormalizedSchema[K];
|
||||
|
||||
set<K extends NormalizedSchemaKey>(key: K, value: NormalizedSchema[K]): void;
|
||||
set<K extends NormalizedSchemaKey>(key: K, value: NormalizedSchema[K]): Promise<void>;
|
||||
|
||||
onChange<K extends NormalizedSchemaKey>(
|
||||
key: K,
|
||||
listener: (v: NormalizedSchema[K]) => void,
|
||||
): EventDisposable;
|
||||
}
|
||||
|
||||
export const ISchemaService = createDecorator<ISchemaService>('schemaService');
|
||||
@ -33,13 +40,18 @@ export class SchemaService implements ISchemaService {
|
||||
setterValidation: schemaValidation,
|
||||
});
|
||||
|
||||
private notifyEmiiter = new EventEmitter();
|
||||
|
||||
constructor(
|
||||
@ILifeCycleService private lifeCycleService: ILifeCycleService,
|
||||
@ICodeRuntimeService private codeRuntimeService: ICodeRuntimeService,
|
||||
) {
|
||||
this.lifeCycleService.when(LifecyclePhase.Ready).then(() => {
|
||||
const constants = this.get('constants') ?? {};
|
||||
this.codeRuntimeService.getScope().set('constants', constants);
|
||||
this.onChange('constants', (value = {}) => {
|
||||
this.codeRuntimeService.getScope().set('constants', value);
|
||||
});
|
||||
|
||||
this.lifeCycleService.when(LifecyclePhase.Destroying, () => {
|
||||
this.notifyEmiiter.removeAll();
|
||||
});
|
||||
}
|
||||
|
||||
@ -54,11 +66,21 @@ export class SchemaService implements ISchemaService {
|
||||
});
|
||||
}
|
||||
|
||||
set<K extends NormalizedSchemaKey>(key: K, value: NormalizedSchema[K]): void {
|
||||
this.store.set(key, value);
|
||||
async set<K extends NormalizedSchemaKey>(key: K, value: NormalizedSchema[K]): Promise<void> {
|
||||
if (value !== this.get(key)) {
|
||||
this.store.set(key, value);
|
||||
await this.notifyEmiiter.emit(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
get<K extends NormalizedSchemaKey>(key: K): NormalizedSchema[K] {
|
||||
return this.store.get(key) as NormalizedSchema[K];
|
||||
}
|
||||
|
||||
onChange<K extends keyof NormalizedSchema>(
|
||||
key: K,
|
||||
listener: (v: NormalizedSchema[K]) => void | Promise<void>,
|
||||
): EventDisposable {
|
||||
return this.notifyEmiiter.on(key, listener);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,21 +3,24 @@ 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';
|
||||
|
||||
export interface AppOptions {
|
||||
schema: Spec.Project;
|
||||
packages?: Spec.Package[];
|
||||
plugins?: Plugin[];
|
||||
|
||||
/**
|
||||
* 运行模式
|
||||
*/
|
||||
mode?: 'development' | 'production';
|
||||
|
||||
/**
|
||||
* code runtime 设置选项
|
||||
*/
|
||||
codeRuntime?: CodeRuntimeInitializeOptions;
|
||||
/**
|
||||
* 数据源创建工厂函数
|
||||
*/
|
||||
dataSourceCreator?: ModelScopeDataSourceCreator;
|
||||
}
|
||||
|
||||
export type RendererApplication<Render = unknown> = {
|
||||
@ -28,4 +31,6 @@ export type RendererApplication<Render = unknown> = {
|
||||
readonly packageManager: IPackageManagementService;
|
||||
|
||||
use(plugin: Plugin): Promise<void>;
|
||||
|
||||
destroy(): Promise<void>;
|
||||
} & Render;
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
} from './history';
|
||||
import { createRouterMatcher } from './matcher';
|
||||
import { type PathParserOptions, type PathParams } from './utils/path-parser';
|
||||
import { parseURL, stringifyURL } from './utils/url';
|
||||
import { mergeSearchParams, parseURL, stringifyURL } from './utils/url';
|
||||
import { isSameRouteLocation } from './utils/helper';
|
||||
import type {
|
||||
RouteRecord,
|
||||
@ -77,11 +77,11 @@ export function createRouter(options: RouterOptions): Router {
|
||||
|
||||
function resolve(
|
||||
rawLocation: RawRouteLocation,
|
||||
currentLocation?: RouteLocationNormalized,
|
||||
current?: RouteLocationNormalized,
|
||||
): RouteLocationNormalized & {
|
||||
href: string;
|
||||
} {
|
||||
currentLocation = Object.assign({}, currentLocation || currentLocation);
|
||||
currentLocation = Object.assign({}, current || currentLocation);
|
||||
|
||||
if (typeof rawLocation === 'string') {
|
||||
const locationNormalized = parseURL(rawLocation);
|
||||
@ -89,7 +89,10 @@ export function createRouter(options: RouterOptions): Router {
|
||||
const href = routerHistory.createHref(locationNormalized.fullPath);
|
||||
|
||||
return Object.assign(locationNormalized, matchedRoute, {
|
||||
searchParams: locationNormalized.searchParams,
|
||||
searchParams: mergeSearchParams(
|
||||
currentLocation.searchParams,
|
||||
locationNormalized.searchParams,
|
||||
),
|
||||
hash: decodeURIComponent(locationNormalized.hash),
|
||||
redirectedFrom: undefined,
|
||||
href,
|
||||
|
||||
@ -45,3 +45,14 @@ export function stringifyURL(location: {
|
||||
const searchStr = location.searchParams ? location.searchParams.toString() : '';
|
||||
return location.path + (searchStr && '?') + searchStr + (location.hash || '');
|
||||
}
|
||||
|
||||
export function mergeSearchParams(
|
||||
a: URLSearchParams | undefined,
|
||||
b: URLSearchParams | undefined,
|
||||
): URLSearchParams {
|
||||
if (!a && !b) return new URLSearchParams();
|
||||
if (!a) return b!;
|
||||
if (!b) return a;
|
||||
|
||||
return new URLSearchParams([...Array.from(a.entries()), ...Array.from(b.entries())]);
|
||||
}
|
||||
|
||||
@ -44,7 +44,7 @@ export interface Project {
|
||||
/**
|
||||
* 当前应用元数据信息
|
||||
*/
|
||||
meta?: Record<string, JSONObject>;
|
||||
meta?: Record<string, JSONValue | JSONObject>;
|
||||
/**
|
||||
* 当前应用的公共数据源
|
||||
* @deprecated
|
||||
@ -92,13 +92,13 @@ export interface ProjectConfig {
|
||||
*/
|
||||
export interface ComponentMap {
|
||||
/**
|
||||
* 协议中的组件名,唯一性,对应包导出的组件名,是一个有效的 JS 标识符,而且是大写字母打头
|
||||
* 协议中的组件名,对应包导出的组件名,是一个有效的 JS 标识符
|
||||
*/
|
||||
componentName: string;
|
||||
componentName?: string;
|
||||
/**
|
||||
* npm 公域的 package name
|
||||
* npm 公域的 package name,唯一性
|
||||
*/
|
||||
package?: string;
|
||||
package: string;
|
||||
/**
|
||||
* package version
|
||||
*/
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user