diff --git a/jsconfig.json b/jsconfig.json index 9011eef09..3f7722f09 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -7,6 +7,7 @@ "@opentiny/tiny-engine": ["packages/design-core/index.js"], "@opentiny/tiny-engine-meta-register": ["packages/register/src/index.js"], "@opentiny/tiny-engine-canvas": ["packages/canvas/src/index.js"], + "@opentiny/tiny-engine-renderer": ["packages/renderer/src/index.js"], "@opentiny/tiny-engine-plugin-materials": ["packages/plugins/materials/index"], "@opentiny/tiny-engine-plugin-state": ["packages/plugins/state/index"], "@opentiny/tiny-engine-plugin-script": ["packages/plugins/script/index"], diff --git a/mockServer/src/mock/get/app-center/v1/apps/schema/918.json b/mockServer/src/mock/get/app-center/v1/apps/schema/918.json index 17656258e..6a49846a8 100644 --- a/mockServer/src/mock/get/app-center/v1/apps/schema/918.json +++ b/mockServer/src/mock/get/app-center/v1/apps/schema/918.json @@ -2130,14 +2130,6 @@ "main": "" } }, - { - "name": "npm", - "type": "function", - "content": { - "type": "JSFunction", - "value": "''" - } - }, { "name": "Pager", "type": "npm", diff --git a/packages/build/vite-config/src/vite-plugins/devAliasPlugin.js b/packages/build/vite-config/src/vite-plugins/devAliasPlugin.js index 2f2cd3b58..9eb8e87b4 100644 --- a/packages/build/vite-config/src/vite-plugins/devAliasPlugin.js +++ b/packages/build/vite-config/src/vite-plugins/devAliasPlugin.js @@ -55,6 +55,7 @@ const getDevAlias = (useSourceAlias) => { '@opentiny/tiny-engine-svgs': path.resolve(basePath, 'packages/svgs/index.js'), '@opentiny/tiny-engine-canvas/render': path.resolve(basePath, 'packages/canvas/render/index.js'), '@opentiny/tiny-engine-canvas': path.resolve(basePath, 'packages/canvas/index.js'), + '@opentiny/tiny-engine-renderer': path.resolve(basePath, 'packages/renderer/src/index.js'), '@opentiny/tiny-engine-utils': path.resolve(basePath, 'packages/utils/src/index.js'), '@opentiny/tiny-engine-webcomponent-core': path.resolve(basePath, 'packages/webcomponent/src/lib.js'), '@opentiny/tiny-engine-i18n-host': path.resolve(basePath, 'packages/i18n/src/lib.js'), diff --git a/packages/canvas/container/src/container.js b/packages/canvas/container/src/container.js index d758c61a4..a46f0e83b 100644 --- a/packages/canvas/container/src/container.js +++ b/packages/canvas/container/src/container.js @@ -21,7 +21,7 @@ import { } from '../../common' import { useCanvas, useLayout, useResource, useTranslate, useMaterial } from '@opentiny/tiny-engine-meta-register' import { isVsCodeEnv } from '@opentiny/tiny-engine-common/js/environments' -import Builtin from '../../render/src/builtin/builtin.json' //TODO 画布内外应该分开 +import { BuiltinBundle } from '@opentiny/tiny-engine-renderer' export const POSITION = Object.freeze({ TOP: 'top', @@ -901,7 +901,7 @@ export const canvasApi = { setDesignMode, getDocument, canvasDispatch, - Builtin, + BuiltinBundle, setDataSourceMap: (...args) => { return canvasState.renderer.setDataSourceMap(...args) }, diff --git a/packages/canvas/package.json b/packages/canvas/package.json index bb80e1e1c..f1a74596f 100644 --- a/packages/canvas/package.json +++ b/packages/canvas/package.json @@ -36,12 +36,12 @@ "type": "module", "dependencies": { "@babel/core": "7.18.13", - "@opentiny/tiny-engine-builtin-component": "workspace:*", "@opentiny/tiny-engine-common": "workspace:*", "@opentiny/tiny-engine-i18n-host": "workspace:*", "@opentiny/tiny-engine-meta-register": "workspace:*", "@opentiny/tiny-engine-utils": "workspace:*", "@opentiny/tiny-engine-webcomponent-core": "workspace:*", + "@opentiny/tiny-engine-renderer": "workspace:*", "@vue/babel-plugin-jsx": "1.1.1", "@vue/shared": "^3.3.4", "@vueuse/core": "^9.6.0" diff --git a/packages/canvas/render/src/lowcode.js b/packages/canvas/render/src/lowcode.js index 27214c2e7..d533c8723 100644 --- a/packages/canvas/render/src/lowcode.js +++ b/packages/canvas/render/src/lowcode.js @@ -12,8 +12,9 @@ import { getCurrentInstance, nextTick, provide, inject } from 'vue' import { I18nInjectionKey } from 'vue-i18n' -import { api } from './RenderMain' -import { collectionMethodsMap, generateFn, globalNotify } from './render' +import { api } from '@opentiny/tiny-engine-renderer' + +const { getCollectionMethodsMap, generateFn, globalNotify, getUtils, getDataSourceMap } = api export const lowcodeWrap = (props, context) => { const global = {} @@ -49,10 +50,10 @@ export const lowcodeWrap = (props, context) => { location: { get: location }, history: { get: history }, utils: { - get: api.getUtils + get: getUtils }, bridge: { get: () => ({}) }, - dataSourceMap: { get: api.getDataSourceMap }, + dataSourceMap: { get: getDataSourceMap }, $: { get: () => ref } }) @@ -61,7 +62,7 @@ export const lowcodeWrap = (props, context) => { const fnName = fn.name if (fn.toString().includes('return this')) { return () => global - } else if (fnName && collectionMethodsMap[fnName.slice(0, -1)]) { + } else if (fnName && getCollectionMethodsMap()[fnName.slice(0, -1)]) { // 这里区块打包的时候会在方法名称后面多加一个字符串,所以此处需要截取下函数名称 fn.realName = fnName.slice(0, -1) return generateFn(fn) diff --git a/packages/canvas/render/src/runner.js b/packages/canvas/render/src/runner.js index deb1ce1b9..d088102d9 100644 --- a/packages/canvas/render/src/runner.js +++ b/packages/canvas/render/src/runner.js @@ -13,7 +13,7 @@ import { createApp } from 'vue' import { addScript, addStyle, dynamicImportComponents, updateDependencies } from '../../common' import TinyI18nHost, { I18nInjectionKey } from '@opentiny/tiny-engine-common/js/i18n' -import Main, { api } from './RenderMain' +import Main, { api } from '@opentiny/tiny-engine-renderer' import lowcode from './lowcode' import { supportUmdBlock } from './supportUmdBlock' diff --git a/packages/canvas/render/src/supportUmdBlock.js b/packages/canvas/render/src/supportUmdBlock.js index b07d50658..60c8a3dcf 100644 --- a/packages/canvas/render/src/supportUmdBlock.js +++ b/packages/canvas/render/src/supportUmdBlock.js @@ -5,7 +5,9 @@ import * as TinyVueIcon from '@opentiny/vue-icon' import TinyVue from '@opentiny/vue' import TinyI18nHost from '@opentiny/tiny-engine-common/js/i18n' import { camelize, capitalize } from '@vue/shared' -import { blockSlotDataMap, getComponent } from './render' +import { api } from '@opentiny/tiny-engine-renderer' + +const { getBlockSlotDataMap, getComponent } = api // 和 @opentiny/tiny-engine-block-build 打包umd方式相适配 export function supportUmdBlock() { @@ -33,6 +35,8 @@ export function supportUmdBlock() { // 如果是作用域插槽,则获取作用域插槽传递过来的参数 if (slotData) { + const blockSlotDataMap = getBlockSlotDataMap() + if (blockSlotDataMap[blockName]) { blockSlotDataMap[blockName][slotName] = slotData } else { diff --git a/packages/plugins/materials/src/composable/useMaterial.js b/packages/plugins/materials/src/composable/useMaterial.js index 91365bfea..8c3dd971e 100644 --- a/packages/plugins/materials/src/composable/useMaterial.js +++ b/packages/plugins/materials/src/composable/useMaterial.js @@ -404,9 +404,9 @@ const updateCanvasDependencies = (blocks) => { } const initBuiltinMaterial = () => { - const { Builtin } = useCanvas().canvasApi.value + const { BuiltinBundle } = useCanvas().canvasApi.value // 添加画布物料 - addMaterials(Builtin.data.materials) + addMaterials(BuiltinBundle.data.materials) // 添加builtin-component NPM包物料 addMaterials(BuiltinComponentMaterials) } diff --git a/packages/renderer/.eslintrc.cjs b/packages/renderer/.eslintrc.cjs new file mode 100644 index 000000000..7e042cff2 --- /dev/null +++ b/packages/renderer/.eslintrc.cjs @@ -0,0 +1,42 @@ +/** +* 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. +* +*/ + +module.exports = { + env: { + browser: true, + es2015: true, + node: true, + jest: true + }, + extends: ['eslint:recommended', 'plugin:vue/vue3-essential'], + parser: 'vue-eslint-parser', + parserOptions: { + parser: '@babel/eslint-parser', + ecmaVersion: 'latest', + sourceType: 'module', + requireConfigFile: false, + babelOptions: { + parserOpts: { + plugins: ['jsx'] + } + } + }, + plugins: ['vue'], + rules: { + 'no-console': 'error', + 'no-debugger': 'error', + 'space-before-function-paren': 'off', + 'vue/multi-word-component-names': 'off', + 'no-use-before-define': 'error', + 'no-unused-vars': ['error', { ignoreRestSiblings: true, varsIgnorePattern: '^_', argsIgnorePattern: '^_' }] + } +} diff --git a/packages/renderer/.gitignore b/packages/renderer/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/packages/renderer/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/packages/renderer/README.md b/packages/renderer/README.md new file mode 100644 index 000000000..9a22e301c --- /dev/null +++ b/packages/renderer/README.md @@ -0,0 +1,45 @@ +# tiny-engine-renderer + +## Introduction + +A Vue3 renderer for tiny-engine. + +## Usage + +```javascript +// xxx.vue +import { h } from 'vue' +import Main, { api } from '@opentiny/tiny-engine-renderer' + +const getSchema = () => { + return newPromise((resolve) => { + setTimeout(() => { + const data = { + state: {}, + children: [ + { + componentName: 'Text', + props: { + text: 'Title' + } + } + ] + } + resolve(data) + }, 100) + }) +} + +export default { + setup() { + onMounted(async () => { + const schema = await getSchema() + + api.setSchema(schema) + }) + }, + render() { + return h(Main) + } +} +``` diff --git a/packages/renderer/README.zh-CN.md b/packages/renderer/README.zh-CN.md new file mode 100644 index 000000000..360ae0378 --- /dev/null +++ b/packages/renderer/README.zh-CN.md @@ -0,0 +1,45 @@ +# tiny-engine-renderer + +## 简介 + +基于 Vue 3 的低代码渲染引擎。 + +## 用法 + +```javascript +// xxx.vue +import { h } from 'vue' +import Main, { api } from '@opentiny/tiny-engine-renderer' + +const getSchema = () => { + return newPromise((resolve) => { + setTimeout(() => { + const data = { + state: {}, + children: [ + { + componentName: 'Text', + props: { + text: '标题' + } + } + ] + } + resolve(data) + }, 100) + }) +} + +export default { + setup() { + onMounted(async () => { + const schema = await getSchema() + + api.setSchema(schema) + }) + }, + render() { + return h(Main) + } +} +``` diff --git a/packages/renderer/package.json b/packages/renderer/package.json new file mode 100644 index 000000000..8ee891fec --- /dev/null +++ b/packages/renderer/package.json @@ -0,0 +1,58 @@ +{ + "name": "@opentiny/tiny-engine-renderer", + "version": "2.0.0-alpha.4", + "publishConfig": { + "access": "public" + }, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "main": "dist/index.js", + "module": "dist/index.js", + "files": [ + "dist" + ], + "exports": { + ".": { + "import": "./dist/index.js" + } + }, + "repository": { + "type": "git", + "url": "https://github.com/opentiny/tiny-engine", + "directory": "packages/renderer" + }, + "bugs": { + "url": "https://github.com/opentiny/tiny-engine/issues" + }, + "author": "OpenTiny Team", + "license": "MIT", + "homepage": "https://opentiny.design/tiny-engine", + "type": "module", + "dependencies": { + "@babel/core": "7.18.13", + "@opentiny/tiny-engine-builtin-component": "workspace:*", + "@opentiny/tiny-engine-i18n-host": "workspace:*", + "@opentiny/tiny-engine-meta-register": "workspace:*", + "@opentiny/tiny-engine-utils": "workspace:*", + "@vue/babel-plugin-jsx": "1.1.1", + "@vue/shared": "^3.3.4", + "@vueuse/core": "^9.6.0" + }, + "devDependencies": { + "@opentiny/tiny-engine-vite-plugin-meta-comments": "workspace:*", + "@vitejs/plugin-vue": "^5.1.2", + "@vitejs/plugin-vue-jsx": "^4.0.1", + "rollup-plugin-polyfill-node": "^0.13.0", + "vite": "^5.4.2" + }, + "peerDependencies": { + "@opentiny/vue": "^3.14.0", + "@opentiny/vue-icon": "^3.14.0", + "@opentiny/vue-renderless": "^3.14.0", + "vue": "^3.4.15", + "vue-i18n": "^9.9.0" + } +} diff --git a/packages/renderer/src/CanvasEmpty.vue b/packages/renderer/src/CanvasEmpty.vue new file mode 100644 index 000000000..c6dc2c454 --- /dev/null +++ b/packages/renderer/src/CanvasEmpty.vue @@ -0,0 +1,14 @@ + + + diff --git a/packages/canvas/render/src/RenderMain.js b/packages/renderer/src/RenderMain.js similarity index 89% rename from packages/canvas/render/src/RenderMain.js rename to packages/renderer/src/RenderMain.js index bb41b5fc7..e238ffb0d 100644 --- a/packages/canvas/render/src/RenderMain.js +++ b/packages/renderer/src/RenderMain.js @@ -16,7 +16,17 @@ import TinyVue from '@opentiny/vue' import * as TinyVueIcon from '@opentiny/vue-icon' import { useBroadcastChannel } from '@vueuse/core' import { constants, utils as commonUtils } from '@opentiny/tiny-engine-utils' -import renderer, { parseData, setConfigure, setController, globalNotify, isStateAccessor } from './render' +import renderer, { + parseData, + setConfigure, + setController, + globalNotify, + isStateAccessor, + generateFn, + getCollectionMethodsMap, + getBlockSlotDataMap, + getComponent +} from './render' import { getNode as getNodeById, clearNodes, @@ -43,7 +53,7 @@ const reset = (obj) => { const refreshKey = ref(0) const methods = {} const schema = reactive({}) -const state = shallowReactive({}) +const state = reactive({}) const bridge = {} const utils = {} const props = {} @@ -67,13 +77,27 @@ watchEffect(() => { }) }) +const dynamicImport = async ({ name, content }) => { + const { cdnLink, destructuring, exportName } = content + + if (!cdnLink) return + + const module = await import(/* @vite-ignore */ cdnLink) + + return { + name, + module: destructuring ? module[exportName] : module + } +} + const getUtils = () => utils -const setUtils = (data, clear, isForceRefresh) => { +const setUtils = async (data, clear, isForceRefresh) => { if (clear) { reset(utils) } const utilsCollection = {} + const npmUtilPromises = [] // 目前画布还不具备远程加载utils工具类的功能,目前只能加载TinyVue组件库中的组件工具 data?.forEach((item) => { const util = TinyVue[item.content.exportName] @@ -92,7 +116,18 @@ const setUtils = (data, clear, isForceRefresh) => { const defaultFn = () => {} utilsCollection[item.name] = generateFunction(item.content.value, context) || defaultFn } + + if (item.type === 'npm' && item.content.cdnLink) { + npmUtilPromises.push(dynamicImport(item)) + } + }) + + const npmUtils = await Promise.all(npmUtilPromises) + + npmUtils.forEach(({ name, module }) => { + utilsCollection[name] = module }) + Object.assign(utils, utilsCollection) // 因为工具类并不具有响应式行为,所以需要通过修改key来强制刷新画布 @@ -358,6 +393,7 @@ const setSchema = async (data) => { const getNode = (id, parent) => (id ? getNodeById(id, parent) : schema) +// 设置自定义渲染器 let canvasRenderer = null const defaultRenderer = function () { @@ -441,6 +477,7 @@ export const api = { getProps, setProps, getContext, + setContext, getNode, getRoot, setPagecss, @@ -454,6 +491,34 @@ export const api = { setNode, getRenderer, setRenderer, + globalNotify, + generateFn, + getCollectionMethodsMap, + getBlockSlotDataMap, + getComponent, getDesignMode, setDesignMode } + +/** + * schema和元数据组装context + * @param {*} schema + * @param {*} metaData + */ +export const generateContext = async (schema, metaData) => { + const { globalState, utils, dataSource } = metaData + + if (Array.isArray(globalState)) { + setGlobalState(globalState) + } + + if (Array.isArray(utils)) { + await setUtils(utils) + } + + if (Array.isArray(dataSource.list)) { + await setDataSourceMap(dataSource.list) + } + + await setSchema(schema) +} diff --git a/packages/canvas/render/src/builtin/CanvasBox.vue b/packages/renderer/src/builtin/CanvasBox.vue similarity index 100% rename from packages/canvas/render/src/builtin/CanvasBox.vue rename to packages/renderer/src/builtin/CanvasBox.vue diff --git a/packages/canvas/render/src/builtin/CanvasCollection.js b/packages/renderer/src/builtin/CanvasCollection.js similarity index 97% rename from packages/canvas/render/src/builtin/CanvasCollection.js rename to packages/renderer/src/builtin/CanvasCollection.js index b5cb22762..75502aa16 100644 --- a/packages/canvas/render/src/builtin/CanvasCollection.js +++ b/packages/renderer/src/builtin/CanvasCollection.js @@ -10,7 +10,6 @@ * */ -import { getController } from '../render' import { api } from '../RenderMain' import { useModal } from '@opentiny/tiny-engine-meta-register' @@ -45,7 +44,7 @@ const genRemoteMethodToLifeSetup = (variableName, sourceRef, pageSchema) => { const removeState = (pageSchema, variableName) => { delete pageSchema.state[variableName] - const { parse, traverse, generate } = getController().ast + const { parse, traverse, generate } = api.getController().ast const setupFn = pageSchema.lifeCycles?.setup?.value try { @@ -76,7 +75,7 @@ const defaultHandlerTemplate = ({ node, sourceRef, schemaId, pageSchema }) => { const genVarName = (schemaId) => `${NAME_PREFIX.loop}${schemaId}` const updateNode = () => { - const { configure } = getController().getMaterial(node?.componentName) + const { configure } = api.getController().getMaterial(node?.componentName) if (!configure?.loop) { return diff --git a/packages/canvas/render/src/builtin/CanvasCollection.vue b/packages/renderer/src/builtin/CanvasCollection.vue similarity index 94% rename from packages/canvas/render/src/builtin/CanvasCollection.vue rename to packages/renderer/src/builtin/CanvasCollection.vue index d033924f3..1242a879f 100644 --- a/packages/canvas/render/src/builtin/CanvasCollection.vue +++ b/packages/renderer/src/builtin/CanvasCollection.vue @@ -8,13 +8,13 @@