diff --git a/package.json b/package.json index 8586040..077ce9c 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,8 @@ "vite": "^4.3.7", "vite-plugin-monaco-editor": "^1.0.10", "vite-plugin-svg-icons": "^2.0.1", - "vue-eslint-parser": "^8.0.1" + "vue-eslint-parser": "^8.0.1", + "vue-i18n": "^9.9.0" }, "browserslist": [ "> 1%", diff --git a/packages/build/vite-plugin-generate-comments/index.js b/packages/build/vite-plugin-generate-comments/index.js new file mode 100644 index 0000000..3bb5233 --- /dev/null +++ b/packages/build/vite-plugin-generate-comments/index.js @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2024 - present TinyEngine Authors. + * Copyright (c) 2024 - 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 { transform } from './src/transform.js' +import { transformSFC } from './src/transform-sfc.js' + +export default function () { + return { + name: 'vite-plugin-generate-comments', + enforce: 'pre', + transform(code, id) { + if (id.endsWith('.vue')) { + const result = transformSFC(code, id) + return result + } + + if (id.endsWith('.js') || id.endsWith('.jsx') || id.endsWith('.ts')) { + const result = transform(code, id) + return result + } + } + } +} diff --git a/packages/build/vite-plugin-generate-comments/package.json b/packages/build/vite-plugin-generate-comments/package.json new file mode 100644 index 0000000..a9ff63f --- /dev/null +++ b/packages/build/vite-plugin-generate-comments/package.json @@ -0,0 +1,23 @@ +{ + "name": "@opentiny/vite-plugin-generate-comments", + "version": "1.0.0", + "description": "", + "main": "index.js", + "module": "index.js", + "type": "module", + "scripts": { + "test": "node ./src/test/index.js" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.4", + "vite": "^5.2.7", + "@babel/parser": "^7.18.13", + "@babel/traverse": "^7.18.13", + "@babel/generator": "^7.18.13", + "@babel/template": "^7.18.13", + "@vue/compiler-sfc": "^3.4.21" + }, + "keywords": [], + "author": "", + "license": "ISC" +} \ No newline at end of file diff --git a/packages/build/vite-plugin-generate-comments/src/test/code/entry.js b/packages/build/vite-plugin-generate-comments/src/test/code/entry.js new file mode 100644 index 0000000..33c83d1 --- /dev/null +++ b/packages/build/vite-plugin-generate-comments/src/test/code/entry.js @@ -0,0 +1,57 @@ +/* metaService */ +/** + * Copyright (c) 2024 - present TinyEngine Authors. + * Copyright (c) 2024 - 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 'loash-es' +export const useRenderless = ({ props }) => { + const state = reactive({ + tableData: props.data || props.op.data || [] + }) + + onMounted(() => {}) + onMounted(() => {}) + onMounted(() => {}) + + beforeMount(() => {}) + + const logMessage = () => { + console.log('我是纯函数我不需要闭包参数') + } + + const aaa = 'aaa', + bbb = 'bbb' + + const handleClick = (e) => { + console.log(e.target, aaa) + state.tableData.push({ + key: 'TinyEngine', + zhCN: '低代码引擎', + enUS: 'TinyEngine' + }) + } + + const ccc = 111 + + const sendMessage = () => { + logMessage('自定义是的范德萨') + } + + function last() {} + + return { + state, + aa, + handleClick, + sendMessage + } +} diff --git a/packages/build/vite-plugin-generate-comments/src/test/code/output.js b/packages/build/vite-plugin-generate-comments/src/test/code/output.js new file mode 100644 index 0000000..eee7ebb --- /dev/null +++ b/packages/build/vite-plugin-generate-comments/src/test/code/output.js @@ -0,0 +1,575 @@ +import { callEntry as _callEntry, beforeCallEntry as _beforeCallEntry, afterCallEntry as _afterCallEntry, useCompile as _useCompile } from '@opentiny/tiny-engine-entry'; +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; + } +}); \ No newline at end of file diff --git a/packages/build/vite-plugin-generate-comments/src/test/index.js b/packages/build/vite-plugin-generate-comments/src/test/index.js new file mode 100644 index 0000000..981bc5d --- /dev/null +++ b/packages/build/vite-plugin-generate-comments/src/test/index.js @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2024 - present TinyEngine Authors. + * Copyright (c) 2024 - 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 'fs' +import { transform } from '../transform.js' +import { fileURLToPath } from 'node:url' +import * as path from 'path' + +const __filename = fileURLToPath(import.meta.url) + +const __dirname = path.dirname(__filename) + +const code = fs.readFileSync(path.join(__dirname, './code/entry.js'), 'utf8') + +const id = path.resolve(__dirname, './code/entry.js') + +fs.writeFileSync(path.join(__dirname, './code/output.js'), transform(code, id) || '', 'utf8') diff --git a/packages/plugins/i18n/mock/test.js b/packages/build/vite-plugin-generate-comments/src/test/meta.js similarity index 100% rename from packages/plugins/i18n/mock/test.js rename to packages/build/vite-plugin-generate-comments/src/test/meta.js diff --git a/packages/build/vite-plugin-generate-comments/src/transform-sfc.js b/packages/build/vite-plugin-generate-comments/src/transform-sfc.js new file mode 100644 index 0000000..bbb2568 --- /dev/null +++ b/packages/build/vite-plugin-generate-comments/src/transform-sfc.js @@ -0,0 +1,62 @@ +/** + * Copyright (c) 2024 - present TinyEngine Authors. + * Copyright (c) 2024 - 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 { parse } from '@vue/compiler-sfc' //vue 处理sfc 的专用库 +import { transform } from './transform.js' +import { isCallEntryFile } from './utils.js' + +const getAttrsString = (attrs) => { + let attrStr = '' + if (!attrs) { + return attrStr + } + + Object.keys(attrs).forEach((key) => { + const val = attrs[key] + if (val === true) { + attrStr += ` ${key}` + } else { + attrStr += ` ${key}="${val}"` + } + }) + return attrStr +} +const generateTagContent = (tagDescriptor) => { + if (!tagDescriptor) { + return '' + } + const { attrs, content, type } = tagDescriptor + return `<${type}${getAttrsString(attrs)}>${content}` +} + +export const generateSFC = (descriptor) => { + const { script, scriptSetup, styles = [], template } = descriptor + return `${generateTagContent(template)} +${generateTagContent(script)} +${generateTagContent(scriptSetup)} +${styles.map(generateTagContent).join('\n')} +` +} + +export const transformSFC = (code, id) => { + const { descriptor } = parse(code) + const { script, scriptSetup } = descriptor + if (!isCallEntryFile(code) || (!script && !scriptSetup)) { + return + } + if (script) { + script.content = transform(script.content, id) || script.content + } + if (scriptSetup) { + scriptSetup.content = transform(scriptSetup.content, id) || scriptSetup.content + } + return generateSFC(descriptor) +} diff --git a/packages/build/vite-plugin-generate-comments/src/transform.js b/packages/build/vite-plugin-generate-comments/src/transform.js new file mode 100644 index 0000000..570a3a1 --- /dev/null +++ b/packages/build/vite-plugin-generate-comments/src/transform.js @@ -0,0 +1,179 @@ +/** + * Copyright (c) 2024 - present TinyEngine Authors. + * Copyright (c) 2024 - 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 { parse } from '@babel/parser' +import generate from '@babel/generator' +import traverse from '@babel/traverse' +import template from '@babel/template' +import { + wrapEntryFuncNode, + COMMON_PACKAGE_NAME, + CALLENTRY, + BEFORE_CALLENTRY, + AFTER_CALLENTRY, + USE_COMPILE, + METADATANAME, + isCallEntryFile, + isCompileFile, + getMeataPath, + wrapExportComp, + vueLifeHook, + wrapHookCall, + getModuleId +} from './utils.js' + +const generateTraverse = traverse.default + +export const transform = (code, id) => { + // 如果不包含metaService或者metaComponent的文件直接退出 + const isCallEntry = isCallEntryFile(code) + const isCompile = isCompileFile(code) + if (!isCallEntry && !isCompile) { + return + } + + // 本次转换保存的状态 + const state = { + varName: {}, // 变量名对应的映射表 + hooksName: {}, + hooksIndex: {}, + varDeclartion: new Map(), + moduleId: '', // 自定义的模块ID,用于区分元服务中不同文件, + noUseVars: [] + } + + // 找不到meta.js告警并返回 + const metaPath = getMeataPath(id) + if (!metaPath) { + console.log('找不到对应的meta.js') + return + } + + // 将源码解析为ast语法数 + const resultAst = parse(code, { + sourceType: 'module', + plugins: ['typescript', 'jsx'] + }) + + generateTraverse(resultAst, { + // 使用特定的类型回调处理、函数表达式、箭头函数、带导出的函数 + 'ArrowFunctionExpression|FunctionExpression'(path) { + const parentNode = path.parentPath || {} + const functionName = parentNode.node?.id?.name + + // 只有拿到函数的名称才可以被复写 + if (functionName) { + wrapEntryFuncNode({ + path, + functionName, + varName: state.varName, + state + }) + } + }, + ImportDeclaration(path) { + // 解析vue的引入 + const depName = path.node?.source?.value + if (depName === 'vue') { + const specifiers = path.node.specifiers + specifiers?.forEach((importSpecifier) => { + const { imported, local } = importSpecifier + const hookName = vueLifeHook.find((name) => imported.name === name) + if (hookName) { + state.hooksName[local.name] = hookName + } + state.noUseVars.push(local.name) + }) + } else if (depName === '@opentiny/vue') { + const specifiers = path.node.specifiers + specifiers?.forEach((importSpecifier) => { + const { local } = importSpecifier + state.noUseVars.push(local.name) + }) + } + }, + VariableDeclaration(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) + } + }) + }, + ExpressionStatement(path) { + const { hooksName, varName, hooksIndex } = state + const callName = path.node.expression?.callee?.name + const hookName = hooksName[callName] + if (hookName) { + let hookIndex + if (hooksIndex[hookName]) { + hookIndex = hooksIndex[hookName] + hooksIndex[hookName] = hookIndex + 1 + } else { + hooksIndex[hookName] = 1 + hookIndex = 0 + } + const functionName = `${hookName}[${hookIndex}]` + wrapHookCall({ + path, + varName, + hooksName, + functionName, + callName, + state + }) + } + }, + Program(path) { + const code = path.toString() + state.moduleId = getModuleId(code) + const metaData = path.scope.generateUid(METADATANAME) + state.varName[METADATANAME] = metaData + 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}'` + )() + ) + }, + ExportDefaultDeclaration(path) { + const comment = path.node.leadingComments + const lastComment = comment && comment[comment.length - 1].value + // 只判断最接近export default的注释节点 + if (comment && lastComment.includes('metaComponent')) { + wrapExportComp({ path, varName: state.varName }) + path.skip() + } + } + }) + + return generate.default(resultAst).code || '' +} diff --git a/packages/build/vite-plugin-generate-comments/src/utils.js b/packages/build/vite-plugin-generate-comments/src/utils.js new file mode 100644 index 0000000..89794c7 --- /dev/null +++ b/packages/build/vite-plugin-generate-comments/src/utils.js @@ -0,0 +1,246 @@ +/** + * Copyright (c) 2024 - present TinyEngine Authors. + * Copyright (c) 2024 - 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 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-entry' +export const vueLifeHook = [ + 'onMounted', + 'onUpdated', + 'onUnmounted', + 'onBeforeMount', + 'onBeforeUpdate', + 'onBeforeUnmount', + 'onActivated', + 'onDeactivated' +] +const callEntryExp = /\/\*\s*metaService/ +const compileExp = /\/\*\s*metaComponent/ + +export const isCallEntryFile = (code) => { + return callEntryExp.test(code) +} + +export const isCompileFile = (code) => { + return compileExp.test(code) +} + +export const getModuleId = (str) => { + const [, moduleId = ''] = str.match(/\/\*\s*metaService: \s*(.+?)\s*\*\//) || [] + return moduleId +} + +// 将注释中的参数提取出来,并组合成目前参数格式 +export const getEntryParam = ({ functionName = '', syncVars, 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 } + }` + if (functionName) { + return `{ ${METADATANAME}: { id: ${id} }, ctx: ${ctx}}` + } + + 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) => { + const outerBindings = {} + const allBindings = path.scope.getAllBindings() + const selfBindings = path.scope.bindings + Object.keys(allBindings).forEach((key) => { + if (allBindings[key] && !selfBindings[key]) { + outerBindings[key] = allBindings[key] + } + }) + 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 +} + +export const getModuleBindings = (path) => { + const moduleBindings = {} + const allBindings = path.scope.getAllBindings() + Object.keys(allBindings).forEach((key) => { + if (allBindings[key].kind === 'module') { + moduleBindings[key] = allBindings[key] + } + }) + return moduleBindings +} + +// 生成callEntry表达式并包裹当前函数,如果有参与还需要处理参数 +export const wrapEntryFuncNode = ({ path, functionName = '', varName, state }) => { + const syncVars = getValidBingdinngs({ path, state, functionName }) + const asyncVars = getOuterBingdings(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 = template.statement(`${callEntry}(${entryParam})`)() + const beforeEntryAst = template.statement(`${beforeCallEntry}(${entryParam})`)() + const afterEntryAst = template.statement(`${afterCallEntry}(${entryParam})`)() + + const resultNode = path.node + generateBeforeAfterEntry({ path, beforeEntryAst, afterEntryAst }) + + entryAst.expression.arguments.unshift(JSON.parse(JSON.stringify(resultNode))) + // 替换整个节点 + path.replaceWith(entryAst) +} + +// 获取两个文件路径的相对路径,入参为两个文件绝对路径 +export const getRelFilePath = (path1, path2) => { + const dir1 = path.join(path1, '..') + const dir2 = path.join(path2, '..') + const relPath = path.relative(dir1, dir2) || '.' + return `${relPath}/${path.basename(path2)}`.replaceAll('\\', '/') +} + +// 向上获取meta.js的相对路径 +export const getMeataPath = (id) => { + let tempPath = path.join(id, '../meta.js') + + const endCondition = () => { + // 找到了meta.js + const findMeta = fs.existsSync(tempPath) + // 发现了package.json说明到达子包根目录 + const isSubRoot = fs.existsSync(path.join(tempPath, '../package.json')) + // 到达系统根节点,防止死循环 + const isRoot = tempPath === path.join(tempPath, '../../meta.js') + return findMeta || isSubRoot || isRoot + } + + while (!endCondition()) { + tempPath = path.join(tempPath, '../../meta.js') + } + if (fs.existsSync(tempPath)) { + return getRelFilePath(id, tempPath) + } + return null +} + +export const wrapExportComp = ({ path, varName }) => { + const properties = path.node.declaration?.properties || [] + const metaData = varName[METADATANAME] + const useCompile = varName[USE_COMPILE] + + // 对键值为component属性包一层useCompile + properties.forEach((prop) => { + if (prop.key?.name === 'component') { + const val = prop.value + const compileAst = template.statement(`${useCompile}({ component: null, ${METADATANAME}: ${metaData} });`)() + compileAst.expression.arguments[0].properties[0].value = val + path.traverse({ + enter(subPath) { + if (subPath.node === val) { + subPath.replaceWith(compileAst) + subPath.skip() + } + } + }) + } + }) +} + +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 + }) + const wrapAst = template.statement(`${callName}(${callEntry}(${entryParam}))`)() + wrapAst.expression.arguments[0].arguments.unshift(argument) + path.replaceWith(wrapAst) + path.skip() +} diff --git a/packages/design-core/index.js b/packages/design-core/index.js new file mode 100644 index 0000000..8e6f714 --- /dev/null +++ b/packages/design-core/index.js @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2024 - present TinyEngine Authors. + * Copyright (c) 2024 - 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 { defineEntry } from '@opentiny/tiny-engine-entry' +import { createApp } from 'vue' +import EngineApp from './src/index.js' +import defaultResigry from './registry.js' +import { merge } from 'lodash-es' +import initSvgs from '@opentiny/tiny-engine-svgs' +import { setGlobalConfig } from '@opentiny/tiny-engine-controller' +import i18n from '@opentiny/tiny-engine-controller/js/i18n' +import globalConfig from './config/lowcode.config' +import { initMonitor } from '@opentiny/tiny-engine-controller/js/monitor' +import { injectGlobalComponents } from '@opentiny/tiny-engine-common' +import { initHttp } from '@opentiny/tiny-engine-http' +import 'virtual:svg-icons-register' + +import TinyThemeTool from '@opentiny/vue-theme/theme-tool' +import { tinySmbTheme } from '@opentiny/vue-theme/theme' // SMB 主题 + +const type = (obj) => { + return Object.prototype.toString.call(obj).match(/\[object (.*)\]/)[1] +} + +const mergeRegistry = (registry) => { + Object.entries(registry).forEach(([key, value]) => { + const defaultConfig = defaultResigry[key] + if (Array.isArray(value) && defaultConfig) { + value.forEach((meta, index) => { + const defaultMeta = defaultConfig.find((item) => item.id === meta.id) + if (defaultMeta) { + value[index] = merge(defaultMeta, meta) + } + }) + } + + if (type(value) === 'Object' && defaultConfig) { + registry[key] = merge(defaultConfig, registry[key]) + } + }) + + return registry +} + +const init = (selector, registry) => { + // 合并用户自定义注册表 + const newRegistry = mergeRegistry(registry) + + // 在common层注入合并后的注册表 + defineEntry(newRegistry) + const app = createApp(EngineApp) + initHttp({ env: import.meta.env }) + + // eslint-disable-next-line no-new + new TinyThemeTool(tinySmbTheme, 'smbtheme') // 初始化主题 + + if (import.meta.env.VITE_ERROR_MONITOR === 'true' && import.meta.env.VITE_ERROR_MONITOR_URL) { + initMonitor(import.meta.env.VITE_ERROR_MONITOR_URL) + } + + window.TinyGlobalConfig = globalConfig + setGlobalConfig(globalConfig) + + initSvgs(app) + window.lowcodeI18n = i18n + app.use(i18n).use(injectGlobalComponents).mount('#app') + app.mount(selector) +} +export { init } diff --git a/packages/design-core/package.json b/packages/design-core/package.json index 0c6a123..3677998 100644 --- a/packages/design-core/package.json +++ b/packages/design-core/package.json @@ -1,6 +1,7 @@ { "name": "@opentiny/tiny-engine", "version": "1.0.0-beta.4", + "type": "module", "description": "TinyEngine enables developers to customize low-code platforms, build low-bit platforms online in real time, and support secondary development or integration of low-bit platform capabilities.", "homepage": "https://opentiny.design/tiny-engine", "keywords": [ @@ -11,6 +12,14 @@ "lowcode", "tiny-engine" ], + "module": "index.js", + "main": "index.js", + "exports": { + ".": "index.js", + "./vite.config.js": "./vite.config.js", + "./config/lowcode.config": "./config/lowcode.config.js", + "./scripts/externalDeps": "./scripts/externalDeps.js" + }, "scripts": { "dev": "cross-env NODE_OPTIONS=--max-old-space-size=10240 VITE_API_MOCK=mock vite", "serve": "cross-env NODE_OPTIONS=--max-old-space-size=10240 vite", @@ -34,6 +43,8 @@ "@babel/generator": "~7.23.2", "@babel/parser": "~7.23.2", "@babel/traverse": "~7.23.2", + "@opentiny/vite-plugin-generate-comments": "workspace:*", + "@opentiny/tiny-engine-entry": "workspace:*", "@opentiny/tiny-engine-canvas": "workspace:*", "@opentiny/tiny-engine-common": "workspace:*", "@opentiny/tiny-engine-controller": "workspace:*", @@ -126,7 +137,8 @@ "vite-plugin-monaco-editor": "^1.1.0", "vite-plugin-static-copy": "^0.16.0", "vite-plugin-svg-icons": "^2.0.1", - "vue-eslint-parser": "^8.0.1" + "vue-eslint-parser": "^8.0.1", + "lodash-es": "^4.17.21" }, "browserslist": [ "> 1%", diff --git a/packages/design-core/registry.js b/packages/design-core/registry.js new file mode 100644 index 0000000..219353e --- /dev/null +++ b/packages/design-core/registry.js @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2024 - present TinyEngine Authors. + * Copyright (c) 2024 - 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 i18nMeta from '@opentiny/tiny-engine-plugin-i18n' + +export default { + plugins: [i18nMeta] +} diff --git a/packages/design-core/src/index.js b/packages/design-core/src/index.js new file mode 100644 index 0000000..1a78372 --- /dev/null +++ b/packages/design-core/src/index.js @@ -0,0 +1,15 @@ +/** + * Copyright (c) 2024 - present TinyEngine Authors. + * Copyright (c) 2024 - 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 component from './App.vue' + +export default component diff --git a/packages/design-core/vite.config.js b/packages/design-core/vite.config.js index 3d292c8..e2dc194 100644 --- a/packages/design-core/vite.config.js +++ b/packages/design-core/vite.config.js @@ -2,18 +2,29 @@ import { defineConfig, loadEnv } from 'vite' import path from 'path' import vue from '@vitejs/plugin-vue' -import monacoEditorPlugin from 'vite-plugin-monaco-editor' +import monacoEditorPluginCjs from 'vite-plugin-monaco-editor' import vueJsx from '@vitejs/plugin-vue-jsx' -import nodeGlobalsPolyfillPlugin from '@esbuild-plugins/node-globals-polyfill' -import nodeModulesPolyfillPlugin from '@esbuild-plugins/node-modules-polyfill' +import nodeGlobalsPolyfillPluginCjs from '@esbuild-plugins/node-globals-polyfill' +import nodeModulesPolyfillPluginCjs from '@esbuild-plugins/node-modules-polyfill' import nodePolyfill from 'rollup-plugin-polyfill-node' import esbuildCopy from 'esbuild-plugin-copy' -import lowcodeConfig from './config/lowcode.config' +import lowcodeConfig from './config/lowcode.config.js' import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' -import { importmapPlugin } from './scripts/externalDeps' -import visualizer from 'rollup-plugin-visualizer' +import { importmapPlugin } from './scripts/externalDeps.js' +import visualizerCjs from 'rollup-plugin-visualizer' +import { fileURLToPath } from 'node:url' +import generateComment from '@opentiny/vite-plugin-generate-comments' import { getBaseUrlFromCli, copyBundleDeps, copyPreviewImportMap, copyLocalImportMap } from './scripts/localCdnFile' +const monacoEditorPlugin = monacoEditorPluginCjs.default +const nodeGlobalsPolyfillPlugin = nodeGlobalsPolyfillPluginCjs.default +const nodeModulesPolyfillPlugin = nodeModulesPolyfillPluginCjs.default +const visualizer = visualizerCjs.default + +const __filename = fileURLToPath(import.meta.url) + +const __dirname = path.dirname(__filename) + const origin = 'http://localhost:9090/' const config = { @@ -52,6 +63,7 @@ const config = { open: false }, plugins: [ + generateComment(), visualizer({ filename: 'tmp/report.html', title: 'Bundle Analyzer' @@ -183,7 +195,8 @@ const devAlias = { '@opentiny/tiny-engine-utils': path.resolve(__dirname, '../utils/src/index.js'), '@opentiny/tiny-engine-webcomponent-core': path.resolve(__dirname, '../webcomponent/src/lib.js'), '@opentiny/tiny-engine-i18n-host': path.resolve(__dirname, '../i18n/src/lib.js'), - '@opentiny/tiny-engine-builtin-component': path.resolve(__dirname, '../builtinComponent/index.js') + '@opentiny/tiny-engine-builtin-component': path.resolve(__dirname, '../builtinComponent/index.js'), + '@opentiny/tiny-engine-entry': path.resolve(__dirname, '../entry/index.js') } const prodAlias = { @@ -197,8 +210,12 @@ const commonAlias = { '@opentiny/tiny-engine-app-addons': path.resolve(__dirname, './config/addons.js') } -export default defineConfig(({ command, mode }) => { - const { VITE_CDN_DOMAIN, VITE_LOCAL_IMPORT_MAPS, VITE_LOCAL_BUNDLE_DEPS } = loadEnv(mode, process.cwd(), '') +export default defineConfig(({ command = 'serve', mode = 'serve' }) => { + const { + VITE_CDN_DOMAIN = 'https://npm.onmicrosoft.cn', + VITE_LOCAL_IMPORT_MAPS, + VITE_LOCAL_BUNDLE_DEPS + } = loadEnv(mode, process.cwd(), '') const isLocalImportMap = VITE_LOCAL_IMPORT_MAPS === 'true' // true公共依赖库使用本地打包文件,false公共依赖库使用公共CDN const isCopyBundleDeps = VITE_LOCAL_BUNDLE_DEPS === 'true' // true bundle里的cdn依赖处理成本地依赖, false 不处理 diff --git a/packages/engine-cli/bin/cli.js b/packages/engine-cli/bin/cli.js new file mode 100644 index 0000000..0a70e5b --- /dev/null +++ b/packages/engine-cli/bin/cli.js @@ -0,0 +1,2 @@ +#!/usr/bin/env node +import '../src/index.js' diff --git a/packages/engine-cli/package.json b/packages/engine-cli/package.json new file mode 100644 index 0000000..70cbbfa --- /dev/null +++ b/packages/engine-cli/package.json @@ -0,0 +1,27 @@ +{ + "name": "@opentiny/tiny-engine-cli", + "version": "1.0.0", + "type": "module", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "bin": { + "engine-cli": "./bin/cli.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "chalk": "^5.3.0", + "commander": "^12.0.0", + "execa": "^8.0.1", + "fs-extra": "^11.2.0", + "globby": "^14.0.1", + "handlebars": "^4.7.8", + "inquirer": "^9.2.17", + "ora": "^8.0.1", + "vite": "^5.1.6" + } +} diff --git a/packages/engine-cli/src/commands/build.js b/packages/engine-cli/src/commands/build.js new file mode 100644 index 0000000..e631761 --- /dev/null +++ b/packages/engine-cli/src/commands/build.js @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2024 - present TinyEngine Authors. + * Copyright (c) 2024 - 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 { build } from 'vite' +import { resolveViteConfig } from '../common.js' + +export default async function () { + const viteConfig = await resolveViteConfig() + await build({ + configFile: false, + ...viteConfig, + // root: __dirname, + server: { + port: 8000 + } + }) +} diff --git a/packages/engine-cli/src/commands/create.js b/packages/engine-cli/src/commands/create.js new file mode 100644 index 0000000..7fb4dd7 --- /dev/null +++ b/packages/engine-cli/src/commands/create.js @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2024 - present TinyEngine Authors. + * Copyright (c) 2024 - 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 { fileURLToPath } from 'node:url' +import { cwd } from 'node:process' +import path from 'node:path' +import fs from 'fs-extra' +import chalk from 'chalk' + +const __filename = fileURLToPath(import.meta.url) + +const __dirname = path.dirname(__filename) + +export default function (name) { + const sourcePath = path.join(__dirname, '../../template') + const destPath = path.join(cwd(), name) + fs.copySync(sourcePath, destPath) + console.log( + chalk.green(`create finish, run the follow command to start project: \ncd ${name} && npm install && npm run dev`) + ) +} diff --git a/packages/engine-cli/src/commands/serve.js b/packages/engine-cli/src/commands/serve.js new file mode 100644 index 0000000..7116c1f --- /dev/null +++ b/packages/engine-cli/src/commands/serve.js @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2024 - present TinyEngine Authors. + * Copyright (c) 2024 - 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 { createServer } from 'vite' +import { resolveViteConfig } from '../common.js' + +export default async function () { + const viteConfig = await resolveViteConfig() + const server = await createServer({ + configFile: false, + ...viteConfig, + // root: __dirname, + server: { + ...{ + port: 8000 + }, + ...viteConfig.server + } + }) + await server.listen() + + server.printUrls() + server.bindCLIShortcuts({ print: true }) +} diff --git a/packages/engine-cli/src/common.js b/packages/engine-cli/src/common.js new file mode 100644 index 0000000..a005f4c --- /dev/null +++ b/packages/engine-cli/src/common.js @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2024 - present TinyEngine Authors. + * Copyright (c) 2024 - 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 { cwd } from 'node:process' +import path from 'node:path' +import { defineConfig } from 'vite' + +const resolveViteConfig = async () => { + const configPath = path.join(cwd(), 'engine.config.js') + let config = {} + try { + const allConfig = (await import(`file://${configPath}`)).default + config = allConfig?.viteConfig || {} + } catch (err) { + console.log(err) + } + return defineConfig(config) +} + +export { resolveViteConfig } diff --git a/packages/engine-cli/src/index.js b/packages/engine-cli/src/index.js new file mode 100644 index 0000000..8048637 --- /dev/null +++ b/packages/engine-cli/src/index.js @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2024 - present TinyEngine Authors. + * Copyright (c) 2024 - 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 { fileURLToPath } from 'node:url' +import fs from 'fs-extra' +import * as globby from 'globby' +import { Command } from 'commander' +import * as path from 'path' +import chalk from 'chalk' +import create from './commands/create.js' +import serve from './commands/serve.js' +import build from './commands/build.js' + +const __filename = fileURLToPath(import.meta.url) + +const __dirname = path.dirname(__filename) +const program = new Command() + +let commandsPath = [] +let pkgVersion = '' +let pkgName = '' + +// 获取当前包的信息 +const getPkgInfo = () => { + const jsonPath = path.join(__dirname, '../package.json') + const jsonResult = fs.readJSONSync(jsonPath) + pkgVersion = jsonResult.version + return pkgVersion +} + +program + .command('create ') + .description('创建一个新工程') + .action((name) => { + create(name) + }) + +program + .command('serve') + .description('开启服务') + .action(() => { + serve() + }) + +program + .command('build') + .description('构建') + .action(() => { + build() + }) + +program.parse(process.argv) diff --git a/packages/engine-cli/template/engine.config.js b/packages/engine-cli/template/engine.config.js new file mode 100644 index 0000000..2c055e1 --- /dev/null +++ b/packages/engine-cli/template/engine.config.js @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2024 - present TinyEngine Authors. + * Copyright (c) 2024 - 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 vue from '@vitejs/plugin-vue' +// TODO: 考虑是否把各项配置合并到一个配置文件当中 +export default { + registry: {}, + viteConfig: { + plugins: [vue()] + }, // 将vite.config.js配置到此处 + otherConfig: {} +} diff --git a/packages/engine-cli/template/index.html b/packages/engine-cli/template/index.html new file mode 100644 index 0000000..795e4fb --- /dev/null +++ b/packages/engine-cli/template/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + Vue + + +
+ + + diff --git a/packages/engine-cli/template/package.json b/packages/engine-cli/template/package.json new file mode 100644 index 0000000..b7fe159 --- /dev/null +++ b/packages/engine-cli/template/package.json @@ -0,0 +1,20 @@ +{ + "name": "engine-cli-demo", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "engine-cli serve", + "build": "engine-cli build" + }, + "dependencies": { + "vue": "^3.4.21", + "@opentiny/tiny-engine": "workspace:^", + "@opentiny/tiny-engine-entry": "workspace:^" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.4", + "vite": "^5.2.7", + "@opentiny/tiny-engine-cli": "workspace:^" + } +} diff --git a/packages/engine-cli/template/registry.js b/packages/engine-cli/template/registry.js new file mode 100644 index 0000000..28bf9bd --- /dev/null +++ b/packages/engine-cli/template/registry.js @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2024 - present TinyEngine Authors. + * Copyright (c) 2024 - 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. + * + */ + +export default { + layout: { id: 'engine.layout' }, + toolbars: [ + { + id: 'engine.toolbars.download' + }, + { + id: 'engine.toolbars.refresh' + } + ], + plugins: [{ id: 'engine.plugins.i18n' }, { id: 'engine.plugins.status' }], + dsls: [{ id: 'engine.dsls.dslvue' }], + settings: [], + canvas: {}, + utils: { id: 'engine.utils' } +} diff --git a/packages/engine-cli/template/src/App.vue b/packages/engine-cli/template/src/App.vue new file mode 100644 index 0000000..a4fa20f --- /dev/null +++ b/packages/engine-cli/template/src/App.vue @@ -0,0 +1,15 @@ + + + + + diff --git a/packages/engine-cli/template/src/defineEntry.js b/packages/engine-cli/template/src/defineEntry.js new file mode 100644 index 0000000..2d7ea20 --- /dev/null +++ b/packages/engine-cli/template/src/defineEntry.js @@ -0,0 +1,15 @@ +/** + * Copyright (c) 2024 - present TinyEngine Authors. + * Copyright (c) 2024 - 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-entry' +defineEntry(registry) diff --git a/packages/engine-cli/template/src/main.js b/packages/engine-cli/template/src/main.js new file mode 100644 index 0000000..ac35c0a --- /dev/null +++ b/packages/engine-cli/template/src/main.js @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2024 - present TinyEngine Authors. + * Copyright (c) 2024 - 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 './defineEntry.js' +import { createApp } from 'vue' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/packages/entry/index.js b/packages/entry/index.js new file mode 100644 index 0000000..aa8a29b --- /dev/null +++ b/packages/entry/index.js @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2024 - present TinyEngine Authors. + * Copyright (c) 2024 - 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 { getMergeMeta } from './src/common' +import { useCompile } from './src/templateHash' +import { defineEntry, callEntry, beforeCallEntry, afterCallEntry, getMergeRegistry } from './src/entryHash' + +import { getLayoutComponent } from './src/layoutHash' + +export { + getMergeMeta, + useCompile, + defineEntry, + callEntry, + beforeCallEntry, + afterCallEntry, + getLayoutComponent, + getMergeRegistry +} diff --git a/packages/entry/package.json b/packages/entry/package.json new file mode 100644 index 0000000..3687d06 --- /dev/null +++ b/packages/entry/package.json @@ -0,0 +1,15 @@ +{ + "name": "@opentiny/tiny-engine-entry", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "dependencies": { + "vue": "^3.4.21" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/packages/entry/src/common.js b/packages/entry/src/common.js new file mode 100644 index 0000000..f32dbdf --- /dev/null +++ b/packages/entry/src/common.js @@ -0,0 +1,127 @@ +/** + * Copyright (c) 2024 - present TinyEngine Authors. + * Copyright (c) 2024 - 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. + * + */ + +const vueLifeHook = [ + 'onMounted', + 'onUpdated', + 'onUnmounted', + 'onBeforeMount', + 'onBeforeUpdate', + 'onBeforeUnmount', + 'onActivated', + 'onDeactivated' +] + +/** + * 自定义方法注册哈希表,形式如下: + * { + * 'engine.plugins.i18n.handleClick': () => { // do something } + * } + */ +export const entryHashMap = {} +/** + * 自定义模板注册哈希表,形式如下: + * { + * 'engine.plugins.status.metas.app': + * } + */ +export const templateHashMap = {} + +/** + * 自定布局hash,形式如下: + * { + * 'engine.plugins.status': customLayout + * } + */ +export const layoutHashMap = {} + +export const metasHashMap = {} + +const handleMethods = (id, methods) => { + Object.entries(methods).forEach(([fileId, idMethods]) => { + if (typeof idMethods === 'object') { + Object.entries(idMethods).forEach(([name, method]) => { + const prefix = fileId ? `.${fileId}` : '' + const methodId = `${id}${prefix}.${name}` + entryHashMap[methodId] = method + }) + } + }) +} + +const handleVueLifeCycle = (id, value) => { + vueLifeHook.forEach((hookName) => { + const hookConfig = value[hookName] + if (!hookConfig) { + return + } + if (typeof hookConfig === 'function') { + const hookId = `${id}.${hookName}[0]` + entryHashMap[hookId] = hookConfig + } + if (hookConfig instanceof Array) { + hookConfig.forEach((hookFn, index) => { + if (typeof hookFn === 'function') { + const hookId = `${id}.${hookName}[${index}]` + entryHashMap[hookId] = hookFn + } + }) + } + }) +} + +const handleLifeCycles = (id, lifeCycles) => { + Object.entries(lifeCycles).forEach(([fileId, idLifeCycles]) => { + const prefix = fileId ? `.${fileId}` : '' + const lifeCycleId = `${id}${prefix}` + handleVueLifeCycle(lifeCycleId, idLifeCycles) + }) +} + +const handleRegistryProp = (id, value) => { + const { template, layout, methods, lifeCycles } = value + // 处理生命周期 + if (lifeCycles) { + handleLifeCycles(id, lifeCycles) + } + // 如果id和模板配置同时存在则放到模板hash表中 + if (template) { + templateHashMap[id] = template + } + if (layout) { + layoutHashMap[id] = layout + } + if (methods) { + handleMethods(id, methods) + } +} + +export const generateRegistry = (registry) => { + Object.entries(registry).forEach(([key, value]) => { + if (typeof value === 'object' && value) { + const { id } = value + // 如果匹配到了id,说明是元服务配置,对元服务配置做读取和写入 + if (id && key !== 'metaData') { + handleRegistryProp(id, value) + metasHashMap[id] = value + } else { + // TODO: 其他类型配置处理 + } + + generateRegistry(value) + } + }) +} + +export const getMergeMeta = (meta) => { + return metasHashMap[meta?.id] +} diff --git a/packages/entry/src/entryHash.js b/packages/entry/src/entryHash.js new file mode 100644 index 0000000..ba69034 --- /dev/null +++ b/packages/entry/src/entryHash.js @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2024 - present TinyEngine Authors. + * Copyright (c) 2024 - 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 { generateRegistry, entryHashMap } from './common' + +const lowcodeRegistry = { registry: null } + +export const getMergeRegistry = () => lowcodeRegistry.registry + +export const defineEntry = (registry) => { + if (!registry) { + throw new Error('请传递正确的注册表') + } + lowcodeRegistry.registry = registry + generateRegistry(registry) +} + +export const callEntry = (fn, params) => { + const { metaData, ctx } = params + const customMethod = entryHashMap[metaData?.id] + if (customMethod) { + const customFn = customMethod.entry ? customMethod.entry : customMethod + if (typeof customFn === 'function') { + return customFn(ctx, fn) + } + } + + return fn +} + +export const beforeCallEntry = ({ metaData, ctx }) => { + const id = metaData?.id + + const customMethod = entryHashMap[id]?.before + if (customMethod) { + customMethod(ctx) + } +} + +export const afterCallEntry = ({ metaData, ctx }) => { + const id = metaData?.id + const customMethod = entryHashMap[id]?.after + if (customMethod) { + customMethod(ctx) + } +} diff --git a/packages/entry/src/layoutHash.js b/packages/entry/src/layoutHash.js new file mode 100644 index 0000000..7e7d9ce --- /dev/null +++ b/packages/entry/src/layoutHash.js @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2024 - present TinyEngine Authors. + * Copyright (c) 2024 - 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 { layoutHashMap } from './common' + +export const getLayoutComponent = (metaData) => { + const customLayout = layoutHashMap[metaData.id] + return customLayout +} diff --git a/packages/entry/src/templateHash.js b/packages/entry/src/templateHash.js new file mode 100644 index 0000000..7421328 --- /dev/null +++ b/packages/entry/src/templateHash.js @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2024 - present TinyEngine Authors. + * Copyright (c) 2024 - 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 { compile } from 'vue/dist/vue.esm-bundler.js' +import { templateHashMap } from './common' + +const generateTemplate = (template) => { + const templateString = template.trim() + if (templateString.startsWith('')) { + return templateString.slice(10, -11) + } + return templateString +} + +export const useCompile = ({ component, metaData }) => { + // 此处compile会缓存template对应的render函数,并且render函数一个纯函数(用到的所有变量都来自参数) + const customTem = templateHashMap[metaData.id] + if (customTem) { + const template = generateTemplate(customTem) + component.render = compile(template) + } + + return component +} diff --git a/packages/plugins/i18n/index.js b/packages/plugins/i18n/index.js index d8aaa4f..4ddb209 100644 --- a/packages/plugins/i18n/index.js +++ b/packages/plugins/i18n/index.js @@ -1,21 +1,18 @@ /** -* 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. -* -*/ - + * 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 component from './src/Main.vue' +import metaData from './meta.js' export default { - id: 'I18n', - title: '国际化', - icon: 'plugin-icon-language', - align: 'top', + ...metaData, component } diff --git a/packages/plugins/i18n/meta.js b/packages/plugins/i18n/meta.js new file mode 100644 index 0000000..d42d1a9 --- /dev/null +++ b/packages/plugins/i18n/meta.js @@ -0,0 +1,8 @@ +import { iconLanguage } from '@opentiny/vue-icon' +export default { + id: 'engine.plugins.i18n', + title: '国际化', + type: 'plugins', + align: 'top', + icon: iconLanguage() +} diff --git a/packages/plugins/i18n/package.json b/packages/plugins/i18n/package.json index c52e897..9d85f1c 100644 --- a/packages/plugins/i18n/package.json +++ b/packages/plugins/i18n/package.json @@ -24,6 +24,7 @@ "license": "MIT", "homepage": "https://opentiny.design/tiny-engine", "dependencies": { + "@opentiny/tiny-engine-entry": "workspace:*", "@opentiny/tiny-engine-common": "workspace:*", "@opentiny/tiny-engine-controller": "workspace:*", "@opentiny/tiny-engine-http": "workspace:*", @@ -40,4 +41,4 @@ "@opentiny/vue-icon": "^3.14.0", "vue": "^3.4.15" } -} +} \ No newline at end of file diff --git a/packages/plugins/i18n/src/Main.vue b/packages/plugins/i18n/src/Main.vue index 9382de9..49586ee 100644 --- a/packages/plugins/i18n/src/Main.vue +++ b/packages/plugins/i18n/src/Main.vue @@ -119,6 +119,7 @@