fix: lifeCycle events and router plugin

This commit is contained in:
1ncounter 2024-06-19 14:12:15 +08:00
parent 37d07f1db6
commit 61bc8e6c3d
34 changed files with 197 additions and 114 deletions

1
.gitignore vendored
View File

@ -13,7 +13,6 @@ pnpm-lock.yaml
deploy-space/packages deploy-space/packages
deploy-space/.env deploy-space/.env
# IDE # IDE
.vscode .vscode
.idea .idea

View File

@ -1,7 +1,7 @@
import { createRenderer, type AppOptions } from '@alilc/lowcode-renderer-core'; import { createRenderer, type AppOptions } from '@alilc/lowcode-renderer-core';
import { type ComponentType } from 'react'; import { type ComponentType } from 'react';
import { type Root, createRoot } from 'react-dom/client'; import { type Root, createRoot } from 'react-dom/client';
import { ApplicationView, RendererContext, extension } from '../app'; import { ApplicationView, RendererContext, boosts } from '../app';
export interface ReactAppOptions extends AppOptions { export interface ReactAppOptions extends AppOptions {
faultComponent?: ComponentType<any>; faultComponent?: ComponentType<any>;
@ -17,7 +17,7 @@ export const createApp = async (options: ReactAppOptions) => {
// } // }
// extends boosts // extends boosts
extension.install(boostsManager); boostsManager.extend(boosts.toExpose());
let root: Root | undefined; let root: Root | undefined;

View File

@ -1,6 +1,6 @@
import { createRenderer, type AppOptions } from '@alilc/lowcode-renderer-core'; import { createRenderer, type AppOptions } from '@alilc/lowcode-renderer-core';
import { FunctionComponent } from 'react'; import { FunctionComponent } from 'react';
import { type LowCodeComponentProps, createComponentBySchema } from '../runtime/component'; import { type LowCodeComponentProps, createComponentBySchema } from '../runtime/schema';
import { RendererContext } from '../app/context'; import { RendererContext } from '../app/context';
interface Render { interface Render {

View File

@ -1,4 +1,4 @@
import { type Plugin, type IBoostsService } from '@alilc/lowcode-renderer-core'; import { type Plugin } from '@alilc/lowcode-renderer-core';
import { type ComponentType, type PropsWithChildren } from 'react'; import { type ComponentType, type PropsWithChildren } from 'react';
export type WrapperComponent = ComponentType<PropsWithChildren<any>>; export type WrapperComponent = ComponentType<PropsWithChildren<any>>;
@ -9,13 +9,13 @@ export interface OutletProps {
export type Outlet = ComponentType<OutletProps>; export type Outlet = ComponentType<OutletProps>;
export interface ReactRendererExtensionApi { export interface ReactRendererBoostsApi {
addAppWrapper(appWrapper: WrapperComponent): void; addAppWrapper(appWrapper: WrapperComponent): void;
setOutlet(outlet: Outlet): void; setOutlet(outlet: Outlet): void;
} }
class ReactRendererExtension { class ReactRendererBoosts {
private wrappers: WrapperComponent[] = []; private wrappers: WrapperComponent[] = [];
private outlet: Outlet | null = null; private outlet: Outlet | null = null;
@ -28,7 +28,7 @@ class ReactRendererExtension {
return this.outlet; return this.outlet;
} }
toExpose(): ReactRendererExtensionApi { toExpose(): ReactRendererBoostsApi {
return { return {
addAppWrapper: (appWrapper) => { addAppWrapper: (appWrapper) => {
if (appWrapper) this.wrappers.push(appWrapper); if (appWrapper) this.wrappers.push(appWrapper);
@ -38,14 +38,10 @@ class ReactRendererExtension {
}, },
}; };
} }
install(boostsService: IBoostsService) {
boostsService.extend(this.toExpose());
}
} }
export const extension = new ReactRendererExtension(); export const boosts = new ReactRendererBoosts();
export function defineRendererPlugin(plugin: Plugin<ReactRendererExtensionApi>) { export function defineRendererPlugin(plugin: Plugin<ReactRendererBoostsApi>) {
return plugin; return plugin;
} }

View File

@ -1,3 +1,3 @@
export * from './context'; export * from './context';
export * from './extension'; export * from './boosts';
export * from './view'; export * from './view';

View File

@ -1,12 +1,12 @@
import { useRenderContext } from './context'; import { useRenderContext } from './context';
import { getComponentByName } from '../runtime/component'; import { getComponentByName } from '../runtime/schema';
import { extension } from './extension'; import { boosts } from './boosts';
export function ApplicationView() { export function ApplicationView() {
const renderContext = useRenderContext(); const renderContext = useRenderContext();
const { schema } = renderContext; const { schema } = renderContext;
const appWrappers = extension.getAppWrappers(); const appWrappers = boosts.getAppWrappers();
const Outlet = extension.getOutlet(); const Outlet = boosts.getOutlet();
if (!Outlet) return null; if (!Outlet) return null;

View File

@ -5,4 +5,4 @@ export * from './router';
export type { Spec, ProCodeComponent, LowCodeComponent } from '@alilc/lowcode-shared'; export type { Spec, ProCodeComponent, LowCodeComponent } from '@alilc/lowcode-shared';
export type { PackageLoader, CodeScope, Plugin } from '@alilc/lowcode-renderer-core'; export type { PackageLoader, CodeScope, Plugin } from '@alilc/lowcode-renderer-core';
export type { RendererExtends } from './app/extension'; export type { ReactRendererBoostsApi } from './app/boosts';

View File

@ -1,2 +1,3 @@
export * from './context'; export * from './context';
export * from './plugin'; export * from './plugin';
export type * from '@alilc/lowcode-renderer-router';

View File

@ -1,4 +1,4 @@
import { defineRendererPlugin } from '../app/extension'; import { defineRendererPlugin } from '../app/boosts';
import { LifecyclePhase } from '@alilc/lowcode-renderer-core'; import { LifecyclePhase } from '@alilc/lowcode-renderer-core';
import { createRouter, type RouterOptions } from '@alilc/lowcode-renderer-router'; import { createRouter, type RouterOptions } from '@alilc/lowcode-renderer-router';
import { createRouterView } from './routerView'; import { createRouterView } from './routerView';
@ -29,13 +29,14 @@ export const routerPlugin = defineRendererPlugin({
const router = createRouter(routerConfig); const router = createRouter(routerConfig);
boosts.codeRuntime.getScope().set('router', router); boosts.codeRuntime.getScope().set('router', router);
boosts.temporaryUse('router', router);
const RouterView = createRouterView(router); const RouterView = createRouterView(router);
boosts.addAppWrapper(RouterView); boosts.addAppWrapper(RouterView);
boosts.setOutlet(RouteOutlet); boosts.setOutlet(RouteOutlet);
whenLifeCylePhaseChange(LifecyclePhase.Ready).then(() => { whenLifeCylePhaseChange(LifecyclePhase.AfterInitPackageLoad).then(() => {
return router.isReady(); return router.isReady();
}); });
}, },

View File

@ -1,8 +1,8 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useRenderContext } from '../app/context'; import { useRenderContext } from '../app/context';
import { OutletProps } from '../app/extension'; import { OutletProps } from '../app/boosts';
import { useRouteLocation } from './context'; import { useRouteLocation } from './context';
import { createComponentBySchema } from '../runtime/component'; import { createComponentBySchema } from '../runtime/schema';
export function RouteOutlet(props: OutletProps) { export function RouteOutlet(props: OutletProps) {
const context = useRenderContext(); const context = useRenderContext();

View File

@ -16,7 +16,7 @@ import { type ComponentType, type ReactInstance, useMemo, createElement } from '
import { useRenderContext } from '../app/context'; import { useRenderContext } from '../app/context';
import { useReactiveStore } from './hooks/useReactiveStore'; import { useReactiveStore } from './hooks/useReactiveStore';
import { useModel } from './context'; import { useModel } from './context';
import { getComponentByName } from './component'; import { getComponentByName } from './schema';
export type ReactComponent = ComponentType<any>; export type ReactComponent = ComponentType<any>;
export type ReactWidget = IWidget<ReactComponent, ReactInstance>; export type ReactWidget = IWidget<ReactComponent, ReactInstance>;

View File

@ -1,6 +1,6 @@
import { IComponentTreeModel } from '@alilc/lowcode-renderer-core'; import { IComponentTreeModel } from '@alilc/lowcode-renderer-core';
import { createContext, useContext, type ReactInstance } from 'react'; import { createContext, useContext, type ReactInstance } from 'react';
import { type ReactComponent } from './render'; import { type ReactComponent } from './components';
export const ModelContext = createContext<IComponentTreeModel<ReactComponent, ReactInstance>>( export const ModelContext = createContext<IComponentTreeModel<ReactComponent, ReactInstance>>(
undefined!, undefined!,

View File

@ -1,2 +1,2 @@
export * from './component'; export * from './schema';
export * from './render'; export * from './components';

View File

@ -4,7 +4,7 @@ import { isValidElementType } from 'react-is';
import { useRenderContext } from '../app/context'; import { useRenderContext } from '../app/context';
import { reactiveStateFactory } from './reactiveState'; import { reactiveStateFactory } from './reactiveState';
import { dataSourceCreator } from './dataSource'; import { dataSourceCreator } from './dataSource';
import { type ReactComponent, type ReactWidget, createElementByWidget } from './render'; import { type ReactComponent, type ReactWidget, createElementByWidget } from './components';
import { ModelContextProvider } from './context'; import { ModelContextProvider } from './context';
import { appendExternalStyle } from '../utils/element'; import { appendExternalStyle } from '../utils/element';

View File

@ -1,5 +1,8 @@
/**
* @vitest-environment jsdom
*/
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
import { CodeScope } from '../../../src/parts/code-runtime'; import { CodeScope } from '../../../src/services/code-runtime';
describe('CodeScope', () => { describe('CodeScope', () => {
it('should return initial values', () => { it('should return initial values', () => {
@ -18,9 +21,8 @@ describe('CodeScope', () => {
it('inject should not overwrite existing values without force', () => { it('inject should not overwrite existing values without force', () => {
const initValue = { a: 1 }; const initValue = { a: 1 };
const scope = new CodeScope(initValue); const scope = new CodeScope(initValue);
scope.set('a', 2); expect(scope.value.a).not.toBe(2);
expect(scope.value.a).toBe(1); scope.set('a', 3);
scope.set('a', 3, true);
expect(scope.value.a).toBe(3); expect(scope.value.a).toBe(3);
}); });

View File

@ -0,0 +1,36 @@
import { describe, it, expect } from 'vitest';
import { LifeCycleService, LifecyclePhase } from '../../src/services/lifeCycleService';
const sleep = () => new Promise((r) => setTimeout(r, 500));
describe('LifeCycleService', () => {
it('it works', async () => {
let result = '';
const lifeCycle = new LifeCycleService();
lifeCycle.when(LifecyclePhase.Ready).then(() => {
result += '1';
});
lifeCycle.when(LifecyclePhase.Ready).finally(() => {
result += '2';
});
lifeCycle.when(LifecyclePhase.AfterInitPackageLoad).then(() => {
result += '3';
});
lifeCycle.when(LifecyclePhase.AfterInitPackageLoad).finally(() => {
result += '4';
});
lifeCycle.phase = LifecyclePhase.Ready;
await sleep();
expect(result).toEqual('12');
lifeCycle.phase = LifecyclePhase.AfterInitPackageLoad;
await sleep();
expect(result).toEqual('1234');
});
});

View File

@ -31,7 +31,7 @@ export class RendererMain<RenderObject> {
@IBoostsService private boostsService: IBoostsService, @IBoostsService private boostsService: IBoostsService,
@ILifeCycleService private lifeCycleService: ILifeCycleService, @ILifeCycleService private lifeCycleService: ILifeCycleService,
) { ) {
this.lifeCycleService.when(LifecyclePhase.OptionsResolved).finally(async () => { this.lifeCycleService.when(LifecyclePhase.OptionsResolved).then(async () => {
const renderContext = { const renderContext = {
schema: this.schemaService, schema: this.schemaService,
packageManager: this.packageManagementService, packageManager: this.packageManagementService,
@ -42,8 +42,6 @@ export class RendererMain<RenderObject> {
this.renderObject = await this.adapter(renderContext); this.renderObject = await this.adapter(renderContext);
await this.packageManagementService.loadPackages(this.initOptions.packages ?? []);
this.lifeCycleService.phase = LifecyclePhase.Ready; this.lifeCycleService.phase = LifecyclePhase.Ready;
}); });
} }
@ -60,13 +58,19 @@ export class RendererMain<RenderObject> {
this.codeRuntimeService.initialize(options.codeRuntime ?? {}); this.codeRuntimeService.initialize(options.codeRuntime ?? {});
this.extensionHostService.registerPlugin(plugins);
this.lifeCycleService.phase = LifecyclePhase.OptionsResolved; this.lifeCycleService.phase = LifecyclePhase.OptionsResolved;
await this.lifeCycleService.when(LifecyclePhase.Ready);
await this.extensionHostService.registerPlugin(plugins);
await this.packageManagementService.loadPackages(this.initOptions.packages ?? []);
this.lifeCycleService.phase = LifecyclePhase.AfterInitPackageLoad;
} }
async getApp(): Promise<RendererApplication<RenderObject>> { async getApp(): Promise<RendererApplication<RenderObject>> {
await this.lifeCycleService.when(LifecyclePhase.Ready); await this.lifeCycleService.when(LifecyclePhase.AfterInitPackageLoad);
// construct application // construct application
return Object.freeze<RendererApplication<RenderObject>>({ return Object.freeze<RendererApplication<RenderObject>>({
@ -79,8 +83,7 @@ export class RendererMain<RenderObject> {
...this.renderObject, ...this.renderObject,
use: (plugin) => { use: (plugin) => {
this.extensionHostService.registerPlugin(plugin); return this.extensionHostService.registerPlugin(plugin);
return this.extensionHostService.doSetupPlugin(plugin);
}, },
}); });
} }

View File

@ -4,7 +4,7 @@ import { ICodeRuntimeService } from '../code-runtime';
import { IRuntimeUtilService } from '../runtimeUtilService'; import { IRuntimeUtilService } from '../runtimeUtilService';
import { IRuntimeIntlService } from '../runtimeIntlService'; import { IRuntimeIntlService } from '../runtimeIntlService';
export type IBoosts<Extends> = IBoostsApi & Extends; export type IBoosts<Extends> = IBoostsApi & Extends & { [key: string]: any };
export interface IBoostsApi { export interface IBoostsApi {
readonly codeRuntime: ICodeRuntimeService; readonly codeRuntime: ICodeRuntimeService;
@ -12,6 +12,10 @@ export interface IBoostsApi {
readonly intl: Pick<IRuntimeIntlService, 't' | 'setLocale' | 'getLocale' | 'addTranslations'>; readonly intl: Pick<IRuntimeIntlService, 't' | 'setLocale' | 'getLocale' | 'addTranslations'>;
readonly util: Pick<IRuntimeUtilService, 'add' | 'remove'>; readonly util: Pick<IRuntimeUtilService, 'add' | 'remove'>;
/**
* boosts 便使
*/
temporaryUse(name: string, value: any): void;
} }
/** /**
@ -43,6 +47,9 @@ export class BoostsService implements IBoostsService {
codeRuntime: this.codeRuntimeService, codeRuntime: this.codeRuntimeService,
intl: this.runtimeIntlService, intl: this.runtimeIntlService,
util: this.runtimeUtilService, util: this.runtimeUtilService,
temporaryUse: (name, value) => {
this.extend(name, value);
},
}; };
} }
@ -55,6 +62,8 @@ export class BoostsService implements IBoostsService {
} else { } else {
if (!this.extendsValue[name]) { if (!this.extendsValue[name]) {
this.extendsValue[name] = value; this.extendsValue[name] = value;
} else {
console.warn(`${name} is exist`);
} }
} }
} else if (isObject(name)) { } else if (isObject(name)) {

View File

@ -3,8 +3,6 @@ import { type Plugin, type PluginContext } from './plugin';
import { IBoostsService } from './boosts'; import { IBoostsService } from './boosts';
import { IPackageManagementService } from '../package'; import { IPackageManagementService } from '../package';
import { ISchemaService } from '../schema'; import { ISchemaService } from '../schema';
import { type RenderAdapter } from './render';
import { IComponentTreeModelService } from '../model';
import { ILifeCycleService, LifecyclePhase } from '../lifeCycleService'; import { ILifeCycleService, LifecyclePhase } from '../lifeCycleService';
interface IPluginRuntime extends Plugin { interface IPluginRuntime extends Plugin {
@ -12,9 +10,7 @@ interface IPluginRuntime extends Plugin {
} }
export interface IExtensionHostService { export interface IExtensionHostService {
registerPlugin(plugin: Plugin | Plugin[]): void; registerPlugin(plugin: Plugin | Plugin[]): Promise<void>;
doSetupPlugin(plugin: Plugin): Promise<void>;
getPlugin(name: string): Plugin | undefined; getPlugin(name: string): Plugin | undefined;
@ -50,15 +46,9 @@ export class ExtensionHostService implements IExtensionHostService {
return this.lifeCycleService.when(phase); return this.lifeCycleService.when(phase);
}, },
}; };
this.lifeCycleService.when(LifecyclePhase.OptionsResolved).then(async () => {
for (const plugin of this.pluginRuntimes) {
await this.doSetupPlugin(plugin);
}
});
} }
registerPlugin(plugins: Plugin | Plugin[]) { async registerPlugin(plugins: Plugin | Plugin[]) {
plugins = Array.isArray(plugins) ? plugins : [plugins]; plugins = Array.isArray(plugins) ? plugins : [plugins];
for (const plugin of plugins) { for (const plugin of plugins) {
@ -67,19 +57,17 @@ export class ExtensionHostService implements IExtensionHostService {
continue; continue;
} }
this.pluginRuntimes.push({ await this.doSetupPlugin(plugin);
...plugin,
status: 'ready',
});
} }
} }
async doSetupPlugin(plugin: Plugin) { private async doSetupPlugin(plugin: Plugin) {
const pluginRuntime = plugin as IPluginRuntime; const pluginRuntime = plugin as IPluginRuntime;
if (!this.pluginRuntimes.some((item) => item.name !== pluginRuntime.name)) { this.pluginRuntimes.push({
return; ...pluginRuntime,
} status: 'ready',
});
const isSetup = (name: string) => { const isSetup = (name: string) => {
const setupPlugins = this.pluginRuntimes.filter((item) => item.status === 'setup'); const setupPlugins = this.pluginRuntimes.filter((item) => item.status === 'setup');

View File

@ -7,11 +7,7 @@ export const enum LifecyclePhase {
Ready = 3, Ready = 3,
BeforeMount = 4, AfterInitPackageLoad = 4,
Mounted = 5,
BeforeUnmount = 6,
} }
export interface ILifeCycleService { export interface ILifeCycleService {
@ -40,7 +36,7 @@ export class LifeCycleService implements ILifeCycleService {
} }
set phase(value: LifecyclePhase) { set phase(value: LifecyclePhase) {
if (value < this.phase) { if (value < this._phase) {
throw new Error('Lifecycle cannot go backwards'); throw new Error('Lifecycle cannot go backwards');
} }

View File

@ -68,7 +68,7 @@ export class PackageManagementService implements IPackageManagementService {
@ISchemaService private schemaService: ISchemaService, @ISchemaService private schemaService: ISchemaService,
@ILifeCycleService private lifeCycleService: ILifeCycleService, @ILifeCycleService private lifeCycleService: ILifeCycleService,
) { ) {
this.lifeCycleService.when(LifecyclePhase.Ready).then(() => { this.lifeCycleService.when(LifecyclePhase.AfterInitPackageLoad).then(() => {
const componentsMaps = this.schemaService.get('componentsMap'); const componentsMaps = this.schemaService.get('componentsMap');
this.resolveComponentMaps(componentsMaps); this.resolveComponentMaps(componentsMaps);
}); });
@ -85,7 +85,6 @@ export class PackageManagementService implements IPackageManagementService {
if (!isProCodeComponentPackage(item)) continue; if (!isProCodeComponentPackage(item)) continue;
const normalized = this.normalizePackage(item); const normalized = this.normalizePackage(item);
await this.loadPackageByNormalized(normalized); await this.loadPackageByNormalized(normalized);
} }
} }

View File

@ -5,6 +5,7 @@ import {
type Spec, type Spec,
type Locale, type Locale,
type Translations, type Translations,
platformLocale,
} from '@alilc/lowcode-shared'; } from '@alilc/lowcode-shared';
import { ILifeCycleService, LifecyclePhase } from './lifeCycleService'; import { ILifeCycleService, LifecyclePhase } from './lifeCycleService';
import { ICodeRuntimeService } from './code-runtime'; import { ICodeRuntimeService } from './code-runtime';
@ -34,7 +35,7 @@ export const IRuntimeIntlService = createDecorator<IRuntimeIntlService>('IRuntim
export class RuntimeIntlService implements IRuntimeIntlService { export class RuntimeIntlService implements IRuntimeIntlService {
private intl: Intl = new Intl(); private intl: Intl = new Intl();
public locale: string = navigator.language; public locale: string = platformLocale;
constructor( constructor(
@ILifeCycleService private lifeCycleService: ILifeCycleService, @ILifeCycleService private lifeCycleService: ILifeCycleService,

View File

@ -23,12 +23,11 @@ export class RuntimeUtilService implements IRuntimeUtilService {
@ILifeCycleService private lifeCycleService: ILifeCycleService, @ILifeCycleService private lifeCycleService: ILifeCycleService,
@ISchemaService private schemaService: ISchemaService, @ISchemaService private schemaService: ISchemaService,
) { ) {
this.lifeCycleService.when(LifecyclePhase.Ready).then(() => { this.lifeCycleService.when(LifecyclePhase.AfterInitPackageLoad).then(() => {
const utils = this.schemaService.get('utils') ?? []; const utils = this.schemaService.get('utils') ?? [];
for (const util of utils) { for (const util of utils) {
this.add(util); this.add(util);
} }
this.toExpose(); this.toExpose();
}); });
} }

View File

@ -1,7 +1,7 @@
import { defineConfig } from 'vitest/config' import { defineProject } from 'vitest/config';
export default defineConfig({ export default defineProject({
test: { test: {
include: ['tests/**/*.spec.ts'] environment: 'jsdom',
} },
}) });

View File

@ -1,23 +0,0 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { EventEmitter } from '../../src';
describe('hookable', () => {
let eventEmitter: EventEmitter;
beforeEach(() => {
eventEmitter = new EventEmitter();
});
it('on', async () => {
const spy = vi.fn();
eventEmitter.on('test', spy);
await eventEmitter.emit('test');
expect(spy).toBeCalled();
});
it('prependListener', () => {
// const spy = vi.fn();
// expect(spy).toC
});
});

View File

@ -0,0 +1,56 @@
import { describe, it, expect } from 'vitest';
import { Barrier } from '../../src';
describe('Barrier', () => {
it('waits for barrier to open', async () => {
const barrier = new Barrier();
setTimeout(() => {
barrier.open();
}, 500); // Simulate opening the barrier after 500ms
const start = Date.now();
await barrier.wait(); // Async operation should await here
const duration = Date.now() - start;
expect(barrier.isOpen()).toBeTruthy();
expect(duration).toBeGreaterThanOrEqual(500); // Ensures we waited for at least 500ms
});
it('mutiple', async () => {
let result = '';
const b1 = new Barrier();
const b2 = new Barrier();
async function run1() {
await b1.wait();
}
async function run2() {
await b2.wait();
}
run1().then(() => {
result += 1;
});
run1().finally(() => {
result += 2;
});
run2().then(() => {
result += 3;
});
run2().finally(() => {
result += 4;
});
b1.open();
await run1();
b2.open();
await run2();
expect(result).toBe('1234');
});
});

View File

@ -5,6 +5,13 @@
"main": "dist/low-code-shared.cjs", "main": "dist/low-code-shared.cjs",
"module": "dist/low-code-shared.js", "module": "dist/low-code-shared.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",
"exports": {
".": {
"import": "./dist/low-code-shared.js",
"require": "./dist/low-code-shared.cjs",
"types": "./dist/index.d.ts"
}
},
"files": [ "files": [
"dist", "dist",
"src", "src",

View File

@ -15,7 +15,7 @@ export type Constructor<T = any> = new (...args: any[]) => T;
export function createDecorator<T>(serviceId: string): ServiceIdentifier<T> { export function createDecorator<T>(serviceId: string): ServiceIdentifier<T> {
const id = <any>( const id = <any>(
function (target: Constructor, targetKey: string, indexOrPropertyDescriptor: any): any { function (target: Constructor, targetKey: string, indexOrPropertyDescriptor: any): any {
return set(serviceId)(target, targetKey, indexOrPropertyDescriptor); return inject(serviceId)(target, targetKey, indexOrPropertyDescriptor);
} }
); );
id.toString = () => serviceId; id.toString = () => serviceId;

View File

@ -1,6 +1,7 @@
import { createIntl, createIntlCache, type IntlShape as IntlFormatter } from '@formatjs/intl'; import { createIntl, createIntlCache, type IntlShape as IntlFormatter } from '@formatjs/intl';
import { mapKeys } from 'lodash-es'; import { mapKeys } from 'lodash-es';
import { signal, computed, effect, type Signal, type ComputedSignal } from '../signals'; import { signal, computed, effect, type Signal, type ComputedSignal } from '../signals';
import { platformLocale } from '../utils';
export { IntlFormatter }; export { IntlFormatter };
@ -14,7 +15,7 @@ export class Intl {
private currentMessage: ComputedSignal<Translations>; private currentMessage: ComputedSignal<Translations>;
private intlShape: IntlFormatter; private intlShape: IntlFormatter;
constructor(defaultLocale: string = navigator.language, messages: LocaleTranslationsRecord = {}) { constructor(defaultLocale: string = platformLocale, messages: LocaleTranslationsRecord = {}) {
if (defaultLocale) { if (defaultLocale) {
defaultLocale = nomarlizeLocale(defaultLocale); defaultLocale = nomarlizeLocale(defaultLocale);
} else { } else {

View File

@ -1,15 +1,15 @@
const userAgent: string = navigator.userAgent; const userAgent: string = window.navigator.userAgent;
export const isWindows = userAgent.indexOf('Windows') >= 0; export const isWindows = userAgent.indexOf('Windows') >= 0;
export const isMacintosh = userAgent.indexOf('Macintosh') >= 0; export const isMacintosh = userAgent.indexOf('Macintosh') >= 0;
export const isLinux = userAgent.indexOf('Linux') >= 0; export const isLinux = userAgent.indexOf('Linux') >= 0;
export const isIOS = export const isIOS =
(isMacintosh || userAgent.indexOf('iPad') >= 0 || userAgent.indexOf('iPhone') >= 0) && (isMacintosh || userAgent.indexOf('iPad') >= 0 || userAgent.indexOf('iPhone') >= 0) &&
!!navigator.maxTouchPoints && !!window.navigator.maxTouchPoints &&
navigator.maxTouchPoints > 0; window.navigator.maxTouchPoints > 0;
export const isMobile = userAgent?.indexOf('Mobi') >= 0; export const isMobile = userAgent?.indexOf('Mobi') >= 0;
export const locale: string | undefined = undefined;
export const platformLocale = navigator.language; export const platformLocale = window.navigator.language;
export const enum Platform { export const enum Platform {
Web, Web,

View File

@ -0,0 +1,3 @@
import { defineProject } from 'vitest/config';
export default defineProject({});

View File

@ -1,5 +1,5 @@
packages: packages:
- 'packages/*' - 'packages/*'
- 'playground' - 'playground/*'
- 'docs' - 'docs'
- 'scripts' - 'scripts'

View File

@ -39,5 +39,11 @@
}, },
"types": ["vite/client", "vitest/globals", "node"] "types": ["vite/client", "vitest/globals", "node"]
}, },
"exclude": ["**/dist", "node_modules"] "include": [
"packages/global.d.ts",
"packages/*/src",
"packages/*/__tests__",
"playground/*/src",
"scripts/*"
]
} }

View File

@ -5,9 +5,12 @@ export default defineWorkspace([
{ {
test: { test: {
include: ['**/__tests__/**/*.spec.{ts?(x),js?(x)}'], include: ['**/__tests__/**/*.spec.{ts?(x),js?(x)}'],
alias: {
'@alilc/lowcode-shared': 'packages/shared',
},
name: 'unit test', name: 'unit test',
environment: 'jsdom', environmentMatchGlobs: [['packages/**', 'jsdom']],
globals: true globals: true,
} },
}, },
]) ]);