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/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-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 636850b..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,28 +1,14 @@ 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('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/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', 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 3e75c59..2c87b93 100644 --- a/packages/mana-core/src/view/decorator.ts +++ b/packages/mana-core/src/view/decorator.ts @@ -1,14 +1,15 @@ 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 +33,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 +72,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 +90,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/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} ); }; 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) {