feat!: registry support hotfix (#1349)

This commit is contained in:
chilingling 2025-06-05 16:43:35 +08:00 committed by GitHub
parent 4a6cf0e542
commit 6bdcd39b62
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
298 changed files with 5914 additions and 1716 deletions

View File

@ -1,3 +1,5 @@
dist
package-lock.json
**/node_modules/**
**/node_modules/**
# 忽略该文件夹下的测试对比文件,防止自动去掉分号之后导致测试失败
packages/build/vite-plugin-meta-comments/test/expected/**

View File

@ -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",

View File

@ -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
}
}
}
}
}

View File

@ -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 依赖 HttpServiceHttpService需要在前面处理
[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
}
}

View File

@ -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 }

View File

@ -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()

View File

@ -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()

View File

@ -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 = {

View File

@ -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

View File

@ -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 插件配置项
}
}
}
```
###
支持的 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\<Array\<string\>\> | 固定在左侧的面板列表 |
| rightFixedPanelsStorage | Ref\<Array\<string\>\> | 固定在右侧的面板列表 |
#### 方法
| 方法名 | 参数 | 返回值 | 描述 |
|-------|------|-------|------|
| closePlugin | (forceClose?: boolean) | void | 关闭左侧插件面板 |
| closeSetting | (forceClose?: boolean) | void | 关闭右侧设置面板 |
| activePlugin | (pluginName: string) | Promise\<IMetaApi\> | 激活左侧插件面板 |
| 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
}
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -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'
// ... 更多应用常量
```

View File

@ -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" }
]
},

View File

@ -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) 查看。

View File

@ -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`

View File

@ -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. 插件 idengine.plugins.i18n
b. 文件 idMain
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. 插件 idengine.plugins.i18n
b. 文件 idMain
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 注册表之后,修复流程大大缩短,大大提高了修复效率。

View File

@ -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` 来移除插件的功能
- 构建时的代码体积优化

View File

@ -1,5 +1,7 @@
# 注册表
注意TinyEngine v2.6 版本之后,注册表的方式有所变化,请参考 [新注册表](./new-registry.md) 了解新的注册表配置方式。
## 什么是注册表
在 新架构介绍中我们引入了注册表的概念二次低代码平台开发用户通过注册表配置元服务元应用TinyEngine底层引擎读取注册表的配置完成元应用元服务的定制然后加载对应的元应用元服务完成低代码平台的启动。

View File

@ -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",

View File

@ -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]',

View File

@ -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*(?<value>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
}

View File

@ -0,0 +1 @@
test/temp

View File

@ -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.
*

View File

@ -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": {

View File

@ -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
}
}
)

View File

@ -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.
*

View File

@ -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 || ''
}

View File

@ -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

View File

@ -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 };

View File

@ -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
})
});

View File

@ -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 };

View File

@ -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
})
});

View File

@ -0,0 +1,42 @@
<template>
<div>
<h1>{{ title }}</h1>
<button @click="increment">Count: {{ state.count }}</button>
</div>
</template>
<script>import { callEntry as _callEntry, useCompile as _useCompile } from '@opentiny/tiny-engine-meta-register';
import _metaData from './meta.js';
/* metaService */
import { reactive, ref } from 'vue';
export default {
name: 'TestComponent',
setup() {
const title = ref('Test Component');
const state = reactive({
count: 0
});
const increment = _callEntry(() => {
state.count++;
}, {
metaData: {
id: `${_metaData.id}.increment`
},
ctx: () => ({
title,
state,
increment
})
});
return {
title,
state,
increment
};
}
};</script>
<style>
h1 {
color: blue;
}
</style>

View File

@ -0,0 +1,30 @@
<template>
<div>
<h1>{{ title }}</h1>
<button @click="increment">Count: {{ count }}</button>
</div>
</template>
<script setup>import { callEntry as _callEntry, useCompile as _useCompile } from '@opentiny/tiny-engine-meta-register';
import _metaData from './meta.js';
/* metaService */
import { ref } from 'vue';
const title = ref('Script Setup Component');
const count = ref(0);
const increment = _callEntry(() => {
count.value++;
}, {
metaData: {
id: `${_metaData.id}.increment`
},
ctx: () => ({
title,
count,
increment
})
});</script>
<style>
h1 {
color: green;
}
</style>

View File

@ -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
})
});

View File

@ -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,

View File

@ -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
})
}
)

View File

@ -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'

View File

@ -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 = `<template>
<div>
<h1>{{ title }}</h1>
<button @click="increment">Count: {{ state.count }}</button>
</div>
</template>
<script>
/* metaService */
import { reactive, ref } from 'vue'
export default {
name: 'TestComponent',
setup() {
const title = ref('Test Component')
const state = reactive({
count: 0
})
const increment = () => {
state.count++
}
return {
title,
state,
increment
}
}
}
</script>
<style>
h1 {
color: blue;
}
</style>`
createTestFile('meta-service.vue', content)
// 执行转换
const result = await testTransformAndVerify('meta-service.vue')
// 检查转换结果是否包含callEntry调用
expect(result).toMatch(/callEntry\(/)
// 检查转换结果是否保留了原始的template和style标签
expect(result).toMatch(/<template>/)
expect(result).toMatch(/<style>/)
})
})
describe('转换包含script setup的Vue SFC', () => {
it('应该正确转换使用setup语法糖的组件', async () => {
// 创建包含script setup的Vue SFC文件
const content = `<template>
<div>
<h1>{{ title }}</h1>
<button @click="increment">Count: {{ count }}</button>
</div>
</template>
<script setup>
/* metaService */
import { ref } from 'vue'
const title = ref('Script Setup Component')
const count = ref(0)
const increment = () => {
count.value++
}
</script>
<style>
h1 {
color: green;
}
</style>`
createTestFile('script-setup.vue', content)
// 执行转换
const result = await testTransformAndVerify('script-setup.vue')
// 检查转换结果是否包含callEntry调用
expect(result).toMatch(/callEntry\(/)
// 检查转换结果是否保留了script setup语法
expect(result).toMatch(/<script setup>/)
})
})
describe('转换不包含元注释的Vue SFC', () => {
it('不应转换没有元注释的组件', async () => {
// 创建不包含元注释的Vue SFC文件
const content = `<template>
<div>
<h1>{{ title }}</h1>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'RegularComponent',
setup() {
const title = ref('Regular Component')
return {
title
}
}
}
</script>`
createTestFile('no-meta.vue', content)
// 执行转换,预期不会被转换
await testTransformAndVerify('no-meta.vue', false)
})
})
})

View File

@ -0,0 +1,216 @@
/**
* 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 { transform } from '../src/transform'
import entrySample from './legacy/code/entry?raw'
// 获取当前文件的目录
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
// 确保测试目录存在
beforeAll(() => {
const testDir = path.join(__dirname, 'temp/transform-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-test-cases/meta.js')
fs.writeFileSync(metaJsPath, metaJsContent, 'utf8')
})
// 创建测试用例文件的辅助函数
const createTestFile = (filename, content) => {
const filePath = path.join(__dirname, 'temp/transform-test-cases', filename)
fs.writeFileSync(filePath, content, 'utf8')
return filePath
}
// 执行转换并验证结果
const testTransformAndVerify = async (fileName, expectedPatterns) => {
const filePath = path.join(__dirname, 'temp/transform-test-cases', fileName)
const code = fs.readFileSync(filePath, 'utf8')
const result = transform(code, filePath)
// 确保转换结果不为空
expect(result).toBeTruthy()
expect(result.length).toBeGreaterThan(0)
// 验证是否包含预期的导入模式
for (const pattern of expectedPatterns) {
expect(result).toMatch(pattern)
}
// 将转换结果写入输出文件,用于手动检查
await expect(result).toMatchFileSnapshot(`./expected/${fileName}.output.js`)
return result
}
describe('transform.js', () => {
describe('转换包含metaService注释的文件', () => {
it('应该正确转换并添加导入语句', async () => {
// 创建测试文件
const content = `/* metaService: testModule */
import { reactive } from 'vue'
export const useTestService = () => {
const state = reactive({
count: 0
})
const increment = () => {
state.count++
}
return {
state,
increment
}
}
`
createTestFile('meta-service.js', content)
// 执行转换
const result = await testTransformAndVerify('meta-service.js', [
/import .* from ['"]\.\/meta\.js['"]/, // 匹配元数据导入
/import.*callEntry.*from ['"]@opentiny\/tiny-engine-meta-register['"]/, // 匹配callEntry导入
/import.*useCompile.*from ['"]@opentiny\/tiny-engine-meta-register['"]/ // 匹配useCompile导入
])
// 检查是否包含callEntry调用
expect(result).toMatch(/callEntry\(/)
})
})
describe('转换包含函数表达式的文件', () => {
it('应该正确转换函数表达式', async () => {
// 创建测试文件
const content = `/* metaService */
const doSomething = function() {
console.log('Doing something')
return true
}
export { doSomething }
`
createTestFile('function-expr.js', content)
// 执行转换
const result = await testTransformAndVerify('function-expr.js', [
/import .* from ['"]\.\/meta\.js['"]/, // 匹配元数据导入
/import.*callEntry.*from ['"]@opentiny\/tiny-engine-meta-register['"]/, // 匹配callEntry导入
/import.*useCompile.*from ['"]@opentiny\/tiny-engine-meta-register['"]/ // 匹配useCompile导入
])
// 检查是否包含callEntry调用和函数表达式
expect(result).toMatch(/callEntry\(/)
expect(result).toMatch(/function \(\) {/)
})
})
describe('转换包含箭头函数的文件', () => {
it('应该正确转换箭头函数', async () => {
// 创建测试文件
const content = `/* metaService */
const arrowFunc = () => {
console.log('Arrow function')
return true
}
export { arrowFunc }
`
createTestFile('arrow-func.js', content)
// 执行转换
const result = await testTransformAndVerify('arrow-func.js', [
/import .* from ['"]\.\/meta\.js['"]/, // 匹配元数据导入
/import.*callEntry.*from ['"]@opentiny\/tiny-engine-meta-register['"]/, // 匹配callEntry导入
/import.*useCompile.*from ['"]@opentiny\/tiny-engine-meta-register['"]/ // 匹配useCompile导入
])
// 检查是否包含callEntry调用和箭头函数
expect(result).toMatch(/callEntry\(/)
expect(result).toMatch(/\(\) =>/)
})
})
describe('转换包含Vue钩子的文件', () => {
it('应该正确转换Vue生命周期钩子', async () => {
// 创建测试文件
const content = `/* metaService */
import { onMounted, onBeforeMount } from 'vue'
export const useHooks = () => {
onMounted(() => {
console.log('Component mounted')
})
onBeforeMount(() => {
console.log('Component will mount')
})
}
`
createTestFile('vue-hooks.js', content)
// 执行转换
const result = await testTransformAndVerify('vue-hooks.js', [
/import .* from ['"]\.\/meta\.js['"]/, // 匹配元数据导入
/import.*callEntry.*from ['"]@opentiny\/tiny-engine-meta-register['"]/, // 匹配callEntry导入
/import.*useCompile.*from ['"]@opentiny\/tiny-engine-meta-register['"]/ // 匹配useCompile导入
])
// 检查是否包含对Vue钩子的处理
expect(result).toMatch(/onMounted\(/)
expect(result).toMatch(/callEntry\(/)
})
})
describe('转换不包含元注释的文件', () => {
it('对于没有元注释的文件transform应返回undefined', () => {
// 创建无元注释的文件
const content = `
export const regularFunction = () => {
console.log('Regular function')
}
`
createTestFile('no-meta.js', content)
const filePath = path.join(__dirname, 'temp/transform-test-cases/no-meta.js')
const code = fs.readFileSync(filePath, 'utf8')
// 执行转换
const result = transform(code, filePath)
// 对于没有元注释的文件transform应返回undefined
expect(result).toBeUndefined()
})
})
describe('转换entry.js文件', () => {
it('转换 entry.js 综合测试', async () => {
createTestFile('entry.js', entrySample)
await testTransformAndVerify('entry.js', [])
})
})
})

View File

@ -0,0 +1,124 @@
/**
* 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 { isCallEntryFile, isCompileFile, getModuleId, getTemplateId, getRelFilePath } from '../src/utils.js'
// 获取当前文件的目录
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
// 创建测试用例文件的辅助函数
const createTestFile = (filename, content) => {
const filePath = path.join(__dirname, 'temp/utils-test-cases', filename)
fs.writeFileSync(filePath, content, 'utf8')
return filePath
}
// 确保测试目录存在
beforeAll(() => {
const testDir = path.join(__dirname, 'temp/utils-test-cases')
if (!fs.existsSync(testDir)) {
fs.mkdirSync(testDir, { recursive: true })
}
})
describe('utils.js', () => {
describe('isCallEntryFile', () => {
it('应该识别包含metaService注释的文件', () => {
const metaServiceContent = `/* metaService */
export const myFunc = () => {}
`
createTestFile('metaService.js', metaServiceContent)
expect(isCallEntryFile(metaServiceContent)).toBe(true)
})
it('应该识别包含metaComponent注释的文件', () => {
const metaComponentContent = `/* metaComponent */
export default {
name: 'MyComponent'
}
`
createTestFile('metaComponent.js', metaComponentContent)
expect(isCallEntryFile(metaComponentContent)).toBe(true)
})
it('不应该识别没有元注释的文件', () => {
const normalContent = `export const myFunc = () => {}`
createTestFile('normal.js', normalContent)
expect(isCallEntryFile(normalContent)).toBe(false)
})
})
describe('isCompileFile', () => {
it('应该识别包含metaComponent注释的文件', () => {
const metaComponentContent = `/* metaComponent */
export default {
name: 'MyComponent'
}
`
expect(isCompileFile(metaComponentContent)).toBe(true)
})
it('不应识别只有metaService注释的文件', () => {
const metaServiceContent = `/* metaService */
export const myFunc = () => {}
`
expect(isCompileFile(metaServiceContent)).toBe(false)
})
})
describe('getModuleId', () => {
it('应该正确提取模块ID', () => {
const content = `/* metaService: myModule */
export const myFunc = () => {}
`
expect(getModuleId(content)).toBe('myModule')
})
it('没有指定模块ID时应该返回空字符串', () => {
const contentNoId = `/* metaService */
export const myFunc = () => {}
`
expect(getModuleId(contentNoId)).toBe('')
})
})
describe('getTemplateId', () => {
it('应该正确提取模板ID', () => {
const comment = ` metaComponent: myTemplate `
expect(getTemplateId(comment)).toBe('myTemplate')
})
it('没有指定模板ID时应该返回空字符串', () => {
const commentNoId = ` metaComponent: `
expect(getTemplateId(commentNoId)).toBe('')
})
})
describe('getRelFilePath', () => {
it('应该返回正确的相对路径', () => {
const path1 = '/test/dir1/file1.js'
const path2 = '/test/dir2/file2.js'
expect(getRelFilePath(path1, path2)).toBe('../dir2/file2.js')
})
it('同一目录下应该返回以./开头的相对路径', () => {
const path3 = '/test/dir/file1.js'
const path4 = '/test/dir/file2.js'
expect(getRelFilePath(path3, path4)).toBe('./file2.js')
})
})
})

View File

@ -0,0 +1,33 @@
/**
* 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 { defineConfig } from 'vitest/config'
import { fileURLToPath } from 'node:url'
import path from 'node:path'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
export default defineConfig({
test: {
globals: true,
environment: 'node',
include: ['./test/**/*.test.js'],
exclude: ['./test/legacy/code/**/*'],
reporters: ['default'],
testTimeout: 10000
},
resolve: {
alias: {
'@': path.resolve(__dirname, './src')
}
}
})

View File

@ -32,7 +32,6 @@ import {
useModal,
usePage,
useMessage,
getMergeRegistry,
getMergeMeta,
getOptions,
getMetaApi,
@ -56,13 +55,12 @@ const componentType = {
export default {
setup() {
const registry = getMergeRegistry('canvas')
const registry = getMergeMeta('engine.canvas')
const materialsPanel = getMergeMeta('engine.plugins.materials')?.entry
const { CanvasRouteBar, CanvasBreadcrumb } = registry.components
const CanvasLayout = registry.layout.entry
const [CanvasContainer] = registry.metas
const footData = ref([])
const showMask = ref(true)
const canvasRef = ref(null)
let showModal = false //
const { canvasSrc = '' } = getOptions(meta.id) || {}
@ -273,7 +271,6 @@ export default {
nodeSelected,
footData,
materialsPanel,
showMask,
controller: {
// canvas/render使
getMaterial: useMaterial().getMaterial,

View File

@ -1,49 +1,3 @@
export { init } from './src/init'
// reexport all plugin, user can import ondemand
export { default as Breadcrumb, BreadcrumbService } from '@opentiny/tiny-engine-toolbar-breadcrumb'
export { default as Fullscreen } from '@opentiny/tiny-engine-toolbar-fullscreen'
export { default as Lang } from '@opentiny/tiny-engine-toolbar-lang'
export { default as ViewSetting } from '@opentiny/tiny-engine-toolbar-view-setting'
export { default as Logo } from '@opentiny/tiny-engine-toolbar-logo'
export { default as Lock } from '@opentiny/tiny-engine-toolbar-lock'
export { default as Media } from '@opentiny/tiny-engine-toolbar-media'
export { default as Redoundo, HistoryService } from '@opentiny/tiny-engine-toolbar-redoundo'
export { default as Save } from '@opentiny/tiny-engine-toolbar-save'
export { default as Clean } from '@opentiny/tiny-engine-toolbar-clean'
export { default as ThemeSwitch, ThemeSwitchService } from '@opentiny/tiny-engine-toolbar-theme-switch'
export { default as Preview } from '@opentiny/tiny-engine-toolbar-preview'
export { default as GenerateCode, SaveLocalService } from '@opentiny/tiny-engine-toolbar-generate-code'
export { default as Refresh } from '@opentiny/tiny-engine-toolbar-refresh'
export { default as Collaboration } from '@opentiny/tiny-engine-toolbar-collaboration'
export { default as Setting } from '@opentiny/tiny-engine-toolbar-setting'
export { default as Materials, ResourceService, MaterialService } from '@opentiny/tiny-engine-plugin-materials'
export { default as State } from '@opentiny/tiny-engine-plugin-state'
export { default as Script } from '@opentiny/tiny-engine-plugin-script'
export { default as Tree } from '@opentiny/tiny-engine-plugin-tree'
export { default as Help, HelpService } from '@opentiny/tiny-engine-plugin-help'
export { default as Schema } from '@opentiny/tiny-engine-plugin-schema'
export { default as Page, PageService } from '@opentiny/tiny-engine-plugin-page'
export { default as I18n, TranslateService } from '@opentiny/tiny-engine-plugin-i18n'
export { default as Bridge } from '@opentiny/tiny-engine-plugin-bridge'
export { default as Block, BlockService } from '@opentiny/tiny-engine-plugin-block'
export { default as Datasource, DataSourceService } from '@opentiny/tiny-engine-plugin-datasource'
export { default as Robot } from '@opentiny/tiny-engine-plugin-robot'
export { default as Props, PropertiesService, PropertyService } from '@opentiny/tiny-engine-setting-props'
export { default as Events } from '@opentiny/tiny-engine-setting-events'
export { default as Styles } from '@opentiny/tiny-engine-setting-styles'
export { default as Layout, LayoutService } from '@opentiny/tiny-engine-layout'
export { default as Canvas } from '@opentiny/tiny-engine-canvas'
export { initPreview } from './src/preview/src/main'
export {
GenerateCodeService,
PluginPanel,
PluginSetting,
ToolbarBase,
GlobalService,
HttpService
} from '@opentiny/tiny-engine-common'
export { default as defaultRegistry } from './registry'
export * from '@opentiny/tiny-engine-meta-register'
export * from './re-export'

View File

@ -0,0 +1,47 @@
// reexport all plugin, user can import ondemand
export { default as Breadcrumb, BreadcrumbService } from '@opentiny/tiny-engine-toolbar-breadcrumb'
export { default as Fullscreen } from '@opentiny/tiny-engine-toolbar-fullscreen'
export { default as Lang } from '@opentiny/tiny-engine-toolbar-lang'
export { default as ViewSetting } from '@opentiny/tiny-engine-toolbar-view-setting'
export { default as Logo } from '@opentiny/tiny-engine-toolbar-logo'
export { default as Lock } from '@opentiny/tiny-engine-toolbar-lock'
export { default as Media } from '@opentiny/tiny-engine-toolbar-media'
export { default as Redoundo, HistoryService } from '@opentiny/tiny-engine-toolbar-redoundo'
export { default as Save } from '@opentiny/tiny-engine-toolbar-save'
export { default as Clean } from '@opentiny/tiny-engine-toolbar-clean'
export { default as ThemeSwitch, ThemeSwitchService } from '@opentiny/tiny-engine-toolbar-theme-switch'
export { default as Preview } from '@opentiny/tiny-engine-toolbar-preview'
export { default as GenerateCode, SaveLocalService } from '@opentiny/tiny-engine-toolbar-generate-code'
export { default as Refresh } from '@opentiny/tiny-engine-toolbar-refresh'
export { default as Collaboration } from '@opentiny/tiny-engine-toolbar-collaboration'
export { default as Setting } from '@opentiny/tiny-engine-toolbar-setting'
export { default as Materials, ResourceService, MaterialService } from '@opentiny/tiny-engine-plugin-materials'
export { default as State } from '@opentiny/tiny-engine-plugin-state'
export { default as Script } from '@opentiny/tiny-engine-plugin-script'
export { default as Tree } from '@opentiny/tiny-engine-plugin-tree'
export { default as Help, HelpService } from '@opentiny/tiny-engine-plugin-help'
export { default as Schema } from '@opentiny/tiny-engine-plugin-schema'
export { default as Page, PageService } from '@opentiny/tiny-engine-plugin-page'
export { default as I18n, TranslateService } from '@opentiny/tiny-engine-plugin-i18n'
export { default as Bridge } from '@opentiny/tiny-engine-plugin-bridge'
export { default as Block, BlockService } from '@opentiny/tiny-engine-plugin-block'
export { default as Datasource, DataSourceService } from '@opentiny/tiny-engine-plugin-datasource'
export { default as Robot } from '@opentiny/tiny-engine-plugin-robot'
export { default as Props, PropertiesService, PropertyService } from '@opentiny/tiny-engine-setting-props'
export { default as Events } from '@opentiny/tiny-engine-setting-events'
export { default as Styles } from '@opentiny/tiny-engine-setting-styles'
export { default as Layout, LayoutService } from '@opentiny/tiny-engine-layout'
export { default as Canvas } from '@opentiny/tiny-engine-canvas'
export { initPreview } from './src/preview/src/main'
export {
GenerateCodeService,
PluginPanel,
PluginSetting,
ToolbarBase,
GlobalService,
HttpService
} from '@opentiny/tiny-engine-common'
export { default as defaultRegistry } from './registry'
export * from '@opentiny/tiny-engine-meta-register'

View File

@ -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.
*
@ -10,7 +10,54 @@
*
*/
/* eslint-disable no-undef */
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,
HttpService
} from './re-export'
window.__TINY_ENGINE_REMOVED_REGISTRY = {}
export default {
root: {
id: 'engine.root',
metas: [HttpService, GenerateCodeService, GlobalService, ThemeSwitchService] // GlobalService 依赖 HttpServiceHttpService需要在前面处理
},
config: {
id: 'engine.config',
// TODO: 主题支持传入主题 package 或者是 url。
@ -61,26 +108,63 @@ export default {
'componentWillUnmount'
]
},
// 生命周期使用提示
lifeCycleTips: {
Vue: '通过Vue解构出来的方法都可以在setup这里使用比如watch、computed、watchEffect等'
}
},
themes: [
{
id: 'engine.theme.light',
text: '浅色主题',
type: 'light',
icon: 'light',
oppositeTheme: 'dark'
},
{
id: 'engine.theme.dark',
text: '深色主题',
type: 'dark',
icon: 'dark',
oppositeTheme: 'light'
}
]
themesList: [
{
id: 'engine.theme.light',
text: '浅色主题',
type: 'light',
icon: 'light',
oppositeTheme: 'dark'
},
{
id: 'engine.theme.dark',
text: '深色主题',
type: 'dark',
icon: 'dark',
oppositeTheme: 'light'
}
]
},
layout: __TINY_ENGINE_REMOVED_REGISTRY['engine.layout'] === false ? null : Layout,
toolbars: [
__TINY_ENGINE_REMOVED_REGISTRY['engine.toolbars.themeSwitch'] === false ? null : ThemeSwitch,
__TINY_ENGINE_REMOVED_REGISTRY['engine.toolbars.logo'] === false ? null : Logo,
__TINY_ENGINE_REMOVED_REGISTRY['engine.toolbars.breadcrumb'] === false ? null : Breadcrumb,
__TINY_ENGINE_REMOVED_REGISTRY['engine.toolbars.lock'] === false ? null : Lock,
__TINY_ENGINE_REMOVED_REGISTRY['engine.toolbars.media'] === false ? null : Media,
__TINY_ENGINE_REMOVED_REGISTRY['engine.toolbars.redoundo'] === false ? null : Redoundo,
__TINY_ENGINE_REMOVED_REGISTRY['engine.toolbars.collaboration'] === false ? null : Collaboration,
__TINY_ENGINE_REMOVED_REGISTRY['engine.toolbars.clean'] === false ? null : Clean,
__TINY_ENGINE_REMOVED_REGISTRY['engine.toolbars.preview'] === false ? null : Preview,
__TINY_ENGINE_REMOVED_REGISTRY['engine.toolbars.refresh'] === false ? null : Refresh,
__TINY_ENGINE_REMOVED_REGISTRY['engine.toolbars.generate-code'] === false ? null : GenerateCode,
__TINY_ENGINE_REMOVED_REGISTRY['engine.toolbars.save'] === false ? null : Save,
__TINY_ENGINE_REMOVED_REGISTRY['engine.toolbars.fullscreen'] === false ? null : Fullscreen,
__TINY_ENGINE_REMOVED_REGISTRY['engine.toolbars.lang'] === false ? null : Lang,
__TINY_ENGINE_REMOVED_REGISTRY['engine.toolbars.viewSetting'] === false ? null : ViewSetting
],
plugins: [
__TINY_ENGINE_REMOVED_REGISTRY['engine.plugins.materials'] === false ? null : Materials,
__TINY_ENGINE_REMOVED_REGISTRY['engine.plugins.outlinetree'] === false ? null : Tree,
__TINY_ENGINE_REMOVED_REGISTRY['engine.plugins.appmanage'] === false ? null : Page,
__TINY_ENGINE_REMOVED_REGISTRY['engine.plugins.blockmanage'] === false ? null : Block,
__TINY_ENGINE_REMOVED_REGISTRY['engine.plugins.collections'] === false ? null : Datasource,
__TINY_ENGINE_REMOVED_REGISTRY['engine.plugins.bridge'] === false ? null : Bridge,
__TINY_ENGINE_REMOVED_REGISTRY['engine.plugins.i18n'] === false ? null : I18n,
__TINY_ENGINE_REMOVED_REGISTRY['engine.plugins.pagecontroller'] === false ? null : Script,
__TINY_ENGINE_REMOVED_REGISTRY['engine.plugins.state'] === false ? null : State,
__TINY_ENGINE_REMOVED_REGISTRY['engine.plugins.schema'] === false ? null : Schema,
__TINY_ENGINE_REMOVED_REGISTRY['engine.plugins.editorhelp'] === false ? null : Help,
__TINY_ENGINE_REMOVED_REGISTRY['engine.plugins.robot'] === false ? null : Robot
],
settings: [
__TINY_ENGINE_REMOVED_REGISTRY['engine.setting.props'] === false ? null : Props,
__TINY_ENGINE_REMOVED_REGISTRY['engine.setting.styles'] === false ? null : Styles,
__TINY_ENGINE_REMOVED_REGISTRY['engine.setting.event'] === false ? null : Events
],
canvas: Canvas
}

View File

@ -1,17 +1,17 @@
<template>
<component :is="registry.layout.component" :registry="registry"></component>
<component :is="layoutRegistry.component"></component>
</template>
<script>
import { watch, onUnmounted } from 'vue'
import {
getMergeRegistry,
getMetaApi,
useModal,
useNotify,
useResource,
useCanvas,
useMessage
useMessage,
getMergeMeta
} from '@opentiny/tiny-engine-meta-register'
import { isVsCodeEnv } from '@opentiny/tiny-engine-common/js/environments'
import { useBroadcastChannel } from '@vueuse/core'
@ -22,7 +22,7 @@ const { BROADCAST_CHANNEL } = constants
export default {
setup() {
const { message } = useModal()
const registry = getMergeRegistry()
const layoutRegistry = getMergeMeta('engine.layout')
const materialsApi = getMetaApi('engine.plugins.materials')
const blockApi = getMetaApi('engine.plugins.blockmanage')
@ -74,7 +74,7 @@ export default {
)
return {
registry
layoutRegistry
}
}
}

View File

@ -18,7 +18,6 @@ import { injectGlobalComponents, setGlobalMonacoEditorTheme, Modal, Notify } fro
import TinyThemeTool from '@opentiny/vue-theme/theme-tool'
import { defaultThemeList } from '@opentiny/tiny-engine-theme-base'
import {
defineEntry,
mergeRegistry,
getMergeMeta,
getMetaApi,
@ -38,15 +37,15 @@ const { guid } = utils
const defaultLifeCycles = {
beforeAppCreate: ({ registry }) => {
// 合并用户自定义注册表
const newRegistry = mergeRegistry(registry, defaultRegistry)
const appId = getMetaApi(META_SERVICE.GlobalService).getBaseInfo().id
if (process.env.NODE_ENV === 'development') {
console.log('default registry:', defaultRegistry) // eslint-disable-line
console.log('merged registry:', registry) // eslint-disable-line
}
mergeRegistry(defaultRegistry, ...(Array.isArray(registry) ? registry : [registry]))
// 在common层注入合并后的注册表
defineEntry(newRegistry)
const appId = getMetaApi(META_SERVICE.GlobalService).getBaseInfo().id
const config = getMergeMeta('engine.config')
if (process.env.NODE_ENV === 'development') {
console.log('custom registry:', registry) // eslint-disable-line
console.log('default registry:', defaultRegistry) // eslint-disable-line
}
// 初始化所有服务
initServices()
@ -55,10 +54,7 @@ const defaultLifeCycles = {
initHook(HOOK_NAME.useNotify, Notify, { useDefaultExport: true })
initHook(HOOK_NAME.useModal, Modal)
// 加载主题样式,尽早加载
// import(`./theme/${newRegistry.config.theme}.js`)
const theme = localStorage.getItem(`tiny-engine-theme-${appId}`) || newRegistry.config.theme || 'light'
const theme = localStorage.getItem(`tiny-engine-theme-${appId}`) || config.theme || 'light'
new TinyThemeTool(defaultThemeList[theme], defaultThemeList[theme]?.id)
document.documentElement?.setAttribute?.('data-theme', theme)
@ -68,7 +64,7 @@ const defaultLifeCycles = {
}
// 这里暴露到 window 是为了让 canvas 可以读取
window.TinyGlobalConfig = newRegistry.config || {}
window.TinyGlobalConfig = config || {}
},
appCreated: ({ app }) => {
initSvgs(app)
@ -129,7 +125,7 @@ const subscribeSignalFinish = (createAppSignal, timeout = 30000) => {
export const init = async ({
selector = '#app',
registry = defaultRegistry,
registry = [],
lifeCycles = {},
configurators = {},
createAppSignal = [],

View File

@ -15,7 +15,7 @@
<script lang="jsx">
import { watch } from 'vue'
import { useBreadcrumb, getMergeRegistry, getMergeMeta } from '@opentiny/tiny-engine-meta-register'
import { useBreadcrumb, getMergeMeta } from '@opentiny/tiny-engine-meta-register'
import { Switch as TinySwitch } from '@opentiny/vue'
import { constants } from '@opentiny/tiny-engine-utils'
import { BROADCAST_CHANNEL } from '../src/preview/srcFiles/constant'
@ -28,8 +28,8 @@ export default {
},
setup() {
const debugSwitch = injectDebugSwitch()
const Breadcrumb = getMergeRegistry('toolbars', 'engine.toolbars.breadcrumb')?.entry
const ChangeLang = getMergeRegistry('toolbars', 'engine.toolbars.lang')?.entry
const Breadcrumb = getMergeMeta('engine.toolbars.breadcrumb')?.entry
const ChangeLang = getMergeMeta('engine.toolbars.lang')?.entry
const langOptions = getMergeMeta('engine.toolbars.lang').options
const ToolbarMedia = null // TODO: Media plugin rely on layout/canvas. Further processing is required.
const { setBreadcrumbPage, setBreadcrumbBlock } = useBreadcrumb()

View File

@ -12,23 +12,19 @@
import { createApp } from 'vue'
import initSvgs from '@opentiny/tiny-engine-svgs'
import { defineEntry, mergeRegistry, initServices } from '@opentiny/tiny-engine-meta-register'
import { mergeRegistry, initServices } from '@opentiny/tiny-engine-meta-register'
import './styles/vars.less'
import defaultRegistry from '../../../registry.js'
import defaultRegistry from './previewDefaultRegistry.js'
import App from './App.vue'
export const initPreview = ({ registry, lifeCycles = {} }) => {
const { beforeAppCreate } = lifeCycles
const mergedRegistry = mergeRegistry(registry, defaultRegistry)
mergeRegistry(defaultRegistry, ...(Array.isArray(registry) ? registry : [registry]))
beforeAppCreate?.()
defineEntry(mergedRegistry)
initServices()
// TODO: 后续需要方案
// import(`../../theme/${mergedRegistry.config.theme || 'light'}.js`)
const app = createApp(App)
initSvgs(app)

View File

@ -0,0 +1,92 @@
/**
* 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 { GenerateCodeService, Breadcrumb, Media, Lang, HttpService } from '../../../re-export'
export default {
root: {
id: 'engine.root',
metas: [HttpService, GenerateCodeService]
},
config: {
id: 'engine.config',
// TODO: 主题支持传入主题 package 或者是 url。
theme: 'light',
// 物料链接
material: [],
// 画布依赖的 script、styles 链接。TODO: 解耦后添加默认 tinyvue 的链接
canvasDependencies: {
styles: [],
scripts: []
},
dslMode: 'Vue',
platformId: 918,
// TODO: 声明周期相关配置拆分到页面管理的配置项里面
// 生命周期函数
lifeCyclesOptions: {
Angular: [
'_constructor_',
'ngOnInit',
'ngOnChanges',
'ngDoCheck',
'ngAfterContentInit',
'ngAfterContentChecked',
'ngAfterViewInit',
'ngAfterViewChecked',
'ngOnDestroy'
],
Vue: [
'setup',
'onBeforeMount',
'onMounted',
'onBeforeUpdate',
'onUpdated',
'onBeforeUnmount',
'onUnmounted',
'onErrorCaptured',
'onActivated',
'onDeactivated'
],
HTML: [],
React: [
'componentWillMount',
'componentDidMount',
'componentWillReceiveProps',
'shouldComponentUpdate',
'componentWillUpdate',
'componentDidUpdate',
'componentWillUnmount'
]
},
// 生命周期使用提示
lifeCycleTips: {
Vue: '通过Vue解构出来的方法都可以在setup这里使用比如watch、computed、watchEffect等'
},
themesList: [
{
id: 'engine.theme.light',
text: '浅色主题',
type: 'light',
icon: 'light',
oppositeTheme: 'dark'
},
{
id: 'engine.theme.dark',
text: '深色主题',
type: 'dark',
icon: 'dark',
oppositeTheme: 'light'
}
]
},
toolbars: [Breadcrumb, Media, Lang]
}

View File

@ -94,7 +94,7 @@ export default defineConfig({
}
}
},
external: ['vue', 'monaco-editor', 'prettier', /@opentiny\/vue.*/, '@opentiny/tiny-engine-meta-register']
external: ['vue', 'monaco-editor', 'prettier', /@opentiny\/vue.*/, /@opentiny\/tiny-engine.*/]
}
}
})

View File

@ -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.
*

View File

@ -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.
*

View File

@ -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,13 @@
* 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 } from '@opentiny/tiny-engine'
import engineConfig from './engine.config'
import { HttpService } from './src/composable'
export default {
root: {
id: 'engine.root',
metas: [HttpService, GenerateCodeService, GlobalService, ThemeSwitchService] // GlobalService 依赖 HttpServiceHttpService需要在前面处理
},
config: engineConfig,
layout: {
...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']
]
}
}
},
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
[META_SERVICE.Http]: HttpService,
'engine.config': {
...engineConfig
}
}

View File

@ -11,9 +11,9 @@
*/
// 导入@opentiny/tiny-engine时内部的依赖包也会逐个导入可能会执行useComplie此时需要templateHashMap。所以需要先执行一次defineEntry
import { registry } from './defineEntry.js'
import { init } from '@opentiny/tiny-engine'
import { configurators } from './configurators/'
import registry from '../registry'
import 'virtual:svg-icons-register'
init({

View File

@ -10,8 +10,7 @@
*
*/
import { initHook, HOOK_NAME, GenerateCodeService, Breadcrumb, Media, Lang } from '@opentiny/tiny-engine'
import { initPreview } from '@opentiny/tiny-engine'
import { initHook, HOOK_NAME, META_SERVICE, initPreview } from '@opentiny/tiny-engine'
import 'virtual:svg-icons-register'
import { HttpService } from './composable'
@ -21,12 +20,7 @@ const beforeAppCreate = () => {
initPreview({
registry: {
root: {
id: 'engine.root',
metas: [HttpService, GenerateCodeService]
},
config: { id: 'engine.config', theme: 'light' },
toolbars: [Breadcrumb, Media, Lang]
[META_SERVICE.Http]: HttpService
},
lifeCycles: {
beforeAppCreate

View File

@ -4,6 +4,7 @@ import { LayoutService } from './src/composable'
import designSmbConfig from '@opentiny/vue-design-smb'
import { ConfigProvider as TinyConfigProvider } from '@opentiny/vue'
import './src/styles/vars.less'
import defaultLayout from './src/defaultLayout'
export default {
...metaData,
@ -13,21 +14,7 @@ export default {
configProviderDesign: designSmbConfig,
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']
]
}
layoutConfig: defaultLayout
},
metas: [LayoutService]
}

View File

@ -14,22 +14,21 @@
:key="index"
:class="{
'list-item': true,
'first-item': index === 0,
active: item.id === renderPanel,
active: item === renderPanel,
prev: state.prevIdex - 1 === index
}"
:title="item.title"
@click="clickMenu({ item, index })"
:title="getMergeMeta(item)?.title"
@click="clickMenu({ item: getMergeMeta(item), index })"
@contextmenu.prevent="showContextMenu($event, true, item, index, PLUGIN_POSITION.leftTop)"
>
<div v-if="getPluginShown(item.id)">
<div v-if="getPluginShown(item)">
<span class="item-icon">
<svg-icon
v-if="typeof iconComponents[item.id] === 'string'"
:name="iconComponents[item.id]"
v-if="typeof getMergeMeta(item)?.icon === 'string'"
:name="getMergeMeta(item)?.icon"
class="panel-icon"
></svg-icon>
<component v-else :is="iconComponents[item.id]" class="panel-icon"></component>
<component v-else :is="getMergeMeta(item)?.icon" class="panel-icon"></component>
</span>
</div>
</div>
@ -50,21 +49,24 @@
:key="index"
:class="[
'list-item',
{ active: renderPanel === item.id, prev: state.prevIdex - 1 === index, 'first-item': index === 0 }
{
active: renderPanel === item,
prev: state.prevIdex - 1 === index
}
]"
:title="item.title"
@click="clickMenu({ item, index })"
:title="getMergeMeta(item)?.title"
@click="clickMenu({ item: getMergeMeta(item), index })"
@contextmenu.prevent="showContextMenu($event, true, item, index, PLUGIN_POSITION.leftBottom)"
>
<div :class="{ 'is-show': renderPanel }" v-if="getPluginShown(item.id)">
<div :class="{ 'is-show': renderPanel }" v-if="getPluginShown(item)">
<span class="item-icon">
<public-icon
v-if="typeof iconComponents[item.id] === 'string'"
:name="iconComponents[item.id]"
v-if="typeof getMergeMeta(item)?.icon === 'string'"
:name="getMergeMeta(item)?.icon"
class="panel-icon"
svgClass="panel-svg"
></public-icon>
<component v-else :is="iconComponents[item.id]" class="panel-icon"></component>
<component v-else :is="getMergeMeta(item)?.icon" class="panel-icon"></component>
</span>
</div>
</div>
@ -75,7 +77,7 @@
<div :class="{ 'not-selected': getMoveDragBarState() }">
<!-- 插件面板 -->
<div
v-show="renderPanel && components[renderPanel]"
v-show="renderPanel && getMergeMeta(renderPanel)?.entry"
id="tiny-engine-left-panel"
:class="[renderPanel, { 'is-fixed': leftFixedPanelsStorage.includes(renderPanel) }]"
>
@ -83,7 +85,7 @@
<keep-alive>
<component
ref="pluginRef"
:is="currentComponent"
:is="getMergeMeta(renderPanel)?.entry"
:fixed-panels="leftFixedPanelsStorage"
@close="close"
@fixPanel="fixPanel"
@ -102,10 +104,11 @@
</template>
<script lang="ts">
import { reactive, ref, watch, computed } from 'vue'
/* metaService: engine.layout.DesignPlugins */
import { reactive, ref, watch } from 'vue'
import { Popover, Tooltip } from '@opentiny/vue'
import { VueDraggableNext } from 'vue-draggable-next'
import { useLayout, usePage, META_APP } from '@opentiny/tiny-engine-meta-register'
import { useLayout, usePage, META_APP, getMergeMeta } from '@opentiny/tiny-engine-meta-register'
import { PublicIcon, PluginRightMenu } from '@opentiny/tiny-engine-common'
export default {
@ -118,7 +121,8 @@ export default {
},
props: {
renderPanel: {
type: String
type: String,
default: ''
},
plugins: {
type: Array,
@ -131,12 +135,9 @@ export default {
},
emits: ['click', 'node-click', 'changeLeftAlign'],
setup(props, { emit }) {
const components: any = {}
const iconComponents: any = {}
const pluginRef = ref<any>(null)
const { isTemporaryPage } = usePage()
const pluginState = useLayout().getPluginState()
const {
changeLeftFixedPanels,
leftFixedPanelsStorage,
@ -146,9 +147,8 @@ export default {
getMoveDragBarState,
isSameSide,
dragPluginLayout,
getPluginsByPosition
getFinalLayoutConfig
} = useLayout()
const rightMenu = ref(null)
const showContextMenu = (event, type, item, index, align) => {
if (!type) {
@ -157,11 +157,10 @@ export default {
rightMenu.value.showContextMenu(event.clientX, event.clientY, type, item, index, align)
}
}
const state = reactive({
prevIdex: -2,
topNavLists: getPluginsByPosition(PLUGIN_POSITION.leftTop, props.pluginList),
bottomNavLists: getPluginsByPosition(PLUGIN_POSITION.leftBottom, props.pluginList)
topNavLists: getFinalLayoutConfig().plugins.left.top,
bottomNavLists: getFinalLayoutConfig().plugins.left.bottom
})
const changeAlign = (pluginId) => {
@ -175,18 +174,6 @@ export default {
state.topNavLists.unshift(item)
}
props.pluginList.forEach(({ id, entry, icon }) => {
components[id] = entry
iconComponents[id] = icon
})
const currentComponent = computed(() => {
const isExistedComponent = [...state.topNavLists, ...state.bottomNavLists].some(
(item) => item.id === props.renderPanel
)
return isExistedComponent ? components[props.renderPanel] : null
})
const clickMenu = ({ item, index }) => {
if (item.id === META_APP.EditorHelp || item.id === META_APP.Robot) return
@ -255,7 +242,6 @@ export default {
return {
leftFixedPanelsStorage,
currentComponent,
changeAlign,
rightMenu,
PLUGIN_POSITION,
@ -269,9 +255,8 @@ export default {
close,
fixPanel,
pluginState,
components,
getMoveDragBarState,
iconComponents
getMergeMeta
}
}
}

View File

@ -2,18 +2,18 @@
<template>
<div :class="{ 'not-selected': getMoveDragBarState() }">
<div
v-show="renderPanel && components[renderPanel]"
v-show="renderPanel && settingPluginsMeta[renderPanel]?.entry"
id="tiny-engine-right-panel"
:class="[renderPanel, { 'is-fixed': rightFixedPanelsStorage.includes(renderPanel) }]"
>
<div class="right-panel-wrap">
<component
:is="currentComponent"
:is="settingPluginsMeta[renderPanel]?.entry"
:fixed-panels="rightFixedPanelsStorage"
@close="close"
@fixPanel="fixPanel"
></component>
<div v-show="activating" class="active2" />
<div v-show="settingsState.activating" class="active2" />
</div>
</div>
</div>
@ -29,14 +29,18 @@
<div
v-for="(item, index) in settingPlugins"
:key="index"
:class="['list-item', { 'first-item': item === settingPlugins[0], active: item.id === renderPanel }]"
:title="item.title"
@click="clickMenu({ item, index })"
:class="['list-item', { active: item === renderPanel }]"
:title="settingPluginsMeta[item]?.title"
@click="clickMenu({ item: settingPluginsMeta[item], index })"
@contextmenu.prevent="showContextMenu($event, true, item, index, PLUGIN_POSITION.rightTop)"
>
<span class="item-icon" v-if="getPluginShown(item.id)">
<svg-icon v-if="iconComponents[item.id]" :name="iconComponents[item.id]" class="panel-icon"></svg-icon>
<component v-else :is="iconComponents[item.id]" class="panel-icon"></component>
<span class="item-icon" v-if="getPluginShown(item)">
<svg-icon
v-if="typeof settingPluginsMeta[item]?.icon === 'string'"
:name="settingPluginsMeta[item]?.icon"
class="panel-icon"
></svg-icon>
<component v-else :is="settingPluginsMeta[item]?.icon" class="panel-icon"></component>
</span>
</div>
<div style="flex: 1" class="list-item" @contextmenu.prevent="showContextMenu($event, false)"></div>
@ -45,16 +49,17 @@
<plugin-right-menu
ref="rightMenu"
:list="settingPlugins"
:list="Object.values(settingPluginsMeta)"
:align="PLUGIN_POSITION.rightTop"
@switchAlign="switchAlign"
/>
</template>
<script lang="ts">
import { computed, ref, watch, toRefs } from 'vue'
/* metaService: engine.layout.DesignSettings */
import { computed, ref, watch } from 'vue'
import { Tabs, TabItem } from '@opentiny/vue'
import { useLayout } from '@opentiny/tiny-engine-meta-register'
import { useLayout, getMergeMeta } from '@opentiny/tiny-engine-meta-register'
import { VueDraggableNext } from 'vue-draggable-next'
import { PluginRightMenu } from '@opentiny/tiny-engine-common'
@ -66,12 +71,9 @@ export default {
VueDraggableNext
},
props: {
settings: {
type: Array,
default: () => []
},
renderPanel: {
type: String
type: String,
default: ''
},
pluginList: {
type: Array,
@ -80,11 +82,7 @@ export default {
},
emits: ['changeRightAlign'],
setup(props, { emit }) {
const components = {}
const iconComponents = {}
const {
getPluginsByPosition,
getPluginById,
PLUGIN_POSITION,
rightFixedPanelsStorage,
@ -93,11 +91,11 @@ export default {
isSameSide,
getPluginShown,
getMoveDragBarState,
layoutState: { settings: settingsState }
layoutState: { settings: settingsState },
getFinalLayoutConfig
} = useLayout()
const rightMenu = ref(null)
const { renderPanel } = toRefs(props)
const showContextMenu = (event, type, item, index, align) => {
if (!type) {
rightMenu.value.showContextMenu(event.clientX, event.clientY, type)
@ -106,16 +104,18 @@ export default {
}
}
props.pluginList.forEach(({ id, entry, icon }) => {
components[id] = entry
iconComponents[id] = icon
})
const settingPlugins = ref(getFinalLayoutConfig().plugins.right.top)
const settingPluginsMeta = computed(() => {
const result: Record<string, any> = {}
const settingPlugins = ref(getPluginsByPosition(PLUGIN_POSITION.rightTop, props.pluginList))
settingPlugins.value.forEach((item) => {
const meta = getMergeMeta(item)
if (meta) {
result[item] = meta
}
})
const currentComponent = computed(() => {
const isExistedComponent = settingPlugins.value.some((item) => item.id === renderPanel.value)
return isExistedComponent ? components[renderPanel.value] : null
return result
})
const close = () => {
@ -152,9 +152,12 @@ export default {
setRender(item.id)
}
watch(renderPanel, (n) => {
setRender(n)
})
watch(
() => props.renderPanel,
(n) => {
setRender(n)
}
)
//
const fixPanel = (pluginName) => {
@ -167,18 +170,10 @@ export default {
dragPluginLayout(e.from.id, e.to.id, e.oldIndex, e.newIndex)
}
const activating = computed(() => settingsState.activating)
const showMask = ref(true)
return {
currentComponent,
changeAlign,
showMask,
activating,
settingsState,
settingPlugins,
components,
iconComponents,
clickMenu,
close,
fixPanel,
@ -189,7 +184,9 @@ export default {
getPluginShown,
switchAlign,
rightMenu,
getMoveDragBarState
getMoveDragBarState,
getMergeMeta,
settingPluginsMeta
}
}
}

View File

@ -2,23 +2,23 @@
<div class="tiny-engine-toolbar">
<div class="toolbar-left">
<component
:is="getMergeMeta(comp).entry"
v-for="comp in state.leftBar"
:is="getMergeMeta(comp)?.entry"
v-for="comp in toolbars.left"
:key="comp"
:options="getMergeMeta(comp).options"
:options="getMergeMeta(comp)?.options"
></component>
</div>
<div class="toolbar-center">
<component
:is="getMergeMeta(comp).entry"
v-for="comp in state.centerBar"
:is="getMergeMeta(comp)?.entry"
v-for="comp in toolbars.center"
:key="comp"
:options="getMergeMeta(comp).options"
:options="getMergeMeta(comp)?.options"
></component>
</div>
<div class="toolbar-right">
<div class="toolbar-right-content">
<div class="toolbar-right-item" v-for="(item, idx) in state.rightBar" :key="idx">
<div class="toolbar-right-item" v-for="(item, idx) in toolbars.right" :key="idx">
<div v-if="typeof item === 'string'">
<component
:is="getMergeMeta(item)?.entry"
@ -39,7 +39,7 @@
</div>
</div>
<toolbar-collapse
:collapseBar="state.collapseBar"
:collapseBar="toolbars.collapse"
v-if="layoutRegistry.options?.isShowCollapse"
></toolbar-collapse>
</div>
@ -47,8 +47,9 @@
</template>
<script lang="ts">
import { reactive } from 'vue'
import { getMergeMeta } from '@opentiny/tiny-engine-meta-register'
/* metaService: engine.layout.DesignToolbars */
import { computed } from 'vue'
import { getMergeMeta, useLayout } from '@opentiny/tiny-engine-meta-register'
import ToolbarCollapse from './ToolbarCollapse.vue'
export default {
@ -58,20 +59,21 @@ export default {
props: {
layoutRegistry: {
type: Object,
default: () => {}
default: () => ({})
}
},
setup(props) {
const state = reactive({
leftBar: props.layoutRegistry?.options?.toolbars?.left,
rightBar: props.layoutRegistry?.options?.toolbars?.right,
centerBar: props.layoutRegistry?.options?.toolbars?.center,
collapseBar: props.layoutRegistry?.options?.toolbars?.collapse
setup() {
const { getFinalLayoutConfig } = useLayout()
const toolbars = computed(() => {
const layoutConfig = getFinalLayoutConfig()
return layoutConfig.toolbars
})
return {
getMergeMeta,
state
toolbars
}
}
}

View File

@ -8,20 +8,19 @@
<design-plugins
v-if="leftMenuShownStorage"
ref="left"
:plugins="registry.plugins"
:plugins="pluginRegistry"
:plugin-list="pluginList"
:render-panel="plugins.render"
@changeLeftAlign="changeLeftAlign"
@click="toggleNav"
></design-plugins>
<component :is="registry.canvas.entry"></component>
<component :is="canvasEntry"></component>
</div>
</div>
<div class="tiny-engine-right-wrap">
<design-settings
v-if="rightMenuShownStorage"
ref="right"
:settings="registry.settings"
:render-panel="settings.render"
:plugin-list="pluginList"
@changeRightAlign="changeRightAlign"
@ -33,13 +32,14 @@
</template>
<script lang="ts">
import { useLayout, getMergeRegistry } from '@opentiny/tiny-engine-meta-register'
/* metaService: engine.layout.Main */
import { ref } from 'vue'
import { useLayout, getMergeMeta, getMergeMetaByType } from '@opentiny/tiny-engine-meta-register'
import { constants } from '@opentiny/tiny-engine-utils'
import DesignToolbars from './DesignToolbars.vue'
import DesignPlugins from './DesignPlugins.vue'
import DesignSettings from './DesignSettings.vue'
import meta from '../meta'
import { ref } from 'vue'
export default {
name: 'TinyLowCode',
@ -53,19 +53,16 @@ export default {
editor: this
}
},
props: {
registry: {
type: Object,
default: () => ({})
}
},
setup(props) {
const layoutRegistry = getMergeRegistry(meta.type)
setup() {
const layoutRegistry = getMergeMeta(meta.id)
const configProvider = layoutRegistry.options.configProvider
const configProviderDesign = layoutRegistry.options.configProviderDesign
const { layoutState, leftMenuShownStorage, rightMenuShownStorage, initPluginStorageReactive } = useLayout()
const { plugins, settings } = layoutState
const canvasEntry = getMergeMeta('engine.canvas')?.entry
const pluginRegistry = getMergeMetaByType('plugins')
// @legacy type: 'setting' plugin type: 'plugins'
const settingRegistry = getMergeMetaByType('setting')
const toggleNav = ({ item }) => {
if (!item.id) return
@ -83,34 +80,17 @@ export default {
}
//
const pluginList = [...props.registry.plugins, ...props.registry.settings]
// align
const alignGroups = {}
const pluginList = [...pluginRegistry, ...settingRegistry]
const plugin = {}
const { PLUGIN_DEFAULT_WIDTH } = constants
pluginList.forEach((item) => {
if (item.id) {
const align = item?.align || 'leftTop'
// alignGroups[align]
if (!alignGroups[align]) {
alignGroups[align] = []
}
// item.id alignGroups
alignGroups[align].push(item.id)
// index
const index = alignGroups[align].indexOf(item.id)
const widthResizable = item?.widthResizable ?? false
plugin[item.id] = {
width: item?.width || PLUGIN_DEFAULT_WIDTH,
align: align,
index: index,
isShow: true,
entry: item.entry,
id: item.id,
@ -136,7 +116,9 @@ export default {
plugins,
settings,
toggleNav,
layoutState
layoutState,
canvasEntry,
pluginRegistry
}
}
}

View File

@ -30,6 +30,7 @@
</template>
<script lang="ts">
/* metaService: engine.layout.ToolbarCollapse */
import { Popover } from '@opentiny/vue'
import { IconPopup } from '@opentiny/vue-icon'
import { getMergeMeta } from '@opentiny/tiny-engine-meta-register'

View File

@ -10,13 +10,16 @@
*
*/
/* metaService: engine.service.layout.useLayout */
import { reactive, nextTick } from 'vue'
import { useStorage } from '@vueuse/core'
import { constants } from '@opentiny/tiny-engine-utils'
import { META_APP as PLUGIN_NAME, getMetaApi } from '@opentiny/tiny-engine-meta-register'
import { META_APP as PLUGIN_NAME, getMetaApi, getMergeMeta } from '@opentiny/tiny-engine-meta-register'
import defaultLayout from '../defaultLayout'
import { utils } from '@opentiny/tiny-engine-utils'
const { PAGE_STATUS, STORAGE_KEY_LEFT_FIXED_PANELS, STORAGE_KEY_RIGHT_FIXED_PANELS, PLUGIN_DEFAULT_WIDTH } = constants
const { deepClone } = utils
// MetaApi 类型定义
export interface IMetaApi {
[key: string]: any
@ -292,9 +295,10 @@ export default () => {
}
const getPluginsByPosition = (position: string, pluginList: IPlugin[]): IPlugin[] => {
return getPluginsByLayout(position)
const res = getPluginsByLayout(position)
.map((pluginId) => getPluginById(pluginList, pluginId))
.filter((plugin): plugin is IPlugin => Boolean(plugin))
return res
}
// 修改某个插件的布局
@ -387,6 +391,124 @@ export default () => {
pluginStorageReactive.value = pluginList
}
const getIsUserCustomLayout = () => {
const defaultLayoutString = JSON.stringify(defaultLayout)
const userLayoutString = JSON.stringify(getMergeMeta('engine.layout')?.options?.layoutConfig)
return defaultLayoutString !== userLayoutString
}
const removeUndefineLayoutId = (layout) => {
if (Array.isArray(layout)) {
layout.forEach((item, index) => {
if (Array.isArray(item)) {
removeUndefineLayoutId(item)
}
// 对象类型,则递归遍历
else if (Object.prototype.toString.call(item) === '[object Object]') {
removeUndefineLayoutId(item)
}
// 注册表中找不到,则删除
else if (typeof item === 'string' && !getMergeMeta(item)) {
layout.splice(index, 1)
}
})
}
if (Object.prototype.toString.call(layout) === '[object Object]') {
Object.values(layout).forEach((value) => {
removeUndefineLayoutId(value)
})
}
}
const removeById = (layout, id) => {
if (Array.isArray(layout)) {
layout.forEach((item, index) => {
if (Array.isArray(item)) {
removeById(item, id)
} else if (Object.prototype.toString.call(item) === '[object Object]') {
removeById(item, id)
} else if (item === id) {
layout.splice(index, 1)
}
})
}
if (Object.prototype.toString.call(layout) === '[object Object]') {
Object.values(layout).forEach((value) => {
removeById(value, id)
})
}
}
const replaceByPosition = (layout, originId, targetId, position) => {
if (Array.isArray(layout)) {
for (let i = 0; i < layout.length; i++) {
const item = layout[i]
if (Array.isArray(item)) {
replaceByPosition(item, originId, targetId, position)
} else if (Object.prototype.toString.call(item) === '[object Object]') {
replaceByPosition(item, originId, targetId, position)
} else if (item === targetId) {
const insertIndex = position === 'before' ? i : i + 1
layout.splice(insertIndex, 0, originId)
// 完成替换,结束循环,提前 return
return
}
}
return
}
if (Object.prototype.toString.call(layout) === '[object Object]') {
Object.values(layout).forEach((value) => {
replaceByPosition(value, originId, targetId, position)
})
}
}
const computeFinalLayoutConfig = (layout, relativeLayoutConfig) => {
const finalLayoutConfig = deepClone(layout)
Object.entries(relativeLayoutConfig).forEach(([key, value]) => {
if (value.insertBefore) {
// 移除原来的 id
removeById(finalLayoutConfig, key)
// 插入到指定 id 前面
replaceByPosition(finalLayoutConfig, key, value.insertBefore, 'before')
} else if (value.insertAfter) {
// 移除原来的 id
removeById(finalLayoutConfig, key)
// 插入到指定 id 后面
replaceByPosition(finalLayoutConfig, key, value.insertAfter, 'after')
}
})
removeUndefineLayoutId(finalLayoutConfig)
return finalLayoutConfig
}
let finalLayoutConfig = null
const getFinalLayoutConfig = () => {
if (finalLayoutConfig) {
return finalLayoutConfig
}
const isUserCustomLayout = getIsUserCustomLayout()
// 用户传了自定义配置,则忽略 insertBefore insertAfter 的配置
if (isUserCustomLayout) {
finalLayoutConfig = getMergeMeta('engine.layout')?.options?.layoutConfig
return finalLayoutConfig
}
const relativeLayoutConfig = getMergeMeta('engine.layout')?.options?.relativeLayoutConfig || {}
finalLayoutConfig = computeFinalLayoutConfig(deepClone(defaultLayout), relativeLayoutConfig)
return finalLayoutConfig
}
return {
isPanelWidthResizable,
getFixedPanelsStatus,
@ -424,6 +546,7 @@ export default () => {
changeMenuShown,
getMoveDragBarState,
changeMoveDragBarState,
getPluginsByPosition
getPluginsByPosition,
getFinalLayoutConfig
}
}

View File

@ -0,0 +1,38 @@
import { META_APP } from '@opentiny/tiny-engine-meta-register'
export default {
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.Page,
META_APP.State
],
bottom: [META_APP.Schema, META_APP.Help, 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]
]
}
}

View File

@ -2,6 +2,5 @@ export default {
id: 'engine.plugins.blockmanage',
title: '区块管理',
type: 'plugins',
align: 'leftTop',
icon: 'plugin-icon-symbol'
}

View File

@ -108,6 +108,7 @@
</template>
<script lang="ts">
/* metaService: engine.plugins.blockmanage.BlockConfig */
import { reactive, ref, computed, nextTick, watchEffect } from 'vue'
import { Input, Tag, Button, Form, FormItem, Radio, Select, Option } from '@opentiny/vue'
import { constants } from '@opentiny/tiny-engine-utils'

View File

@ -21,6 +21,7 @@
</template>
<script lang="ts">
/* metaService: engine.plugins.blockmanage.BlockEvent */
import { computed, reactive } from 'vue'
import { Row as TinyRow, Col as TinyCol, Alert as TinyAlert } from '@opentiny/vue'
import BlockGuide from './BlockGuide.vue'

View File

@ -14,6 +14,7 @@
</template>
<script lang="ts">
/* metaService: engine.plugins.blockmanage.BlockEventForm */
import { computed, reactive, watch } from 'vue'
import { Input as TinyInput, Form as TinyForm, FormItem as TinyFormItem } from '@opentiny/vue'
import { REGEXP_EVENT_NAME, verifyEventName } from '@opentiny/tiny-engine-common/js/verification'

View File

@ -16,6 +16,7 @@
</template>
<script lang="ts">
/* metaService: engine.plugins.blockmanage.BlockEventList */
import { computed } from 'vue'
import { Button as TinyButton } from '@opentiny/vue'
import { MetaListItems, SvgButton } from '@opentiny/tiny-engine-common'

View File

@ -12,6 +12,7 @@
</template>
<script lang="ts">
/* metaService: engine.plugins.blockmanage.BlockGroupArrange */
import { reactive } from 'vue'
export default {

View File

@ -7,6 +7,7 @@
</template>
<script lang="ts">
/* metaService: engine.plugins.blockmanage.BlockGuide */
import { VideoGuide } from '@opentiny/tiny-engine-common'
export default {

View File

@ -17,6 +17,7 @@
</template>
<script lang="ts">
/* metaService: engine.plugins.blockmanage.BlockProperty */
import { computed } from 'vue'
import BlockGuide from './BlockGuide.vue'
import BlockPropertyList from './BlockPropertyList.vue'

View File

@ -113,6 +113,7 @@
</template>
<script lang="ts">
/* metaService: engine.plugins.blockmanage.BlockPropertyForm */
import { computed, ref, watch } from 'vue'
import {
Input as TinyInput,

View File

@ -19,6 +19,7 @@
</template>
<script lang="ts">
/* metaService: engine.plugins.blockmanage.BlockPropertyList */
import { computed } from 'vue'
import { Button as TinyButton } from '@opentiny/vue'
import { remove } from '@opentiny/vue-renderless/common/array'

View File

@ -85,6 +85,7 @@
</template>
<script lang="tsx">
/* metaService: engine.plugins.blockmanage.BlockSetting */
import { reactive, ref, watch, watchEffect, computed } from 'vue'
import { Button as TinyButton, Collapse as TinyCollapse, CollapseItem as TinyCollapseItem } from '@opentiny/vue'
import { useLayout, useModal, getMergeMeta, useBlock } from '@opentiny/tiny-engine-meta-register'

View File

@ -37,6 +37,7 @@
</template>
<script lang="ts" setup>
/* metaService: engine.plugins.blockmanage.CategoryEdit */
import { defineProps, defineEmits, ref, reactive, computed, watch } from 'vue'
import { pinyin } from 'pinyin-pro'
import {

View File

@ -123,6 +123,7 @@
</template>
<script lang="tsx">
/* metaService: engine.plugins.blockmanage.Main */
import { ref, reactive, computed, watch, provide } from 'vue'
import {
Search as TinySearch,

View File

@ -41,6 +41,7 @@
</template>
<script lang="ts">
/* metaService: engine.plugins.blockmanage.SaveNewBlock */
import { reactive, computed, ref } from 'vue'
import { Input, Form, FormItem, Button, DialogBox, Select } from '@opentiny/vue'
import { useBlock, useLayout, useCanvas, useModal, getMetaApi, META_SERVICE } from '@opentiny/tiny-engine-meta-register'

View File

@ -10,6 +10,7 @@
*
*/
/* metaService: engine.service.block.useBlock */
import { ref, reactive, readonly, type DeepReadonly, toRaw } from 'vue'
import { hyphenate } from '@vue/shared'
import { extend, copyArray } from '@opentiny/vue-renderless/common/object'

View File

@ -10,6 +10,7 @@
*
*/
/* metaService: engine.plugins.blockmanage.js-blockPropertyForm */
import { ref, computed, watch } from 'vue'
import { utils, constants } from '@opentiny/tiny-engine-utils'
import { useNotify } from '@opentiny/tiny-engine-meta-register'

View File

@ -10,6 +10,7 @@
*
*/
/* metaService: engine.plugins.blockmanage.js-blockSetting */
import { ref, reactive, readonly, onMounted } from 'vue'
import { extend } from '@opentiny/vue-renderless/common/object'
import { remove } from '@opentiny/vue-renderless/common/array'

View File

@ -10,6 +10,7 @@
*
*/
/* metaService: engine.plugins.blockmanage.js-http */
import { getMetaApi, META_SERVICE } from '@opentiny/tiny-engine-meta-register'
// 区块管理 -- 获取区块列表

View File

@ -2,6 +2,5 @@ export default {
id: 'engine.plugins.bridge',
title: '资源管理',
type: 'plugins',
align: 'leftTop',
icon: 'plugin-icon-sresources'
}

View File

@ -29,6 +29,7 @@
</template>
<script lang="ts">
/* metaService: engine.plugins.bridge.BridgeManage */
import { watchEffect, ref, reactive } from 'vue'
import { Search } from '@opentiny/vue'
import { iconSearch } from '@opentiny/vue-icon'

View File

@ -88,6 +88,7 @@
</template>
<script lang="ts">
/* metaService: engine.plugins.bridge.BridgeSetting */
import { computed, onMounted, reactive, ref, watchEffect, nextTick, watch } from 'vue'
import {
Input as TinyInput,

View File

@ -19,6 +19,7 @@
</template>
<script lang="ts">
/* metaService: engine.plugins.bridge.Main */
import { ref, reactive, computed, provide } from 'vue'
import { PluginPanel, SvgButton } from '@opentiny/tiny-engine-common'
import { useLayout } from '@opentiny/tiny-engine-meta-register'
@ -27,6 +28,7 @@ import BridgeManage from './BridgeManage.vue'
import BridgeSetting, { openPanel, closePanel } from './BridgeSetting.vue'
import { RESOURCE_TIP } from './js/resource'
/* metaComponent: engine.plugins.bridge */
export default {
components: {
PluginPanel,

View File

@ -9,7 +9,7 @@
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
/* metaService: engine.plugins.bridge.http */
import { getMetaApi, META_SERVICE } from '@opentiny/tiny-engine-meta-register'
import { generateBridge, generateUtil } from '@opentiny/tiny-engine-common/js/vscodeGenerateFile'

View File

@ -9,7 +9,7 @@
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
/* metaService: engine.plugins.bridge.js-resource */
import { reactive } from 'vue'
import { useResource, useNotify, getMetaApi, META_SERVICE } from '@opentiny/tiny-engine-meta-register'
import { isVsCodeEnv } from '@opentiny/tiny-engine-common/js/environments'

View File

@ -2,6 +2,5 @@ export default {
id: 'engine.plugins.collections',
title: '数据源',
type: 'plugins',
align: 'leftTop',
icon: 'plugin-icon-data'
}

View File

@ -32,6 +32,7 @@
</template>
<script lang="ts">
/* metaService: engine.plugins.collections.DataSourceField */
import { reactive, ref, watchEffect, nextTick } from 'vue'
import { Button } from '@opentiny/vue'
import DataSourceFieldList from './DataSourceFieldList.vue'

View File

@ -17,6 +17,7 @@
</template>
<script lang="ts">
/* metaService: engine.plugins.collections.DataSourceFieldCheck */
import { computed, inject } from 'vue'
import DataSourceFieldCheckMultipleLine from './DataSourceFieldCheckMultipleLine.vue'
import DataSourceFieldCheckRanger from './DataSourceFieldCheckRanger.vue'

View File

@ -10,6 +10,7 @@
</template>
<script lang="ts">
/* metaService: engine.plugins.collections.DataSourceFieldCheckMultipleLine */
import { inject } from 'vue'
import { Radio, FormItem } from '@opentiny/vue'
import { formDataInjectionSymbols } from './DataSourceFieldForm.vue'

View File

@ -23,6 +23,7 @@
</template>
<script lang="ts">
/* metaService: engine.plugins.collections.DataSourceFieldCheckRanger */
import { inject } from 'vue'
import { Numeric, FormItem } from '@opentiny/vue'
import { formDataInjectionSymbols } from './DataSourceFieldForm.vue'

View File

@ -34,6 +34,7 @@
</template>
<script lang="ts">
/* metaService: engine.plugins.collections.DataSourceFieldForm */
import { reactive, watchEffect, ref, provide, computed } from 'vue'
import { Button, Input, FormItem, Form } from '@opentiny/vue'
import { ButtonGroup, I18nInput } from '@opentiny/tiny-engine-common'

Some files were not shown because too many files have changed in this diff Show More