From e153f5f6d548db59131024e6cd04366df5d60d75 Mon Sep 17 00:00:00 2001 From: brokun Date: Tue, 10 Dec 2024 13:59:15 +0800 Subject: [PATCH 1/4] feat(core): define metadata for views, including components, through view decorators --- apps/docs/package.json | 3 +- .../antd-menu/antd-menu-view.tsx | 3 +- .../configuration/default-node-render.tsx | 4 +- packages/mana-core/src/view/decorator.ts | 41 ++++++++++++++++--- packages/mana-core/src/view/view-protocol.ts | 1 + packages/mana-core/src/view/view-render.tsx | 1 + 6 files changed, 42 insertions(+), 11 deletions(-) diff --git a/apps/docs/package.json b/apps/docs/package.json index fdbbc3a..789daa3 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -25,10 +25,11 @@ "dependencies": { "@ant-design/icons": "^5.1.0", "@difizen/mana-app": "^0.1.31", + "@difizen/mana-l10n": "^0.1.31", "@difizen/mana-react": "^0.1.31", "@difizen/umi-plugin-mana": "^0.1.31", - "@difizen/mana-l10n": "^0.1.31", "antd": "^5.8.6", + "dayjs": "^1.11.13", "octokit": "^3", "react-copy-to-clipboard": "^5.1.0" } diff --git a/apps/docs/src/application-react/antd-menu/antd-menu-view.tsx b/apps/docs/src/application-react/antd-menu/antd-menu-view.tsx index 636850b..9701278 100644 --- a/apps/docs/src/application-react/antd-menu/antd-menu-view.tsx +++ b/apps/docs/src/application-react/antd-menu/antd-menu-view.tsx @@ -20,9 +20,8 @@ export const ManaMenubarComponent = forwardRef(function ManaMenubarComponent( }); @singleton() -@view('AntdMenuView') +@view({ id: 'AntdMenuView', component: ManaMenubarComponent }) export class AntdMenuView extends BaseView { - override view = ManaMenubarComponent; @prop() count = 0; constructor() { diff --git a/apps/docs/src/application-react/setting-editor/configuration/default-node-render.tsx b/apps/docs/src/application-react/setting-editor/configuration/default-node-render.tsx index 0bba5b8..4945913 100644 --- a/apps/docs/src/application-react/setting-editor/configuration/default-node-render.tsx +++ b/apps/docs/src/application-react/setting-editor/configuration/default-node-render.tsx @@ -1,6 +1,6 @@ import type { RenderProps } from '@difizen/mana-core'; import { Checkbox, DatePicker, Input, InputNumber, Select, Switch } from 'antd'; -import moment from 'moment'; +import dayjs from 'dayjs'; import React from 'react'; const { Option } = Select; @@ -58,7 +58,7 @@ export const DefaultDatePicker: React.FC> = ({ onChange, }) => ( onChange(dateString)} /> ); diff --git a/packages/mana-core/src/view/decorator.ts b/packages/mana-core/src/view/decorator.ts index 3e75c59..9685a34 100644 --- a/packages/mana-core/src/view/decorator.ts +++ b/packages/mana-core/src/view/decorator.ts @@ -1,14 +1,17 @@ +import { isPromise } from 'util/types'; + import type { Newable } from '@difizen/mana-common'; import type { Syringe } from '@difizen/mana-syringe'; import { registerSideOption } from '@difizen/mana-syringe'; +import type { ComponentType } from 'react'; import type { ManaModule } from '../module'; import { ManaContext } from '../module'; import { isWrapperViewComponent, ViewWrapper } from './view-container'; import { ViewManager } from './view-manager'; -import type { SlotPreference, ViewPreference } from './view-protocol'; -import type { View } from './view-protocol'; +import type { View, SlotPreference, ViewPreference } from './view-protocol'; +import { ViewComponentToken } from './view-protocol'; import { OriginViewComponent, ViewComponent } from './view-protocol'; import { SlotPreferenceContribution } from './view-protocol'; import { @@ -32,8 +35,28 @@ export interface ViewDecoratorOption { registry?: Syringe.Registry; } -export function view(factoryId: string, viewModule?: ManaModule) { +interface ViewMeta { + id: string; + component: ComponentType; +} + +export function view(meta: ViewMeta): (target: Newable) => void; +export function view( + factoryId: string, + viewModule?: ManaModule, +): (target: Newable) => void; +export function view( + metaOrFactoryId: string | ViewMeta, + viewModule?: ManaModule, +) { return (target: Newable): void => { + let factoryId: string; + if (typeof metaOrFactoryId === 'string') { + factoryId = metaOrFactoryId; + } else { + factoryId = metaOrFactoryId.id; + Reflect.defineMetadata(ViewComponentToken, metaOrFactoryId.component, target); + } Reflect.defineMetadata(ViewDefineToken, factoryId, target); registerSideOption( { @@ -51,10 +74,17 @@ export function view(factoryId: string, viewModule?: ManaModule) container.register({ token: ViewOption, useValue: viewOption }); const current = container.get(target); container.register({ token: ViewInstance, useValue: current }); + + const constructor = current.constructor as any; + const metaComponent = Reflect.getMetadata(ViewComponentToken, constructor); + const maybeComponent = metaComponent || (current.view as any); + const component = maybeComponent; + // if (isPromise(maybeComponent)) { + // component = await maybeComponent; + // } container.register({ token: OriginViewComponent, useDynamic: () => { - const component = current.view as any; if (isWrapperViewComponent(component)) { return component[OriginViewComponent]; } else { @@ -62,11 +92,10 @@ export function view(factoryId: string, viewModule?: ManaModule) } }, }); - const viewComponent = ViewWrapper(current.view, container); + const viewComponent = ViewWrapper(component, container); container.register({ token: ViewComponent, useDynamic: () => { - const component = current.view as any; if (isWrapperViewComponent(component)) { return component; } else { diff --git a/packages/mana-core/src/view/view-protocol.ts b/packages/mana-core/src/view/view-protocol.ts index 9762361..75d1e80 100644 --- a/packages/mana-core/src/view/view-protocol.ts +++ b/packages/mana-core/src/view/view-protocol.ts @@ -114,6 +114,7 @@ export const ViewOption = Symbol('ViewOption'); export const ViewInstance = Symbol('ViewInstance'); export const ViewDefineToken = Symbol('ViewDefineToken'); +export const ViewComponentToken = Symbol('ViewComponentToken'); export interface ViewFactory { /** diff --git a/packages/mana-core/src/view/view-render.tsx b/packages/mana-core/src/view/view-render.tsx index 814a0f3..934af42 100644 --- a/packages/mana-core/src/view/view-render.tsx +++ b/packages/mana-core/src/view/view-render.tsx @@ -26,6 +26,7 @@ const ViewComponentRender = (props: ViewRenderProps) => { {children} ); }; + export const ViewRender = memo(function ViewRender(props: ViewRenderProps) { const { view, shadow } = props; if (isWrapperViewComponent(view.view) && !shadow) { From 5e9b194a2b1b4daebfa630cd325db79c9be96692 Mon Sep 17 00:00:00 2001 From: brokun Date: Tue, 10 Dec 2024 16:19:27 +0800 Subject: [PATCH 2/4] feat(core): #85 supports view components of the react lazy type --- .../antd-menu/antd-menu-component.tsx | 18 ++++++ .../antd-menu/antd-menu-view.tsx | 17 +----- packages/mana-core/src/view/utils.tsx | 5 ++ .../mana-core/src/view/view-container.tsx | 57 +++++++++++++++---- 4 files changed, 72 insertions(+), 25 deletions(-) create mode 100644 apps/docs/src/application-react/antd-menu/antd-menu-component.tsx diff --git a/apps/docs/src/application-react/antd-menu/antd-menu-component.tsx b/apps/docs/src/application-react/antd-menu/antd-menu-component.tsx new file mode 100644 index 0000000..450089e --- /dev/null +++ b/apps/docs/src/application-react/antd-menu/antd-menu-component.tsx @@ -0,0 +1,18 @@ +import { MAIN_MENU_BAR } from '@difizen/mana-app'; +import * as React from 'react'; +import { forwardRef } from 'react'; + +import { MenuRender } from '../workbench/menu/render.js'; + +export const ManaMenubarComponent = forwardRef(function ManaMenubarComponent( + props, + ref: React.ForwardedRef, +) { + return ( +
+ +
+ ); +}); + +export default ManaMenubarComponent; diff --git a/apps/docs/src/application-react/antd-menu/antd-menu-view.tsx b/apps/docs/src/application-react/antd-menu/antd-menu-view.tsx index 9701278..8593fa8 100644 --- a/apps/docs/src/application-react/antd-menu/antd-menu-view.tsx +++ b/apps/docs/src/application-react/antd-menu/antd-menu-view.tsx @@ -1,23 +1,10 @@ import { MacCommandOutlined } from '@ant-design/icons'; -import { MAIN_MENU_BAR } from '@difizen/mana-app'; import { BaseView, view } from '@difizen/mana-app'; import { singleton } from '@difizen/mana-app'; import { prop } from '@difizen/mana-app'; -import * as React from 'react'; -import { forwardRef } from 'react'; +import { lazy } from 'react'; -import { MenuRender } from '../workbench/menu/render.js'; - -export const ManaMenubarComponent = forwardRef(function ManaMenubarComponent( - props, - ref: React.ForwardedRef, -) { - return ( -
- -
- ); -}); +export const ManaMenubarComponent = lazy(() => import('./antd-menu-component.js')); @singleton() @view({ id: 'AntdMenuView', component: ManaMenubarComponent }) diff --git a/packages/mana-core/src/view/utils.tsx b/packages/mana-core/src/view/utils.tsx index bf5b852..b5764f8 100644 --- a/packages/mana-core/src/view/utils.tsx +++ b/packages/mana-core/src/view/utils.tsx @@ -14,6 +14,11 @@ export const isForwardRefComponent = ( component.render !== undefined ); }; + +// 判断是否是懒加载组件的函数 +export const isLazyComponent = (component: any) => { + return component && component.$$typeof === Symbol.for('react.lazy'); +}; /** * hack * @param component react component diff --git a/packages/mana-core/src/view/view-container.tsx b/packages/mana-core/src/view/view-container.tsx index 927f55f..acb8f11 100644 --- a/packages/mana-core/src/view/view-container.tsx +++ b/packages/mana-core/src/view/view-container.tsx @@ -1,11 +1,12 @@ import { ObservableContext, useInject } from '@difizen/mana-observable'; import type { Syringe } from '@difizen/mana-syringe'; import * as React from 'react'; +import { Suspense } from 'react'; import { useMount, useUnmount } from '../utils/hooks'; import { useViewSize } from './hooks'; -import { isForwardRefComponent } from './utils'; +import { isForwardRefComponent, isLazyComponent } from './utils'; import type { View } from './view-protocol'; import type { ViewComponent } from './view-protocol'; import { OriginViewComponent } from './view-protocol'; @@ -53,8 +54,50 @@ export const ViewContainer = React.forwardRef + | React.LazyExoticComponent>, +) => { + const LazyWrapperRender: WrapperViewComponent = ({ + children, + ...props + }: { + children: React.ReactNode; + }) => { + const containerRef = React.useRef(null); + if (isLazyComponent(ViewComponent)) { + return ( + }> + + {children} + + + ); + } + return ( + + {children} + + ); + }; + return LazyWrapperRender; +}; export const ViewWrapper = ( - ViewComponent: React.FC | React.ForwardRefExoticComponent, + ViewComponent: + | React.FC + | React.ForwardRefExoticComponent + | React.LazyExoticComponent>, container: Syringe.Container, ) => { const ViewWrapperRender: WrapperViewComponent = ({ @@ -63,16 +106,10 @@ export const ViewWrapper = ( }: { children: React.ReactNode; }) => { - const containerRef = React.useRef(null); + const ChildComponent = LazyWrapper(ViewComponent); return ( container }}> - - {children} - + {children} ); }; From 5af82e5f276641b001ef9da8405883a19c69b711 Mon Sep 17 00:00:00 2001 From: brokun Date: Wed, 11 Dec 2024 11:01:02 +0800 Subject: [PATCH 3/4] test(core): view decorator --- .changeset/empty-flies-teach.md | 17 ++++++++++++++ .../mana-core/src/view/decorator.spec.tsx | 22 +++++++++++++++++++ packages/mana-core/src/view/decorator.ts | 2 -- 3 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 .changeset/empty-flies-teach.md create mode 100644 packages/mana-core/src/view/decorator.spec.tsx diff --git a/.changeset/empty-flies-teach.md b/.changeset/empty-flies-teach.md new file mode 100644 index 0000000..5b51efa --- /dev/null +++ b/.changeset/empty-flies-teach.md @@ -0,0 +1,17 @@ +--- +'@difizen/babel-preset-mana': patch +'@difizen/mana-observable': patch +'@difizen/umi-plugin-mana': patch +'@difizen/mana-syringe': patch +'@difizen/mana-common': patch +'@difizen/mana-react': patch +'@difizen/mana-react-example': patch +'@difizen/mana-core': patch +'@difizen/mana-l10n': patch +'@difizen/mana-app': patch +'@difizen/mana-umi-example': patch +'@difizen/mana-ui': patch +'@difizen/mana-docs': patch +--- + +You can now define metadata for views through view decorators, and view components can be React Lazy components, so dynamic components are also supported. diff --git a/packages/mana-core/src/view/decorator.spec.tsx b/packages/mana-core/src/view/decorator.spec.tsx new file mode 100644 index 0000000..3d338d4 --- /dev/null +++ b/packages/mana-core/src/view/decorator.spec.tsx @@ -0,0 +1,22 @@ +import 'react'; +import assert from 'assert'; +import 'reflect-metadata'; + +import { view } from './decorator'; +import { BaseView } from './default-view'; +import { ViewComponentToken, ViewDefineToken } from './view-protocol'; + +describe('app', () => { + it('#view factory', () => { + @view('foo') + class Foo extends BaseView {} + assert(Reflect.getMetadata(ViewDefineToken, Foo) === 'foo'); + }); + it('#view meta', () => { + const FooRender = () => <>; + @view({ id: 'foo', component: FooRender }) + class Foo extends BaseView {} + assert(Reflect.getMetadata(ViewDefineToken, Foo) === 'foo'); + assert(Reflect.getMetadata(ViewComponentToken, Foo) === FooRender); + }); +}); diff --git a/packages/mana-core/src/view/decorator.ts b/packages/mana-core/src/view/decorator.ts index 9685a34..2c87b93 100644 --- a/packages/mana-core/src/view/decorator.ts +++ b/packages/mana-core/src/view/decorator.ts @@ -1,5 +1,3 @@ -import { isPromise } from 'util/types'; - import type { Newable } from '@difizen/mana-common'; import type { Syringe } from '@difizen/mana-syringe'; import { registerSideOption } from '@difizen/mana-syringe'; From b0dfb009cf1fd249743b1c67d4306d99eecc8d7f Mon Sep 17 00:00:00 2001 From: brokun Date: Wed, 11 Dec 2024 11:11:18 +0800 Subject: [PATCH 4/4] test: collect coverage from packages only --- jest.config.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/jest.config.js b/jest.config.js index 6324da2..5ec1fce 100644 --- a/jest.config.js +++ b/jest.config.js @@ -16,10 +16,7 @@ module.exports = { '/docs/', '/scripts/mock/', ], - collectCoverageFrom: [ - 'packages/**/*/src/**/*.{js,jsx,ts,tsx}', - 'src/**/*.{js,jsx,ts,tsx}', - ], + collectCoverageFrom: ['packages/**/*/src/**/*.{js,jsx,ts,tsx}'], moduleDirectories: ['node_modules', 'lib', 'es', 'dist'], moduleNameMapper: { '\\.(less|css)$': 'jest-less-loader',