From 85b75207428e92cdccd89e6c667b57a93ae22989 Mon Sep 17 00:00:00 2001 From: chilingling Date: Mon, 3 Nov 2025 21:34:18 +0800 Subject: [PATCH] chore: enhance common package types --- packages/common/composable/http/index.js | 72 ------ packages/common/composable/http/index.ts | 160 ++++++++++++++ packages/common/i18n/{index.js => index.ts} | 0 packages/common/js/{app.js => app.ts} | 0 packages/common/js/ast.js | 166 -------------- packages/common/js/ast.ts | 205 ++++++++++++++++++ packages/common/js/{canvas.js => canvas.ts} | 14 +- packages/common/js/{comment.js => comment.ts} | 2 +- .../{eslint-rule.js => eslint-rule.ts} | 3 +- .../{prettierrc.js => prettierrc.ts} | 0 .../common/js/{constants.js => constants.ts} | 0 packages/common/js/{css.js => css.ts} | 13 +- .../js/{environments.js => environments.ts} | 2 +- packages/common/js/{example.js => example.ts} | 4 +- packages/common/js/http.js | 89 -------- packages/common/js/http.ts | 138 ++++++++++++ packages/common/js/{i18n.js => i18n.ts} | 22 +- .../common/js/{importMap.js => importMap.ts} | 0 packages/common/js/{linter.js => linter.ts} | 49 ++++- packages/common/js/{monitor.js => monitor.ts} | 14 +- packages/common/js/{preview.js => preview.ts} | 68 ++++-- .../js/{verification.js => verification.ts} | 12 +- packages/common/js/vscodeGenerateFile.js | 113 ---------- packages/common/js/vscodeGenerateFile.ts | 186 ++++++++++++++++ packages/common/package.json | 15 +- packages/common/tsconfig.json | 12 + packages/common/vite.config.ts | 7 +- 27 files changed, 872 insertions(+), 494 deletions(-) delete mode 100644 packages/common/composable/http/index.js create mode 100644 packages/common/composable/http/index.ts rename packages/common/i18n/{index.js => index.ts} (100%) rename packages/common/js/{app.js => app.ts} (100%) delete mode 100644 packages/common/js/ast.js create mode 100644 packages/common/js/ast.ts rename packages/common/js/{canvas.js => canvas.ts} (79%) rename packages/common/js/{comment.js => comment.ts} (87%) rename packages/common/js/config-files/{eslint-rule.js => eslint-rule.ts} (94%) rename packages/common/js/config-files/{prettierrc.js => prettierrc.ts} (100%) rename packages/common/js/{constants.js => constants.ts} (100%) rename packages/common/js/{css.js => css.ts} (79%) rename packages/common/js/{environments.js => environments.ts} (93%) rename packages/common/js/{example.js => example.ts} (94%) delete mode 100644 packages/common/js/http.js create mode 100644 packages/common/js/http.ts rename packages/common/js/{i18n.js => i18n.ts} (66%) rename packages/common/js/{importMap.js => importMap.ts} (100%) rename packages/common/js/{linter.js => linter.ts} (58%) rename packages/common/js/{monitor.js => monitor.ts} (90%) rename packages/common/js/{preview.js => preview.ts} (82%) rename packages/common/js/{verification.js => verification.ts} (74%) delete mode 100644 packages/common/js/vscodeGenerateFile.js create mode 100644 packages/common/js/vscodeGenerateFile.ts create mode 100644 packages/common/tsconfig.json diff --git a/packages/common/composable/http/index.js b/packages/common/composable/http/index.js deleted file mode 100644 index 89adfa8fd4..0000000000 --- a/packages/common/composable/http/index.js +++ /dev/null @@ -1,72 +0,0 @@ -import { defineService, META_SERVICE } from '@opentiny/tiny-engine-meta-register' -import axios from 'axios' - -let http = null - -const createInterceptorHandler = - (http) => - ({ data, type }) => { - if (typeof data === 'function') { - http.interceptors[type].use(data) - - return - } - - if (Array.isArray(data)) { - data.forEach((item) => { - if (!item) return - - if (Array.isArray(item)) { - http.interceptors[type].use(...item) - - return - } - - if (typeof item === 'function') { - http.interceptors[type].use(item) - } - }) - } - } - -export default defineService({ - id: META_SERVICE.Http, - type: 'MetaService', - options: { - axiosConfig: { - // axios 配置 - baseURL: '', - withCredentials: false, // 跨域请求时是否需要使用凭证 - headers: {} // 请求头 - }, - interceptors: { - // 拦截器 - request: [], // 支持配置多个请求拦截器,先注册后执行 - response: [] // 支持配置多个响应拦截器,先注册先执行 - } - }, - init: ({ options = {} }) => { - const { axiosConfig = {}, interceptors = {} } = options - const { request = [], response = [] } = interceptors - - http = axios.create(axiosConfig) - const addInterceptors = createInterceptorHandler(http) - addInterceptors({ data: request, type: 'request' }) - addInterceptors({ data: response, type: 'response' }) - }, - apis: () => ({ - getHttp: () => http, - get: (...args) => http?.get(...args), - post: (...args) => http?.post(...args), - request: (...args) => http?.request(...args), - put: (...args) => http?.put(...args), - delete: (...args) => http?.delete(...args), - stream: (config) => { - const streamConfig = { - responseType: 'stream', - ...config - } - return http?.request(streamConfig) - } - }) -}) diff --git a/packages/common/composable/http/index.ts b/packages/common/composable/http/index.ts new file mode 100644 index 0000000000..7b7953e044 --- /dev/null +++ b/packages/common/composable/http/index.ts @@ -0,0 +1,160 @@ +import { defineService, META_SERVICE } from '@opentiny/tiny-engine-meta-register' +import axios, { type AxiosInstance, type AxiosRequestConfig, type AxiosResponse, type CreateAxiosDefaults } from 'axios' + +// 请求拦截器 fulfilled 函数类型 +type RequestInterceptorFulfilled = (config: AxiosRequestConfig) => AxiosRequestConfig | Promise + +// 响应拦截器 fulfilled 函数类型 +type ResponseInterceptorFulfilled = ( + response: AxiosResponse +) => AxiosResponse | Promise> + +// 拦截器 rejected 函数类型 +type InterceptorRejected = (error: unknown) => unknown + +// 请求拦截器函数类型(单个 fulfilled 函数 或 [fulfilled, rejected] 元组) +type RequestInterceptorFunction = RequestInterceptorFulfilled | [RequestInterceptorFulfilled, InterceptorRejected?] + +// 响应拦截器函数类型(单个 fulfilled 函数 或 [fulfilled, rejected] 元组) +type ResponseInterceptorFunction = + | ResponseInterceptorFulfilled + | [ResponseInterceptorFulfilled, InterceptorRejected?] + +// 请求拦截器配置类型(支持单个函数、数组、嵌套数组) +type RequestInterceptorConfig = RequestInterceptorFunction | (RequestInterceptorFunction | undefined)[] + +// 响应拦截器配置类型(支持单个函数、数组、嵌套数组) +type ResponseInterceptorConfig = ResponseInterceptorFunction | (ResponseInterceptorFunction | undefined)[] + +// 通用拦截器类型(用于内部处理) +type InterceptorFunction = RequestInterceptorFunction | ResponseInterceptorFunction +type InterceptorConfig = RequestInterceptorConfig | ResponseInterceptorConfig + +// 拦截器配置接口 +interface InterceptorsConfig { + request?: RequestInterceptorConfig + response?: ResponseInterceptorConfig +} + +// HTTP 服务选项接口 +interface HttpServiceOptions { + axiosConfig?: CreateAxiosDefaults + interceptors?: InterceptorsConfig +} + +let http: AxiosInstance | null = null + +// 通用拦截器 fulfilled 函数类型 +type InterceptorFulfilled = RequestInterceptorFulfilled | ResponseInterceptorFulfilled + +// 通用拦截器处理器类型(内部使用) +type InterceptorHandler = { + use: (onFulfilled?: (value: any) => any, onRejected?: (error: unknown) => unknown) => number +} + +/** + * 注册单个拦截器 + */ +const registerInterceptor = (handler: InterceptorHandler, interceptor: InterceptorFunction): void => { + if (typeof interceptor === 'function') { + handler.use(interceptor) + } else if (Array.isArray(interceptor)) { + handler.use(interceptor[0], interceptor[1]) + } +} + +/** + * 注册拦截器配置(支持单个函数、元组、或数组) + */ +const registerInterceptors = (handler: InterceptorHandler, config?: InterceptorConfig): void => { + if (!config) return + + // 单个函数 + if (typeof config === 'function') { + handler.use(config) + return + } + + // 数组情况:判断是元组还是拦截器数组 + if (Array.isArray(config)) { + const isTuple = config.length <= 2 && typeof config[0] === 'function' && typeof config[1] !== 'object' + + if (isTuple) { + // [fulfilled, rejected?] 元组 + handler.use(config[0] as InterceptorFulfilled, config[1] as InterceptorRejected | undefined) + } else { + // 拦截器数组 + config.forEach((item) => item && registerInterceptor(handler, item as InterceptorFunction)) + } + } +} + +export default defineService({ + id: META_SERVICE.Http, + type: 'MetaService', + initialState: {}, + options: { + axiosConfig: { + // axios 配置 + baseURL: '', + withCredentials: false, // 跨域请求时是否需要使用凭证 + headers: {} // 请求头 + }, + interceptors: { + // 拦截器 + request: [], // 支持配置多个请求拦截器,先注册后执行 + response: [] // 支持配置多个响应拦截器,先注册先执行 + } + } as HttpServiceOptions, + init: ({ options = {} }: { options?: HttpServiceOptions }) => { + const { axiosConfig = {}, interceptors = {} } = options + const { request, response } = interceptors + + http = axios.create(axiosConfig) + registerInterceptors(http.interceptors.request, request) + registerInterceptors(http.interceptors.response, response) + }, + apis: () => ({ + /** 获取 axios 实例 */ + getHttp: (): AxiosInstance | null => http, + + /** GET 请求 */ + get: , D = unknown>( + url: string, + config?: AxiosRequestConfig + ): Promise | undefined => http?.get(url, config), + + /** POST 请求 */ + post: , D = unknown>( + url: string, + data?: D, + config?: AxiosRequestConfig + ): Promise | undefined => http?.post(url, data, config), + + /** 通用请求方法 */ + request: , D = unknown>(config: AxiosRequestConfig): Promise | undefined => + http?.request(config), + + /** PUT 请求 */ + put: , D = unknown>( + url: string, + data?: D, + config?: AxiosRequestConfig + ): Promise | undefined => http?.put(url, data, config), + + /** DELETE 请求 */ + delete: , D = unknown>( + url: string, + config?: AxiosRequestConfig + ): Promise | undefined => http?.delete(url, config), + + /** 流式请求 */ + stream: (config: AxiosRequestConfig): Promise> | undefined => { + const streamConfig: AxiosRequestConfig = { + responseType: 'stream', + ...config + } + return http?.request(streamConfig) + } + }) +}) diff --git a/packages/common/i18n/index.js b/packages/common/i18n/index.ts similarity index 100% rename from packages/common/i18n/index.js rename to packages/common/i18n/index.ts diff --git a/packages/common/js/app.js b/packages/common/js/app.ts similarity index 100% rename from packages/common/js/app.js rename to packages/common/js/app.ts diff --git a/packages/common/js/ast.js b/packages/common/js/ast.js deleted file mode 100644 index 0ad63a0c31..0000000000 --- a/packages/common/js/ast.js +++ /dev/null @@ -1,166 +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 { parse, parseExpression } from '@babel/parser' -import generate from '@babel/generator' -import traverse from '@babel/traverse' -import prettier from 'prettier' -import parserHtml from 'prettier/parser-html' -import parseCss from 'prettier/parser-postcss' -import parserBabel from 'prettier/parser-babel' -import prettierConfig from './config-files/prettierrc' - -const METHOD_REGEXP = /function.*?\(/ - -export const insertName = (name, content) => content.replace(METHOD_REGEXP, `function ${name}(`) - -export const removeName = (content) => content.replace(METHOD_REGEXP, 'function (') - -export const string2Ast = (string = '') => parse(string, { sourceType: 'module', plugins: ['typescript', 'jsx'] }) - -export const ast2String = (ast) => generate(ast, { retainLines: true }).code - -const formatScript = (string) => { - let newStr = string - const options = { - parser: 'babel', - plugins: [parserBabel], - ...prettierConfig - } - try { - const ast = string2Ast(string) - // javascript 在 prettier 格式化的时候,会自动在前面加上逗号“;”,因此该种情形需要特殊处理格式化 - if (ast.program.body?.length === 1 && ast.program.body?.[0]?.type === 'ExpressionStatement') { - // 将 javascript 表达式 包裹在 "!()" 中,格式化完成之后,再从里面取出来格式化之后的值 - newStr = prettier.format(`!(${string})`, options).replace(/\r?\n$/, '') - - // 格式化后,仍存在包裹的 "!()" - if (newStr.match(/^!\([\s\S]*\)$/)) { - newStr = newStr.replace(/^!\(([\s\S]*)\)$/, '$1') - } else { - // 格式化后,仅存在开头的 "!" - newStr = newStr.replace(/^!/, '') - } - } else { - // 其他类型,不需要特殊处理 - newStr = prettier.format(string, options) - } - } catch (error) { - newStr = prettier.format(newStr, options) - } - - return newStr -} - -const formatJson = (string) => - prettier.format(string, { - parser: 'json', - plugins: [parserBabel], - trailingComma: 'es5', - ...prettierConfig - }) - -const formatHtml = (string) => - prettier.format(string, { - parser: 'html', - plugins: [parserBabel, parserHtml], - ...prettierConfig - }) - -const formatCss = (string) => - prettier.format(string, { - parser: 'css', - plugins: [parseCss], - ...prettierConfig - }) - -const formatterMap = { - json: formatJson, - typescript: formatScript, - javascript: formatScript, - html: formatHtml, - css: formatCss -} - -export const formatString = (str, language) => { - const formatter = formatterMap[language] || formatJson - let result = str - try { - result = formatter(str) - } catch (error) { - const printer = console - printer.log(error) - } - - return result -} - -export { parse, parseExpression, traverse, generate } - -export const includedExpression = (code, expression) => { - let flag = false - try { - traverse(parse(code), { - ExpressionStatement(path) { - if (path.toString().includes(expression)) { - flag = true - - return - } - } - }) - } catch (err) { - const printer = console - printer.log(err) - } - - return flag -} - -export const includedExpressionInSchema = (schemaObj, expression) => { - let hadFlag = false - - const checkReferencedFromSchema = (_schemaObj) => { - Object.values(_schemaObj).forEach((schemaObjIner) => { - if ( - ['[object Array]', '[object Object]'].includes(Object.prototype.toString.call(schemaObjIner)) && - Object.keys(schemaObjIner).length - ) { - if (schemaObjIner.type && ['jsstring', 'JSExpression', 'JSFunction'].includes(schemaObjIner.type)) { - if (includedExpression(schemaObjIner.value, expression)) { - hadFlag = true - - return - } - } else { - checkReferencedFromSchema(schemaObjIner) - } - } - }) - } - - checkReferencedFromSchema(schemaObj) - - return hadFlag -} - -export const findExpressionInAppSchema = (pageSchemas, expression) => { - const includedPage = [] - - pageSchemas.forEach((pageSchema) => { - if (includedExpressionInSchema(pageSchema, expression)) { - includedPage.push(pageSchema.fileName) - } - }) - - return includedPage -} diff --git a/packages/common/js/ast.ts b/packages/common/js/ast.ts new file mode 100644 index 0000000000..55ff615e7d --- /dev/null +++ b/packages/common/js/ast.ts @@ -0,0 +1,205 @@ +/** + * 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 { parse, parseExpression } from '@babel/parser' +import generate from '@babel/generator' +import traverse from '@babel/traverse' +import prettier from 'prettier' +import parserHtml from 'prettier/parser-html' +import parseCss from 'prettier/parser-postcss' +import parserBabel from 'prettier/parser-babel' +import prettierConfig from './config-files/prettierrc' + +type PrettierOptions = prettier.Options & { + [key: string]: unknown +} + +const METHOD_REGEXP = /function.*?\(/ +const basePrettierConfig = prettierConfig as PrettierOptions // 统一断言 prettier 配置,便于后续复用 + +export const insertName = (name: string, content: string) => content.replace(METHOD_REGEXP, `function ${name}(`) // 简单给函数补名字,避免匿名 +export const removeName = (content: string) => content.replace(METHOD_REGEXP, 'function (') // 移除函数名,维持匿名状态 + +type BabelAst = ReturnType +type GeneratorInput = Parameters[0] + +export const string2Ast = (string = ''): BabelAst => + parse(string, { sourceType: 'module', plugins: ['typescript', 'jsx'] }) // 先解析成 Babel AST 方便后续处理 + +export const ast2String = (ast: GeneratorInput): string => generate(ast, { retainLines: true }).code // 将 AST 再转回字符串 + +type FormatterFn = (input: string) => string +type SupportedLanguage = 'json' | 'typescript' | 'javascript' | 'html' | 'css' + +const formatScript: FormatterFn = (string) => { + let newStr = string + const options: PrettierOptions = { + parser: 'babel', + plugins: [parserBabel], + ...basePrettierConfig + } + try { + const ast = string2Ast(string) + // javascript 在 prettier 格式化的时候,会自动在前面加上逗号“;”,因此该种情形需要特殊处理格式化 + if (ast.program.body?.length === 1 && ast.program.body?.[0]?.type === 'ExpressionStatement') { + // 将 javascript 表达式 包裹在 "!()" 中,格式化完成之后,再从里面取出来格式化之后的值 + newStr = prettier.format(`!(${string})`, options).replace(/\r?\n$/, '') + + // 格式化后,仍存在包裹的 "!()" + if (newStr.match(/^!\([\s\S]*\)$/)) { + newStr = newStr.replace(/^!\(([\s\S]*)\)$/, '$1') + } else { + // 格式化后,仅存在开头的 "!" + newStr = newStr.replace(/^!/, '') + } + } else { + // 其他类型,不需要特殊处理 + newStr = prettier.format(string, options) + } + } catch (error) { + newStr = prettier.format(newStr, options) + } + + return newStr +} + +const formatJson: FormatterFn = (string) => + prettier.format(string, { + parser: 'json', + plugins: [parserBabel], + trailingComma: 'es5', + ...basePrettierConfig + }) + +const formatHtml: FormatterFn = (string) => + prettier.format(string, { + parser: 'html', + plugins: [parserBabel, parserHtml], + ...basePrettierConfig + }) + +const formatCss: FormatterFn = (string) => + prettier.format(string, { + parser: 'css', + plugins: [parseCss], + ...basePrettierConfig + }) + +const formatterMap: Record = { + json: formatJson, + typescript: formatScript, + javascript: formatScript, + html: formatHtml, + css: formatCss +} + +export const formatString = (str: string, language: string): string => { + const formatter = (formatterMap[language as SupportedLanguage] ?? formatJson) as FormatterFn // 若语言不在列表内,默认走 JSON 格式化兜底 + let result = str + try { + result = formatter(str) + } catch (error) { + const printer: Console = console + printer.log(error) + } + + return result +} + +export { parse, parseExpression, traverse, generate } + +export const includedExpression = (code: string, expression: string): boolean => { + let flag = false + try { + traverse(parse(code), { + ExpressionStatement(path: { toString(): string }) { + if (path.toString().includes(expression)) { + flag = true + + return + } + } + }) + } catch (err) { + const printer = console + printer.log(err) + } + + return flag +} + +type SchemaPrimitive = string | number | boolean | null // Schema 列表中的原子类型 +interface SchemaExpressionNode { + type?: string + value?: string + [key: string]: SchemaValue | undefined +} +type SchemaValue = SchemaPrimitive | SchemaExpressionNode | SchemaValue[] | { [key: string]: SchemaValue } + +export const includedExpressionInSchema = (schemaObj: SchemaValue, expression: string): boolean => { + let hadFlag = false + + // 递归扫描 schema,定位 JS 表达式引用 + const checkReferencedFromSchema = (_schemaObj: SchemaValue): void => { + if (Array.isArray(_schemaObj)) { + _schemaObj.forEach((schemaItem) => { + if (!hadFlag) { + checkReferencedFromSchema(schemaItem) + } + }) + + return + } + + if (_schemaObj && typeof _schemaObj === 'object') { + const schemaRecord = _schemaObj as SchemaExpressionNode + if ( + schemaRecord.type && + ['jsstring', 'JSExpression', 'JSFunction'].includes(schemaRecord.type) && + typeof schemaRecord.value === 'string' + ) { + if (includedExpression(schemaRecord.value, expression)) { + hadFlag = true + + return + } + } + + Object.values(schemaRecord).forEach((innerSchema) => { + if (!hadFlag && innerSchema !== undefined) { + checkReferencedFromSchema(innerSchema) + } + }) + } + } + + checkReferencedFromSchema(schemaObj) + + return hadFlag +} + +interface PageSchema { + fileName: string + [key: string]: SchemaValue +} + +export const findExpressionInAppSchema = (pageSchemas: PageSchema[], expression: string): string[] => { + const includedPage: string[] = [] + + pageSchemas.forEach((pageSchema) => { + if (includedExpressionInSchema(pageSchema, expression)) { + includedPage.push(pageSchema.fileName) + } + }) + + return includedPage +} diff --git a/packages/common/js/canvas.js b/packages/common/js/canvas.ts similarity index 79% rename from packages/common/js/canvas.js rename to packages/common/js/canvas.ts index 9214d8504f..4fb1ddd19f 100644 --- a/packages/common/js/canvas.js +++ b/packages/common/js/canvas.ts @@ -13,7 +13,19 @@ import { PAGE_STATUS } from './constants' import { useResource, getMetaApi, META_SERVICE } from '@opentiny/tiny-engine-meta-register' -export const getCanvasStatus = (data) => { +// 占用者信息接口 +interface Occupier { + id: number + username: string +} + +// 画布状态返回值接口 +interface CanvasStatus { + state: string + data: Occupier | undefined +} + +export const getCanvasStatus = (data: Occupier | undefined): CanvasStatus => { const globalState = getMetaApi(META_SERVICE.GlobalService).getState() const isDemo = useResource().appSchemaState.isDemo let state = '' diff --git a/packages/common/js/comment.js b/packages/common/js/comment.ts similarity index 87% rename from packages/common/js/comment.js rename to packages/common/js/comment.ts index 335d8b640b..edba16d60f 100644 --- a/packages/common/js/comment.js +++ b/packages/common/js/comment.ts @@ -10,7 +10,7 @@ * */ -export const getCommentByKey = (key) => ({ +export const getCommentByKey = (key: string): { start: string; end: string } => ({ start: `start-${key} 设计器生成的代码,为了避免出现问题,请勿修改`, end: `end-${key}` }) diff --git a/packages/common/js/config-files/eslint-rule.js b/packages/common/js/config-files/eslint-rule.ts similarity index 94% rename from packages/common/js/config-files/eslint-rule.js rename to packages/common/js/config-files/eslint-rule.ts index ad64125826..07c2ff8e83 100644 --- a/packages/common/js/config-files/eslint-rule.js +++ b/packages/common/js/config-files/eslint-rule.ts @@ -1,4 +1,5 @@ -import eslintRecommended from '@eslint/js/src/configs/eslint-recommended.js' +import eslintRecommended from '@eslint/js/src/configs/eslint-recommended' + export default { ...eslintRecommended.rules, 'no-console': 'error', diff --git a/packages/common/js/config-files/prettierrc.js b/packages/common/js/config-files/prettierrc.ts similarity index 100% rename from packages/common/js/config-files/prettierrc.js rename to packages/common/js/config-files/prettierrc.ts diff --git a/packages/common/js/constants.js b/packages/common/js/constants.ts similarity index 100% rename from packages/common/js/constants.js rename to packages/common/js/constants.ts diff --git a/packages/common/js/css.js b/packages/common/js/css.ts similarity index 79% rename from packages/common/js/css.js rename to packages/common/js/css.ts index 3dfd04f35f..c8e0035e48 100644 --- a/packages/common/js/css.js +++ b/packages/common/js/css.ts @@ -11,6 +11,7 @@ */ import * as cssTree from 'css-tree' +import type { StyleSheet, Rule } from 'css-tree' import { utils } from '@opentiny/tiny-engine-utils' const { hyphenate } = utils @@ -22,13 +23,13 @@ const { hyphenate } = utils * @param {string} styleStr css 字符串 * @returns object { [string]: string } */ -export const getCssObjectFromStyleStr = (styleStr) => { - const ast = cssTree.parse(styleStr) - const cssObject = {} +export const getCssObjectFromStyleStr = (styleStr: string): Record => { + const ast = cssTree.parse(styleStr) as StyleSheet + const cssObject: Record = {} ast.children - .filter(({ type }) => type === 'Rule') - .forEach((item) => { + .filter((node): node is Rule => node.type === 'Rule') + .forEach((item: Rule) => { const matchCode = cssTree.generate(item).match(/^(.+){(.+)}$/) if (!matchCode) { @@ -47,7 +48,7 @@ export const styleStrAddRoot = (str = '') => { return `:root { ${str}\n}` } -export const obj2StyleStr = (obj = {}, addRoot = true) => { +export const obj2StyleStr = (obj: Record = {}, addRoot = true) => { const list = Object.entries(obj).map(([key, value]) => (value ? `${hyphenate(key)}: ${value};` : '')) return addRoot ? styleStrAddRoot(list.join('\n ')) : ` { \n ${list.join('\n ')} \n}` diff --git a/packages/common/js/environments.js b/packages/common/js/environments.ts similarity index 93% rename from packages/common/js/environments.js rename to packages/common/js/environments.ts index 9f5d82ac2b..dc769913d0 100644 --- a/packages/common/js/environments.js +++ b/packages/common/js/environments.ts @@ -20,7 +20,7 @@ export const VITE_CDN_TYPE = import.meta.env.VITE_CDN_TYPE export const isMock = VITE_API_MOCK === 'mock' -export const isVsCodeEnv = window.vscodeBridge +export const isVsCodeEnv = (window as any).vscodeBridge as boolean export const isDevelopEnv = MODE?.includes('dev') diff --git a/packages/common/js/example.js b/packages/common/js/example.ts similarity index 94% rename from packages/common/js/example.js rename to packages/common/js/example.ts index 3ceeef4543..29caa6ad01 100644 --- a/packages/common/js/example.js +++ b/packages/common/js/example.ts @@ -93,7 +93,7 @@ const exampleMap = { ] ` } -export const getExample = (name) => { +export const getExample = (name: string): string => { const resetName = `${name || ''}`.toLocaleLowerCase() - return exampleMap[resetName] + return exampleMap[resetName as keyof typeof exampleMap] || '' } diff --git a/packages/common/js/http.js b/packages/common/js/http.js deleted file mode 100644 index c733345fe8..0000000000 --- a/packages/common/js/http.js +++ /dev/null @@ -1,89 +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 { isVsCodeEnv } from './environments' -import { generateRouter, generatePage } from './vscodeGenerateFile' -import { usePage, useNotify, useBreadcrumb, useMessage } from '@opentiny/tiny-engine-meta-register' -import { getMetaApi, META_SERVICE } from '@opentiny/tiny-engine-meta-register' - -/** - * 异常情况埋点上传 - * @param { json } params {"event_type": design_error,"url": "elit in reprehenderit enim incididunt" } - * @returns { Promise } - */ -export const requestEvent = (url, params) => { - if (!url) { - return - } - - return getMetaApi(META_SERVICE.Http) - .post(url, params) - .catch(() => {}) -} - -/** - * 页面更新 - * @param { string } id 页面ID - * @param { json } params 页面信息 - * @returns { Promise } - * - */ -export const handlePageUpdate = (updateParams) => { - const { id, params, routerChange = false, isCurEditPage = true, isUpdateTree = true } = updateParams - - return getMetaApi(META_SERVICE.Http) - .post(`/app-center/api/pages/update/${id}`, params) - .then((res) => { - if (isVsCodeEnv) { - generatePage({ - id, - name: params.name, - page_content: params.page_content - }) - - if (routerChange) { - generateRouter({ - id, - componentsTree: params - }) - } - } - - if (isUpdateTree) { - useNotify({ message: '保存成功!', type: 'success' }) - } - - // 发布 Schema 变动通知 - useMessage().publish({ - topic: 'pageOrBlockInit', - data: params.page_content - }) - - if (isCurEditPage) { - const { setBreadcrumbPage } = useBreadcrumb() - setBreadcrumbPage([params.name]) - } - - return res - }) - .catch((err) => { - useNotify({ title: '保存失败', message: `${err?.message || ''}`, type: 'error' }) - }) - .finally(() => { - const { pageSettingState } = usePage() - // 更新页面管理的列表,如果不存在,说明还没有打开过页面管理面板 - if (isUpdateTree) { - pageSettingState.updateTreeData?.() - } - pageSettingState.isNew = false - }) -} diff --git a/packages/common/js/http.ts b/packages/common/js/http.ts new file mode 100644 index 0000000000..e5ad9c2212 --- /dev/null +++ b/packages/common/js/http.ts @@ -0,0 +1,138 @@ +/** + * 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 { isVsCodeEnv } from './environments' +import { generateRouter, generatePage } from './vscodeGenerateFile' +import { usePage, useNotify, useBreadcrumb, useMessage } from '@opentiny/tiny-engine-meta-register' +import { getMetaApi, META_SERVICE } from '@opentiny/tiny-engine-meta-register' + +// ============ 类型定义 ============ + +/** + * 埋点事件参数接口 + */ +interface EventParams { + event_type: string // 事件类型,如 'design_error', 'design_JSError' 等 + url: string // 当前页面URL + unit?: string // URL参数信息 + content?: string // 事件详细内容 + [key: string]: any // 支持其他自定义字段 +} + +/** + * 页面内容接口 + */ +interface PageContent { + fileName?: string // 文件名 + lifeCycles?: any // 生命周期 + [key: string]: any // 页面 schema 的其他属性 +} + +/** + * 页面参数接口 + */ +interface PageParams { + name?: string // 页面名称 + page_content?: PageContent // 页面内容 + route?: string // 路由信息 + message?: string // 更新消息 + [key: string]: any // 其他页面属性 +} + +/** + * 页面更新参数接口 + */ +interface UpdateParams { + id: number | string // 页面ID + params: PageParams // 页面参数 + routerChange?: boolean // 路由是否改变,默认 false + isCurEditPage?: boolean // 是否是当前编辑页面,默认 true + isUpdateTree?: boolean // 是否更新树结构,默认 true +} + +// ============ 函数实现 ============ + +/** + * 异常情况埋点上传 + * @param { string } url 埋点上传地址 + * @param { EventParams } params 埋点事件参数 {"event_type": "design_error","url": "elit in reprehenderit enim incididunt" } + * @returns { Promise | undefined } 返回 Promise 或 undefined + */ +export const requestEvent = (url: string, params: EventParams): Promise | undefined => { + if (!url) { + return + } + + return getMetaApi(META_SERVICE.Http) + .post(url, params) + .catch(() => {}) +} + +/** + * 页面更新 + * @param { UpdateParams } updateParams 页面更新参数,包含 id、params、routerChange、isCurEditPage、isUpdateTree + * @returns { Promise } + * + */ +export const handlePageUpdate = (updateParams: UpdateParams): Promise => { + const { id, params, routerChange = false, isCurEditPage = true, isUpdateTree = true } = updateParams + + return ( + getMetaApi(META_SERVICE.Http) + .post(`/app-center/api/pages/update/${id}`, params) + // TODO: 优化返回类型 + .then((res: any) => { + if (isVsCodeEnv) { + generatePage({ + id: Number(id), + name: params.name || '', + page_content: params.page_content + }) + + if (routerChange) { + generateRouter({ + pageId: String(id), + componentsTree: params + }) + } + } + + if (isUpdateTree) { + useNotify({ message: '保存成功!', type: 'success' }) + } + + // 发布 Schema 变动通知 + useMessage().publish({ + topic: 'pageOrBlockInit', + data: params.page_content + }) + + if (isCurEditPage) { + const { setBreadcrumbPage } = useBreadcrumb() + setBreadcrumbPage([params.name || '']) + } + + return res + }) + .catch((err: any) => { + useNotify({ title: '保存失败', message: `${err?.message || ''}`, type: 'error' }) + }) + .finally(() => { + const { pageSettingState } = usePage() + // 更新页面管理的列表,如果不存在,说明还没有打开过页面管理面板 + if (isUpdateTree) { + pageSettingState.updateTreeData?.() + } + pageSettingState.isNew = false + }) + ) +} diff --git a/packages/common/js/i18n.js b/packages/common/js/i18n.ts similarity index 66% rename from packages/common/js/i18n.js rename to packages/common/js/i18n.ts index cd7163024c..3abad639a4 100644 --- a/packages/common/js/i18n.js +++ b/packages/common/js/i18n.ts @@ -15,11 +15,20 @@ import i18n, { defineCustomI18n } from '@opentiny/tiny-engine-i18n-host' import tinyLocale from '@opentiny/vue-locale' import { i18nKeyMaps } from './constants' +// 类型定义 +type LocaleKeyMap = keyof typeof i18nKeyMaps +type LocaleMessages = Record + +interface CreateI18nParams { + locale: string + messages: Record +} + // 此处处理TinyVue组件库的国际化zhCN --> zh_CN -const customCreateI18n = ({ locale, messages }) => { - const newMessages = {} +const customCreateI18n = ({ locale, messages }: CreateI18nParams) => { + const newMessages: Record = {} Object.keys(messages).forEach((key) => { - const lang = i18nKeyMaps[key] + const lang = i18nKeyMaps[key as LocaleKeyMap] newMessages[lang] = messages[key] }) @@ -30,7 +39,9 @@ const customCreateI18n = ({ locale, messages }) => { }) } -const customI18n = tinyLocale.initI18n({ +// 初始化 TinyVue 组件库的 i18n 实例 +// 注意:@opentiny/vue-locale 可能缺少完整的类型定义,此处暂时使用 any 类型 +const customI18n: any = tinyLocale.initI18n({ i18n: { locale: i18nKeyMaps.zhCN }, createI18n: customCreateI18n, messages: {} @@ -39,6 +50,9 @@ const customI18n = tinyLocale.initI18n({ // 合并组件库的i18n配置 defineCustomI18n(customI18n) +// 导出类型 +export type { LocaleKeyMap, LocaleMessages, CreateI18nParams } + export { I18nInjectionKey, i18nKeyMaps } // i18n对象可以多处使用。模板中直接使用$t,setup环境或普通环境中可以引入后使用i18n.global.t diff --git a/packages/common/js/importMap.js b/packages/common/js/importMap.ts similarity index 100% rename from packages/common/js/importMap.js rename to packages/common/js/importMap.ts diff --git a/packages/common/js/linter.js b/packages/common/js/linter.ts similarity index 58% rename from packages/common/js/linter.js rename to packages/common/js/linter.ts index edfb3f1a61..7b8465e101 100644 --- a/packages/common/js/linter.js +++ b/packages/common/js/linter.ts @@ -11,9 +11,44 @@ */ import eslintWorkerUrl from './worker-files/eslint.worker?worker&url' +import type * as monaco from 'monaco-editor' -export const initLinter = (editor, monacoInstance, state) => { - let workerUrl = new URL(eslintWorkerUrl, import.meta.url) +// Linter 状态接口 +interface LinterState { + hasError: boolean + [key: string]: any +} + +// ESLint 检查结果标记接口 +interface ESLintMarker { + severity: monaco.MarkerSeverity | 'Error' | 'Warning' | 'Info' | 'Hint' + message: string + startLineNumber: number + startColumn: number + endLineNumber: number + endColumn: number + source?: string + code?: string +} + +// Worker 响应消息接口 +interface WorkerResponseMessage { + markers: ESLintMarker[] + version: number +} + +// Worker 请求消息接口 +interface WorkerRequestMessage { + code: string + version: number +} + +export const initLinter = ( + editor: monaco.editor.IStandaloneCodeEditor, + monacoInstance: typeof monaco, + state: LinterState +): Worker => { + let workerUrl: URL | string = new URL(eslintWorkerUrl, import.meta.url) // 线上环境,存在 worker 资源跨域的情况 if (workerUrl.origin !== location.origin) { @@ -26,7 +61,7 @@ export const initLinter = (editor, monacoInstance, state) => { const worker = new Worker(workerUrl, { type: 'module' }) // 监听 ESLint web worker 的返回 - worker.onmessage = function (event) { + worker.onmessage = function (event: MessageEvent) { const { markers, version } = event.data const model = editor.getModel() @@ -34,16 +69,16 @@ export const initLinter = (editor, monacoInstance, state) => { // 判断当前 model 的 versionId 与请求时是否一致 if (model && model.getVersionId() === version) { - monacoInstance.editor.setModelMarkers(model, 'ESLint', markers) + monacoInstance.editor.setModelMarkers(model, 'ESLint', markers as monaco.editor.IMarkerData[]) } } return worker } -let timer = null +let timer: ReturnType | null = null -export const lint = (model, worker) => { +export const lint = (model: monaco.editor.ITextModel, worker: Worker): void => { if (timer) { clearTimeout(timer) } @@ -55,6 +90,6 @@ export const lint = (model, worker) => { code: model.getValue(), // 发起 ESLint 静态检查时,携带 versionId version: model.getVersionId() - }) + } as WorkerRequestMessage) }, 500) } diff --git a/packages/common/js/monitor.js b/packages/common/js/monitor.ts similarity index 90% rename from packages/common/js/monitor.js rename to packages/common/js/monitor.ts index 7f3152588e..8e9efca459 100644 --- a/packages/common/js/monitor.js +++ b/packages/common/js/monitor.ts @@ -10,7 +10,7 @@ * */ -import { requestEvent } from './http.js' +import { requestEvent } from './http' let monitorUrl = '' @@ -24,10 +24,10 @@ let monitorUrl = '' */ const getUrlUnit = () => { const urlUnit = window.location?.search?.substring(1)?.split('&') - const unit = {} + const unit: Record = {} if (urlUnit.length) { urlUnit.forEach((item) => { - let unitItem = item.split('=') + const unitItem = item.split('=') unit[unitItem[0]] = unitItem[1] }) } @@ -36,7 +36,7 @@ const getUrlUnit = () => { } const globalMonitoring = () => { - window.onerror = function (errorMessage, scriptURI, lineNo, columnNo, error) { + window.onerror = function (errorMessage, scriptURI, _lineNo, columnNo, error) { requestEvent(monitorUrl, { event_type: 'design_JSError', url: window.location.href, @@ -59,7 +59,7 @@ const promiseMonitoring = () => { event.preventDefault() let message let matchResult = '' - let reason = event.reason + const reason = event.reason if (typeof reason === 'string') { message = reason } else if (typeof reason === 'object') { @@ -97,7 +97,7 @@ export const iframeMonitoring = () => { return false } - window.frames[0].onerror = function (errorMessage, scriptURI, lineNo, columnNo, error) { + window.frames[0].onerror = function (errorMessage, scriptURI, _lineNo, columnNo, error) { requestEvent(monitorUrl, { event_type: 'design_iframeError', url: window.location.href, @@ -112,7 +112,7 @@ export const iframeMonitoring = () => { } } -export const initMonitor = (url) => { +export const initMonitor = (url: string) => { monitorUrl = url globalMonitoring() promiseMonitoring() diff --git a/packages/common/js/preview.js b/packages/common/js/preview.ts similarity index 82% rename from packages/common/js/preview.js rename to packages/common/js/preview.ts index 52f842c405..9be6251801 100644 --- a/packages/common/js/preview.js +++ b/packages/common/js/preview.ts @@ -27,14 +27,44 @@ import { isDevelopEnv } from './environments' const { deepClone } = utils +// 消息事件数据接口 +interface PreviewMessageEventData { + source?: 'preview' | 'designer' + event?: string + type?: string + data?: any +} + +// 预览参数接口 +interface PreviewParams { + previewType?: string + history?: string + [key: string]: any +} + +interface PackageDep { + name: string + package: string + version: string + script: string + css?: string +} +// 脚本依赖项接口 +interface ScriptDep { + [key: string]: PackageDep +} + // 保存预览窗口引用 -let previewWindow = null +let previewWindow: Window | null = null -const getScriptAndStyleDeps = () => { +const getScriptAndStyleDeps = (): { + scripts: ScriptDep + styles: string[] +} => { const { scripts, styles } = useMaterial().getCanvasDeps() const utilsDeps = useResource().getUtilsDeps() - const scriptsDeps = [...scripts, ...utilsDeps].reduce((res, item) => { + const scriptsDeps = [...scripts, ...utilsDeps].reduce((res, item: PackageDep) => { res[item.package] = res[item.package] || item.script return res @@ -47,7 +77,12 @@ const getScriptAndStyleDeps = () => { } } -const getSchemaParams = async () => { +const getSchemaParams = async (): Promise<{ + currentPage: any + ancestors: any[] + scripts: Record + styles: string[] +}> => { const { isBlock, getPageSchema, getCurrentPage, getSchema } = useCanvas() const isBlockPreview = isBlock() const { scripts, styles } = getScriptAndStyleDeps() @@ -88,14 +123,14 @@ const getSchemaParams = async () => { } // 当 schema 变化时发送更新 -const sendSchemaUpdate = (data) => { - previewWindow.postMessage( +const sendSchemaUpdate = (data: Awaited>) => { + previewWindow!.postMessage( { source: 'designer', type: 'schema', data }, - previewWindow.origin || window.location.origin + previewWindow!.origin || window.location.origin ) } @@ -163,7 +198,7 @@ export const setupSchemaChangeListener = () => { // 监听来自预览页面的消息 const setupMessageListener = () => { - window.addEventListener('message', async (event) => { + window.addEventListener('message', async (event: MessageEvent) => { const parsedOrigin = new URL(event.origin) const parsedHost = new URL(window.location.href) // 确保消息来源安全 @@ -171,7 +206,7 @@ const setupMessageListener = () => { const { event: eventType, source } = event.data || {} // 通过 heartbeat 消息来重新建立连接,避免刷新页面后 previewWindow 为 null if (source === 'preview' && eventType === 'connect' && !previewWindow) { - previewWindow = event.source + previewWindow = event.source as Window setupSchemaChangeListener() } @@ -197,9 +232,12 @@ const setupMessageListener = () => { // 初始化消息监听 setupMessageListener() -const handleHistoryPreview = (params, url) => { - let historyPreviewWindow = null - const handlePreviewReady = (event) => { +const handleHistoryPreview = ( + params: PreviewParams & Partial>>, + url: string +) => { + let historyPreviewWindow: Window | null = null + const handlePreviewReady = (event: MessageEvent): void => { if (event.origin === window.location.origin || event.origin.includes(window.location.hostname)) { const { event: eventType, source } = event.data || {} if (source === 'preview' && eventType === 'onMounted' && historyPreviewWindow) { @@ -230,7 +268,7 @@ const handleHistoryPreview = (params, url) => { historyPreviewWindow = window.open(url, '_blank') } -const getQueryParams = (params = {}, isHistory = false) => { +const getQueryParams = (params: PreviewParams = {}, isHistory: boolean = false): string => { const paramsMap = new URLSearchParams(location.search) const tenant = paramsMap.get('tenant') || '' const pageId = paramsMap.get('pageid') @@ -260,7 +298,7 @@ const getQueryParams = (params = {}, isHistory = false) => { return query } -const open = (params = {}, isHistory = false) => { +const open = (params: PreviewParams = {}, isHistory: boolean = false) => { const href = window.location.href.split('?')[0] || './' const { scripts, styles } = getScriptAndStyleDeps() const query = getQueryParams(params, isHistory) @@ -300,6 +338,6 @@ const open = (params = {}, isHistory = false) => { setupSchemaChangeListener() } -export const previewPage = (params = {}, isHistory = false) => { +export const previewPage = (params: PreviewParams = {}, isHistory: boolean = false) => { open(params, isHistory) } diff --git a/packages/common/js/verification.js b/packages/common/js/verification.ts similarity index 74% rename from packages/common/js/verification.js rename to packages/common/js/verification.ts index e217f3d6bd..32264ac2be 100644 --- a/packages/common/js/verification.js +++ b/packages/common/js/verification.ts @@ -12,19 +12,19 @@ export const REGEXP_EVENT_NAME = /^[a-z]+([A-Z][a-z]*)*$/ -export const verifyEventName = (name) => REGEXP_EVENT_NAME.test(name) +export const verifyEventName = (name: string) => REGEXP_EVENT_NAME.test(name) export const REGEXP_BLOCK_NAME = /^([A-Z][a-z0-9]*){2,}$/ -export const verifyBlockName = (string) => REGEXP_BLOCK_NAME.test(string) +export const verifyBlockName = (string: string) => REGEXP_BLOCK_NAME.test(string) export const REGEXP_BLOCK_ID = /^[A-Za-z]+$/ -export const verifyBlockId = (string) => REGEXP_BLOCK_ID.test(string) +export const verifyBlockId = (string: string) => REGEXP_BLOCK_ID.test(string) export const REGEXP_BLOCK_PATH = /^[\w-][/\w-]*?[\w-]*?$/ -export const verifyBlockPath = (string) => !string || REGEXP_BLOCK_PATH.test(string) +export const verifyBlockPath = (string: string) => !string || REGEXP_BLOCK_PATH.test(string) export const REGEXP_GROUP_NAME = /^[\u4e00-\u9fa5a-zA-Z0-9_-]+$/ @@ -42,6 +42,6 @@ export const REGEXP_JS_VAR = /^[a-zA-Z_]\w*$/ export const REGEXP_JS_VAR_SYMBOL = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/ -export const verifyJsVarName = (name) => REGEXP_JS_VAR.test(name) +export const verifyJsVarName = (name: string) => REGEXP_JS_VAR.test(name) -export const verifyJsVarSymbolName = (name) => REGEXP_JS_VAR_SYMBOL.test(name) +export const verifyJsVarSymbolName = (name: string) => REGEXP_JS_VAR_SYMBOL.test(name) diff --git a/packages/common/js/vscodeGenerateFile.js b/packages/common/js/vscodeGenerateFile.js deleted file mode 100644 index 357f6effc9..0000000000 --- a/packages/common/js/vscodeGenerateFile.js +++ /dev/null @@ -1,113 +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 { getMetaApi, META_SERVICE } from '@opentiny/tiny-engine-meta-register' - -/** - * vscode生成路由文件 - * - * - * @param { json } params - { - pageId:"123", // 当前页面ID - componentsTree:{} // 整个应用的路由对象 - } - * @returns { string } - */ - -const generateRouter = (params) => getMetaApi(META_SERVICE.Http).post('/generate/api/generateRouter', params) - -/** - * vscode生成本地国际化词条 - * - * @param { json } params - { - - key:'lowcode.preview' // 词条的唯一key值 - contents: { - en_US: "preview", // 英文 - zh_CN: "预览" // 中文 - } - } - * @returns { string } - */ - -const generateI18n = (params) => getMetaApi(META_SERVICE.Http).post('/generate/api/generateI18n', params) - -/** - * vscode生成区块 - * - * @param { json } params - { - - schema: '', // 区块的schema - blockPath: ''// 区块的分类ID,或者说传保存路径 -} - * @returns { string } - */ - -const generateBlock = (params) => getMetaApi(META_SERVICE.Http).post('/generate/api/generateBlock', params) - -/** - * vscode生成页面 - * - * @param { json } params - { - id: 2645, // 页面ID - name: 'xh-test', // 页面名称 - page_content:{} //页面的schema - } - * @returns { string } - */ -const generatePage = (params) => getMetaApi(META_SERVICE.Http).post('/generate/api/generatePage', params) - -/** - * vscode生成数据源 - * - * @param { json } params - { - list:[], // 新的数据源合集 - dataHanlder:{ - //全局的处理函数,可以从apps/schema/:id 接口返回中的dataSource中获取 - type: "JSFunction", - value: "" - } -} - * @returns { string } - */ -const generateDataSource = (params) => getMetaApi(META_SERVICE.Http).post('/generate/api/generateDataSource', params) - -/** - * vscode生成桥接源 - * - * @param { json } params - { - //桥接源合集,可以从apps/schema/:id 接口返回中的bridge中获取 - bridge:[] - } - * @returns { string } - */ -const generateBridge = (params) => getMetaApi(META_SERVICE.Http).post('/generate/api/generateBridge', params) - -/** - * vscode生成工具类 - * - * @param { json } params - { - //桥接源合集,可以从apps/schema/:id 接口返回中的utils中获取 - utils:[] - } - * @returns { string } - */ -const generateUtil = (params) => getMetaApi(META_SERVICE.Http).post('/generate/api/generateUtil', params) - -export { generateRouter, generateI18n, generateBlock, generatePage, generateDataSource, generateBridge, generateUtil } diff --git a/packages/common/js/vscodeGenerateFile.ts b/packages/common/js/vscodeGenerateFile.ts new file mode 100644 index 0000000000..1f048cc9ad --- /dev/null +++ b/packages/common/js/vscodeGenerateFile.ts @@ -0,0 +1,186 @@ +/** + * 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 { getMetaApi, META_SERVICE } from '@opentiny/tiny-engine-meta-register' + +// ============ 类型定义 ============ + +/** + * 路由生成参数接口 + */ +interface GenerateRouterParams { + pageId: string + componentsTree: any +} + +/** + * 国际化词条参数接口 + */ +interface GenerateI18nParams { + key: string + contents: { + en_US: string + zh_CN: string + [key: string]: string // 支持其他语言 + } +} + +/** + * 区块生成参数接口 + */ +interface GenerateBlockParams { + schema: any + blockPath: string +} + +/** + * 页面生成参数接口 + */ +interface GeneratePageParams { + id: number + name: string + page_content: any +} + +/** + * 数据源生成参数接口 + */ +interface GenerateDataSourceParams { + list: any[] + dataHandler: { + type: string + value: string + } +} + +/** + * 桥接源生成参数接口 + */ +interface GenerateBridgeParams { + bridge: any[] +} + +/** + * 工具类生成参数接口 + */ +interface GenerateUtilParams { + utils: any[] +} + +// ============ 函数实现 ============ + +/** + * vscode生成路由文件 + * + * + * @param { json } params + { + pageId:"123", // 当前页面ID + componentsTree:{} // 整个应用的路由对象 + } + * @returns { string } + */ + +const generateRouter = (params: GenerateRouterParams): Promise => + getMetaApi(META_SERVICE.Http).post('/generate/api/generateRouter', params) + +/** + * vscode生成本地国际化词条 + * + * @param { json } params + { + + key:'lowcode.preview' // 词条的唯一key值 + contents: { + en_US: "preview", // 英文 + zh_CN: "预览" // 中文 + } + } + * @returns { string } + */ + +const generateI18n = (params: GenerateI18nParams): Promise => + getMetaApi(META_SERVICE.Http).post('/generate/api/generateI18n', params) + +/** + * vscode生成区块 + * + * @param { json } params + { + + schema: '', // 区块的schema + blockPath: ''// 区块的分类ID,或者说传保存路径 + } + * @returns { string } + */ + +const generateBlock = (params: GenerateBlockParams): Promise => + getMetaApi(META_SERVICE.Http).post('/generate/api/generateBlock', params) + +/** + * vscode生成页面 + * + * @param { json } params + { + id: 2645, // 页面ID + name: 'xh-test', // 页面名称 + page_content:{} //页面的schema + } + * @returns { string } + */ +const generatePage = (params: GeneratePageParams): Promise => + getMetaApi(META_SERVICE.Http).post('/generate/api/generatePage', params) + +/** + * vscode生成数据源 + * + * @param { json } params + { + list:[], // 新的数据源合集 + dataHandler:{ + //全局的处理函数,可以从apps/schema/:id 接口返回中的dataSource中获取 + type: "JSFunction", + value: "" + } +} + * @returns { string } + */ +const generateDataSource = (params: GenerateDataSourceParams): Promise => + getMetaApi(META_SERVICE.Http).post('/generate/api/generateDataSource', params) + +/** + * vscode生成桥接源 + * + * @param { json } params + { + //桥接源合集,可以从apps/schema/:id 接口返回中的bridge中获取 + bridge:[] + } + * @returns { string } + */ +const generateBridge = (params: GenerateBridgeParams): Promise => + getMetaApi(META_SERVICE.Http).post('/generate/api/generateBridge', params) + +/** + * vscode生成工具类 + * + * @param { json } params + { + //桥接源合集,可以从apps/schema/:id 接口返回中的utils中获取 + utils:[] + } + * @returns { string } + */ +const generateUtil = (params: GenerateUtilParams): Promise => + getMetaApi(META_SERVICE.Http).post('/generate/api/generateUtil', params) + +export { generateRouter, generateI18n, generateBlock, generatePage, generateDataSource, generateBridge, generateUtil } diff --git a/packages/common/package.json b/packages/common/package.json index c1d4e8e676..67dd0ea953 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -11,8 +11,14 @@ "module": "dist/index.js", "type": "module", "exports": { - ".": "./dist/index.js", - "./js/": "./dist/js/" + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + }, + "./js/*": { + "types": "./dist/js/*.d.ts", + "import": "./dist/js/*.js" + } }, "files": [ "dist", @@ -52,10 +58,15 @@ }, "devDependencies": { "@opentiny/tiny-engine-vite-plugin-meta-comments": "workspace:*", + "@types/babel__generator": "^7.27.0", + "@types/babel__traverse": "^7.28.0", + "@types/css-tree": "2.3.11", + "@types/prettier": "^2.7.0", "@vitejs/plugin-vue": "^5.1.2", "@vitejs/plugin-vue-jsx": "^4.0.1", "glob": "^10.3.4", "vite": "^5.4.2", + "vite-plugin-dts": "^4.5.4", "vite-plugin-static-copy": "^1.0.6" }, "peerDependencies": { diff --git a/packages/common/tsconfig.json b/packages/common/tsconfig.json new file mode 100644 index 0000000000..a5c892725a --- /dev/null +++ b/packages/common/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.base.json", + "include": [ + "./index.ts", + "./js/**/*.ts", + "./i18n/*.ts", + "./component/**/*.vue", + "./component/**/*.ts", + "./component/**/*.tsx", + "./composables/**/*.ts" + ] +} diff --git a/packages/common/vite.config.ts b/packages/common/vite.config.ts index 93ce0ec32b..6b95445987 100644 --- a/packages/common/vite.config.ts +++ b/packages/common/vite.config.ts @@ -18,8 +18,9 @@ import { glob } from 'glob' import { fileURLToPath } from 'node:url' import generateComments from '@opentiny/tiny-engine-vite-plugin-meta-comments' import { viteStaticCopy } from 'vite-plugin-static-copy' +import dts from 'vite-plugin-dts' -const jsEntries = glob.sync('./js/**/*.js').map((file) => { +const jsEntries = glob.sync('./js/**/*.{js,ts}').map((file) => { return [file.slice(0, file.length - path.extname(file).length), fileURLToPath(new URL(file, import.meta.url))] }) @@ -37,6 +38,10 @@ export default defineConfig({ dest: '.' } ] + }), + dts({ + tsconfigPath: path.resolve(__dirname, './tsconfig.json'), + rollupTypes: false // 不合并,为每个入口生成独立的 .d.ts }) ], publicDir: false,