From 6bdcd39b620d8c77f1fa8d032fdaa8874621b231 Mon Sep 17 00:00:00 2001 From: chilingling <26962197+chilingling@users.noreply.github.com> Date: Thu, 5 Jun 2025 16:43:35 +0800 Subject: [PATCH] feat!: registry support hotfix (#1349) --- .prettierignore | 4 +- designer-demo/package.json | 1 + designer-demo/registry.example | 62 ++ designer-demo/registry.js | 135 +--- designer-demo/src/defineEntry.js | 18 - designer-demo/src/main.js | 22 +- designer-demo/src/preview.js | 46 +- designer-demo/vite.config.js | 3 +- docs/README.md | 15 + docs/api/frontend-api/global-layout-api.md | 345 ++++++++- docs/api/frontend-api/imgs/platformExpend.png | Bin 0 -> 113666 bytes .../frontend-api/imgs/showCollapseFalse.png | Bin 0 -> 5593 bytes .../frontend-api/imgs/showCollapseTrue.png | Bin 0 -> 5570 bytes docs/api/frontend-api/imgs/showLineFalse.png | Bin 0 -> 4532 bytes docs/api/frontend-api/imgs/showLineTrue.png | Bin 0 -> 4271 bytes docs/api/frontend-api/registry-api.md | 332 +++++++++ docs/catalog.json | 11 + docs/changelog/changelog.md | 35 + docs/changelog/v2.6-upgrade-guide.md | 413 +++++++++++ .../new-registry-advanced.md | 255 +++++++ .../new-registry.md | 310 ++++++++ .../registry.md | 2 + packages/build/vite-config/package.json | 2 + .../build/vite-config/src/default-config.js | 2 + .../src/vite-plugins/treeShakingPlugin.js | 69 ++ .../vite-plugin-meta-comments/.gitignore | 1 + .../build/vite-plugin-meta-comments/index.js | 4 +- .../vite-plugin-meta-comments/package.json | 7 +- .../src/test/code/output.js | 598 ---------------- .../src/transform-sfc.js | 4 +- .../src/transform.js | 69 +- .../vite-plugin-meta-comments/src/utils.js | 233 +++--- .../test/expected/arrow-func.js.output.js | 15 + .../test/expected/entry.js.output.js | 124 ++++ .../test/expected/function-expr.js.output.js | 15 + .../test/expected/meta-service.js.output.js | 32 + .../test/expected/meta-service.vue.output.vue | 42 ++ .../test/expected/script-setup.vue.output.vue | 30 + .../test/expected/vue-hooks.js.output.js | 33 + .../{src/test => test/legacy}/code/entry.js | 13 +- .../test/legacy/code/output.js | 136 ++++ .../{src/test => test/legacy}/index.js | 6 +- .../{src/test => test/legacy}/meta.js | 0 .../test/transform-sfc.test.js | 195 +++++ .../test/transform.test.js | 216 ++++++ .../test/utils.test.js | 124 ++++ .../vitest.config.js | 33 + .../canvas/DesignCanvas/src/DesignCanvas.vue | 5 +- packages/design-core/index.js | 48 +- packages/design-core/re-export.js | 47 ++ packages/design-core/registry.js | 124 +++- packages/design-core/src/App.vue | 10 +- packages/design-core/src/init.js | 26 +- .../design-core/src/preview/src/Toolbar.vue | 6 +- packages/design-core/src/preview/src/main.js | 10 +- .../src/preview/src/previewDefaultRegistry.js | 92 +++ packages/design-core/vite.config.js | 2 +- packages/engine-cli/src/commands/create.js | 4 +- packages/engine-cli/src/index.js | 4 +- .../engine-cli/template/designer/registry.js | 117 +-- .../engine-cli/template/designer/src/main.js | 2 +- .../template/designer/src/preview.js | 10 +- packages/layout/index.ts | 17 +- packages/layout/src/DesignPlugins.vue | 71 +- packages/layout/src/DesignSettings.vue | 83 ++- packages/layout/src/DesignToolbars.vue | 38 +- packages/layout/src/Main.vue | 48 +- packages/layout/src/ToolbarCollapse.vue | 1 + packages/layout/src/composable/useLayout.ts | 131 +++- packages/layout/src/defaultLayout.js | 38 + packages/plugins/block/meta.js | 1 - packages/plugins/block/src/BlockConfig.vue | 1 + packages/plugins/block/src/BlockEvent.vue | 1 + packages/plugins/block/src/BlockEventForm.vue | 1 + packages/plugins/block/src/BlockEventList.vue | 1 + .../plugins/block/src/BlockGroupArrange.vue | 1 + packages/plugins/block/src/BlockGuide.vue | 1 + packages/plugins/block/src/BlockProperty.vue | 1 + .../plugins/block/src/BlockPropertyForm.vue | 1 + .../plugins/block/src/BlockPropertyList.vue | 1 + packages/plugins/block/src/BlockSetting.vue | 1 + packages/plugins/block/src/CategoryEdit.vue | 1 + packages/plugins/block/src/Main.vue | 1 + packages/plugins/block/src/SaveNewBlock.vue | 1 + .../plugins/block/src/composable/useBlock.ts | 1 + .../plugins/block/src/js/blockPropertyForm.ts | 1 + .../plugins/block/src/js/blockSetting.tsx | 1 + packages/plugins/block/src/js/http.ts | 1 + packages/plugins/bridge/meta.js | 1 - packages/plugins/bridge/src/BridgeManage.vue | 1 + packages/plugins/bridge/src/BridgeSetting.vue | 1 + packages/plugins/bridge/src/Main.vue | 2 + packages/plugins/bridge/src/http.ts | 2 +- packages/plugins/bridge/src/js/resource.ts | 2 +- packages/plugins/datasource/meta.js | 1 - .../datasource/src/DataSourceField.vue | 1 + .../datasource/src/DataSourceFieldCheck.vue | 1 + .../src/DataSourceFieldCheckMultipleLine.vue | 1 + .../src/DataSourceFieldCheckRanger.vue | 1 + .../datasource/src/DataSourceFieldForm.vue | 1 + .../datasource/src/DataSourceFieldList.vue | 1 + .../datasource/src/DataSourceFieldType.vue | 1 + .../plugins/datasource/src/DataSourceForm.vue | 1 + .../src/DataSourceGlobalDataHandler.vue | 1 + .../plugins/datasource/src/DataSourceList.vue | 1 + .../plugins/datasource/src/DataSourceName.vue | 1 + .../datasource/src/DataSourceRecordForm.vue | 1 + .../datasource/src/DataSourceRecordList.vue | 1 + .../datasource/src/DataSourceRecordUpload.vue | 1 + .../src/DataSourceRemoteAutoload.vue | 1 + .../src/DataSourceRemoteDataAdapter.vue | 1 + .../src/DataSourceRemoteDataResult.vue | 1 + .../datasource/src/DataSourceRemoteForm.vue | 1 + .../datasource/src/DataSourceRemotePanel.vue | 331 +++++++++ .../src/DataSourceRemoteParameter.vue | 1 + .../src/DataSourceRemoteResultMapping.vue | 1 + .../datasource/src/DataSourceTemplate.vue | 1 + .../plugins/datasource/src/DataSourceType.vue | 1 + packages/plugins/datasource/src/Main.vue | 1 + .../datasource/src/RemoteDataAdapterForm.vue | 1 + .../src/composable/useDataSource.ts | 1 + packages/plugins/datasource/src/config.ts | 1 + .../plugins/datasource/src/js/datasource.ts | 1 + packages/plugins/datasource/src/js/http.ts | 1 + packages/plugins/help/meta.js | 3 +- packages/plugins/help/src/HelpIcon.vue | 1 + .../plugins/help/src/composable/useHelp.ts | 1 + packages/plugins/i18n/meta.js | 1 - packages/plugins/i18n/src/Main.vue | 1 + .../i18n/src/composable/useTranslate.ts | 1 + packages/plugins/materials/meta.js | 3 +- packages/plugins/materials/src/Main.vue | 5 +- .../materials/src/composable/block-compile.ts | 13 + .../materials/src/composable/useMaterial.ts | 1 + .../materials/src/composable/useResource.ts | 1 + .../plugins/materials/src/meta/block/meta.js | 3 +- .../src/meta/block/src/BlockDetail.vue | 1 + .../src/meta/block/src/BlockGroup.vue | 1 + .../src/meta/block/src/BlockGroupArrange.vue | 1 + .../src/meta/block/src/BlockGroupFilters.vue | 1 + .../src/meta/block/src/BlockGroupPanel.vue | 1 + .../src/meta/block/src/BlockGroupSort.vue | 1 + .../src/meta/block/src/BlockGroupTransfer.vue | 1 + .../block/src/BlockGroupTransferPanel.vue | 1 + .../src/meta/block/src/BlockList.vue | 1 + .../src/meta/block/src/BlockPanel.vue | 1 + .../src/meta/block/src/BlockVersionSelect.vue | 1 + .../materials/src/meta/block/src/Main.vue | 1 + .../materials/src/meta/block/src/http.ts | 1 + .../src/meta/block/src/js/usePanel.ts | 2 + .../materials/src/meta/component/meta.js | 3 +- .../materials/src/meta/component/src/Main.vue | 1 + .../plugins/materials/src/meta/layout/meta.js | 3 +- .../materials/src/meta/layout/src/Main.vue | 1 + packages/plugins/page/meta.js | 3 +- packages/plugins/page/src/LayerLines.vue | 1 + packages/plugins/page/src/Main.vue | 2 + .../plugins/page/src/PageFolderSetting.vue | 5 +- packages/plugins/page/src/PageGeneral.vue | 1 + packages/plugins/page/src/PageHistory.vue | 1 + packages/plugins/page/src/PageHome.vue | 1 + packages/plugins/page/src/PageInputOutput.vue | 1 + packages/plugins/page/src/PageSetting.vue | 5 +- packages/plugins/page/src/PageTree.vue | 1 + packages/plugins/page/src/Tree.vue | 1 + .../plugins/page/src/composable/usePage.ts | 1 + packages/plugins/page/src/http.ts | 1 + packages/plugins/robot/index.ts | 4 +- packages/plugins/robot/meta.js | 6 +- packages/plugins/robot/src/Main.vue | 1 + .../plugins/robot/src/RobotSettingPopover.vue | 1 + packages/plugins/robot/src/js/robotSetting.ts | 1 + packages/plugins/schema/meta.js | 1 - packages/plugins/schema/src/Main.vue | 1 + packages/plugins/script/meta.js | 1 - packages/plugins/script/src/Main.vue | 1 + packages/plugins/script/src/js/method.ts | 1 + packages/plugins/state/meta.js | 1 - .../plugins/state/src/CreateRemoteAPI.vue | 1 + .../state/src/CreateRemoteFunction.vue | 1 + packages/plugins/state/src/CreateStore.vue | 1 + packages/plugins/state/src/CreateVariable.vue | 1 + packages/plugins/state/src/DataSourceList.vue | 1 + .../state/src/DataSourceRightPanel.vue | 1 + packages/plugins/state/src/EditorI18nTool.vue | 1 + packages/plugins/state/src/Main.vue | 1 + .../plugins/state/src/StateFullscreenHead.vue | 1 + packages/plugins/state/src/js/common.ts | 1 + packages/plugins/state/src/js/http.ts | 1 + packages/plugins/tree/meta.js | 3 +- packages/plugins/tree/src/DraggableTree.vue | 1 + packages/plugins/tree/src/Main.vue | 1 + packages/plugins/tutorial/meta.js | 3 +- packages/register/src/common.ts | 196 ++--- packages/register/src/constants.ts | 20 +- packages/register/src/entryHash.ts | 215 ++++-- packages/register/src/index.ts | 9 +- packages/register/src/layoutHash.ts | 18 - packages/register/src/service.ts | 3 +- packages/register/src/templateHash.ts | 11 +- packages/register/test/common.test.ts | 243 +++++++ packages/register/test/configurators.test.ts | 83 +++ packages/register/test/entryHash.test.ts | 669 +++++++++++++----- packages/register/test/hooks.test.ts | 100 +++ packages/register/test/service.test.ts | 147 ++++ packages/register/test/templateHash.test.ts | 137 ++++ packages/register/test/useMessage.test.ts | 212 ++++++ packages/settings/events/meta.js | 3 +- packages/settings/events/src/Main.vue | 1 + .../settings/events/src/commonjs/events.ts | 2 + .../events/src/components/AddEventsDialog.vue | 1 + .../events/src/components/AdvanceConfig.vue | 1 + .../events/src/components/BindEvents.vue | 1 + .../src/components/BindEventsDialog.vue | 1 + .../components/BindEventsDialogContent.vue | 1 + .../components/BindEventsDialogSidebar.vue | 1 + packages/settings/panel/src/Main.vue | 1 + packages/settings/props/meta.js | 3 +- packages/settings/props/src/Main.vue | 1 + .../settings/props/src/components/Empty.vue | 1 + .../props/src/components/groups/LifeCycle.vue | 1 + .../src/components/groups/TableColumn.vue | 1 + .../src/components/groups/TablePager.vue | 1 + .../settings/props/src/components/index.ts | 1 + .../src/components/inputs/BindFunction.vue | 1 + .../src/components/inputs/CheckValue.vue | 1 + .../src/components/inputs/CodeEditor.vue | 1 + .../components/inputs/DraggableOptions.vue | 1 + .../src/components/inputs/NumericSelect.vue | 1 + .../src/components/inputs/SelectOption.vue | 1 + .../src/components/inputs/SwitchValue.vue | 1 + .../src/components/modal/ModalContent.vue | 1 + .../props/src/composable/useProperties.ts | 1 + .../props/src/composable/useProperty.ts | 1 + packages/settings/styles/meta.js | 3 +- packages/settings/styles/src/Main.vue | 1 + .../components/background/BackgroundGroup.vue | 1 + .../background/BackgroundImageGradient.vue | 1 + .../background/BackgroundImageSetting.vue | 1 + .../components/background/ColorOverlay.vue | 1 + .../components/background/ImageSetting.vue | 1 + .../components/background/LinearGradient.vue | 1 + .../components/background/PositionOrigin.vue | 1 + .../components/background/RadialGradient.vue | 1 + .../src/components/border/BorderGroup.vue | 1 + .../src/components/buttons/ButtonGroup.vue | 1 + .../components/classNamesContainer/index.vue | 1 + .../src/components/effects/EffectGroup.vue | 1 + .../settings/styles/src/components/index.ts | 1 + .../src/components/inputs/ImageSelect.vue | 1 + .../src/components/inputs/InputSelect.vue | 1 + .../src/components/inputs/ModalMask.vue | 1 + .../src/components/inputs/NumericSelect.vue | 1 + .../src/components/inputs/ResetButton.vue | 1 + .../styles/src/components/layout/FlexBox.vue | 1 + .../styles/src/components/layout/GridBox.vue | 1 + .../src/components/layout/LayoutGroup.vue | 1 + .../src/components/position/PositionGroup.vue | 1 + .../src/components/shadow/BoxShadowGroup.vue | 1 + .../styles/src/components/size/SizeGroup.vue | 1 + .../src/components/spacing/SpacingGroup.vue | 1 + .../src/components/spacing/SpacingSetting.vue | 1 + .../components/typography/TypographyGroup.vue | 1 + .../components/typography/TypographyMore.vue | 1 + packages/settings/styles/src/js/cssConvert.ts | 2 + packages/settings/styles/src/js/parser.ts | 2 + packages/settings/styles/src/js/useEditor.ts | 1 + packages/settings/styles/src/js/useLayout.ts | 1 + packages/settings/styles/src/js/useStyle.ts | 1 + packages/toolbars/breadcrumb/src/Main.vue | 1 + .../src/composable/useBreadcrumb.ts | 1 + packages/toolbars/clean/src/Main.vue | 1 + packages/toolbars/collaboration/src/Main.vue | 1 + packages/toolbars/fullscreen/package.json | 3 +- packages/toolbars/fullscreen/src/Main.vue | 1 + .../generate-code/src/FileSelector.vue | 1 + packages/toolbars/generate-code/src/Main.vue | 1 + .../src/composable/useSaveLocal.ts | 1 + packages/toolbars/generate-code/src/http.ts | 2 + packages/toolbars/lang/package.json | 1 + packages/toolbars/lang/src/Main.vue | 1 + packages/toolbars/lock/src/Main.vue | 1 + packages/toolbars/lock/src/http.ts | 1 + packages/toolbars/logo/src/AppList.vue | 1 + packages/toolbars/logo/src/Main.vue | 1 + packages/toolbars/media/src/Main.vue | 1 + packages/toolbars/preview/src/Main.vue | 1 + packages/toolbars/redoundo/src/Main.vue | 1 + .../redoundo/src/composable/useHistory.ts | 1 + packages/toolbars/refresh/src/Main.vue | 1 + packages/toolbars/save/src/Main.vue | 1 + packages/toolbars/save/src/js/index.ts | 1 + packages/toolbars/setting/src/Main.vue | 1 + packages/toolbars/themeSwitch/src/Main.vue | 1 + .../themeSwitch/src/composable/index.ts | 11 +- packages/toolbars/view-setting/package.json | 1 + packages/toolbars/view-setting/src/Main.vue | 1 + packages/vue-generator/package.json | 3 +- 298 files changed, 5914 insertions(+), 1716 deletions(-) create mode 100644 designer-demo/registry.example delete mode 100644 designer-demo/src/defineEntry.js create mode 100644 docs/api/frontend-api/imgs/platformExpend.png create mode 100644 docs/api/frontend-api/imgs/showCollapseFalse.png create mode 100644 docs/api/frontend-api/imgs/showCollapseTrue.png create mode 100644 docs/api/frontend-api/imgs/showLineFalse.png create mode 100644 docs/api/frontend-api/imgs/showLineTrue.png create mode 100644 docs/api/frontend-api/registry-api.md create mode 100644 docs/changelog/changelog.md create mode 100644 docs/changelog/v2.6-upgrade-guide.md create mode 100644 docs/extension-capabilities-overview/new-registry-advanced.md create mode 100644 docs/extension-capabilities-overview/new-registry.md create mode 100644 packages/build/vite-config/src/vite-plugins/treeShakingPlugin.js create mode 100644 packages/build/vite-plugin-meta-comments/.gitignore delete mode 100644 packages/build/vite-plugin-meta-comments/src/test/code/output.js create mode 100644 packages/build/vite-plugin-meta-comments/test/expected/arrow-func.js.output.js create mode 100644 packages/build/vite-plugin-meta-comments/test/expected/entry.js.output.js create mode 100644 packages/build/vite-plugin-meta-comments/test/expected/function-expr.js.output.js create mode 100644 packages/build/vite-plugin-meta-comments/test/expected/meta-service.js.output.js create mode 100644 packages/build/vite-plugin-meta-comments/test/expected/meta-service.vue.output.vue create mode 100644 packages/build/vite-plugin-meta-comments/test/expected/script-setup.vue.output.vue create mode 100644 packages/build/vite-plugin-meta-comments/test/expected/vue-hooks.js.output.js rename packages/build/vite-plugin-meta-comments/{src/test => test/legacy}/code/entry.js (79%) create mode 100644 packages/build/vite-plugin-meta-comments/test/legacy/code/output.js rename packages/build/vite-plugin-meta-comments/{src/test => test/legacy}/index.js (82%) rename packages/build/vite-plugin-meta-comments/{src/test => test/legacy}/meta.js (100%) create mode 100644 packages/build/vite-plugin-meta-comments/test/transform-sfc.test.js create mode 100644 packages/build/vite-plugin-meta-comments/test/transform.test.js create mode 100644 packages/build/vite-plugin-meta-comments/test/utils.test.js create mode 100644 packages/build/vite-plugin-meta-comments/vitest.config.js create mode 100644 packages/design-core/re-export.js create mode 100644 packages/design-core/src/preview/src/previewDefaultRegistry.js create mode 100644 packages/layout/src/defaultLayout.js create mode 100644 packages/plugins/datasource/src/DataSourceRemotePanel.vue delete mode 100644 packages/register/src/layoutHash.ts create mode 100644 packages/register/test/common.test.ts create mode 100644 packages/register/test/configurators.test.ts create mode 100644 packages/register/test/hooks.test.ts create mode 100644 packages/register/test/service.test.ts create mode 100644 packages/register/test/templateHash.test.ts create mode 100644 packages/register/test/useMessage.test.ts diff --git a/.prettierignore b/.prettierignore index 2f118dae..339277e4 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,5 @@ dist package-lock.json -**/node_modules/** \ No newline at end of file +**/node_modules/** +# 忽略该文件夹下的测试对比文件,防止自动去掉分号之后导致测试失败 +packages/build/vite-plugin-meta-comments/test/expected/** \ No newline at end of file diff --git a/designer-demo/package.json b/designer-demo/package.json index b3bf27d9..13a8bd99 100644 --- a/designer-demo/package.json +++ b/designer-demo/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@opentiny/tiny-engine": "workspace:^", + "@opentiny/tiny-engine-meta-register": "workspace:^", "@opentiny/tiny-engine-utils": "workspace:*", "@opentiny/vue": "~3.20.0", "@opentiny/vue-design-smb": "~3.20.0", diff --git a/designer-demo/registry.example b/designer-demo/registry.example new file mode 100644 index 00000000..0001d73f --- /dev/null +++ b/designer-demo/registry.example @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2023 - present TinyEngine Authors. + * Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd. + * + * Use of this source code is governed by an MIT-style license. + * + * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, + * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR + * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. + * + */ +// 注册表示例 +import { META_SERVICE, META_APP } from '@opentiny/tiny-engine-meta-register' +import engineConfig from './engine.config' +import { HttpService } from './src/composable' +import scriptPlugin from './src/plugins/script' + +export default { + [META_SERVICE.Http]: HttpService, + 'engine.config': { + ...engineConfig + }, + // 配置 false 隐藏工具栏清空按钮 + [META_APP.Clean]: false, + // 配置 false 隐藏大纲树,手动配置 tree-shaking 为 false,仍然不会被 tree-shaking + // #__TINY_ENGINE_TREE_SHAKING__: false + [META_APP.OutlineTree]: false, + // 替换整个页面JS插件,手动配置 tree-shaking 为 true + /* #__TINY_ENGINE_TREE_SHAKING__: true */ + [META_APP.Page]: scriptPlugin, + // 新增模块 + 'engine.plugins.custom_id': { + ...cuttomPlugin, + id: 'engine.plugins.custom_id' + }, + // 调整插件顺序 + [META_APP.Layout]: { + options: { + relativeLayoutConfig: { + [META_APP.Script]: { + insertBefore: META_APP.AppManage + }, + // 调整插件顺序 + [META_APP.Materials]: { + insertAfter: META_APP.State + }, + // 调整插件上下位置 + [META_APP.Schema]: { + insertBefore: META_APP.Materials + }, + // 调整工具栏顺序 + [META_APP.Save]: { + insertBefore: META_APP.ThemeSwitch + }, + // 支持切换组 + [META_APP.Lang]: { + insertAfter: META_APP.Breadcrumb + } + } + } + } +} diff --git a/designer-demo/registry.js b/designer-demo/registry.js index 032a8a96..23d7d0c8 100644 --- a/designer-demo/registry.js +++ b/designer-demo/registry.js @@ -1,6 +1,6 @@ /** - * Copyright (c) 2024 - present TinyEngine Authors. - * Copyright (c) 2024 - present Huawei Cloud Computing Technologies Co., Ltd. + * Copyright (c) 2023 - present TinyEngine Authors. + * Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd. * * Use of this source code is governed by an MIT-style license. * @@ -9,116 +9,39 @@ * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. * */ - -import { - Breadcrumb, - Fullscreen, - Lang, - ViewSetting, - Logo, - Lock, - Media, - Redoundo, - Save, - Clean, - ThemeSwitch, - Preview, - GenerateCode, - Refresh, - Collaboration, - Materials, - State, - Script, - Tree, - Help, - Schema, - Page, - I18n, - Bridge, - Block, - Datasource, - Robot, - Props, - Events, - Styles, - Layout, - Canvas, - GenerateCodeService, - GlobalService, - ThemeSwitchService -} from '@opentiny/tiny-engine' +import { META_SERVICE, META_APP } from '@opentiny/tiny-engine-meta-register' import engineConfig from './engine.config' import { HttpService } from './src/composable' export default { - root: { - id: 'engine.root', - metas: [HttpService, GenerateCodeService, GlobalService, ThemeSwitchService] // GlobalService 依赖 HttpService,HttpService需要在前面处理 + [META_SERVICE.Http]: HttpService, + 'engine.config': { + ...engineConfig }, - config: engineConfig, - layout: { - ...Layout, + // 调整插件顺序示例: + [META_APP.Layout]: { options: { - ...Layout.options, - isShowLine: true, - isShowCollapse: true, - toolbars: { - left: ['engine.toolbars.breadcrumb', 'engine.toolbars.lock', 'engine.toolbars.logo'], - center: ['engine.toolbars.media'], - right: [ - ['engine.toolbars.themeSwitch', 'engine.toolbars.redoundo', 'engine.toolbars.clean'], - ['engine.toolbars.preview'], - ['engine.toolbars.generate-code', 'engine.toolbars.save'] - ], - collapse: [ - ['engine.toolbars.collaboration'], - ['engine.toolbars.refresh', 'engine.toolbars.fullscreen'], - ['engine.toolbars.lang'], - ['engine.toolbars.viewSetting'] - ] + relativeLayoutConfig: { + [META_APP.Page]: { + insertBefore: META_APP.State + }, + // 调整插件顺序 + [META_APP.OutlineTree]: { + insertAfter: META_APP.Materials + }, + // 调整插件上下位置 + [META_APP.Schema]: { + insertBefore: META_APP.Help + }, + // 调整工具栏顺序 + [META_APP.Save]: { + insertAfter: META_APP.GenerateCode + }, + // 支持切换组 + [META_APP.Lang]: { + insertAfter: META_APP.ViewSetting + } } } - }, - themes: [ - { - id: 'engine.theme.light' - }, - { - id: 'engine.theme.dark' - } - ], - toolbars: [ - ThemeSwitch, - Logo, - Breadcrumb, - Lock, - Media, - Redoundo, - Collaboration, - Clean, - Preview, - Refresh, - GenerateCode, - Save, - Fullscreen, - Lang, - ViewSetting - ], - plugins: [ - Materials, - Tree, - Page, - [Block, { options: { ...Block.options, mergeCategoriesAndGroups: true } }], - Datasource, - Bridge, - I18n, - Script, - State, - Schema, - Help, - Robot - ], - dsls: [{ id: 'engine.dsls.dslvue' }], - settings: [Props, Styles, Events], - canvas: Canvas + } } diff --git a/designer-demo/src/defineEntry.js b/designer-demo/src/defineEntry.js deleted file mode 100644 index f8b215c1..00000000 --- a/designer-demo/src/defineEntry.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright (c) 2023 - present TinyEngine Authors. - * Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd. - * - * Use of this source code is governed by an MIT-style license. - * - * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, - * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR - * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. - * - */ - -import registry from '../registry.js' -import { defineEntry } from '@opentiny/tiny-engine' - -defineEntry(registry) - -export { registry } diff --git a/designer-demo/src/main.js b/designer-demo/src/main.js index 10457cd9..be222688 100644 --- a/designer-demo/src/main.js +++ b/designer-demo/src/main.js @@ -9,15 +9,19 @@ * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. * */ - -// 导入@opentiny/tiny-engine时,内部的依赖包也会逐个导入,可能会执行useComplie,此时需要templateHashMap。所以需要先执行一次defineEntry -import { registry } from './defineEntry.js' -import { init } from '@opentiny/tiny-engine' import { configurators } from './configurators/' import 'virtual:svg-icons-register' -init({ - registry, - configurators, - createAppSignal: ['global_service_init_finish'] -}) +async function startApp() { + const registry = await import('../registry') + const { init } = await import('@opentiny/tiny-engine') + + init({ + // 合并多个注册表 + registry: [registry.default], + configurators, + createAppSignal: ['global_service_init_finish'] + }) +} + +startApp() diff --git a/designer-demo/src/preview.js b/designer-demo/src/preview.js index ea23c375..4c38a859 100644 --- a/designer-demo/src/preview.js +++ b/designer-demo/src/preview.js @@ -9,30 +9,34 @@ * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. * */ - -import { initHook, HOOK_NAME, GenerateCodeService, Breadcrumb, Media, Lang } from '@opentiny/tiny-engine' -import { initPreview } from '@opentiny/tiny-engine' +import { defineEntry } from '@opentiny/tiny-engine-meta-register' import 'virtual:svg-icons-register' -import { HttpService } from './composable' -const beforeAppCreate = () => { - initHook(HOOK_NAME.useEnv, import.meta.env) -} +async function startApp () { + const { initHook, HOOK_NAME, META_SERVICE, initPreview } = await import('@opentiny/tiny-engine') + const { HttpService } = await import('./composable') -initPreview({ - registry: { - root: { - id: 'engine.root', - metas: [HttpService, GenerateCodeService] - }, - config: { + const beforeAppCreate = () => { + initHook(HOOK_NAME.useEnv, import.meta.env) + } + + const registry = { + [META_SERVICE.Http]: HttpService, + 'engine.config': { id: 'engine.config', theme: 'light', - material: ['/mock/bundle.json'], - }, - toolbars: [Breadcrumb, Media, Lang] - }, - lifeCycles: { - beforeAppCreate + material: ['/mock/bundle.json'] + } } -}) + + defineEntry(registry) + + initPreview({ + registry, + lifeCycles: { + beforeAppCreate + } + }) +} + +startApp() diff --git a/designer-demo/vite.config.js b/designer-demo/vite.config.js index e6088179..b61244c6 100644 --- a/designer-demo/vite.config.js +++ b/designer-demo/vite.config.js @@ -8,7 +8,8 @@ export default defineConfig((configEnv) => { root: __dirname, iconDirs: [path.resolve(__dirname, './node_modules/@opentiny/tiny-engine/assets/')], useSourceAlias: true, - envDir: './env' + envDir: './env', + registryPath: './registry.js' }) const customConfig = { diff --git a/docs/README.md b/docs/README.md index 032c7fbb..dd66df81 100644 --- a/docs/README.md +++ b/docs/README.md @@ -29,6 +29,13 @@ - [数据源和Collection—远程字段](./advanced-features/data-source-and-collection-remote-fields.md) - [数据源和Collection—mock数据](./advanced-features/data-source-and-collection-mock-data.md) - [数据源和Collection—使用数据源](./advanced-features/data-source-and-collection-usage.md) + - 路由能力 + - [页面支持嵌套路由](./advanced-features/route-capabilities/page-support-nested-route.md) + - [路由bar一键清除预览页面路径](./advanced-features/route-capabilities/route-bar-clear-preview-page.md) + - [路由bar高亮显示预览页面路径](./advanced-features/route-capabilities/route-bar-current-page-highlight.md) + - [RouterView组件支持预览子界面](./advanced-features/route-capabilities/route-view-support-preview-subpage.md) + - [主题切换功能](./advanced-features/theme-switch.md) + - [画布快捷操作](./advanced-features/canvas-shortcuts.md) - 教程 - [从零搭建一个页面](./tutorials/build-a-page-from-scratch.md) - [第一期2023.10.27](./tutorials/issue-1-2023.10.27.md) @@ -39,6 +46,9 @@ - [简介](./development-getting-started/dev-intro.md) - [快速上手](./development-getting-started/dev-quick-start.md) - [前后端启动联调(Java服务端)](./development-getting-started/debugging-of-java-backend.md) +- 更新日志 + - [更新日志](./changelog/changelog.md) + - [v2.6升级指南](./changelog/v2.6-upgrade-guide.md) - 解决方案 - [Java服务端部署](./solutions/server-deployment-solution-java.md) - [Node.js服务端部署](./solutions/server-deployment-solution.md) @@ -47,9 +57,13 @@ - [设计器中引入第三方组件库](./solutions/third-party-library-in-designer.md) - [物料同步方案](./solutions/material-sync-solution.md) - [本地化CDN方案](./solutions/import-map-local.md) + - [全新区块构建方案](./solutions/block-construction-solution.md) + - [全新画布通信方案](./solutions/canvas-communication-solution.md) - 扩展能力介绍 - [新架构介绍](./extension-capabilities-overview/new-architecture.md) - [注册表](./extension-capabilities-overview/registry.md) + - [注册表(新版)](./extension-capabilities-overview/new-registry.md) + - [注册表高级特性](./extension-capabilities-overview/new-registry-advanced.md) - [元服务和元应用](./extension-capabilities-overview/meta-services-and-meta-apps.md) - 扩展能力使用教程 - [如何开发插件](./extension-capabilities-tutorial/how-to-develop-plugins.md) @@ -63,6 +77,7 @@ - [定制元服务逻辑](./extension-capabilities-tutorial/customize-meta-service-logic.md) - [开发设置器组件](./extension-capabilities-tutorial/develop-configurator-components.md) - [AI插件使用前配置](./extension-capabilities-tutorial/ai-plugin-configuration.md) + - [如何自定义主题](./extension-capabilities-tutorial/how-to-custom-theme.md) - API - [API总览](./api/api-overview.md) - 前端API diff --git a/docs/api/frontend-api/global-layout-api.md b/docs/api/frontend-api/global-layout-api.md index bfba2336..cc274686 100644 --- a/docs/api/frontend-api/global-layout-api.md +++ b/docs/api/frontend-api/global-layout-api.md @@ -1,39 +1,322 @@ -# 布局模块 +# Layout 插件 -## 布局元应用 +## layout 插件 -布局元应用配置如下 +Layout 插件是 Tiny Engine 的核心布局组件,它定义了整个设计器的界面结构。主要包含以下部分: + +- 顶部工具栏(Toolbars) +- 左侧插件(Plugins) +- 右侧设置插件(Settings) +- 中间内容区域(Canvas) + +![设计器布局示意图](./imgs/platformExpend.png) + +### 使用方法 + +#### 注册表中修改配置 + +如果我们要修改 layout 插件的配置,我们可以在注册表 `registry.js` 文件中修改配置。 ```javascript -{ - options: { - configProvider, // 全局配置组件 - configProviderDesign // 规范,可以通过该属于定制一些自定义的交互规范 - }, - metas: [LayoutService] -} -``` - -## 布局元服务 - -布局元服务api如下 - -```javascript -{ - apis: { - PLUGIN_NAME, // 对象,插件对应的元应用id - activePlugin, // 激活plugin面板,参数(插件名,是否激活对应面板) - activeSetting, // 激活setting面板并高亮提示,参数(属性名) - closePlugin, // 关闭插件面板,参数(是否强制关闭) - getDimension, // 获取样式范围 - getPluginState, // 获取插件面板状态 - getScale, // 获取规定比例 - isEmptyPage, // 判断页面状态是否为空 - layoutState, // 对象,layout状态 - pluginState, // 对象,插件状态 - setDimension // 设置样式范围 +export default { + [META_APP.Layout]: { + options: { + // 局部调整插件顺序 + relativeLayoutConfig: {} + // ... laout 插件配置项 + } } } ``` -### \ No newline at end of file +支持的 options 配置项为: +- configProvider 全局配置组件 +- configProviderDesign 规范,可以通过该配置项定制一些自定义的交互规范 +- isShowLine 工具栏是否显示分隔线 +- isShowCollapse 工具栏是否显示折叠菜单 +- layoutConfig 完整布局配置,一旦配置此项,relativeLayoutConfig 将失效 +- relativeLayoutConfig 局部调整插件顺序,仅当 layoutConfig 未配置时生效 + +#### 自定义插件位置 + +自定义插件位置可以使用 options.layoutConfig 配置项,也可以使用 options.relativeLayoutConfig 配置项。 +⚠️注意:当使用 `options.relativeLayoutConfig` 配置项时,`options.layoutConfig` 配置项将失效。 + +##### 使用 options.relativeLayoutConfig 配置项修改插件位置 + +options.relativeLayoutConfig 可以局部调整插件位置。 +适用场景:仅调整局部的插件位置,或者是增加新插件时,指定顺序。 + +支持的相对位置有: +- insertBefore 显示在指定的插件前面 +- insertAfter 显示在指定的插件后面 + +使用示例: + +```javascript +export default { + [META_APP.Layout]: { + options: { + // 局部调整插件顺序 + relativeLayoutConfig: { + // 将页面 JS 插件显示在页面管理插件前面 + [META_APP.Script]: { + insertBefore: META_APP.AppManage + }, + // 将物料面板显示在状态管理插件后面 + [META_APP.Materials]: { + insertAfter: META_APP.State + }, + // 将页面 schema 插件显示在物料插件前面 + [META_APP.Schema]: { + insertBefore: META_APP.Materials + }, + // 将保存按钮显示在主题切换按钮前面 + [META_APP.Save]: { + insertBefore: META_APP.ThemeSwitch + }, + // 将自定义插件显示在大纲树后面 + 'engine.plugins.customPlugin': { + insertAfter: META_APP.OutlineTree + } + } + } + } +} +``` + +##### 使用 options.layoutConfig 配置项修改插件位置 + +options.layoutConfig 可以全局调整插件位置。 +适用场景:调整全局的插件、工具栏的位置,包括新增的插件。 +⚠️注意: +1. 使用 options.layoutConfig 配置项时,options.relativeLayoutConfig 配置项将失效 +2. 使用该配置项时,需要列举完整的插件列表。没有列举的插件,将不会显示。 + +默认的布局配置,请参考 [默认布局配置](https://github.com/opentiny/tiny-engine/blob/develop/packages/layout/src/defaultLayout.js)。 + +使用示例: + +```javascript +export default { + [META_APP.Layout]: { + options: { + layoutConfig: { + plugins: { + left: { + top: [ + META_APP.Materials, + META_APP.OutlineTree, + META_APP.AppManage, + META_APP.BlockManage, + META_APP.Collections, + META_APP.Bridge, + META_APP.I18n, + META_APP.PageController, + META_APP.State + ], + bottom: [META_APP.Schema, META_APP.EditorHelp, META_APP.Robot] + }, + right: { + top: [META_APP.Props, META_APP.Styles, META_APP.Event] + } + }, + toolbars: { + left: [META_APP.Breadcrumb, META_APP.Lock, META_APP.Logo], + center: [META_APP.Media], + right: [ + [META_APP.ThemeSwitch, META_APP.RedoUndo, META_APP.Clean], + [META_APP.Preview], + [META_APP.GenerateCode, META_APP.Save] + ], + collapse: [ + [META_APP.Collaboration], + [META_APP.Refresh, META_APP.Fullscreen], + [META_APP.Lang], + [META_APP.ViewSetting] + ] + } + } + } + } +} +``` + +#### isShowLine 是否显示工具栏分隔线 + +isShowLine 配置项用于控制工具栏是否显示分隔线。 + +使用示例: + +```javascript +export default { + [META_APP.Layout]: { + options: { + isShowLine: false + } + } +} +``` + +显示分隔线示意图: + +![显示分隔线示意图](./imgs/showLineTrue.png) + +不显示分隔线示意图: + +![不显示分隔线示意图](./imgs/showLineFalse.png) + +#### isShowCollapse 是否显示工具栏折叠菜单 + +isShowCollapse 配置项用于控制工具栏是否显示折叠菜单。 + +使用示例: + +```javascript +export default { + [META_APP.Layout]: { + options: { + isShowCollapse: true + } + } +} +``` + +显示折叠菜单示意图: + +![显示折叠菜单示意图](./imgs/showCollapseTrue.png) + +不显示折叠菜单示意图: + +![不显示折叠菜单示意图](./imgs/showCollapseFalse.png) + +#### configProvider 全局配置组件 + +等同于 @opentiny/vue 的 [ConfigProvider](https://opentiny.design/tiny-vue/zh-CN/os-theme/components/config-provider#demos) 全局配置。 + +使用示例: + +```javascript +import { ConfigProvider as TinyConfigProvider } from '@opentiny/vue' + +export default { + [META_APP.Layout]: { + options: { + configProvider: TinyConfigProvider + } + } +} +``` + +#### configProviderDesign 规范 + +configProviderDesign 配置项用于配置规范,相当于给 ConfigProvider 组件的 design 配置项。 + +[ConfigProvider 组件文档](https://opentiny.design/tiny-vue/zh-CN/os-theme/components/config-provider#demos) + +使用示例: + +```javascript +import designSmbConfig from '@opentiny/vue-design-smb' + +export default { + [META_APP.Layout]: { + options: { + configProviderDesign: designSmbConfig + } + } +} +``` + +## useLayout 元服务 + +`useLayout` 是一个元服务,提供了管理布局状态和与布局交互的功能。 + +### 基本用法 + +```javascript +import { useLayout } from '@opentiny/tiny-engine' + +export default { + setup() { + const { + layoutState, + closePlugin, + closeSetting + } = useLayout() + + // 使用布局服务提供的方法和状态 + + return { + // ... + } + } +} +``` + +### API 参考 + +#### 状态 + +| 状态名称 | 类型 | 描述 | +|---------|------|------| +| layoutState | Object | 布局状态对象,包含 plugins 和 settings 状态 | +| leftFixedPanelsStorage | Ref\\> | 固定在左侧的面板列表 | +| rightFixedPanelsStorage | Ref\\> | 固定在右侧的面板列表 | + +#### 方法 + +| 方法名 | 参数 | 返回值 | 描述 | +|-------|------|-------|------| +| closePlugin | (forceClose?: boolean) | void | 关闭左侧插件面板 | +| closeSetting | (forceClose?: boolean) | void | 关闭右侧设置面板 | +| activePlugin | (pluginName: string) | Promise\ | 激活左侧插件面板 | +| activeSetting | (pluginName: string) | void | 激活右侧设置面板 | +| changeLeftFixedPanels | (pluginName: string) | void | 更改左侧固定面板 | +| changeRightFixedPanels | (pluginName: string) | void | 更改右侧固定面板 | + +### 示例 + +#### 切换插件面板 + +```javascript +import { useLayout, META_APP } from '@opentiny/tiny-engine' + +export default { + setup() { + const { activePlugin, activeSetting } = useLayout() + + // 打开材料面板 + const openMaterialsPanel = () => { + activePlugin(META_APP.Materials) + } + + // 打开属性设置面板 + const openPropsPanel = () => { + activeSetting(META_APP.Props) + } + } +} +``` + +#### 固定面板 + +```javascript +import { useLayout, META_APP } from '@opentiny/tiny-engine' + +export default { + setup() { + const { changeLeftFixedPanels, leftFixedPanelsStorage } = useLayout() + + // 固定或取消固定材料面板 + const toggleFixMaterialsPanel = () => { + changeLeftFixedPanels(META_APP.Materials) + } + + return { + toggleFixMaterialsPanel, + leftFixedPanelsStorage + } + } +} +``` diff --git a/docs/api/frontend-api/imgs/platformExpend.png b/docs/api/frontend-api/imgs/platformExpend.png new file mode 100644 index 0000000000000000000000000000000000000000..26bf82749eb8db9930e410e581289c7e54ff2c28 GIT binary patch literal 113666 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%0AI{loAp%L zq}`}^*r>R}<}9;e^6gAY8~Ze#k*xEZPapjIp_fYJAfPLh&7%}HDQqI<*;q!Up2CL2 zGa)#nc1W7Y&*N5wVKS{sMoJxM8W7J7Hs9TBWn&m^=BG3uKGik`QwcA8Pq}N($S_7e zrA?aIq{?Qx%%)f|T50rdkI683GHffoBbCDDn*P80n<|?(x4vz=&a9`bAxUqZ+(gKS9;z4_ z{1V6*WP@)Cn+a^*nP-#2=Ci=&EoF0iX4sI~<~k4N4;!LvtW^vfYdzsM3TqT>iVUX3 zd<1kWEvb?r)3uCDk~`t&%I3k3ckh-<_wFq%5!hr;OL3|~S*f~NN6D75S!W@eTgZ8C zH5;#5S}HvF;h*_C@r2EVb2dPvsWoxN0yM4hx~PeBD-0Wvre*n?-$G?mx3P0bZt!4) zJHJVhk;5Oe(UMFz&)X6i8lO+BLcDm+=0TybbYH<$DBLY5r&T4BWVmeADVG+!Y^kg$ zxl*;76_l9O*TQ0aVRO+&A%5*PKWoV{4Rs{)bE}E6(KPUH0vpL1_F znAJTF!$!Il^KR5%f9a;FoBFJU-=vOaf7_>)X{x{cLb_4;dgZ5tpG_m-Fk{s-jKR$Q zW;;_X*28rQQi9Fsxw65qaa_T9w}ORikXyi<5j-Uu!bVX%H&<|CR)T|Jlerc5b2k6| z2BO#vwsYe6;kcGgpE`& zpWLMV%?aWVHr(XNbFu(fhE3o$SX=d}NXcQ zBpEiODi}5{rj3A~+P6z-I6^jXyec+NSoyeF#q$bI;Bs-Rc*Ewr8-?UG8O~T(+OW{X z-}5T;8zObqjmle$waFwGH6b6Ea0d(XaiqxS86uFZWDlIU5DVjP0y{?u#W;iYv9yINfnaW7|DeV zHbU#n*-4q%!z&Wk7?Q+nTH&1ZM8oFMJ2vV^E8cW)1IP1EC4(fOe|JRGL)&P z6U0(IU#u7jJd_p={**{qWn2qs?QjwAS(1~$b;he_nGJDCoX3$^#uc(&P!{md-s8sb zV0Sp&9lGxDccY@_*^tDT=TVKOfd+_Yn(_oOC1S@T0)W41 zS=P*HVPgV_G*$G&IEY0ka@V(Q=QqP?mx(LMkg$>|*6Y_nTHDC9ECTJY0iL*xm~VBX z2yB*|z^i&cdahG#xSm&18r9<+Md6-*+Yp8?UwykXJZ`wI>6f?G;vBbdU2CI~5VWVL%)u2fEGDQJaQ>TRups;O2(8oRsLZFFJ z$|hsk7J1wf=b|!`et)G%jMOy(5JHw_<;Zz9r#32iPX{?$WEaUr1N&M6mT6M}J+t|6HwsX64QimK^FB|~lxbmu zg^BO0+s)?2VRHn1aq55=Y1_*y%gZ|Q5nyFHQrXy6J&BnOiD`^UHf)@>rao0E*~CeO zYkQLko7Ic3DLc-6uhej}Lh#rNI8In7>O*(4PT7WH)$*b0Alx08!!i_6$Ecbv=nd%77JJEIDGCZk zEG!Fi-@+b)RDq^-i~2X{qU)f;-1n&ev{iEA#C9~w46zftU9t`mNJ55wdhh$*_r8TT zqFF=`C*;e)hLBk!jeVsS#=^~)f=xBkVo{~KnUREAaNEpYhH!hH%{UnNofbM@EyVb5 zUEr|a`6?02rOVtJbkE0rw}m|MRyXRx%~|ff9KG2)e6xT0=6Ez3{d{2`9sRub-~Ww9 zM{kafFOFVZym{(I{UNZq&TRg=Ibj%EITIEkQ~+uzR$6F7LQ0UUqae@Q>a+1e5&=X! z`=?Dk!6YU$7iJn4R!za(3`a@f#lTZ?N+t$0MCB7eIW4RdBQQ>)>v=YAZ&Yu5)@#q@ zak<5Eu*u@{dv}6i!wqzI+7%f3>1@=Z5A;vEXRnV(8F7q4YueLa+)orU|Fm<$ub`6)s$I^1Ve8Z*u-Yd zYAL}~$Elu3xe(L2k*S%2DDkLORyUZ9-*tiK?XkXo7%K3C{#idL`(OM0%b+|juY61G zab|xKchM1R`EEVx49@&s0{;CYIZ*)*;3T-WgU*O%rM(im9G4gS66hBSfaLi4{c& zx!sNG2fcC7rkYKslx2%_)TdM5qtV{!gFF@@H+eRlzda0} z%?GdvVMxfVHaaE~fGPkn%e7?0K8q;&*aN=_HW3XI&m&xlxJt8FXiHPlY{F}!aQwOH zgPW~(Er5aqhy~`gXQU8vy(%PVEN4nknB3a+Js9`ev^Q$-p2Yo92K~-mOli1(<@x2_ zsX@EI=BgX@>hwcE->aj&{fBh+_FsS3V6%K=Wpr<|EsDFLPApXbk>(__&ynK21u+H&!9t-;UZWN6?5EdX8 z+UgqQRgjYzl3F(llvo|LRYoBcA)J7v1R3VKwu;Dz73GSFIl~FsSs-RCoi;`^%mNLI z6}v1jQ_6`&G2jZ!xAsQ$&s!Dx*^Whqj|Ikd0E4BcWj<*;nk-l)!o*>rjvP`DE;7zF)| zM(eaFPV4!VjJgDy;|IGpYX1Q;n*_%S8mXt;+3c2thSZxtKoTP?>j~L5pAB+eJQhz4 z%e+ijq*=+vWP}o_2~DEi1!BhX<$OU{j2W$FD$7{LIOY&17%&2EGMjP7@4aimHPQW8 zQt6%r{k#9b{@Erm^gZG{`flITM$CHvn}2U*HjE0vQ!8OuTFaU%0z7CH(xUbvgy=SA zLz4s}f@hi+G0|%yYGDi@nJ1SdB)fOn5x!`g@>*0eV6%ypQn8A&B4aLnIEuWR%%;;X z2je#U_Lk+QLF7Dl_w#-G_LBx1UDbX34Cnb}$>s@czKM*YDx_wu6!9=qX}!`ZXEVhh zVXG-7YOBu%TZx5HQ3ydRPK=SlG%4qNDZS`Rz=qH31ptc5qTo}QPiUnwka3UMcMh=^ z-DEa{q3>GXkKbq0eLn)_pWRQF$bOV_4Q?#U-8L|r|2#39Zv{43<*Z(KAZH||DzA+W z*{`tzX{1vMAlcgbhV}^>60LE{7}Z9~IrEq&G=ks@WE7JE5ELT0#-ULn#n{TIY=YAw zN}^4?&2?ro?)v__c9a%6ZWmu?_%`Tv?mo7&H|UmUPOX()mtgVOo8Zr%WYm3t%@&`H zE7PtPzXDE35yFtpjYw%oQMD8~CKPTp^&!tb)9v)WA9xGKo3f47 zZI4|>-Rwp^fz5q(qi|B-nJ~ErIVX&SkdfbXT>vfeipB9(2eF3`f(Z{fb0AUWRhH3g zDox5FE3tO_Y=F=fQkc>L5;M_CXQ>v{WZH$N1A=eYZ2JB4K}!Y(%l_u#cgp6@OEzv+ zVK5wb`!060^TcePz-IT);h$wT_JYUZTss9LguP2`YGVYzImth5E8Q~nxujyqLlp9< zu^0om1q&%CDaec@V0X=CDHkB0EHwr!=J9OOsC+S*@wviK#SywW^*sz+&F1mfhh6gL z`0X$VE;p{+J-GU=9~G2CHz)P@q5Sz}GwKO!?ig%-{Cs#8OcIFWytz7c-K_O$o)SW7 zM0W0t3b`AtlEy++y(qpoJd1|2#)6taE`At7n7P|$!?Ql@jT)5xBevB$N>OgUT^XE} z_y0YI!I|&&yae6BqlTQ@%;sc&bn^1${{By=hX)4-Cr3vwPJj6O>A}I_!A~zPPWDd@ zjt&p@pB)?@j-DN!p8Rlpa&hwP=*1J*{E59$UI-)TQGltv*#rL1-r2i0j^=UPj|$rm zBpJk5R`EQA@$(o|DrfOvEp9Xr1}yD0Dnb}|K&iJG4&yKxfs+-EO$sq-)VVSU!xF=i z!me%H%pb6@#r!Gv$Z;lnGv`V09hW)bhZKH^YzXL+@9*`?<0BD{Ki%D|2fvLc-4%ey zECX61A{Z;G@m$!b2!Y|y1P`#;vKI7F*;Fd+_ZhwkD%I*5H|2KP_wo|w^syS1;b7{% z^8E-Khr0C2>%P;ecI9=t)YWZ?dai$=VR0x+{pPO5o-Qih%)_DMnF+o5eEUg^iHT zWQqa+7(f9E>T_X3_$MmB_|B58uR4fN^GB!c>}D0pn|w{B(5N(DU?XA5y3@4SUMZ)8 znOUXHH+?q8HTNnUz0nrh_0Muhy;!%euNPq){x%(5*B80bV)2JR9gRk--@=7A;%|hj z_`2lpVY7p8R9e2~!a{vGFmibev;m5_?EhQ>*Z2!v+yyZKC-k zY<_w$scRf%IGd2xyYq!|<9&1U>?K0E%CD8QK9%LMg)cjZA96Ns$8kE{nBKZmy6Rrt zdVyC8Xb{l45WCwfL?4oSQnl`CCxj$Uy6a}tuQ0k7E z_6R=#8y-0soJ#;2hQcGNXTt_V#ehUL#Lc%q>Gj>LHnMExQd#%cG|%5x(==_h{J)1? zwOwd`Sg)NZXE+i6wX%_3(_k6CiNjU63>MUhJGLjqT))Jlu;VU#{vdu6hNDq9ibpyA zI@dO_w-2Ls@{JPjle3DrIfy6_77-JS)K2&5Fbb*ZM-WRzg~IT z5%Z{;Z+xg^c&FTYGNHQec6pfse{)@vI%;NnAoJ^-jehLb7m~EfO-oC^6X5kdZFKqC2ggZesQLnlj@x?{3WODJBW8tHjs$` z@gG14CTh_r0yZQ)lceF4g2kN0o^H zL%=}WH}zS##hA%3F`3%JeU2#;p&nqx-*Hi8EtFQ@nPCYgvltKneo!{m{AuGu27RL{ z8`u=ur>FUH`NeZ3&2~B{wRxC9w69~RzrZG-T)MmLq@D^u(6QA z1l2t2F?_+ynQ_sdvzFCA1DHGvNB{sJ07*naRQ%JDWP3@Qz{{bjUi7S)0Vi*JW)GME zTLY{Lv({UF?ToaBMl?_ET8D@Kun>B5#BAoz%h~%++bnMkysK;X?H7klxllMge*j?y zGdJHTo7Ya~?_S4|q>fypt|Uv6W9zOS==y=4>r&5`N^;KkbUV;!iMq5d)osny_ORKd z8U<8?DfrSNun39}gE_Ep5swMJVFGDwU3hLN27};kK9ARW3K9|E z-atv4Wrm+V_YItg7BFtmxIYIyytRKuxMRkdiN=#iJW);%vfxLn7x7>!l~fZ&xE#*s zVoIU3of+aU)u?qCRnA^T_5m-aX{x0C0|YMVeAH2j^=c*c0neba`Ii+F$9jo&eBG_N zT8DDsq8)IFqn9Mdvn5B8sqfexm3&WgY+3hfC7L^^`LeW!%`VEOX98r*T9atl0|~*A z$+=+`*`!yzVGx4tCUpoIVt;@T92%zj@q&k9Q8GLkT$s@;nqabB*#PyDq498lxt^Iv z6LEI)IpL8PO3XnL&F^|+Mka=uKzwK^#QKu6X*7=BXSMR(Y&YBOA45u%*CsEiT~)c= z%$~IQM%f(HkKMri+dId%gCIw}gHFi{N>`5W`jT$fTrbB#)bV`ZcO6Zt^Y}@Z{J`5c z_3cuPGJ6mYJ|Tc-C;dBs66(Nci3NttB#P#C zM03PPKXk^tl zs@llEOu75taE}Lel_vjNZ#sXnp0jy$*&-R4Pesy~eeSeC5fq z@93V#!z=!lmPS$hx7__8<_^k+4f~Nv%$C){#*9l192Lb2gvSgH1R`wTXTwknVZ;Eo z3;|mw=*~o!rU3_N%ww^LYz?EZW#JP85^yF=2Fe}(>PhcYazl#$Wbb-j8%NTpq=RI} zqpAYYmUU1aj8uX;Dv>Gd#TNyHLDJ4ehp;RN?8{zq=|fPHj=?*D^adT=Gk?QC(-#|t zKJ=c`^rioRhWTT*%K5dEZ1Au!SS7j=NJ!{N4$`_>LS9 z*IM3BqjWt=&swQ9>lvdc*=}Nt(*0AnzL@x>hJJQqZ_~E%-xEKZpolrPymVL&5uioj zc!~mpj4BLJ=unh11mm14lyjYImIF%`o;ZuI37;SkCRg1l6{|uX+-+lwiQolU(_iL_2S%$ep;tBcq;1qful+T&9l52 zWCf%Y32zJNFpvYqbD)Ee=Qv6rrJs!u5O-YFw{VGxd-c;{Sr*t?Z8ae9z{7 zD&^y_#P2&RPH5h}cfagX4r{I({;X*(ho)EmUVT`-Us+#<<<;`bC<+J|3lN7HUdJg4LQ`u7c7-iBbp3Fw`S{Z1#Tw^Eq3?^-Z*8*l{ScF|6e44yYIn@0uNy|2Lh5e7 zW}8EqM2)<|Xwd7oQ$hUSyHQ8LVNS7dng%uyREvU9Ckv<;z6St82OtO$5^#eYDW_59 z?aDRArmC9bvgD;pr?ceGed!Zb^>=}di8bQ;%ZaLn1b57GC)g0%vT?^Y#~re?eN2X> zWnJQN87}!1#*<$;RT|swIP^KzxDPs;q#x<$t&*8-=gPg&eBL+G=2a^k+x2n=Z|Y3UPE%5U-^mm_dMI3Kb6k!ETV8I-6~e#U>qsZHt#U?hu#0I__Lv zg`~7EcUnwn7H33uhgeHIQOR!atkBj%EE>dktPWLTabiPM+nNlCg*B^Vao{2$Ar}8K z$2#(`_1#aRw?=z(AN|{5k3Q#oE!7)X9_pjyb^C^YkNs=_Jw|nQ*f_AsW&x@kCtwQa zG+Bly0E~em<8b463Ott6sqe-F*!ERkJrUbpnx$%5J_$`;&rHZpTTm_RgyS(UemGKK z+xK^d(_!Hu%AsxOmnFt3853JAO-^mptIM#|O7R_nCu95Iz$G}mck0u7t;+gNw;C3G zZZuZQBC=#gN!uI5fE|M=VvXzBcvL}I3=!mIlas+y1iZk>mD8BU&~wnSlsX&jc6d}` z)6z7{^e38sS-O6w4EDxzPV6RcX;4~ZLMJLH$MFxFg+0}Syy79R5-!#{UT&TI}Q z{=I@YGLocCqwiaTDt+529yM|JowjiB{gl}RM>(rO^U5d%FozaOpfH%z(`u6kup6-I zQ&k|2K#rWu=8vA|bT)t9`Mp!0n0;Tgq-WC;3sHlEL?!GXG!7PJLcTKavaiBxj9sn7nNJ>AOKMpqL zp1|gY*`x`lHZ4h4_^!8|_K4ui#=o9HdM$3UkhG*0j4(SmCj?=OED}mQ-gW2eXNUc~&dRDDlYlMtx%- z&1>mJy3rD_dE#etDe(!o4wh2ic@Atc1ZcgeQBdV@n$-ev;;%U?=%UK_!@f6XK7L^ci)=G1 zic^>a@JT5=$ni$rMa<@c&5L_xlNkJ6ezfUr!6urI453g=8xJ=lQSS9bd#T?BuzBKV za|bqY(48%UMKC>?feO}N7j^A1VP*pWq|n=2bPM!2l>w*q=ijccH`nWR@uonr1E`dK zHh1U6qtg(b&5L^lvEGj^dJ_M!Z{4ws{wU=<;XIP=_WN(ee!8K{o+tIh7L?|*6ZTR# z@Cow<*p!n41$8s@5E+ARXpR)-S-W&LyTN9^%tjy0^|vw;8_o>+w}IY9U&64xt>@16 zwx!OKdhBO&Otau5J3K@UJHc$`?~%@5`m#y~>dif)`4 z$<41{W}}ZMyHQ8paa|lTuVE)gd0hbpTa~WRaIksM*+hoyA$6j~%HPlWiHr;_9c{Pn zCC`(3GK~URK#N75!-gHoP*HfWD<|0eLsqc4RS@^)>AA#SPN~r!@FJ?048($?gbXd> zZ?E-K_B^S_el`IJ0%uXxCk;DaPwE0v_>I}@h1u{onbQ^0?GovL*CIlbE8nt_&Jl7H zog1>}Nj;6bb3s~WMb+ar8{HH%A^0D@Y&@mczyDP9Bt&B5`2CMxx4?BpT(f9%HMzMT; zH?PkIDZs!g1;$-aex$E>aH@(pz0#7WY*ZPfR1$A5^wKXO1Yi;Ik5?|TDpO$bTal-1 z8pCdj(H+ED)3DHLq?=*br%n0SVXKjS8%*}wJltoKv1SjO?_r}1KzN7vco29>h(r8=q(%{wsv4I zjMF`#-$f>DSnRjiE1N$NHmXWwEMsing$00xF}9F+3NR&39&FP4EfA=(fn*FJwNM-q z(`yKih7Ev(qsD7H@{|p=QGGT`-?xRER=r&A?*MCt)DI4wMvKvYo1gZu`5v&z>9bL& zgEG&~)@NIBbtWS!SzsP)5Rjp$0usbQ_Tl^f{PlQjKH5ZK1B(zMc`Lq>vthH(=C-A# zFQj$*?hcHtcPVeun+uHg+dSB3vxm+1Q#RE9aPv6vgH2Fsq)$oEQ>f@s^KFz&g$?Q5 z_)!o=z90MXyJ*Rc9}62(AwZ_~4Q)l9vZ)`knX{>9dtjWh(@iHZ8x7`Rlg?T+(qqU> z_S@{0&7WC~0@ThVzQS#6l)_`fIsKx&Ermk%3}SF!SX&dDYeO(d&leiY4nkA=-S z;L?;0er(RBemLx&FqyGQPqhpWo!iPW`}yH|I=CPlv)XU7hs_iGOnqhR`#2DgmKn8R2xRAnArB{#{VmCc%ps5t(tsO}bqMX zc3;&Qv{8-h@a2p2ReHD+2T?LFSZ1`}<_r65_OSUMz~=YLW;2ukphY5IG%&Cgo41y} ze=%0bIT3wAFowB(HoS^b04;r<;|Wp?a=MOtO5(>Hg^#7+!ZW;UYS`k;wPleHo6XdB zOL)PS4XO9DBx+iM+QDmYf7K6N-z&3+&A0Ef*}w)OET%RJ5j3{&G5}FzTFA0dU@E*Q;@oOf1{--9i+Id)oMQ0n zXgN05{`?}laajZ#8&Uyf8Mb|pJw*G|ygqY}6fWFci7ON9oI+ zJ3R}=^R;iv8)BK*3dTuvVelNrgu+IpzY>d-O#mCo;{9y4a!Vg)G`y*{kXF^%OV2&>_OSUD*!<(mup!8>5}!N3 zy>qPi%Rr$b17i{Mftijyfg)uSj4^l7>s=sZ7#uW+nFIiji(xaKr$zHPO4BvzL(FaJ zyW2rL6k6RnOS=88SaW=m_VVqXjVe3B$svO_YHOcO=(TlSS}fjQFBWelZ8Fu}5Be{^ zy$`*WbmTp+T`wqos+DjLnWe3i-!2n%yIDx~TN^5oQjsCQwBegddkf<>^g4lMdtTgbNP zg9gkU#Qc0W>Q$Yccha=stgjN07%GJH_Qm@S>6=~J(jHq|IUR}@H`ty=jdp{af^wAzRPoQiFZ-6M^ zER2x?u;FiB`zSWS7~dbn%;~cMW)k>*&*F@EyrhndD0mFHLS=*IaoT6&-`j4q%K47k^6NMu;E;jl!Ibe5#nr>oI_Q91-w3 zz=mxiqcA~yu%;3!9jf|zQ{U&=sP5tLn1KZ<^}EjwRW;>q-Pw8Aw8~w9A$z;M&t~w~ zqves!QO+ht7^y}NV{07yhGqI3IOjyH6u|})!V_{p?z;L!yI?k|>*eO-@ORcJDd)Pb$ zY&c{X$-5+uGHo&c*G6ev-4OZr;|gC^HjyeSNHs}IPmX{Ve$plsB*b~c+A9WH(SfsZ>39zhxPvl*7Um$I}1V|?6Yx0SMP+Q zkuFK@OMT!;9bI=v^dpp9sZymvr&_9w_BorUP&Nc%I|=^F-qp0Wt)*c}3tNmv=LiIm zwa{4veZW}_qBaM+xKI^Bz!KePQG&ssth?#L*fcdHZb)LX&`mbk#Sj7}5L~#J!Ct3y zGe6)$7V}f)m6IlIe1+%E#9%dflQ@yEa0EV&pZDWDPYI}rN-Bn1zhxS)?6zwevaHST zM!8Ar+vpv1-R<*I!v#vjzmn(HZ1`j&=KTQi#;aQmxmVPBvj; zrr-z#Qh*~GN}Pw7ggx$=+5Gqx|6iGnY`k_4He6XC1n@?#e;2W`H(XV7dHcU$HWf(5 zc(;BaS`DI#7v0w~;kV}w^$D-lA2wpXPLTz@+x4B_%okT{84!d?z^0H}o9?VAmkYl- z*d+G}cbS#@oYAw9-kEO|&;KAf*potqnb?#tRT9@G$fcMsam+^^e%|%{*TCiC(SDpD{&~*E77dxmR`2H0jkdipYInQsZoS)1#I(qK`7HUTuN0HI zdp(MD

upcP_MGA)!^ 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

c&xl(pYe_g5Cm1?zQrv;m} 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 =o(!D8>!h++G1VTqH=l9RydvOg&wDz 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 BfA0VQ literal 0 HcmV?d00001 diff --git a/docs/api/frontend-api/imgs/showCollapseFalse.png b/docs/api/frontend-api/imgs/showCollapseFalse.png new file mode 100644 index 0000000000000000000000000000000000000000..1d43fd69593f773931722c0c5f5e8e8f37006fc2 GIT binary patch literal 5593 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 Nja#}Zr8n>5{|A|y2g?8e literal 0 HcmV?d00001 diff --git a/docs/api/frontend-api/imgs/showCollapseTrue.png b/docs/api/frontend-api/imgs/showCollapseTrue.png new file mode 100644 index 0000000000000000000000000000000000000000..8b0a2d5f5de15ae0c493acf5d3a4a1cd0cbaaa29 GIT binary patch literal 5570 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 literal 0 HcmV?d00001 diff --git a/docs/api/frontend-api/imgs/showLineFalse.png b/docs/api/frontend-api/imgs/showLineFalse.png new file mode 100644 index 0000000000000000000000000000000000000000..fc26d090b393bb01f7df45a818f68a198e1677fb GIT binary patch literal 4532 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 literal 0 HcmV?d00001 diff --git a/docs/api/frontend-api/registry-api.md b/docs/api/frontend-api/registry-api.md new file mode 100644 index 00000000..fd465c9b --- /dev/null +++ b/docs/api/frontend-api/registry-api.md @@ -0,0 +1,332 @@ +# 注册表 API + +注册表 API 是 TinyEngine 的核心功能模块,提供了元应用、元服务的注册、管理、查询和通信能力。本文档详细介绍所有可用的 API 接口。 + +## 核心注册表 API + +### getMergeMeta + +根据 ID 获取合并后的元应用或元服务配置。 + +```javascript +import { getMergeMeta } from '@opentiny/tiny-engine' + +// 获取特定元应用 +const layoutPlugin = getMergeMeta('engine.layout') +const materialsPlugin = getMergeMeta('engine.plugins.materials') +// 获取自己自定义的插件 +const customPlugin = getMergeMeta('engine.customPlugin') +``` + +**参数:** +- `id`: 元应用或元服务的唯一标识符 + +**返回值:** +- 元应用或元服务的完整配置对象,如果不存在则返回 `undefined` + +### getMetaApi + +获取元应用或元服务提供的 API 接口。 + +```javascript +import { getMetaApi } from '@opentiny/tiny-engine' + +// 获取完整 API 对象 +const globalServiceApi = getMetaApi('engine.service.globalService') + +// 获取特定 API 方法 +const getPageList = getMetaApi('engine.service.globalService', 'getPageList') +``` + +**参数:** +- `id`: 元应用或元服务的 ID +- `key` (可选): 特定 API 方法名 + +**返回值:** +- 当不提供 `key` 时,返回完整的 API 对象 +- 当提供 `key` 时,返回对应的 API 方法 +- 如果不存在则返回 `undefined` + +### getOptions + +获取元应用或元服务的配置选项。 + +```javascript +import { getOptions } from '@opentiny/tiny-engine' + +// 获取布局配置选项 +const layoutOptions = getOptions('engine.layout') +``` + +**参数:** +- `id`: 元应用或元服务的 ID + +**返回值:** +- 配置选项对象,如果不存在则返回 `undefined` + +### getMergeMetaByType + +根据类型获取所有对应的元应用或元服务。 + +```javascript +import { getMergeMetaByType } from '@opentiny/tiny-engine' + +// 获取所有插件 +const services = getMergeMetaByType('plugins') +``` + +**参数:** +- `type`: 元应用或元服务的类型 + +**返回值:** +- 匹配类型的元应用或元服务数组 + +### getAllMergeMeta + +获取所有已注册的元应用和元服务。 + +```javascript +import { getAllMergeMeta } from '@opentiny/tiny-engine' + +// 获取所有注册的元应用和元服务 +const allMetas = getAllMergeMeta() +``` + +**返回值:** +- 所有已注册元应用和元服务的数组 + +## 消息通信 API + +### useMessage + +提供事件订阅和发布机制,实现组件间通信。 + +```javascript +import { useMessage } from '@opentiny/tiny-engine' + +export default { + setup() { + const { subscribe, publish, unsubscribe, broadcast } = useMessage() + + // 订阅消息 + const subscription = subscribe({ + topic: 'schemaChange', + subscriber: 'my-plugin', + callback: (data) => { + console.log('Schema changed:', data) + } + }) + + // 发布消息 + const handleSave = () => { + publish({ + topic: 'schemaChange', + data: { operation: 'save', timestamp: Date.now() } + }) + } + + // 广播消息(会存储为最后一条消息) + const handleBroadcast = () => { + broadcast({ + topic: 'globalUpdate', + data: { type: 'system', message: 'System updated' } + }) + } + + // 取消订阅 + onUnmounted(() => { + unsubscribe(subscription) + // 或者 + unsubscribe({ topic: 'schemaChange', subscriber: 'my-plugin' }) + }) + + return { + handleSave, + handleBroadcast + } + } +} +``` + +**API 方法:** + +#### subscribe + +订阅消息。 + +**参数:** +- `options.topic`: 消息主题 +- `options.subscriber` (可选): 订阅者标识 +- `options.callback`: 消息回调函数 + +**返回值:** +- 订阅信息对象 `{ topic, subscriber }` + +#### publish + +发布消息到指定主题的所有订阅者。 + +**参数:** +- `options.topic`: 消息主题 +- `options.data`: 消息数据 + +#### broadcast + +广播消息,与 `publish` 类似,但会存储为最后一条消息,新的订阅者会自动收到。 + +**参数:** +- `options.topic`: 消息主题 +- `options.data`: 消息数据 + +#### unsubscribe + +取消消息订阅。 + +**参数:** +- `options.topic`: 消息主题 +- `options.subscriber` (可选): 订阅者标识,不传递 subscriber时,会取消所有订阅 + +## 服务定义 API + +### defineService + +定义一个新的元服务。 + +```javascript +import { defineService } from '@opentiny/tiny-engine' + +const MyService = defineService({ + id: 'engine.service.myService', + type: 'MetaService', + initialState: { + count: 0, + items: [] + }, + options: { + enableCache: true + }, + init: ({ state, options }) => { + // 服务初始化逻辑 + console.log('Service initialized with options:', options) + }, + apis: { + increment: (amount = 1) => { + const currentState = MyService.apis.getState() + MyService.apis.setState({ count: currentState.count + amount }) + }, + getItems: () => { + return MyService.apis.getState().items + }, + addItem: (item) => { + const currentState = MyService.apis.getState() + MyService.apis.setState({ + items: [...currentState.items, item] + }) + } + } +}) +``` + +**参数:** +- `serviceOptions.id`: 服务唯一标识 +- `serviceOptions.type`: 必须为 'MetaService' +- `serviceOptions.initialState`: 初始状态对象 +- `serviceOptions.options`: 服务配置选项 +- `serviceOptions.init`: 初始化函数 +- `serviceOptions.apis`: API 方法定义(对象或函数) + +**返回值:** +- 服务对象,包含 `id`、`type`、`options` 和 `apis` 属性 + +**内置 API:** +- `getState()`: 获取当前状态(只读) +- `setState(kv)`: 更新状态 +- `setOptions(kv)`: 更新配置选项 + +## 配置器 API + +### addConfigurator + +添加属性配置器组件。 + +```javascript +import { addConfigurator } from '@opentiny/tiny-engine' +import InputConfigurator from './InputConfigurator.vue' +import SelectConfigurator from './SelectConfigurator.vue' + +addConfigurator([ + { + name: 'InputConfigurator', + component: InputConfigurator + }, + { + name: 'SelectConfigurator', + component: SelectConfigurator + } +]) +``` + +**参数:** +- `components`: 配置器组件数组,每个元素包含 `name` 和 `component` + +### getConfigurator + +获取指定名称的配置器组件。 + +```javascript +import { getConfigurator } from '@opentiny/tiny-engine' + +const InputConfigurator = getConfigurator('InputConfigurator') +``` + +**参数:** +- `name`: 配置器名称 + +**返回值:** +- 配置器组件,如果不存在则返回 `undefined` + +## 高级功能 API + +### initHotfixRegistry + +获取并初始化热修复注册表。 + +```javascript +import { initHotfixRegistry } from '@opentiny/tiny-engine-meta-register' + +// 从远程 URL 加载热修复注册表 +await initHotfixRegistry({ + url: 'https://example.com/hotfix-registry.js' +}) +``` + +**参数:** +- `options.url`: 热修复注册表文件的 URL +- `options.request` (可选): 自定义请求函数 + +## 常量定义 + +### META_SERVICE + +定义了所有内置元服务的 ID 常量。 + +```javascript +import { META_SERVICE } from '@opentiny/tiny-engine' + +console.log(META_SERVICE.GlobalService) // 'engine.service.globalService' +console.log(META_SERVICE.Layout) // 'engine.service.layout' +// ... 更多服务常量 +``` + +### META_APP + +定义了所有内置元应用的 ID 常量。 + +```javascript +import { META_APP } from '@opentiny/tiny-engine' + +console.log(META_APP.Layout) // 'engine.layout' +console.log(META_APP.Materials) // 'engine.plugins.materials' +console.log(META_APP.Canvas) // 'engine.canvas' +// ... 更多应用常量 +``` diff --git a/docs/catalog.json b/docs/catalog.json index a2dd86f6..db74af24 100644 --- a/docs/catalog.json +++ b/docs/catalog.json @@ -81,6 +81,15 @@ { "title": "前后端启动联调(Java服务端)", "name": "debugging-of-java-backend.md" } ] }, + + { + "title": "更新日志", + "name": "changelog", + "articles": [ + { "title": "更新日志", "name": "changelog.md" }, + { "title": "v2.6升级指南", "name": "v2.6-upgrade-guide.md" } + ] + }, { "title": "解决方案", "name": "solutions", @@ -102,6 +111,8 @@ "articles": [ { "title": "新架构介绍", "name": "new-architecture.md" }, { "title": "注册表", "name": "registry.md" }, + { "title": "注册表(新版)", "name": "new-registry.md" }, + { "title": "注册表高级特性", "name": "new-registry-advanced.md" }, { "title": "元服务和元应用", "name": "meta-services-and-meta-apps.md" } ] }, diff --git a/docs/changelog/changelog.md b/docs/changelog/changelog.md new file mode 100644 index 00000000..b11faeeb --- /dev/null +++ b/docs/changelog/changelog.md @@ -0,0 +1,35 @@ +# 更新日志 + +## v2.6 + +### 升级指南 + +[v2.6 升级指南](./v2.6-upgrade-guide.md) + +### What's Change + +#### 🎉 Exciting New Features + +- **新的注册表声明方式**:采用基于唯一 ID 的扁平结构,更加灵活和精确 +- **布局配置优化**:支持 `layoutConfig` 和 `relativeLayoutConfig` 两种布局配置方式 +- **注册表热修复功能**:通过覆盖官方插件的特定函数或模板,实现紧急 bug 修复 +- **默认注册表内置**:内置全量默认注册表,无需重复声明未修改的插件 + +#### 🐛 Bug Fixes + +- 修复了多个与注册表相关的问题 +- 优化了插件的加载和初始化流程 + +#### 📚 Documentation + +- 新增 [v2.6 升级指南](./v2.6-upgrade-guide.md) +- 新增 [新注册表](../extension-capabilities-overview/new-registry.md) 文档 +- 新增 [注册表高级配置](../extension-capabilities-overview/new-registry-advanced.md) 文档 + +#### ⚙️ Other changes + +- 废弃插件 `align` 配置,改用布局配置来定位插件 +- 废弃插件 `type: setting` 配置,统一使用 `type: plugins` +- 优化注册表合并机制,提高性能 + +## 完整的更新日志,请前往 [GitHub Release](https://github.com/opentiny/tiny-engine/releases) 查看。 diff --git a/docs/changelog/v2.6-upgrade-guide.md b/docs/changelog/v2.6-upgrade-guide.md new file mode 100644 index 00000000..c373eb9b --- /dev/null +++ b/docs/changelog/v2.6-upgrade-guide.md @@ -0,0 +1,413 @@ +# v2.6 升级指南 + +> 本文档为 v2.6 版本的升级指南,主要介绍了 v2.6 版本的主要新特性、升级步骤和注意事项。 +> +> 升级前请先阅读 [v2.6 更新日志](./changelog.md),了解 v2.6 版本的变更内容。 + +## 主要变更内容 + +### 1. 注册表声明方式变更 + +v2.6 版本对注册表的声明方式进行了重大调整,采用了基于唯一 ID 的新注册方式,使配置更加灵活和精确。 + +#### 旧版注册表方式 + +```javascript +// 旧版注册表配置示例 +const register = { + root: { + id: 'engine.root', + metas: [GenerateCodeService, GlobalService] + }, + config: engineConfig, + layout: { + ...Layout + options: {...} + }, + themes: [ + { + id: 'engine.theme.light' + }, + { + id: 'engine.theme.dark' + } + ], + toolbars: [Media, Save], + plugins: [Materials, Tree], + settings: [Props, Styles], + Canvas: Canvas +} +``` + +#### 新版注册表方式 + +```javascript +import { META_APP } from '@opentiny/tiny-engine' +// 新版注册表配置示例 +const register = { + 'engine.root': { + id: 'engine.root', + metas: [GenerateCodeService, GlobalService] + }, + 'engine.config': engineConfig, + // 覆盖官方的配置 + [META_APP.Layout]: { + options: {...} + }, + // 配置 false 隐藏工具栏清空按钮,并且在构建的时候,会将工具栏插件的相关代码做 tree-shaking + [META_APP.Clean]: false, + // 替换整个页面JS插件,手动配置 tree-shaking 为 true,会将原来的页面JS插件的代码做 tree-shaking + /* #__TINY_ENGINE_TREE_SHAKING__: true */ + [META_APP.Script]: scriptPlugin, + // 新增的插件,需要使用与官方插件不相同的唯一 id + 'engine.plugins.customPlugin': { + ...customPlugin, + id: 'engine.plugins.customPlugin' + } +} +``` + +#### 升级步骤 + +1. 将注册表配置从旧的分类式结构(`toolbars`, `plugins`, `settings`等)调整为基于唯一 ID 的扁平结构 +2. 对于已存在的官方组件,使用其唯一 ID 作为对象的键 +3. 移除不需要的插件时,将其值设置为 `false` +4. 添加新插件时,确保使用与官方插件不同的唯一 ID + +> **重要提示⚠️**:v2.6 开始,如果对原插件没有改动(配置、替换、删除),则不需要在注册表中进行声明,因为官方内置了全量的注册表。 + +详细内容请参考 [新注册表](../extension-capabilities-overview/new-registry.md)。 + +## API 变更 + +### @opentiny/tiny-engine-meta-register 包 API 变化 + +v2.6 版本对 `@opentiny/tiny-engine-meta-register` 包的 API 进行了重大重构,以下是主要变化: + +#### 移除的 API + +##### 1. `getMergeRegistry` 函数 + +**变更说明**:`getMergeRegistry` 函数已被完全移除,不再提供此 API。 + +**旧版用法**: +```javascript +import { getMergeRegistry } from '@opentiny/tiny-engine' + +// 根据类型和 ID 获取合并后的注册表项 +const plugin = getMergeRegistry('plugins', 'engine.plugins.materials') +const allPlugins = getMergeRegistry('plugins') +``` + +**新版替代方案**: +```javascript +import { getMergeMeta, getMergeMetaByType, getAllMergeMeta } from '@opentiny/tiny-engine' + +// 根据 ID 获取特定的注册表项 +const plugin = getMergeMeta('engine.plugins.materials') + +// 根据类型获取所有注册表项 +const allPlugins = getMergeMetaByType('plugins') + +// 获取所有注册表项 +const allMetas = getAllMergeMeta() +``` + +##### 2. `getLayoutComponent` 函数 + +**变更说明**:`getLayoutComponent` 函数已被移除,不再单独提供布局组件获取功能。 + +**旧版用法**: +```javascript +import { getLayoutComponent } from '@opentiny/tiny-engine-meta-register' + +const layoutComponent = getLayoutComponent({ id: 'engine.layout.header' }) +``` + +**新版替代方案**: +布局组件现在通过新的注册表机制进行管理,应使用 `getMergeMeta` 获取: + +```javascript +import { getMergeMeta } from '@opentiny/tiny-engine' + +const layoutMeta = getMergeMeta('engine.layout') +``` + +#### 新增的 API + +##### 1. `getMergeMetaByType` 函数 + +**功能说明**:根据类型获取所有匹配的注册表项。 + +```javascript +import { getMergeMetaByType } from '@opentiny/tiny-engine' + +// 获取所有插件类型的注册表项 +const plugins = getMergeMetaByType('plugins') +``` + +##### 2. `getAllMergeMeta` 函数 + +**功能说明**:获取所有的注册表项。 + +```javascript +import { getAllMergeMeta } from '@opentiny/tiny-engine' + +// 获取完整的注册表 +const allMetas = getAllMergeMeta() +``` + +##### 3. `initHotfixRegistry` 函数 + +**功能说明**:初始化热修复注册表,支持远程动态加载注册表配置。 + +```javascript +import { initHotfixRegistry } from '@opentiny/tiny-engine-meta-register' + +// 从远程 URL 加载热修复注册表 +await initHotfixRegistry({ + url: 'https://example.com/hotfix-registry.js' +}) +``` + +#### 升级指导 + +1. **替换 `getMergeRegistry` 调用**: + - 将 `getMergeRegistry(type, id)` 替换为 `getMergeMeta(id)` + - 将 `getMergeRegistry(type)` 替换为 `getMergeMetaByType(type)` + +2. **移除 `getLayoutComponent` 调用**: + - 使用 `getMergeMeta()` 获取布局相关的注册表项 + - 通过新的布局配置机制管理布局组件 + +3. **利用新增 API**: + - 使用 `getAllMergeMeta()` 获取完整注册表信息,便于调试和开发 + - 使用 `initHotfixRegistry()` 实现热修复功能 + +详细API请参考 [注册表 API](../api/frontend-api/registry-api.md)。 + +## Vite 配置要求 + +### registryPath 配置 + +**重要说明⚠️**:v2.6 版本开始,为了使注册表的 tree-shaking 功能正常工作,您需要在 `vite.config.js` 中配置 `registryPath` 参数。 + +```javascript +// vite.config.js +import { defineConfig, mergeConfig } from 'vite' +import { useTinyEngineBaseConfig } from '@opentiny/tiny-engine-vite-config' + +export default defineConfig((configEnv) => { + const baseConfig = useTinyEngineBaseConfig({ + viteConfigEnv: configEnv, + root: __dirname, + // 其他配置... + registryPath: './registry.js' // 必须配置,指向注册表文件路径 + }) + + // 其他配置... + return mergeConfig(baseConfig, customConfig) +}) +``` + +这个配置主要用于: +- 支持插件的 tree-shaking 优化 +- 识别被设置为 `false` 的插件并在构建时移除相关代码 +- 解析注册表中的特殊注释指令 + +### 2. 布局(layout)变更 + +v2.6 版本对布局配置进行了优化,使布局更加灵活可配置。 + +#### 布局配置变更 + +新版布局配置提供了两种配置方式: +- `layoutConfig`:完整布局配置,自定义整个布局结构 +- `relativeLayoutConfig`:局部调整插件顺序,适用于仅调整部分插件位置 + +```javascript +// 布局配置示例 +export default { + 'engine.layout': { + options: { + // 完整布局配置(二选一) + layoutConfig: { + plugins: { + left: { + top: [/* 左侧顶部插件列表 */], + bottom: [/* 左侧底部插件列表 */] + }, + right: { + top: [/* 右侧顶部插件列表 */] + } + }, + toolbars: { + left: [/* 左侧工具栏 */], + center: [/* 中间工具栏 */], + right: [/* 右侧工具栏(二维数组支持分组) */], + collapse: [/* 折叠菜单(二维数组支持分组) */] + } + }, + + // 局部调整插件顺序(二选一) + relativeLayoutConfig: { + // 插件相对位置配置 + 'engine.plugins.customPlugin': { + insertBefore: 'engine.plugins.materials' // 显示在物料面板前面 + }, + 'engine.plugins.anotherPlugin': { + insertAfter: 'engine.plugins.i18n' // 显示在国际化插件后面 + } + } + } + } +} +``` + +> **注意⚠️**:当同时配置 `layoutConfig` 和 `relativeLayoutConfig` 时,`layoutConfig` 优先级更高,`relativeLayoutConfig` 将被忽略。 + +详细内容请参考 [布局配置](../api/frontend-api/global-layout-api.md)。 + +### 3. 插件配置变更 + +#### 3.1 插件 `align` 配置的废弃 + +在 v2.6 版本中,插件的 `align` 配置属性已被废弃,不再作为定位插件位置的方式。新版本中应使用 `layoutConfig` 或 `relativeLayoutConfig` 来定位插件。 + +旧版写法: +```javascript +// 不再支持的写法 +const plugin = { + id: 'engine.plugins.customPlugin', + align: 'leftTop' // 已废弃 + // 其他配置... +} +``` + +新版写法: +```javascript +// 新写法 - 使用 layoutConfig 定位插件 +const register = { + 'engine.layout': { + options: { + layoutConfig: { + plugins: { + left: { + top: ['engine.plugins.customPlugin', /* 其他插件... */] + } + } + } + } + } +} + +// 或使用 relativeLayoutConfig +const register = { + 'engine.layout': { + options: { + relativeLayoutConfig: { + 'engine.plugins.customPlugin': { + insertBefore: 'engine.plugins.materials' + } + } + } + } +} +``` + +#### 3.2 插件 `type: setting` 配置的废弃 + +在 v2.6 版本中,右侧设置面板插件不再使用 `type: 'setting'` 来标识,而是统一使用插件 ID 来区分。 + +旧版写法: +```javascript +// 不再支持的写法 +export default { + id: 'engine.setting.props', + title: '属性', + type: 'settings', // 已废弃 + name: 'props', + icon: 'form' +} +``` + +新版写法: +```javascript +// 新写法 +export default { + id: 'engine.setting.props', + title: '属性', + type: 'plugins', // 统一使用 plugins 类型 + name: 'props', + icon: 'form' +} +``` + +右侧设置面板的插件现在也通过布局配置的 `layoutConfig` 或 `relativeLayoutConfig` 进行定位: + +```javascript +const register = { + 'engine.layout': { + options: { + layoutConfig: { + plugins: { + right: { + top: ['engine.setting.props', 'engine.setting.styles', 'engine.setting.event'] + } + } + } + } + } +} +``` + +### 4. 注册表热修复功能 + +v2.6 版本新增了注册表热修复(hotfix)功能,可以通过覆盖官方插件的特定函数或模板,实现紧急 bug 修复,而不需要等待官方版本发布。 + +```javascript +// hotfix 注册表示例 +export default { + 'engine.plugins.i18n': { + overwrite: { + lifeCycles: { + 'Main': { + onMounted: [ + (ctx) => () => { + // 覆盖 i18n 插件的 onMounted 生命周期方法 + const { i18nSearchTypes, currentSearchType } = ctx() + currentSearchType.value = i18nSearchTypes[0].value + } + ] + } + } + } + } +} +``` + +详细内容请参考 [注册表高级配置](../extension-capabilities-overview/new-registry-advanced.md)。 + +## 其他改进 + +- **默认注册表内置**:v2.6 版本内置了全量的默认注册表,如果对原插件没有改动(配置、替换、删除),则不需要在注册表中进行声明。 + +## 升级步骤建议 + +1. 检查当前项目中的注册表配置,将其调整为新的基于唯一 ID 的扁平结构 +2. 更新布局配置,使用 `layoutConfig` 或 `relativeLayoutConfig` 来定位插件 +3. 移除插件中的 `align` 配置和 `type: 'setting'` 配置 +4. 如有必要,使用注册表热修复功能解决紧急问题 + +## 常见问题 + +### 如何查找插件的唯一 ID? + +可以参考官方默认的全局注册表:[默认注册表](https://github.com/opentiny/tiny-engine/blob/develop/packages/design-core/registry.js) + +### 我的插件在升级后无法正常显示 + +请检查以下几点: +1. 插件的唯一 ID 是否正确 +2. 布局配置中是否包含了该插件 +3. 插件是否被设置为 `false` diff --git a/docs/extension-capabilities-overview/new-registry-advanced.md b/docs/extension-capabilities-overview/new-registry-advanced.md new file mode 100644 index 00000000..80ca0d3e --- /dev/null +++ b/docs/extension-capabilities-overview/new-registry-advanced.md @@ -0,0 +1,255 @@ +# 注册表高级配置 + + +## 注册表 hotfix 功能,实现紧急 bug 修复功能 + +> 注:该功能可用版本:2.6.0+ +> +> ⚠️ 该功能应该仅作为紧急 bug 修复使用,不应该滥用,一旦官方已经修复 bug,请及时移除 hotfix 注册表。 + +背景:开源的开发过程中,难免会遇到一些紧急的 bug 需要修复,如果等待开源版本的下个版本发布,可能需要经过这样一个流程: + +1. 用户向TinyEngine团队反馈 bug。(30min - 1h) +2. TinyEngine团队分析 bug 原因,并给出修复方案。(1h - 2h) +3. 验证修复方案,发布新版本。(1h) +4. 用户同步新版本,验证新版本。(1h-2h) +5. 用户确认无误,提交审批流程给领导,发布新版本。(1h-2h) +6. 新版本上线,用户可以正常使用。(1h-2h) + +经过上述的一个流程可以看到,整个标准的修复流程相对比较长,如果是一些对用户影响比较大的问题,在商业上可能无法满足要求。 + +因此,我们推出了注册表的 hotfix 功能,可以通过传入 hotfix 的注册表,对某些插件实现函数级别的覆盖能力,从而实现快速修复紧急 bug。 + +### hotfix 注册表功能使用示例: + +1. 在后端增加一个接口,返回临时的 hotfix 注册表。比如 `/hotfix-registry.js`。没有紧急 bug 的时候,返回空对象。 + +2. 在 TinyEngine 初始化的时候,调用这个接口,获取临时的 hotfix 注册表。 + +```javascript +// 这里获取线上的注册表 +const fetchHotfixRegistry = async (url) => { + const response = await import(/* @vite-ignore */ url) + return response.default +} + +async function startApp() { + // 调用 initHotfixRegistry 方法,传入接口地址以及请求方法,获取临时的 hotfix 注册表并提前注册。 + const hotfixRegistry = + (await initHotfixRegistry({ + url: 'http://localhost:8090/hotfixRegistry.js', + request: fetchHotfixRegistry + })) || {} + + const registry = await import('../registry') + const { init } = await import('@opentiny/tiny-engine') + + init({ + // 合并多个注册表 + registry: [registry.default, hotfixRegistry], + configurators, + createAppSignal: ['global_service_init_finish'] + }) +} + +startApp() +``` + +示例 hotfix 注册表: + +```javascript +// hotfixRegistry.js +export default { + 'engine.plugins.i18n': { + overwrite: { + methods: { + 'Main': { + // 覆盖 i18n 插件的 openEditor 方法 + openEditor: (ctx) => (_event, row) => { + const { isEditMode, editingRow, i18nTable, langList, getActiveRow, utils } = ctx() + isEditMode.value = Boolean(row.key) + editingRow.value = row + if (!isEditMode.value) { + row.key = `custom.${utils.guid()}` + langList.value.unshift(row) + } + i18nTable.value.setActiveRow(row).then(() => { + getActiveRow() + }) + } + } + }, + lifeCycles: { + 'Main': { + onMounted: [ + // i18n 插件 Main.vue 文件的第一个 onMounted 方法,不覆盖 + '', + // 覆盖 i18n 插件 Main.vue 文件的第二个 onMounted 方法 + (ctx) => () => { + const { i18nSearchTypes, currentSearchType } = ctx() + console.log('overWrite i18n onMounted', i18nSearchTypes, currentSearchType.value) + currentSearchType.value = i18nSearchTypes[0].value + } + ] + } + } + } + } +} +``` + +### 注册表 hotfix 功能说明 + +#### 注册表的 hotfix 功能,需要提前注册,因为 overWrite 的逻辑需要提前读取。 + +即 initHotfixRegistry 方法的调用,必须在 registry 以及 init 方法之前。(所以 注册表 以及 init 方法都需要改成异步的 import) + +```javascript +async function startApp() { + + const hotfixRegistry = + (await initHotfixRegistry({ + url: 'http://localhost:8090/hotfixRegistry.js', + request: fetchHotfixRegistry + })) || {} + + const registry = await import('../registry') + const { init } = await import('@opentiny/tiny-engine') + + init({ + // 合并多个注册表 + registry: [registry.default, hotfixRegistry], + configurators, + createAppSignal: ['global_service_init_finish'] + }) +} +``` + +#### hotfix 注册表的覆盖能力 + +1. 覆盖插件的 methods 方法(自定义方法) +2. 覆盖插件的 lifeCycles 方法(vue 生命周期) + +### hotfix 注册表覆盖示例 + +#### 覆盖插件的 methods 方法 + +假如我们希望覆盖 i18n 插件中 Main.vue 文件的 openEditor 方法: + +1. 查看 i18n 插件的 src/Main.vue 文件,发现有 metaService 的注释,确认可以对该文件进行覆盖。(没有 metaService 或者是 metaComponent 注释的文件,无法进行覆盖) + +metaService 或者是 metaComponent 注释的格式如下: + +```javascript +/* metaService: engine.plugins.i18n.Main */ + +/* metaComponent: engine.plugins.i18n.Main */ +``` + +2. 查看 Main.vue 文件的 metaService 注释,确认 id 为 engine.plugins.i18n.Main。我们将这个 id 拆分成两个部分: + +a. 插件 id:engine.plugins.i18n +b. 文件 id:Main + +3. 根据插件 id 和文件id,我们就可以确定配置的相关 key。于是:我们就可以得到如下代码: + +```javascript +export default { + 'engine.plugins.i18n': { + overwrite: { + methods: { + 'Main': { + openEditor: (ctx) => (_event, row) => { + const { isEditMode, editingRow, i18nTable, langList, getActiveRow, utils } = ctx() + isEditMode.value = Boolean(row.key) + editingRow.value = row + if (!isEditMode.value) { + row.key = `custom.${utils.guid()}` + langList.value.unshift(row) + } + i18nTable.value.setActiveRow(row).then(() => { + getActiveRow() + }) + } + } + } + } + } +} +``` + +代码解析: +- 'engine.plugins.i18n',指定我们要配置 i18n 插件。 +- overwrite 指定我们要使用覆盖功能。 +- methods 指定我们要覆盖 i18n 插件的 methods 方法。 +- 'Main',指定我们要覆盖 i18n 插件的 Main.vue 文件。 +- openEditor 指定我们要覆盖 i18n 插件的 openEditor 方法。 + +方法覆盖说明: +- 方法覆盖的格式为:`方法名: (ctx) => (_event, row) => { ... }`。 +- ctx 为上下文对象方法,通过 ctx() 获取,可以得到原来的上下文对象。 +- _event, row 为原来方法形参(入参),不可以覆盖。 +- `{...}` 为新方法的实现,在这里实现函数覆盖的逻辑。 + +##### 覆盖插件的 lifeCycles 方法 + +假如我们希望覆盖 i18n 插件的 onMounted 方法: + +1. 查看 i18n 插件的 src/Main.vue 文件,发现有 metaService 的注释,确认可以对该文件进行覆盖。(没有 metaService 或者是 metaComponent 注释的文件,无法进行覆盖) + +2. 查看 Main.vue 文件的 metaService 注释,确认 id 为 engine.plugins.i18n.Main。我们将这个 id 拆分成两个部分: + +a. 插件 id:engine.plugins.i18n +b. 文件 id:Main + +3. 根据插件 id 和文件id,我们就可以确定配置的相关 key。于是:我们就可以得到如下代码: + +```javascript +export default { + 'engine.plugins.i18n': { + overwrite: { + lifeCycles: { + 'Main': { + onMounted: [ + (ctx) => () => { + const { i18nSearchTypes, currentSearchType } = ctx() + console.log('overWrite i18n onMounted', i18nSearchTypes, currentSearchType.value) + currentSearchType.value = i18nSearchTypes[0].value + } + ] + } + } + } + } +} +``` + +代码解析: + +- 'engine.plugins.i18n',指定我们要配置 i18n 插件。 +- overwrite 指定我们要使用覆盖功能。 +- lifeCycles 指定我们要覆盖 i18n 插件的 lifeCycles 方法 (vue 生命周期)。 +- 'Main',指定我们要覆盖 i18n 插件的 Main.vue 文件。 +- onMounted 指定我们要覆盖 i18n 插件的 onMounted 方法,由于 onMounted 方法可能会声明多次,所以我们这里需要使用数组,需要覆盖第几次 onMounted 方法,就在数组对应的排列顺序上写覆盖方法,如果前面的不需要覆盖,则写空字符串。 + +比如: + +```javascript +onMounted: [ + '', + '', + '', + // 这里覆盖第4次 onMounted 方法 + 'onMounted: (ctx) => () => { ... }' +] +``` + +现在,让我们再来看看使用了 hotfix 注册表之后的修复流程: + +1. 二开用户向TinyEngine团队反馈 bug。(30min - 1h) +2. TinyEngine 分析 bug 原因,并给出修复方案。(1h - 2h) +3. 二开用户使用 hotfix 注册表功能,覆盖官方的某个函数或者是模板。(10min) +4. 用户验证修复方案,推送到生产环境注册表。(1h) +5. 生产环境生效,用户正常使用。 + +可以看到,使用 hotfix 注册表之后,修复流程大大缩短,大大提高了修复效率。 diff --git a/docs/extension-capabilities-overview/new-registry.md b/docs/extension-capabilities-overview/new-registry.md new file mode 100644 index 00000000..ae13b3b8 --- /dev/null +++ b/docs/extension-capabilities-overview/new-registry.md @@ -0,0 +1,310 @@ +# 注册表(新) + +⚠️注意:该文档仅适用于 TinyEngine v2.6+ 版本,如果需要了解旧的注册表配置方式,请参考 [旧注册表](./registry.md)。 + +## 什么是注册表 + +在 新架构介绍中,我们引入了注册表的概念,二次低代码平台开发用户通过注册表配置元服务元应用,TinyEngine底层引擎读取注册表的配置,完成元应用元服务的定制,然后加载对应的元应用元服务,完成低代码平台的启动。 + +所以注册表就是完成元应用元服务注册、配置、覆盖的TinyEngine提供的底层核心功能。 + +注册表的作用: + +- 接收元应用元服务的配置,传递到低代码底层引擎,完成低代码平台的定制化。 +- 合并默认的元应用元服务的配置项以及用户的自定义配置项。 +- 提供查询能力,使得元服务与元服务之间能够相互通信,或者相关状态变量。 + +## 注册表配置结构 + +传入到TinyEngine底层引擎的示例: + +```javascript +import { META_APP } from '@opentiny/tiny-engine' +// 注册表配置示例 +const register = { + 'engine.root': { + id: 'engine.root', + metas: [GenerateCodeService, GlobalService] + }, + 'engine.config': engineConfig, + // 覆盖官方的配置 + [META_APP.Layout]: { + options: {...} + }, + // 配置 false 隐藏工具栏清空按钮,并且在构建的时候,会将工具栏插件的相关代码做 tree-shaking + [META_APP.Clean]: false, + // 替换整个页面JS插件,手动配置 tree-shaking 为 true,会将原来的页面JS插件的代码做 tree-shaking + /* #__TINY_ENGINE_TREE_SHAKING__: true */ + [META_APP.Script]: scriptPlugin, + // 新增的插件,需要使用与官方插件不相同的唯一 id + 'engine.plugins.customPlugin': { + ...customPlugin, + id: 'engine.plugins.customPlugin' + } +} +``` + +示例解读: +1. 最外层为一个对象结构,每个键都是一个唯一的注册表ID。 +2. `engine.root`:配置核心的元服务,许多的插件依赖这些核心的元服务。 +3. `engine.config`:低代码引擎的配置,主要配置物料、主题等等。 +4. `[META_APP.Layout]`:配置低代码引擎的布局,可以通过扩展官方布局来自定义。 +5. `[META_APP.Clean]: false`:通过设置为 `false` 来隐藏特定工具栏按钮,同时在构建时会进行 tree-shaking 优化。 +6. `[META_APP.Script]`:替换整个页面JS插件,并通过注释 `#__TINY_ENGINE_TREE_SHAKING__: true` 指示构建工具对原插件代码进行 tree-shaking。 +7. `engine.plugins.customPlugin`:添加新的自定义插件,需要使用与官方插件不同的唯一ID。 + +通过这种基于ID的注册方式,可以更精细地控制平台的各个部分,实现添加、替换或移除特定功能,而不需要重新配置整个注册表结构。 + + +注意⚠️:v2.6 开始,如果对原插件没有改动(配置、替换、删除),则不需要在注册表中进行声明,因为官方内置了全量的注册表: + +官方默认的全局注册表,请参考 [默认注册表](https://github.com/opentiny/tiny-engine/blob/develop/packages/design-core/registry.js)。 + +## 注册表使用 + +### 初始化时传入注册表 + +我们可以在初始化的时候传入注册表,初始化的时候会合并默认的注册表和传入的注册表,然后生成新的注册表。并根据注册表的配置,完成低代码平台的定制化。 + +1. 在 registry.js 中声明注册表 + +```javascript +// registry.js +import scriptPlugin from './src/plugins/script' + +export default { + [META_SERVICE.Http]: HttpService, + 'engine.config': { + ...engineConfig + }, + // 删除工具栏清空按钮 + [META_APP.Clean]: false, + // 替换整个页面JS插件 + [META_APP.Script]: scriptPlugin, + // 传入插件配置 + [META_APP.Layout]: { + options: { + relativeLayoutConfig: { + // ... + } + } + } +} +``` + +2. 调用 init 方法传入注册表,初始化TinyEngine + +```javascript +async function startApp() { + const registry = await import('../registry') + const { init } = await import('@opentiny/tiny-engine') + + init({ + // 传入注册表 + registry: [registry.default], + // 配置器 + configurators, + // 其他配置项 + createAppSignal: ['global_service_init_finish'] + }) +} + +// 初始化 TinyEngine +startApp() +``` + + +### 运行时使用注册表 + +在 TinyEngine 启动起来之后,我们可以通过注册表提供的能力,获取到元服务、元应用、配置,并进行事件订阅、插件之间的通信等等。 + +#### 获取元服务或元应用 + +```javascript +import { getMergeMeta, getMetaApi } from '@opentiny/tiny-engine' + +export default { + setup() { + // 获取物料面板插件 + const materialsPlugin = getMergeMeta('engine.plugins.materials') + + // 获取物料面板插件的入口组件 + const materialsEntry = materialsPlugin?.entry + + // 获取全局服务的 API + const globalServiceApi = getMetaApi('engine.service.globalService') + + // 获取生成代码服务的 API + const generateCodeApi = getMetaApi('engine.service.generateCode') + + // 获取页面JS插件 + const pageControllerPlugin = getMergeMeta('engine.plugins.pagecontroller') + } +} +``` + +#### 获取配置 + +```javascript +import { getMergeMeta, getOptions } from '@opentiny/tiny-engine' + +export default { + setup() { + // 获取引擎配置 + const engineConfig = getMergeMeta('engine.config') + + // 获取特定配置项 + const platformId = engineConfig?.platformId + const materials = engineConfig?.materials + const editMode = engineConfig?.editMode + + // 获取布局配置选项 + const layoutOptions = getOptions('engine.layout') + + // 或者通过 getMergeMeta 获取布局配置 + const layoutConfig = getMergeMeta('engine.layout')?.options + + return { + platformId, + materials, + editMode, + layoutOptions, + layoutConfig + } + } +} +``` + +#### 事件订阅与发布 + +```javascript +import { useMessage, getMetaApi } from '@opentiny/tiny-engine' + +export default { + setup() { + // 获取消息订阅发布系统 + const { subscribe, publish, unsubscribe } = useMessage() + + // 订阅事件 + subscribe({ + topic: 'schemaChange', + subscriber: 'custom-plugin', + callback: (data) => { + console.log('schema 发生了变化', data) + } + }) + + // 发布事件 + const notifyPageSaved = () => { + publish({ + topic: 'schemaChange', + data: { + operation: {...} + } + }) + } + + // 组件销毁时取消订阅 + onUnmounted(() => { + unsubscribe({ + topic: 'schemaChange', + subscriber: 'custom-plugin' + }) + }) + + // 获取全局服务也可以使用事件机制 + const globalService = getMetaApi('engine.service.globalService') + if (globalService?.getBaseInfo) { + globalService.getBaseInfo().then((data) => { + console.log('全局应用信息', data) + }) + } + + return { + notifyPageSaved + } + } +} +``` + +#### 使用内置Hook API + +TinyEngine提供了许多内置的Hook API,可以更便捷地访问各种官方的元应用元服务: + +```javascript +import { + useCanvas, + usePage, + useLayout, + useProperties, + useMaterial +} from '@opentiny/tiny-engine' + +export default { + setup() { + // 使用schema服务相关 API + const canvas = useCanvas() + + // 使用页面相关API + const page = usePage() + + // 使用布局相关API + const layout = useLayout() + + // 使用属性面板相关API + const properties = useProperties() + + // 使用物料相关API + const material = useMaterial() + + const toggleSidePanel = (panelName) => { + layout.activePlugin(panelName) + } + + // 详细的插件 API,请参考各个元服务的 API 文档 + } +} +``` + +通过以上示例,可以看到注册表提供了一种统一的方式来获取和操作低代码平台中的各种服务和插件,实现了解耦和灵活的通信机制。这使得开发者可以更容易地扩展和定制 TinyEngine 平台,而不需要深入了解底层实现细节。 + +更多高级特性,请参考 [注册表高级配置](./new-registry-advanced.md)。 + + +## Vite 配置要求 + +**重要说明⚠️**:为了使注册表的 tree-shaking 功能正常工作,您需要在 `vite.config.js` 中配置 `registryPath` 参数,指向您的注册表文件路径。 + +```javascript +// vite.config.js +import { defineConfig, mergeConfig } from 'vite' +import { useTinyEngineBaseConfig } from '@opentiny/tiny-engine-vite-config' + +export default defineConfig((configEnv) => { + const baseConfig = useTinyEngineBaseConfig({ + viteConfigEnv: configEnv, + root: __dirname, + // 其他配置... + registryPath: './registry.js' // 必须配置,指向注册表文件路径 + }) + + const customConfig = { + // 您的自定义配置... + } + + return mergeConfig(baseConfig, customConfig) +}) +``` + +### 为什么需要配置 registryPath? + +1. **Tree-shaking 优化**:TinyEngine 需要在构建时解析注册表文件,识别哪些插件被设置为 `false`,从而在构建时移除相关代码,减小最终打包体积。 + +2. **注释指令解析**:支持解析注册表中的特殊注释(如 `#__TINY_ENGINE_TREE_SHAKING__: true`),实现更精细的代码优化。 + +3. **构建优化**:通过静态分析注册表配置,在构建时就确定最终需要包含的功能模块,提高运行时性能。 + +如果没有配置 `registryPath`,以下功能可能无法正常工作: +- 插件的 tree-shaking 优化 +- 通过设置 `false` 来移除插件的功能 +- 构建时的代码体积优化 diff --git a/docs/extension-capabilities-overview/registry.md b/docs/extension-capabilities-overview/registry.md index f8af68b7..1d5165a7 100644 --- a/docs/extension-capabilities-overview/registry.md +++ b/docs/extension-capabilities-overview/registry.md @@ -1,5 +1,7 @@ # 注册表 +⚠️注意:TinyEngine v2.6 版本之后,注册表的方式有所变化,请参考 [新注册表](./new-registry.md) 了解新的注册表配置方式。 + ## 什么是注册表 在 新架构介绍中,我们引入了注册表的概念,二次低代码平台开发用户通过注册表配置元服务元应用,TinyEngine底层引擎读取注册表的配置,完成元应用元服务的定制,然后加载对应的元应用元服务,完成低代码平台的启动。 diff --git a/packages/build/vite-config/package.json b/packages/build/vite-config/package.json index da9702b6..49945803 100644 --- a/packages/build/vite-config/package.json +++ b/packages/build/vite-config/package.json @@ -16,10 +16,12 @@ "dependencies": { "@babel/core": "~7.23.2", "@babel/generator": "~7.23.2", + "@babel/parser": "^7.27.2", "@babel/traverse": "~7.23.2", "@esbuild-plugins/node-globals-polyfill": "^0.2.3", "@esbuild-plugins/node-modules-polyfill": "^0.2.2", "@opentiny/tiny-engine-vite-plugin-meta-comments": "workspace:^", + "@rollup/plugin-replace": "^6.0.2", "@types/node": "^18.0.0", "@vitejs/plugin-vue": "^5.1.2", "@vitejs/plugin-vue-jsx": "^4.0.1", diff --git a/packages/build/vite-config/src/default-config.js b/packages/build/vite-config/src/default-config.js index 67a9d3c9..0c53dced 100644 --- a/packages/build/vite-config/src/default-config.js +++ b/packages/build/vite-config/src/default-config.js @@ -14,6 +14,7 @@ import { getBaseUrlFromCli, copyBundleDeps, importMapLocalPlugin } from './local import { devAliasPlugin } from './vite-plugins/devAliasPlugin.js' import { htmlUpgradeHttpsPlugin } from './vite-plugins/upgradeHttpsPlugin.js' import { canvasDevExternal } from './canvas-dev-external.js' +import { treeShakingPlugin } from './vite-plugins/treeShakingPlugin.js' const monacoEditorPlugin = monacoEditorPluginCjs.default const nodeGlobalsPolyfillPlugin = nodeGlobalsPolyfillPluginCjs.default @@ -151,6 +152,7 @@ export function useTinyEngineBaseConfig(engineConfig) { const config = getDefaultConfig(engineConfig) config.plugins.push( + treeShakingPlugin(engineConfig.registryPath), createSvgIconsPlugin({ iconDirs: engineConfig.iconDirs || [], symbolId: 'icon-[name]', diff --git a/packages/build/vite-config/src/vite-plugins/treeShakingPlugin.js b/packages/build/vite-config/src/vite-plugins/treeShakingPlugin.js new file mode 100644 index 00000000..b0dea343 --- /dev/null +++ b/packages/build/vite-config/src/vite-plugins/treeShakingPlugin.js @@ -0,0 +1,69 @@ +import fs from 'node:fs' +import path from 'node:path' +import replace from '@rollup/plugin-replace' +import { parse } from '@babel/parser' + +export function treeShakingPlugin(registryPath) { + if (!registryPath) { + return null + } + + const envReplace = {} + const filePath = path.resolve(process.cwd(), registryPath) + + if (!fs.existsSync(filePath)) { + return null + } + + try { + const fileContent = fs.readFileSync(filePath, 'utf-8') + const ast = parse(fileContent, { sourceType: 'module' }) + const commentPattern = /#__TINY_ENGINE_TREE_SHAKING__:\s*(?true|false)/ + + ast.program.body.forEach((item) => { + // 过滤默认导出 且导出类型为 object + if (item.type === 'ExportDefaultDeclaration' && item.declaration?.type === 'ObjectExpression') { + item.declaration.properties.forEach((propertyItem) => { + // 是属性 且 key 为 string + if (propertyItem.type === 'ObjectProperty' && propertyItem.key?.type === 'StringLiteral') { + const key = propertyItem.key.value + // 有 comment,解析 comment。 + // 通过 comment 指定 treeshaking,优先以 comment 为标准 + if (propertyItem.leadingComments?.length) { + for (const commentItem of propertyItem.leadingComments) { + const match = commentItem.value.match(commentPattern) + + if (!match) { + continue + } + + if (match.groups.value === 'true') { + envReplace[`__TINY_ENGINE_REMOVED_REGISTRY["${key}"]`] = false + } + + return + } + } + + // 注册表注释了插件,默认 tree-shaking, + if (propertyItem.value.type === 'BooleanLiteral' && propertyItem.value.value === false) { + envReplace[`__TINY_ENGINE_REMOVED_REGISTRY["${key}"]`] = false + } + } + }) + } + }) + + return replace({ + values: { + ...envReplace + }, + delimiters: ['', ''] + }) + } catch (error) { + const logger = console + logger.warn('[TinyEngine] tree-shaking plugin error', error) + } + + return null +} diff --git a/packages/build/vite-plugin-meta-comments/.gitignore b/packages/build/vite-plugin-meta-comments/.gitignore new file mode 100644 index 00000000..0b0136f5 --- /dev/null +++ b/packages/build/vite-plugin-meta-comments/.gitignore @@ -0,0 +1 @@ +test/temp diff --git a/packages/build/vite-plugin-meta-comments/index.js b/packages/build/vite-plugin-meta-comments/index.js index 3bb52331..1adfd8cb 100644 --- a/packages/build/vite-plugin-meta-comments/index.js +++ b/packages/build/vite-plugin-meta-comments/index.js @@ -1,6 +1,6 @@ /** - * Copyright (c) 2024 - present TinyEngine Authors. - * Copyright (c) 2024 - present Huawei Cloud Computing Technologies Co., Ltd. + * Copyright (c) 2023 - present TinyEngine Authors. + * Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd. * * Use of this source code is governed by an MIT-style license. * diff --git a/packages/build/vite-plugin-meta-comments/package.json b/packages/build/vite-plugin-meta-comments/package.json index 9b89e182..e3ffa151 100644 --- a/packages/build/vite-plugin-meta-comments/package.json +++ b/packages/build/vite-plugin-meta-comments/package.json @@ -10,7 +10,9 @@ ], "scripts": { "build": "vite build", - "test": "node ./src/test/index.js" + "test": "vitest run", + "test:watch": "vitest", + "test:legacy": "node ./test/legacy/index.js" }, "devDependencies": { "@babel/generator": "^7.18.13", @@ -19,7 +21,8 @@ "@babel/traverse": "^7.18.13", "@vitejs/plugin-vue": "^5.1.2", "@vue/compiler-sfc": "^3.4.21", - "vite": "^5.4.2" + "vite": "^5.4.2", + "vitest": "^3.1.3" }, "keywords": [], "publishConfig": { diff --git a/packages/build/vite-plugin-meta-comments/src/test/code/output.js b/packages/build/vite-plugin-meta-comments/src/test/code/output.js deleted file mode 100644 index ff2bb833..00000000 --- a/packages/build/vite-plugin-meta-comments/src/test/code/output.js +++ /dev/null @@ -1,598 +0,0 @@ -import { - callEntry as _callEntry, - beforeCallEntry as _beforeCallEntry, - afterCallEntry as _afterCallEntry, - useCompile as _useCompile -} from '@opentiny/tiny-engine-meta-register' -import _metaData from '../meta.js' -/* metaService */ -import { reactive, onMounted, onBeforeMount as beforeMount } from 'vue' -import { deepCopy } from 'loash-es' -export const useRenderless = _callEntry( - ({ props }) => { - const state = reactive({ - tableData: props.data || props.op.data || [] - }) - onMounted( - _callEntry(() => {}, { - metaData: { - id: `${_metaData.id}.onMounted[0]` - }, - ctx: () => { - let asyncVars = {} - try { - asyncVars = { - props, - state, - logMessage, - aaa, - bbb, - handleClick, - ccc, - sendMessage, - last, - reactive, - onMounted, - beforeMount, - deepCopy, - useRenderless - } - } catch (e) { - return { - reactive, - onMounted, - beforeMount, - deepCopy, - useRenderless - } - } - return asyncVars - } - }) - ) - onMounted( - _callEntry(() => {}, { - metaData: { - id: `${_metaData.id}.onMounted[1]` - }, - ctx: () => { - let asyncVars = {} - try { - asyncVars = { - props, - state, - logMessage, - aaa, - bbb, - handleClick, - ccc, - sendMessage, - last, - reactive, - onMounted, - beforeMount, - deepCopy, - useRenderless - } - } catch (e) { - return { - reactive, - onMounted, - beforeMount, - deepCopy, - useRenderless - } - } - return asyncVars - } - }) - ) - onMounted( - _callEntry(() => {}, { - metaData: { - id: `${_metaData.id}.onMounted[2]` - }, - ctx: () => { - let asyncVars = {} - try { - asyncVars = { - props, - state, - logMessage, - aaa, - bbb, - handleClick, - ccc, - sendMessage, - last, - reactive, - onMounted, - beforeMount, - deepCopy, - useRenderless - } - } catch (e) { - return { - reactive, - onMounted, - beforeMount, - deepCopy, - useRenderless - } - } - return asyncVars - } - }) - ) - beforeMount( - _callEntry(() => {}, { - metaData: { - id: `${_metaData.id}.onBeforeMount[0]` - }, - ctx: () => { - let asyncVars = {} - try { - asyncVars = { - props, - state, - logMessage, - aaa, - bbb, - handleClick, - ccc, - sendMessage, - last, - reactive, - onMounted, - beforeMount, - deepCopy, - useRenderless - } - } catch (e) { - return { - reactive, - onMounted, - beforeMount, - deepCopy, - useRenderless - } - } - return asyncVars - } - }) - ) - _beforeCallEntry({ - metaData: { - id: `${_metaData.id}.logMessage` - }, - ctx: () => { - let asyncVars = {} - try { - asyncVars = { - props, - state, - logMessage, - aaa, - bbb, - handleClick, - ccc, - sendMessage, - last, - reactive, - onMounted, - beforeMount, - deepCopy, - useRenderless - } - } catch (e) { - return { - props, - state, - last, - reactive, - onMounted, - beforeMount, - deepCopy, - useRenderless - } - } - return asyncVars - } - }) - const logMessage = _callEntry( - () => { - console.log('我是纯函数我不需要闭包参数') - }, - { - metaData: { - id: `${_metaData.id}.logMessage` - }, - ctx: () => { - let asyncVars = {} - try { - asyncVars = { - props, - state, - logMessage, - aaa, - bbb, - handleClick, - ccc, - sendMessage, - last, - reactive, - onMounted, - beforeMount, - deepCopy, - useRenderless - } - } catch (e) { - return { - props, - state, - last, - reactive, - onMounted, - beforeMount, - deepCopy, - useRenderless - } - } - return asyncVars - } - } - ) - _afterCallEntry({ - metaData: { - id: `${_metaData.id}.logMessage` - }, - ctx: () => { - let asyncVars = {} - try { - asyncVars = { - props, - state, - logMessage, - aaa, - bbb, - handleClick, - ccc, - sendMessage, - last, - reactive, - onMounted, - beforeMount, - deepCopy, - useRenderless - } - } catch (e) { - return { - props, - state, - last, - reactive, - onMounted, - beforeMount, - deepCopy, - useRenderless - } - } - return asyncVars - } - }) - const aaa = 'aaa', - bbb = 'bbb' - _beforeCallEntry({ - metaData: { - id: `${_metaData.id}.handleClick` - }, - ctx: () => { - let asyncVars = {} - try { - asyncVars = { - e, - props, - state, - logMessage, - aaa, - bbb, - handleClick, - ccc, - sendMessage, - last, - reactive, - onMounted, - beforeMount, - deepCopy, - useRenderless - } - } catch (e) { - return { - props, - state, - logMessage, - aaa, - bbb, - last, - reactive, - onMounted, - beforeMount, - deepCopy, - useRenderless - } - } - return asyncVars - } - }) - const handleClick = _callEntry( - (e) => { - console.log(e.target, aaa) - state.tableData.push({ - key: 'TinyEngine', - zhCN: '低代码引擎', - enUS: 'TinyEngine' - }) - }, - { - metaData: { - id: `${_metaData.id}.handleClick` - }, - ctx: () => { - let asyncVars = {} - try { - asyncVars = { - e, - props, - state, - logMessage, - aaa, - bbb, - handleClick, - ccc, - sendMessage, - last, - reactive, - onMounted, - beforeMount, - deepCopy, - useRenderless - } - } catch (e) { - return { - props, - state, - logMessage, - aaa, - bbb, - last, - reactive, - onMounted, - beforeMount, - deepCopy, - useRenderless - } - } - return asyncVars - } - } - ) - _afterCallEntry({ - metaData: { - id: `${_metaData.id}.handleClick` - }, - ctx: () => { - let asyncVars = {} - try { - asyncVars = { - e, - props, - state, - logMessage, - aaa, - bbb, - handleClick, - ccc, - sendMessage, - last, - reactive, - onMounted, - beforeMount, - deepCopy, - useRenderless - } - } catch (e) { - return { - props, - state, - logMessage, - aaa, - bbb, - last, - reactive, - onMounted, - beforeMount, - deepCopy, - useRenderless - } - } - return asyncVars - } - }) - const ccc = 111 - _beforeCallEntry({ - metaData: { - id: `${_metaData.id}.sendMessage` - }, - ctx: () => { - let asyncVars = {} - try { - asyncVars = { - props, - state, - logMessage, - aaa, - bbb, - handleClick, - ccc, - sendMessage, - last, - reactive, - onMounted, - beforeMount, - deepCopy, - useRenderless - } - } catch (e) { - return { - props, - state, - logMessage, - aaa, - bbb, - handleClick, - ccc, - last, - reactive, - onMounted, - beforeMount, - deepCopy, - useRenderless - } - } - return asyncVars - } - }) - const sendMessage = _callEntry( - () => { - logMessage('自定义是的范德萨') - }, - { - metaData: { - id: `${_metaData.id}.sendMessage` - }, - ctx: () => { - let asyncVars = {} - try { - asyncVars = { - props, - state, - logMessage, - aaa, - bbb, - handleClick, - ccc, - sendMessage, - last, - reactive, - onMounted, - beforeMount, - deepCopy, - useRenderless - } - } catch (e) { - return { - props, - state, - logMessage, - aaa, - bbb, - handleClick, - ccc, - last, - reactive, - onMounted, - beforeMount, - deepCopy, - useRenderless - } - } - return asyncVars - } - } - ) - _afterCallEntry({ - metaData: { - id: `${_metaData.id}.sendMessage` - }, - ctx: () => { - let asyncVars = {} - try { - asyncVars = { - props, - state, - logMessage, - aaa, - bbb, - handleClick, - ccc, - sendMessage, - last, - reactive, - onMounted, - beforeMount, - deepCopy, - useRenderless - } - } catch (e) { - return { - props, - state, - logMessage, - aaa, - bbb, - handleClick, - ccc, - last, - reactive, - onMounted, - beforeMount, - deepCopy, - useRenderless - } - } - return asyncVars - } - }) - function last() {} - return { - state, - aa, - handleClick, - sendMessage - } - }, - { - metaData: { - id: `${_metaData.id}.useRenderless` - }, - ctx: () => { - let asyncVars = {} - try { - asyncVars = { - props, - state, - logMessage, - aaa, - bbb, - handleClick, - ccc, - sendMessage, - last, - reactive, - onMounted, - beforeMount, - deepCopy, - useRenderless - } - } catch (e) { - return { - reactive, - onMounted, - beforeMount, - deepCopy - } - } - return asyncVars - } - } -) diff --git a/packages/build/vite-plugin-meta-comments/src/transform-sfc.js b/packages/build/vite-plugin-meta-comments/src/transform-sfc.js index bbb2568b..6c396a66 100644 --- a/packages/build/vite-plugin-meta-comments/src/transform-sfc.js +++ b/packages/build/vite-plugin-meta-comments/src/transform-sfc.js @@ -1,6 +1,6 @@ /** - * Copyright (c) 2024 - present TinyEngine Authors. - * Copyright (c) 2024 - present Huawei Cloud Computing Technologies Co., Ltd. + * Copyright (c) 2023 - present TinyEngine Authors. + * Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd. * * Use of this source code is governed by an MIT-style license. * diff --git a/packages/build/vite-plugin-meta-comments/src/transform.js b/packages/build/vite-plugin-meta-comments/src/transform.js index 6ad8b6af..8930ca67 100644 --- a/packages/build/vite-plugin-meta-comments/src/transform.js +++ b/packages/build/vite-plugin-meta-comments/src/transform.js @@ -1,6 +1,6 @@ /** - * Copyright (c) 2024 - present TinyEngine Authors. - * Copyright (c) 2024 - present Huawei Cloud Computing Technologies Co., Ltd. + * Copyright (c) 2023 - present TinyEngine Authors. + * Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd. * * Use of this source code is governed by an MIT-style license. * @@ -11,15 +11,13 @@ */ import { parse } from '@babel/parser' -import generate from '@babel/generator' -import traverse from '@babel/traverse' +import generateLib from '@babel/generator' +import traverseLib from '@babel/traverse' import template from '@babel/template' import { wrapEntryFuncNode, COMMON_PACKAGE_NAME, CALLENTRY, - BEFORE_CALLENTRY, - AFTER_CALLENTRY, USE_COMPILE, METADATANAME, isCallEntryFile, @@ -31,7 +29,9 @@ import { getModuleId } from './utils.js' -const generateTraverse = traverse.default +// 在ESM模式中,traverse默认是以命名导出的形式提供 +const traverse = traverseLib.default || traverseLib +const generate = generateLib.default || generateLib function handleFunctionExpression(state) { return function (path) { @@ -40,6 +40,7 @@ function handleFunctionExpression(state) { // 只有拿到函数的名称才可以被复写 if (functionName) { + state.ArrowOrFunctionExpression.push(functionName) wrapEntryFuncNode({ path, functionName, @@ -74,15 +75,26 @@ function handleImportDeclaration(state) { } } +/** + * 处理变量声明节点 + * @param {Object} state - 状态对象,用于存储变量声明信息 + * @returns {Function} 返回处理变量声明的函数 + */ function handleVariableDeclaration(state) { return function (path) { + // 遍历所有变量声明 path.node.declarations?.forEach((val) => { + // 获取变量名 const name = val.id.name + // 获取变量所在的作用域块 const block = path.scope.block + + // 如果该作用域块还没有记录过变量,则创建新数组 if (!state.varDeclartion.has(block)) { const arr = [name] state.varDeclartion.set(block, arr) } else { + // 如果该作用域块已存在,则将变量名添加到数组中 const arr = state.varDeclartion.get(block) arr.push(name) } @@ -127,19 +139,13 @@ function handleProgram(state, metaPath) { path.node.body.unshift(template.statement(`import ${metaData} from '${metaPath}'`)()) const callEntry = path.scope.generateUid(CALLENTRY) - const beforeCallEntry = path.scope.generateUid(BEFORE_CALLENTRY) - const afterCallEntry = path.scope.generateUid(AFTER_CALLENTRY) const useCompile = path.scope.generateUid(USE_COMPILE) state.varName[CALLENTRY] = callEntry - state.varName[BEFORE_CALLENTRY] = beforeCallEntry - state.varName[AFTER_CALLENTRY] = afterCallEntry state.varName[USE_COMPILE] = useCompile path.node.body.unshift( template.statement( `import { ${CALLENTRY} as ${callEntry}, - ${BEFORE_CALLENTRY} as ${beforeCallEntry}, - ${AFTER_CALLENTRY} as ${afterCallEntry}, ${USE_COMPILE} as ${useCompile} } from '${COMMON_PACKAGE_NAME}'` )() @@ -156,8 +162,7 @@ function handleExportDefaultDeclaration(state) { const lastComment = comment[comment.length - 1].value // 只判断最接近export default的注释节点 if (lastComment.includes('metaComponent')) { - wrapExportComp({ path, varName: state.varName }) - path.skip() + wrapExportComp({ path, varName: state.varName, lastComment }) } } } @@ -177,14 +182,16 @@ export const transform = (code, id) => { hooksIndex: {}, varDeclartion: new Map(), moduleId: '', // 自定义的模块ID,用于区分元服务中不同文件, - noUseVars: [] + noUseVars: [], + fileId: id, + ArrowOrFunctionExpression: [] } // 找不到meta.js告警并返回 const metaPath = getMeataPath(id) if (!metaPath) { // eslint-disable-next-line no-console - console.log('找不到对应的meta.js') + console.log(`${id}: 找不到对应的meta.js`) return } @@ -194,9 +201,33 @@ export const transform = (code, id) => { plugins: ['typescript', 'jsx'] }) - generateTraverse(resultAst, { + const handleCallExpression = (state) => (path) => { + const callee = path.node.callee + const name = callee.name + if (name === state.varName[CALLENTRY]) { + return + } + if (name) { + const bindings = path.scope.bindings + // 判断调用的函数是否来自本文件中的函数表达式,且在同一个函数作用域内 + if ( + callee.type === 'Identifier' && + bindings?.[name] && + bindings?.[name]?.kind !== 'module' && + state.ArrowOrFunctionExpression.includes(name) + ) { + // eslint-disable-next-line no-console + console.warn( + `文件 ${state.fileId} 中函数 ${name} 在声明后直接调用了,可能造成函数覆盖场景报错!整改建议:1、导入后再调用。2、在文件最后调用` + ) + } + } + } + + traverse(resultAst, { // 使用特定的类型回调处理、函数表达式、箭头函数、带导出的函数 'ArrowFunctionExpression|FunctionExpression': handleFunctionExpression(state), + CallExpression: handleCallExpression(state), ImportDeclaration: handleImportDeclaration(state), VariableDeclaration: handleVariableDeclaration(state), ExpressionStatement: handleExpressionStatement(state), @@ -204,5 +235,5 @@ export const transform = (code, id) => { ExportDefaultDeclaration: handleExportDefaultDeclaration(state) }) - return generate.default(resultAst).code || '' + return generate(resultAst).code || '' } diff --git a/packages/build/vite-plugin-meta-comments/src/utils.js b/packages/build/vite-plugin-meta-comments/src/utils.js index 4ea489ad..7d82cfb0 100644 --- a/packages/build/vite-plugin-meta-comments/src/utils.js +++ b/packages/build/vite-plugin-meta-comments/src/utils.js @@ -1,6 +1,6 @@ /** - * Copyright (c) 2024 - present TinyEngine Authors. - * Copyright (c) 2024 - present Huawei Cloud Computing Technologies Co., Ltd. + * Copyright (c) 2023 - present TinyEngine Authors. + * Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd. * * Use of this source code is governed by an MIT-style license. * @@ -14,12 +14,13 @@ import template from '@babel/template' import path from 'node:path' import fs from 'node:fs' -export const CALLENTRY = 'callEntry' -export const BEFORE_CALLENTRY = 'beforeCallEntry' -export const AFTER_CALLENTRY = 'afterCallEntry' -export const USE_COMPILE = 'useCompile' -export const METADATANAME = 'metaData' -export const COMMON_PACKAGE_NAME = '@opentiny/tiny-engine-meta-register' +// 定义各种常量,用于标识不同的入口点和编译选项 +export const CALLENTRY = 'callEntry' // 主入口点 +export const USE_COMPILE = 'useCompile' // 编译选项 +export const METADATANAME = 'metaData' // 元数据名称 +export const COMMON_PACKAGE_NAME = '@opentiny/tiny-engine-meta-register' // 公共包名 + +// Vue生命周期钩子列表 export const vueLifeHook = [ 'onMounted', 'onUpdated', @@ -30,41 +31,61 @@ export const vueLifeHook = [ 'onActivated', 'onDeactivated' ] + +// 用于匹配metaService和metaComponent注释的正则表达式 const callEntryExp = /\/\*\s*metaService/ const compileExp = /\/\*\s*metaComponent/ +// 创建Babel模板语句的辅助函数 const statement = (code) => template.statement(code, { placeholderPattern: false }) +/** + * 检查文件是否包含metaService注释 + * @param {string} code - 文件内容 + * @returns {boolean} 是否包含metaService注释 + */ export const isCallEntryFile = (code) => { - return callEntryExp.test(code) + return callEntryExp.test(code) || compileExp.test(code) } +/** + * 检查文件是否包含metaComponent注释 + * @param {string} code - 文件内容 + * @returns {boolean} 是否包含metaComponent注释 + */ export const isCompileFile = (code) => { return compileExp.test(code) } +/** + * 从注释中提取模块ID + * @param {string} str - 包含metaService注释的字符串 + * @returns {string} 提取的模块ID + */ export const getModuleId = (str) => { const [, moduleId = ''] = str.match(/\/\*\s*metaService: \s*(.+?)\s*\*\//) || [] return moduleId } -// 将注释中的参数提取出来,并组合成目前参数格式 -export const getEntryParam = ({ functionName = '', syncVars, asyncVars, state }) => { +export const getTemplateId = (str) => { + const [, moduleId = ''] = str.match(/\s*metaComponent: \s*(.+)\s*/) || [] + return moduleId.trim() +} + +/** + * 从注释中提取参数并组合成标准格式 + * @param {Object} params - 参数对象 + * @param {string} params.functionName - 函数名 + * @param {Object} params.asyncVars - 异步变量 + * @param {Object} params.state - 状态对象 + * @returns {string} 格式化后的参数 + */ +export const getEntryParam = ({ functionName = '', asyncVars, state }) => { const { varName, moduleId, noUseVars } = state const metaData = varName[METADATANAME] const id = moduleId ? `'${moduleId}.${functionName}'` : `\`\${${metaData}.id}.${functionName}\`` - const syncVarsKey = Object.keys(syncVars).filter((key) => !noUseVars.includes(key)) - const asyncVarsKey = Object.keys(asyncVars).filter((key) => !noUseVars.includes(key) && !syncVarsKey.includes(key)) - const ctx = ` () => { - let asyncVars = {} - const syncVars = {${syncVarsKey.join(',')}} - try { - asyncVars = { ${asyncVarsKey.join(',')} } - } catch { - return syncVars - } - return { ...syncVars, ...asyncVars } - }` + const asyncVarsKey = Object.keys(asyncVars).filter((key) => !noUseVars.includes(key)) + const ctx = `() => ({${asyncVarsKey.join(',')}})` if (functionName) { return `{ ${METADATANAME}: { id: ${id} }, ctx: ${ctx}}` } @@ -72,27 +93,12 @@ export const getEntryParam = ({ functionName = '', syncVars, asyncVars, state }) return `{ ${METADATANAME}: ${metaData} }` } -const getParentVariableDeclaration = (path) => { - if (!path) { - return - } - - if (path.type === 'VariableDeclaration' && path.parentPath.type !== 'ExportNamedDeclaration') { - return path - } else { - return getParentVariableDeclaration(path?.parentPath) - } -} - -const generateBeforeAfterEntry = ({ path, beforeEntryAst, afterEntryAst }) => { - const parent = getParentVariableDeclaration(path) - if (parent) { - parent.insertBefore(beforeEntryAst) - parent.insertAfter(afterEntryAst) - } -} - -export const getOuterBingdings = (path) => { +/** + * 获取外部绑定变量 + * @param {Object} path - Babel路径对象 + * @returns {Object} 外部绑定变量对象 + */ +export const getOuterBindings = (path) => { const outerBindings = {} const allBindings = path.scope.getAllBindings() const selfBindings = path.scope.bindings @@ -104,39 +110,11 @@ export const getOuterBingdings = (path) => { return outerBindings } -// 获取当前上下文已经可以使用的scope变量 -export const getValidBingdinngs = ({ path, state, functionName }) => { - const validBindings = {} - const { varDeclartion } = state - let varArr = [] - let parentPath = path.parentPath - let block - while (parentPath) { - const newBlock = parentPath.scope.block - parentPath = parentPath.parentPath - if (newBlock === block) { - continue - } - block = newBlock - varArr = varArr.concat(varDeclartion.get(block)) - } - - const allBindings = path.scope.getAllBindings() - const selfBindings = path.scope.bindings - Object.keys(allBindings).forEach((key) => { - if (selfBindings[key]) { - return - } - const value = allBindings[key] - // 如果是变量定义,并且此时还没有初始化,则过滤掉 - if ((['var', 'const', 'let'].includes(value.kind) && !varArr.includes(key)) || key === functionName) { - return - } - validBindings[key] = value - }) - return validBindings -} - +/** + * 获取模块级别的绑定变量 + * @param {Object} path - Babel路径对象 + * @returns {Object} 模块绑定变量对象 + */ export const getModuleBindings = (path) => { const moduleBindings = {} const allBindings = path.scope.getAllBindings() @@ -148,33 +126,37 @@ export const getModuleBindings = (path) => { return moduleBindings } -// 生成callEntry表达式并包裹当前函数,如果有参与还需要处理参数 +/** + * 生成callEntry表达式并包裹当前函数 + * @param {Object} params - 参数对象 + * @param {Object} params.path - Babel路径对象 + * @param {string} params.functionName - 函数名 + * @param {Object} params.varName - 变量名对象 + * @param {Object} params.state - 状态对象 + */ export const wrapEntryFuncNode = ({ path, functionName = '', varName, state }) => { - const syncVars = getValidBingdinngs({ path, state, functionName }) - const asyncVars = getOuterBingdings(path) + const asyncVars = getOuterBindings(path) const entryParam = getEntryParam({ functionName, - syncVars, asyncVars, varName, state }) const callEntry = varName[CALLENTRY] - const beforeCallEntry = varName[BEFORE_CALLENTRY] - const afterCallEntry = varName[AFTER_CALLENTRY] const entryAst = statement(`${callEntry}(${entryParam})`)() - const beforeEntryAst = statement(`${beforeCallEntry}(${entryParam})`)() - const afterEntryAst = statement(`${afterCallEntry}(${entryParam})`)() - const resultNode = path.node - generateBeforeAfterEntry({ path, beforeEntryAst, afterEntryAst }) entryAst.expression.arguments.unshift(JSON.parse(JSON.stringify(resultNode))) // 替换整个节点 path.replaceWith(entryAst) } -// 获取两个文件路径的相对路径,入参为两个文件绝对路径 +/** + * 获取两个文件路径的相对路径 + * @param {string} path1 - 第一个文件的绝对路径 + * @param {string} path2 - 第二个文件的绝对路径 + * @returns {string} 相对路径 + */ export const getRelFilePath = (path1, path2) => { const dir1 = path.join(path1, '..') const dir2 = path.join(path2, '..') @@ -182,7 +164,11 @@ export const getRelFilePath = (path1, path2) => { return `${relPath}/${path.basename(path2)}`.replaceAll('\\', '/') } -// 向上获取meta.js的相对路径 +/** + * 向上查找meta.js的相对路径 + * @param {string} id - 文件ID + * @returns {string|null} meta.js的相对路径或null + */ export const getMeataPath = (id) => { let tempPath = path.join(id, '../meta.js') @@ -205,38 +191,99 @@ export const getMeataPath = (id) => { return null } -export const wrapExportComp = ({ path, varName }) => { +/** + * 包装导出组件 + * @param {Object} params - 参数对象 + * @param {Object} params.path - Babel路径对象 + * @param {Object} params.varName - 变量名对象 + */ +/** + * 包装导出组件,对组件进行useCompile包装处理 + * @param {Object} params - 参数对象 + * @param {Object} params.path - Babel路径对象,用于AST操作 + * @param {Object} params.varName - 变量名对象,包含元数据和编译函数名 + * @param {string} params.lastComment - 最后一个注释,用于生成模板ID + */ +export const wrapExportComp = ({ path, varName, lastComment }) => { + // 获取导出对象的所有属性 const properties = path.node.declaration?.properties || [] + // 获取元数据变量名 const metaData = varName[METADATANAME] + // 获取useCompile函数名 const useCompile = varName[USE_COMPILE] + // 从注释中获取模板ID + const templateId = getTemplateId(lastComment) - // 对键值为component属性包一层useCompile + // 遍历导出对象的所有属性 properties.forEach((prop) => { + // 处理单个组件导出的情况 if (prop.key?.name === 'component') { + // 获取组件值 const val = prop.value + // 创建useCompile包装的AST节点 const compileAst = statement(`${useCompile}({ component: null, ${METADATANAME}: ${metaData} });`)() + // 将原组件值设置到useCompile的component参数中 compileAst.expression.arguments[0].properties[0].value = val + // 遍历AST,替换原组件节点 path.traverse({ enter(subPath) { if (subPath.node === val) { + // 用useCompile包装后的节点替换原节点 subPath.replaceWith(compileAst) + // 跳过子节点遍历 subPath.skip() } } }) } + // 处理多个组件导出的情况 + else if (prop.key?.name === 'components') { + const val = prop.value + // 确保components是一个对象 + if (val?.properties) { + const properties = val.properties + // 遍历所有子组件 + properties.forEach((item) => { + const value = item.value + // 生成元数据对象,如果有templateId则使用"templateId.组件名"作为id + const metaObj = templateId ? `{id:'${templateId}.${value.name}'}` : metaData + // 创建useCompile包装的AST节点 + const compileAst = statement(`${useCompile}({ component: null, ${METADATANAME}: ${metaObj} });`)() + // 将原组件值设置到useCompile的component参数中 + compileAst.expression.arguments[0].properties[0].value = value + // 遍历AST,替换原组件节点 + path.traverse({ + enter(subPath) { + if (subPath.node === value) { + // 用useCompile包装后的节点替换原节点 + subPath.replaceWith(compileAst) + // 跳过子节点遍历 + subPath.skip() + } + } + }) + }) + } + } }) } +/** + * 包装钩子调用 + * @param {Object} params - 参数对象 + * @param {Object} params.path - Babel路径对象 + * @param {Object} params.varName - 变量名对象 + * @param {string} params.functionName - 函数名 + * @param {string} params.callName - 调用名 + * @param {Object} params.state - 状态对象 + */ export const wrapHookCall = ({ path, varName, functionName, callName, state }) => { // vue的生命周期hook只有一个参数 const argument = path.node.expression.arguments[0] const callEntry = varName[CALLENTRY] - const syncVars = getValidBingdinngs({ path, state, functionName }) const asyncVars = path.scope.getAllBindings() const entryParam = getEntryParam({ functionName, - syncVars, asyncVars, varName, state diff --git a/packages/build/vite-plugin-meta-comments/test/expected/arrow-func.js.output.js b/packages/build/vite-plugin-meta-comments/test/expected/arrow-func.js.output.js new file mode 100644 index 00000000..1f7675e6 --- /dev/null +++ b/packages/build/vite-plugin-meta-comments/test/expected/arrow-func.js.output.js @@ -0,0 +1,15 @@ +import { callEntry as _callEntry, useCompile as _useCompile } from '@opentiny/tiny-engine-meta-register'; +import _metaData from './meta.js'; +/* metaService */ +const arrowFunc = _callEntry(() => { + console.log('Arrow function'); + return true; +}, { + metaData: { + id: `${_metaData.id}.arrowFunc` + }, + ctx: () => ({ + arrowFunc + }) +}); +export { arrowFunc }; \ No newline at end of file diff --git a/packages/build/vite-plugin-meta-comments/test/expected/entry.js.output.js b/packages/build/vite-plugin-meta-comments/test/expected/entry.js.output.js new file mode 100644 index 00000000..ad7eb778 --- /dev/null +++ b/packages/build/vite-plugin-meta-comments/test/expected/entry.js.output.js @@ -0,0 +1,124 @@ +import { callEntry as _callEntry, useCompile as _useCompile } from '@opentiny/tiny-engine-meta-register'; +import _metaData from './meta.js'; +/* metaService */ +/** + * Copyright (c) 2023 - present TinyEngine Authors. + * Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd. + * + * Use of this source code is governed by an MIT-style license. + * + * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, + * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR + * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. + * + */ + +import { reactive, onMounted, onBeforeMount as beforeMount } from 'vue'; +import { deepCopy } from 'lodash-es'; +export const useRenderless = _callEntry(({ + props +}) => { + const state = reactive({ + tableData: props.data || props.op.data || [] + }); + const last1 = useLayout(last1); + beforeMount(_callEntry(() => {}, { + metaData: { + id: `${_metaData.id}.onBeforeMount[0]` + }, + ctx: () => ({ + props, + state, + last1, + logMessage, + aaa, + bbb, + handleClick, + ccc, + sendMessage, + deepCopy, + useRenderless + }) + })); + const logMessage = _callEntry(() => { + console.log('我是纯函数我不需要闭包参数'); + }, { + metaData: { + id: `${_metaData.id}.logMessage` + }, + ctx: () => ({ + props, + state, + last1, + logMessage, + aaa, + bbb, + handleClick, + ccc, + sendMessage, + deepCopy, + useRenderless + }) + }); + const aaa = 'aaa', + bbb = 'bbb'; + const handleClick = _callEntry(e => { + state.tableData.push({ + key: 'TinyEngine', + zhCN: '低代码引擎', + enUS: 'TinyEngine' + }); + }, { + metaData: { + id: `${_metaData.id}.handleClick` + }, + ctx: () => ({ + props, + state, + last1, + logMessage, + aaa, + bbb, + handleClick, + ccc, + sendMessage, + deepCopy, + useRenderless + }) + }); + const ccc = 111; + const sendMessage = _callEntry(() => { + logMessage('自定义是的范德萨'); + }, { + metaData: { + id: `${_metaData.id}.sendMessage` + }, + ctx: () => ({ + props, + state, + last1, + logMessage, + aaa, + bbb, + handleClick, + ccc, + sendMessage, + deepCopy, + useRenderless + }) + }); + return { + state, + aa, + handleClick, + sendMessage + }; +}, { + metaData: { + id: `${_metaData.id}.useRenderless` + }, + ctx: () => ({ + deepCopy, + useRenderless + }) +}); \ No newline at end of file diff --git a/packages/build/vite-plugin-meta-comments/test/expected/function-expr.js.output.js b/packages/build/vite-plugin-meta-comments/test/expected/function-expr.js.output.js new file mode 100644 index 00000000..8839116a --- /dev/null +++ b/packages/build/vite-plugin-meta-comments/test/expected/function-expr.js.output.js @@ -0,0 +1,15 @@ +import { callEntry as _callEntry, useCompile as _useCompile } from '@opentiny/tiny-engine-meta-register'; +import _metaData from './meta.js'; +/* metaService */ +const doSomething = _callEntry(function () { + console.log('Doing something'); + return true; +}, { + metaData: { + id: `${_metaData.id}.doSomething` + }, + ctx: () => ({ + doSomething + }) +}); +export { doSomething }; \ No newline at end of file diff --git a/packages/build/vite-plugin-meta-comments/test/expected/meta-service.js.output.js b/packages/build/vite-plugin-meta-comments/test/expected/meta-service.js.output.js new file mode 100644 index 00000000..e5c0d8b1 --- /dev/null +++ b/packages/build/vite-plugin-meta-comments/test/expected/meta-service.js.output.js @@ -0,0 +1,32 @@ +import { callEntry as _callEntry, useCompile as _useCompile } from '@opentiny/tiny-engine-meta-register'; +import _metaData from './meta.js'; +/* metaService: testModule */ +import { reactive } from 'vue'; +export const useTestService = _callEntry(() => { + const state = reactive({ + count: 0 + }); + const increment = _callEntry(() => { + state.count++; + }, { + metaData: { + id: 'testModule.increment' + }, + ctx: () => ({ + state, + increment, + useTestService + }) + }); + return { + state, + increment + }; +}, { + metaData: { + id: 'testModule.useTestService' + }, + ctx: () => ({ + useTestService + }) +}); \ No newline at end of file diff --git a/packages/build/vite-plugin-meta-comments/test/expected/meta-service.vue.output.vue b/packages/build/vite-plugin-meta-comments/test/expected/meta-service.vue.output.vue new file mode 100644 index 00000000..90381799 --- /dev/null +++ b/packages/build/vite-plugin-meta-comments/test/expected/meta-service.vue.output.vue @@ -0,0 +1,42 @@ + + + + diff --git a/packages/build/vite-plugin-meta-comments/test/expected/script-setup.vue.output.vue b/packages/build/vite-plugin-meta-comments/test/expected/script-setup.vue.output.vue new file mode 100644 index 00000000..e4550461 --- /dev/null +++ b/packages/build/vite-plugin-meta-comments/test/expected/script-setup.vue.output.vue @@ -0,0 +1,30 @@ + + + + diff --git a/packages/build/vite-plugin-meta-comments/test/expected/vue-hooks.js.output.js b/packages/build/vite-plugin-meta-comments/test/expected/vue-hooks.js.output.js new file mode 100644 index 00000000..eea2de5b --- /dev/null +++ b/packages/build/vite-plugin-meta-comments/test/expected/vue-hooks.js.output.js @@ -0,0 +1,33 @@ +import { callEntry as _callEntry, useCompile as _useCompile } from '@opentiny/tiny-engine-meta-register'; +import _metaData from './meta.js'; +/* metaService */ +import { onMounted, onBeforeMount } from 'vue'; +export const useHooks = _callEntry(() => { + onMounted(_callEntry(() => { + console.log('Component mounted'); + }, { + metaData: { + id: `${_metaData.id}.onMounted[0]` + }, + ctx: () => ({ + useHooks + }) + })); + onBeforeMount(_callEntry(() => { + console.log('Component will mount'); + }, { + metaData: { + id: `${_metaData.id}.onBeforeMount[0]` + }, + ctx: () => ({ + useHooks + }) + })); +}, { + metaData: { + id: `${_metaData.id}.useHooks` + }, + ctx: () => ({ + useHooks + }) +}); \ No newline at end of file diff --git a/packages/build/vite-plugin-meta-comments/src/test/code/entry.js b/packages/build/vite-plugin-meta-comments/test/legacy/code/entry.js similarity index 79% rename from packages/build/vite-plugin-meta-comments/src/test/code/entry.js rename to packages/build/vite-plugin-meta-comments/test/legacy/code/entry.js index 33c83d15..c96077be 100644 --- a/packages/build/vite-plugin-meta-comments/src/test/code/entry.js +++ b/packages/build/vite-plugin-meta-comments/test/legacy/code/entry.js @@ -1,7 +1,7 @@ /* metaService */ /** - * Copyright (c) 2024 - present TinyEngine Authors. - * Copyright (c) 2024 - present Huawei Cloud Computing Technologies Co., Ltd. + * Copyright (c) 2023 - present TinyEngine Authors. + * Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd. * * Use of this source code is governed by an MIT-style license. * @@ -12,15 +12,13 @@ */ import { reactive, onMounted, onBeforeMount as beforeMount } from 'vue' -import { deepCopy } from 'loash-es' +import { deepCopy } from 'lodash-es' export const useRenderless = ({ props }) => { const state = reactive({ tableData: props.data || props.op.data || [] }) - onMounted(() => {}) - onMounted(() => {}) - onMounted(() => {}) + const last1 = useLayout(last1) beforeMount(() => {}) @@ -32,7 +30,6 @@ export const useRenderless = ({ props }) => { bbb = 'bbb' const handleClick = (e) => { - console.log(e.target, aaa) state.tableData.push({ key: 'TinyEngine', zhCN: '低代码引擎', @@ -46,8 +43,6 @@ export const useRenderless = ({ props }) => { logMessage('自定义是的范德萨') } - function last() {} - return { state, aa, diff --git a/packages/build/vite-plugin-meta-comments/test/legacy/code/output.js b/packages/build/vite-plugin-meta-comments/test/legacy/code/output.js new file mode 100644 index 00000000..610fc7ca --- /dev/null +++ b/packages/build/vite-plugin-meta-comments/test/legacy/code/output.js @@ -0,0 +1,136 @@ +import { callEntry as _callEntry, useCompile as _useCompile } from '@opentiny/tiny-engine-meta-register' +import _metaData from '../meta.js' +/* metaService */ +/** + * Copyright (c) 2023 - present TinyEngine Authors. + * Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd. + * + * Use of this source code is governed by an MIT-style license. + * + * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, + * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR + * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. + * + */ + +import { reactive, onMounted, onBeforeMount as beforeMount } from 'vue' +import { deepCopy } from 'lodash-es' +export const useRenderless = _callEntry( + ({ props }) => { + const state = reactive({ + tableData: props.data || props.op.data || [] + }) + const last1 = useLayout(last1) + beforeMount( + _callEntry(() => {}, { + metaData: { + id: `${_metaData.id}.onBeforeMount[0]` + }, + ctx: () => ({ + props, + state, + last1, + logMessage, + aaa, + bbb, + handleClick, + ccc, + sendMessage, + deepCopy, + useRenderless + }) + }) + ) + const logMessage = _callEntry( + () => { + console.log('我是纯函数我不需要闭包参数') + }, + { + metaData: { + id: `${_metaData.id}.logMessage` + }, + ctx: () => ({ + props, + state, + last1, + logMessage, + aaa, + bbb, + handleClick, + ccc, + sendMessage, + deepCopy, + useRenderless + }) + } + ) + const aaa = 'aaa', + bbb = 'bbb' + const handleClick = _callEntry( + (e) => { + state.tableData.push({ + key: 'TinyEngine', + zhCN: '低代码引擎', + enUS: 'TinyEngine' + }) + }, + { + metaData: { + id: `${_metaData.id}.handleClick` + }, + ctx: () => ({ + props, + state, + last1, + logMessage, + aaa, + bbb, + handleClick, + ccc, + sendMessage, + deepCopy, + useRenderless + }) + } + ) + const ccc = 111 + const sendMessage = _callEntry( + () => { + logMessage('自定义是的范德萨') + }, + { + metaData: { + id: `${_metaData.id}.sendMessage` + }, + ctx: () => ({ + props, + state, + last1, + logMessage, + aaa, + bbb, + handleClick, + ccc, + sendMessage, + deepCopy, + useRenderless + }) + } + ) + return { + state, + aa, + handleClick, + sendMessage + } + }, + { + metaData: { + id: `${_metaData.id}.useRenderless` + }, + ctx: () => ({ + deepCopy, + useRenderless + }) + } +) diff --git a/packages/build/vite-plugin-meta-comments/src/test/index.js b/packages/build/vite-plugin-meta-comments/test/legacy/index.js similarity index 82% rename from packages/build/vite-plugin-meta-comments/src/test/index.js rename to packages/build/vite-plugin-meta-comments/test/legacy/index.js index 981bc5d9..a1dcb094 100644 --- a/packages/build/vite-plugin-meta-comments/src/test/index.js +++ b/packages/build/vite-plugin-meta-comments/test/legacy/index.js @@ -1,6 +1,6 @@ /** - * Copyright (c) 2024 - present TinyEngine Authors. - * Copyright (c) 2024 - present Huawei Cloud Computing Technologies Co., Ltd. + * Copyright (c) 2023 - present TinyEngine Authors. + * Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd. * * Use of this source code is governed by an MIT-style license. * @@ -11,7 +11,7 @@ */ import fs from 'fs' -import { transform } from '../transform.js' +import { transform } from '../../src/transform.js' import { fileURLToPath } from 'node:url' import * as path from 'path' diff --git a/packages/build/vite-plugin-meta-comments/src/test/meta.js b/packages/build/vite-plugin-meta-comments/test/legacy/meta.js similarity index 100% rename from packages/build/vite-plugin-meta-comments/src/test/meta.js rename to packages/build/vite-plugin-meta-comments/test/legacy/meta.js diff --git a/packages/build/vite-plugin-meta-comments/test/transform-sfc.test.js b/packages/build/vite-plugin-meta-comments/test/transform-sfc.test.js new file mode 100644 index 00000000..b5919b64 --- /dev/null +++ b/packages/build/vite-plugin-meta-comments/test/transform-sfc.test.js @@ -0,0 +1,195 @@ +/** + * Copyright (c) 2023 - present TinyEngine Authors. + * Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd. + * + * Use of this source code is governed by an MIT-style license. + * + * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, + * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR + * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS. + * + */ + +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { describe, it, expect, beforeAll } from 'vitest' +import { transformSFC } from '../src/transform-sfc.js' + +// 获取当前文件的目录 +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) + +// 确保测试目录存在并创建meta.js +beforeAll(() => { + const testDir = path.join(__dirname, 'temp/transform-sfc-test-cases') + if (!fs.existsSync(testDir)) { + fs.mkdirSync(testDir, { recursive: true }) + } + + // 创建meta.js文件用于测试 + const metaJsContent = ` +export default { + id: 'test-meta', + name: '测试元数据' +} +` + const metaJsPath = path.join(__dirname, 'temp/transform-sfc-test-cases/meta.js') + fs.writeFileSync(metaJsPath, metaJsContent, 'utf8') +}) + +// 创建测试用例文件的辅助函数 +const createTestFile = (filename, content) => { + const filePath = path.join(__dirname, 'temp/transform-sfc-test-cases', filename) + fs.writeFileSync(filePath, content, 'utf8') + return filePath +} + +// 执行转换并验证结果 +const testTransformAndVerify = async (fileName, expectTransformed = true) => { + const filePath = path.join(__dirname, 'temp/transform-sfc-test-cases', fileName) + const code = fs.readFileSync(filePath, 'utf8') + + const result = transformSFC(code, filePath) + + if (expectTransformed) { + // 确保转换结果不为空 + expect(result).toBeTruthy() + expect(result.length).toBeGreaterThan(0) + await expect(result).toMatchFileSnapshot(`./expected/${fileName}.output.vue`) + } else { + // 对于不应被转换的文件,transformSFC应返回undefined + expect(result).toBeUndefined() + } + + return result +} + +describe('transform-sfc.js', () => { + describe('转换包含metaService注释的Vue SFC', () => { + it('应该正确转换单文件组件并添加callEntry调用', async () => { + // 创建包含metaService注释的Vue SFC文件 + const content = ` + + + +` + + createTestFile('meta-service.vue', content) + + // 执行转换 + const result = await testTransformAndVerify('meta-service.vue') + + // 检查转换结果是否包含callEntry调用 + expect(result).toMatch(/callEntry\(/) + + // 检查转换结果是否保留了原始的template和style标签 + expect(result).toMatch(/