From 61aa2cd82cbba401a537a81a8558a145a884386e Mon Sep 17 00:00:00 2001 From: Jack Liu Date: Mon, 29 Apr 2024 17:30:14 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0openinula=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E8=BD=AC=E6=8D=A2=E8=84=9A=E6=9C=AC=20(#1567)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internals/cli/package.json | 8 +- .../cli/public/template/openinula/index.ts | 10 + .../public/template/openinula/package.json | 25 + .../public/template/openinula/src/index.ts | 15 + .../template/openinula/src/mobile-first.jsx | 38 ++ .../public/template/openinula/src/mobile.jsx | 39 ++ .../cli/public/template/openinula/src/pc.jsx | 39 ++ .../commands/build/build-entry-openinula.ts | 98 ++++ .../src/commands/build/build-ui-openinula.ts | 479 ++++++++++++++++++ internals/cli/src/commands/build/index.ts | 3 +- .../create/common-mapping-openinula.json | 12 + .../create/create-mapping-openinula.ts | 111 ++++ .../commands/create/create-ui-openinula.ts | 236 +++++++++ internals/cli/src/commands/create/index.ts | 1 + internals/cli/src/index.ts | 34 +- package.json | 32 +- packages/openinula/README.md | 239 +++++++++ packages/openinula/index.ts | 14 +- packages/openinula/mobile-first.ts | 17 + packages/openinula/mobile.ts | 19 + packages/openinula/modules.json | 106 ++++ packages/openinula/package.json | 8 +- packages/openinula/pc.ts | 19 + packages/openinula/src/badge/index.ts | 3 + packages/openinula/src/badge/package.json | 19 + packages/openinula/src/badge/src/index.ts | 13 + packages/openinula/src/badge/src/mobile.jsx | 70 +++ packages/openinula/src/badge/src/pc.jsx | 78 +++ packages/openinula/src/button/index.ts | 3 + packages/openinula/src/button/package.json | 19 + packages/openinula/src/button/src/index.ts | 15 + .../openinula/src/button/src/mobile-first.jsx | 107 ++++ packages/openinula/src/button/src/mobile.jsx | 88 ++++ packages/openinula/src/button/src/pc.jsx | 99 ++++ packages/openinula/src/button/src/token.ts | 66 +++ packages/openinula/src/common/src/fiber.ts | 24 +- packages/openinula/src/common/src/utils.ts | 4 +- packages/openinula/src/common/src/vm.ts | 4 +- .../openinula/src/common/src/vue-instance.ts | 6 +- packages/openinula/src/switch/index.ts | 7 + packages/openinula/src/switch/package.json | 25 + packages/openinula/src/switch/src/index.ts | 31 ++ packages/openinula/src/switch/src/mobile.jsx | 59 +++ packages/openinula/src/switch/src/pc.jsx | 67 +++ 44 files changed, 2358 insertions(+), 51 deletions(-) create mode 100644 internals/cli/public/template/openinula/index.ts create mode 100644 internals/cli/public/template/openinula/package.json create mode 100644 internals/cli/public/template/openinula/src/index.ts create mode 100644 internals/cli/public/template/openinula/src/mobile-first.jsx create mode 100644 internals/cli/public/template/openinula/src/mobile.jsx create mode 100644 internals/cli/public/template/openinula/src/pc.jsx create mode 100644 internals/cli/src/commands/build/build-entry-openinula.ts create mode 100644 internals/cli/src/commands/build/build-ui-openinula.ts create mode 100644 internals/cli/src/commands/create/common-mapping-openinula.json create mode 100644 internals/cli/src/commands/create/create-mapping-openinula.ts create mode 100644 internals/cli/src/commands/create/create-ui-openinula.ts create mode 100644 packages/openinula/README.md create mode 100644 packages/openinula/mobile-first.ts create mode 100644 packages/openinula/mobile.ts create mode 100644 packages/openinula/modules.json create mode 100644 packages/openinula/pc.ts create mode 100644 packages/openinula/src/badge/index.ts create mode 100644 packages/openinula/src/badge/package.json create mode 100644 packages/openinula/src/badge/src/index.ts create mode 100644 packages/openinula/src/badge/src/mobile.jsx create mode 100644 packages/openinula/src/badge/src/pc.jsx create mode 100644 packages/openinula/src/button/index.ts create mode 100644 packages/openinula/src/button/package.json create mode 100644 packages/openinula/src/button/src/index.ts create mode 100644 packages/openinula/src/button/src/mobile-first.jsx create mode 100644 packages/openinula/src/button/src/mobile.jsx create mode 100644 packages/openinula/src/button/src/pc.jsx create mode 100644 packages/openinula/src/button/src/token.ts create mode 100644 packages/openinula/src/switch/index.ts create mode 100644 packages/openinula/src/switch/package.json create mode 100644 packages/openinula/src/switch/src/index.ts create mode 100644 packages/openinula/src/switch/src/mobile.jsx create mode 100644 packages/openinula/src/switch/src/pc.jsx diff --git a/internals/cli/package.json b/internals/cli/package.json index 987517fb0..77e500a83 100644 --- a/internals/cli/package.json +++ b/internals/cli/package.json @@ -51,7 +51,11 @@ "build:entry-react": "esno src/index.ts build:entry-react", "create:mapping-react": "esno src/commands/create/create-mapping-react.ts", "build:react": "esno src/index.ts build:react", - "build:chartTheme": "esno src/index.ts build:chartTheme" + "// ----------------------OpenInula脚本---------------------- ": "", + "build:entry-openinula": "esno src/index.ts build:entry-openinula", + "create:mapping-openinula": "esno src/commands/create/create-mapping-openinula.ts", + "build:openinula": "esno src/index.ts build:openinula", + "create:ui-openinula": "esno src/index.ts create:ui-openinula" }, "dependencies": { "@vue/babel-helper-vue-jsx-merge-props": "^1.4.0", @@ -62,4 +66,4 @@ "vite-plugin-svgr": "^3.2.0", "vite-svg-loader": "^3.6.0" } -} +} \ No newline at end of file diff --git a/internals/cli/public/template/openinula/index.ts b/internals/cli/public/template/openinula/index.ts new file mode 100644 index 000000000..7fa4e64fc --- /dev/null +++ b/internals/cli/public/template/openinula/index.ts @@ -0,0 +1,10 @@ +import [[UNAME]] from './src/index[[SUFFIX]]' +import '@opentiny/vue-theme/[[NAME]]/index.less' +import { version } from './package.json' + + +[[UNAME]].version = version + + + +export default [[UNAME]] diff --git a/internals/cli/public/template/openinula/package.json b/internals/cli/public/template/openinula/package.json new file mode 100644 index 000000000..9a8b2988a --- /dev/null +++ b/internals/cli/public/template/openinula/package.json @@ -0,0 +1,25 @@ +{ + "name": "@opentiny/openinula-[[NAME]]", + "version": "3.[[MINOR]].0", + "description": "", + "main": "lib/index.js", + "module": "index.ts", + "sideEffects": false, + "type": "module", + "scripts": { + "build": "pnpm -w build:ui-openinula $npm_package_name", + "//postversion": "pnpm build" + }, + "devDependencies": { + "@opentiny-internal/vue-test-utils": "workspace:*", + "vitest": "^0.31.0" + }, + "dependencies": { + "@opentiny/vue-renderless": "workspace:~", + "@opentiny/openinula-common": "workspace:~", + "@opentiny/openinula-icon": "workspace:~", + "@opentiny/vue-theme": "workspace:~", + "@opentiny/vue-theme-mobile": "workspace:~" + }, + "license": "MIT" +} \ No newline at end of file diff --git a/internals/cli/public/template/openinula/src/index.ts b/internals/cli/public/template/openinula/src/index.ts new file mode 100644 index 000000000..e79744199 --- /dev/null +++ b/internals/cli/public/template/openinula/src/index.ts @@ -0,0 +1,15 @@ +import pc from './pc' +import mobile from './mobile' +import mobileFirst from './mobile-first' + +export default function (props) { + const { tiny_mode = 'pc' } = props + + const S = { + pc, + mobile, + 'mobile-first': mobileFirst + }[tiny_mode] + + return S(props) +} diff --git a/internals/cli/public/template/openinula/src/mobile-first.jsx b/internals/cli/public/template/openinula/src/mobile-first.jsx new file mode 100644 index 000000000..6e02ba86e --- /dev/null +++ b/internals/cli/public/template/openinula/src/mobile-first.jsx @@ -0,0 +1,38 @@ +import { renderless, api } from '@opentiny/vue-renderless/[[NAME]]/vue' +import { vc, If, Component, Slot, Transition, useSetup, useVm } from '@opentiny/openinula-common' +import { IconClose, IconSuccess, IconError, IconHelp, IconWarning } from '@opentiny/openinula-icon' + +const $constants = { + +} + +export default function [[UNAME]](props) { + + + const { + _constants, + + } = props + + const defaultProps = Object.assign( + { + + }, + props + ) + + const { ref, current: vm, parent } = useVm() + + const { [[API]] } = useSetup({ + props: defaultProps, + renderless, + api, + constants: _constants, + vm, + parent + }) + + return ( + [[TEMPLATE]] + ) +} diff --git a/internals/cli/public/template/openinula/src/mobile.jsx b/internals/cli/public/template/openinula/src/mobile.jsx new file mode 100644 index 000000000..4f8e05b54 --- /dev/null +++ b/internals/cli/public/template/openinula/src/mobile.jsx @@ -0,0 +1,39 @@ +import { renderless, api } from '@opentiny/vue-renderless/[[NAME]]/vue' +import { vc, If, Component, Slot, Transition, useSetup, useVm } from '@opentiny/openinula-common' +import { IconClose, IconSuccess, IconError, IconHelp, IconWarning } from '@opentiny/openinula-icon' +import '@opentiny/vue-theme-mobile/[[NAME]]/index.less' + +const $constants = { + +} + +export default function [[UNAME]](props) { + + const { + _constants, + + } = props + + const defaultProps = Object.assign( + { + + }, + props + ) + + + const { ref, current: vm, parent } = useVm() + + const { [[API]] } = useSetup({ + props: defaultProps, + renderless, + api, + constants: _constants, + vm, + parent + }) + + return ( + [[TEMPLATE]] + ) +} diff --git a/internals/cli/public/template/openinula/src/pc.jsx b/internals/cli/public/template/openinula/src/pc.jsx new file mode 100644 index 000000000..f8ec671cb --- /dev/null +++ b/internals/cli/public/template/openinula/src/pc.jsx @@ -0,0 +1,39 @@ +import { renderless, api } from '@opentiny/vue-renderless/[[NAME]]/vue' +import { vc, If, Component, Slot, Transition, useSetup, useVm } from '@opentiny/openinula-common' +import { IconClose, IconSuccess, IconError, IconHelp, IconWarning } from '@opentiny/openinula-icon' +import '@opentiny/vue-theme/[[NAME]]/index.less' + +const $constants = { + +} + +export default function [[UNAME]](props) { + + const { + _constants, + + } = props + + const defaultProps = Object.assign( + { + + }, + props + ) + + + const { ref, current: vm, parent } = useVm() + + const { [[API]] } = useSetup({ + props: defaultProps, + renderless, + api, + constants: _constants, + vm, + parent + }) + + return ( + [[TEMPLATE]] + ) +} diff --git a/internals/cli/src/commands/build/build-entry-openinula.ts b/internals/cli/src/commands/build/build-entry-openinula.ts new file mode 100644 index 000000000..ef03ad0e6 --- /dev/null +++ b/internals/cli/src/commands/build/build-entry-openinula.ts @@ -0,0 +1,98 @@ +/** + * 生成入口文件,包括 pc.js / mobile.js / mobile-first.js / index.js + */ +import fs from 'fs-extra' +import { EOL as endOfLine } from 'node:os' +import { pathFromWorkspaceRoot, capitalizeKebabCase, prettierFormat, logGreen } from '../../shared/utils' +import { getAllModules2 } from './build-ui-openinula' +import handlebarsRender from './handlebars.render' + +const version = (({ key }) => { + const packageJSON = fs.readJSONSync(pathFromWorkspaceRoot('packages/openinula/package.json')) + const packageJsonOption = packageJSON[key] || packageJSON + + return packageJsonOption +})({ key: 'version' }) + +const outputDir = 'packages/openinula' + +const fileNames = { + all: 'index.ts', + pc: 'pc.ts', + mobile: 'mobile.ts', + 'mobile-first': 'mobile-first.ts' +} + +function getMainTemplate() { + return `{{{include}}} + import { $prefix } from '@opentiny/openinula-common' + + const components = [{{{components}}}] + + export const version = '${version}' + + export { + {{{components}}} + } + + export default { + {{{components}}} + } as any + ` +} + +function getComponents(mode) { + const modules = getAllModules2() + + const components = modules + .filter((item) => item.type === 'component') + .filter((item) => mode === 'all' || !item.mode || item.mode.includes(mode)) + + return components +} + +function createEntry(mode) { + const OUTPUT_PATH = pathFromWorkspaceRoot(outputDir, fileNames[mode]) + const MAIN_TEMPLATE = getMainTemplate({ mode }) + const includeTemplate: string[] = [] + const componentsTemplate: string[] = [] + const components = getComponents(mode) + const PKG_PATH = pathFromWorkspaceRoot(outputDir, 'package.json') + const PKGContent = fs.readJSONSync(PKG_PATH) + const PKGDeps = { + '@opentiny/openinula-common': 'workspace:~' + } + + components.forEach((item) => { + const component = capitalizeKebabCase(item.name) + PKGDeps[item.importName] = 'workspace:~' + componentsTemplate.push(` ${component}`) + const importName = mode === 'all' ? item.importName : `${item.importName}/src/${mode}` + includeTemplate.push(`import ${item.name} from '${importName}'`) + }) + + if (mode === 'all') { + PKGContent.dependencies = PKGDeps + fs.writeFileSync(PKG_PATH, JSON.stringify(PKGContent, null, 2)) + } + + const template = handlebarsRender({ + template: MAIN_TEMPLATE, + data: { + include: includeTemplate.join(endOfLine), + components: componentsTemplate.join(',' + endOfLine) + } + }) + + const output = prettierFormat({ str: template }) + + fs.writeFileSync(OUTPUT_PATH, output) +} + +export function buildEntryOpenInula() { + ;['all', 'pc', 'mobile', 'mobile-first'].forEach(createEntry) + + logGreen( + `npm run build:entry done. [${outputDir}/index.ts,${outputDir}/pc.ts,${outputDir}/mobile.ts,${outputDir}/mobile-first.ts]` + ) +} diff --git a/internals/cli/src/commands/build/build-ui-openinula.ts b/internals/cli/src/commands/build/build-ui-openinula.ts new file mode 100644 index 000000000..f066e0ce1 --- /dev/null +++ b/internals/cli/src/commands/build/build-ui-openinula.ts @@ -0,0 +1,479 @@ +import { logGreen, kebabCase, capitalizeKebabCase } from '../../shared/utils' +import { pathFromWorkspaceRoot } from '../../shared/utils' +import fg from 'fast-glob' +import path from 'node:path' +import { build, defineConfig } from 'vite' +import { getBabelOutputPlugin } from '@rollup/plugin-babel' +import { external } from '../../shared/config' +import type { Plugin, NormalizedOutputOptions, OutputBundle } from 'rollup' +import fs from 'fs-extra' +import { sync as findUpSync } from 'find-up' +import replace from 'rollup-plugin-replace' + +// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires +const moduleMap = require(pathFromWorkspaceRoot('packages/openinula/modules.json')) +type mode = 'pc' | 'mobile' | 'mobile-first' + +const pathFromPackages = (...args) => pathFromWorkspaceRoot('packages', ...args) + +let scopeName = '@opentiny' +let buildVersion = '1.0.0' + +export interface Module2 { + /** 源码路径,如 vue/src/button/index.ts */ + path: string + /** 模块类型,可选 component, template, module */ + type: 'component' | 'template' | 'module' + /** 是否排除构建,例如组件尚未开发完,设置 true */ + exclude?: boolean + /** 组件类型支持的模式 */ + mode?: mode[] + /** 模块名称,如 Button */ + name: string + /** 模块构建物路径,如 vue/button/lib/index */ + libPath: string + /** 模块名,如 @opentiny/vue/vue/lib/button/src,@deprecated */ + libName?: string + /** 模块的npm包名,如 @opentiny/vue-button */ + importName: string + /** 构建物文件名,@deprecated */ + tmpName?: string + /** 全局变量名,如 TinyButton */ + global?: string + /** 组件名的大写形态,如 Button */ + UpperName?: string + /** 组件名的小写形态,如 button */ + LowerName?: string + /** 模块的路径 */ + parentDir?: string[] +} + +function getEntryTasks(): Module2[] { + // 读取TinyVue组件库入口文件 + return ['index', 'pc', 'mobile'].map((mode) => ({ + path: `openinula/${mode}.ts`, + dtsRoot: true, + libPath: `openinula/${mode}`, + type: 'module', + name: kebabCase({ str: `${scopeName}/openinula` }), + global: capitalizeKebabCase('opentinyOpenInula'), + importName: `${scopeName}/openinula` + })) +} + +export function getAllModules2(): Module2[] { + return getSortModules({ filterIntercept: () => true }) +} + +const getSortModules = ({ filterIntercept }: { filterIntercept: Function }) => { + let modules: Module2[] = [] + let componentCount = 0 + const importName = `${scopeName}/openinula` + Object.entries(moduleMap).forEach(([key, module]) => { + let component = module as Module2 + + component.name = key + // filterIntercept过滤筛选命令行传过来的组件名称,只输出命令行传递过来的组件 + if (filterIntercept(component) === true && component.exclude !== true) { + const dirs = component.path.split('/') + + // 这段逻辑暂时没有用到 + const componentName = dirs.slice(1, dirs.indexOf('src')) + // UpperName: Todo + component.UpperName = capitalizeKebabCase(componentName.pop() ?? '') + + // LowerName: todo + component.LowerName = kebabCase({ str: component.UpperName }) + + // 工程的父文件夹 + component.parentDir = componentName + + // libPath: 'packages/todo/dist/pc.ts' 组件输出路径 + component.libPath = component.path + .replace('openinula/src/', 'packages/') + .replace('openinula-common/src/', 'packages/common/') + .replace('openinula-locale/src/', 'packages/locale/') + .replace('openinula-icon/src/', 'packages/icon/') + .replace('/index.ts', '/src/index.js') + .replace('/src/', '/dist/lib/') + .replace('.jsx', '.js') + .replace('.tsx', '.js') + + // libName: '@opentiny/vue/todo/pc' + component.libName = component.libPath + .replace('packages/', '') + .replace('/index', '') + .replace('.js', '') + .replace('/dist/', '/') + .replace(/\/lib$/, '') + + // 处理子目录 + if (componentName.length) { + component.libName = component.libName.replace(componentName.join('/'), '').replace(/^\//, '') + } + + // importName: '@opentiny/vue-tag/pc' + component.importName = `${scopeName}/openinula` + '-' + component.libName + + // libName: '@opentiny/vue/todo/pc' + component.libName = importName + '/' + component.libName + + // tmpName: 'pc' + component.tmpName = component.libPath.replace('.ts', '').split('/').slice(-1)[0] + + // global: 'TinyTodoPc' + component.global = 'Tiny' + key + + component.importName = `${scopeName}/openinula-${kebabCase({ str: key })}` + + // "vue-common/src/index.ts" ==> "vue-common/lib/index" + if (component.type === 'module') { + component.libPath = component.path.replace('/src/', '/lib/').replace('index.ts', 'index') + } + + // "vue/src/button/index.ts" ==> "button/lib/index" + if (component.type === 'component') { + component.libPath = component.path.replace('openinula/src/', '').replace('/index.ts', '/lib/index') + } + + // "vue/src/button/src/mobile-first.vue" ==> "button/lib/mobile-first" + if (component.type === 'template') { + component.libPath = component.path.replace('openinula/src/', '').replace('/src/', '/lib/').replace(/\..+$/, '') + } + + modules.push(component) + } + + component.type === 'component' && componentCount++ + }) + + return modules +} + +/** + * 根据指定条件搜索原始模块列表 + * @private + * @param {Function} filterIntercept 搜索条件 + */ +const getModules = (filterIntercept: Function) => { + let modules = {} + + if (typeof filterIntercept === 'function') { + for (const key in moduleMap) { + const component = moduleMap[key] + + if (filterIntercept(component) === true && component.exclude !== true) { + modules[key] = component + } + } + } else { + modules = moduleMap + } + + return modules +} + +const getByName = ({ + name, + inversion = false, + isOriginal = false +}: { + name: string + inversion?: boolean + isOriginal?: boolean +}) => { + const callback = (item) => { + const result = new RegExp(`/${name}/|^openinula-${name}/`).test(item.path) + return inversion ? !result : result + } + + return isOriginal ? getModules(callback) : getSortModules({ filterIntercept: callback }) +} + +function getTasks(names: string[]) { + // 没有指定组件,则全量构建 + if (names.length === 0) { + return [...getAllModules2(), ...getEntryTasks()] + } + + return names + .map((name) => + getByName({ + name: kebabCase({ str: name.replace('@opentiny/openinula-', '') }) + // isSort: false + }) + ) + .flat() +} + +const getAllIcons = () => { + const entries = fg.sync('openinula-icon*/src/*', { cwd: pathFromWorkspaceRoot('packages'), onlyDirectories: true }) + + return entries.map((item) => { + const name = path.basename(item) + + return { + path: item + '/index.ts', + libPath: item.replace('/src/', '/lib/'), + type: 'component', + componentType: 'icon', + name: kebabCase({ str: name }), + global: capitalizeKebabCase(name), + importName: '@opentiny/openinula-' + item + } as Module2 + }) +} + +function toEntry(libs) { + return libs.reduce((result, { libPath, path: file }) => { + const tLibPath = libPath.replace('-lib/', '/lib/') + result[tLibPath] = pathFromPackages(file) + return result + }, {}) +} + +function toTsInclued(libs) { + return new Set( + libs + .filter((item) => ['module', 'component'].includes(item.type)) + .map((lib) => `packages/${lib.dtsRoot ? lib.path : path.dirname(lib.path)}`) + ) +} + +function generatePackageJson({ beforeWriteFile }): Plugin { + return { + name: 'opentiny-vue:generate-package-json', + generateBundle(output: NormalizedOutputOptions, bundle: OutputBundle) { + const cache = {} + Object.entries(bundle).forEach(([, item]) => { + // 主入口文件, button/index.ts, common/src/index.ts + if (item.type === 'chunk' && /\/index\.(js|ts)/.test(item.facadeModuleId!)) { + // 从源文件中往上查找最近的 package.json 文件 + const packageJsonFile = findUpSync('package.json', { cwd: item.facadeModuleId! }) + + if (!packageJsonFile) return + + if (cache[packageJsonFile]) return + + let packageJson + try { + packageJson = JSON.parse(fs.readFileSync(packageJsonFile, { encoding: 'utf-8' })) + } catch {} + + const { filePath, content } = beforeWriteFile(path.dirname(item.fileName), packageJson) + + if (content) { + this.emitFile({ + type: 'asset', + fileName: `${filePath}/package.json`, + source: typeof content === 'string' ? content : JSON.stringify(content, null, 2) + }) + } + + const changelogFile = path.join(path.dirname(packageJsonFile), 'CHANGELOG.md') + if (fs.existsSync(changelogFile)) { + this.emitFile({ + type: 'asset', + fileName: `${filePath}/CHANGELOG.md`, + source: fs.readFileSync(changelogFile, { encoding: 'utf-8' }) + }) + } + + cache[packageJsonFile] = true + } + }) + } + } +} + +// const getOpenInulaPlugins = (openinulaVersion: string) => { +// const pluginMap = { +// '18': () => { +// // const openinula18Plugin = requireModules('examples/openinula-docs/node_modules/@vitejs/plugin-openinula') +// const openinula18Plugin = requireModules('examples/openinula-docs/node_modules/@vitejs/plugin-react') + +// const openinula18SvgPlugin = svgr + +// return [openinula18Plugin(), openinula18SvgPlugin()] +// } +// } + +// return pluginMap[openinulaVersion]() +// } + +function getBaseConfig() { + return defineConfig({ + publicDir: false, + resolve: { + extensions: ['.js', '.ts', '.tsx', '.jsx'] + }, + define: { + 'process.env.BUILD_TARGET': JSON.stringify('component') + }, + plugins: [ + // ...getOpenInulaPlugins('18'), + generatePackageJson({ + beforeWriteFile: (filePath, content) => { + const dependencies: any = {} + + Object.entries(content.dependencies).forEach(([key, value]) => { + // 只替换 openinula 系的依赖,方便调试 + // 其他公用的依赖,vue 之前可能发过包 + const newKey = key.replace('@opentiny/openinula', `${scopeName}/openinula`) + if ((value as string).includes('workspace:~')) { + dependencies[newKey] = '*' + } else { + dependencies[newKey] = value + } + }) + + if (filePath.includes('openinula-common')) { + dependencies.openinula = '18.2.0' + } + + // 如果是主入口或者svg图标则直接指向相同路径 + if (filePath === 'openinula' || filePath === 'openinula-icon') { + content.main = './index.js' + content.module = './index.js' + } else { + content.main = './lib/index.js' + content.module = './lib/index.js' + } + + content.version = buildVersion + content.dependencies = dependencies + content.name = content.name.replace('@opentiny/openinula', `${scopeName}/openinula`) + + delete content.devDependencies + delete content.private + delete content.exports + + return { + filePath: filePath.replace(/[\\/]lib$/, ''), + content + } + } + }) + ] + }) +} + +async function batchBuild({ tasks, formats, message, emptyOutDir, dts, outDir }) { + if (tasks.length === 0) return + logGreen(`====== 开始构建 ${message} ======`) + const entry = toEntry(tasks) + const dtsInclude = toTsInclued(tasks) + + await build({ + ...getBaseConfig({ dts, dtsInclude }), + configFile: false, + build: { + outDir, + emptyOutDir, + minify: false, + rollupOptions: { + plugins: [ + getBabelOutputPlugin({ + presets: [['@babel/preset-env', { loose: true, modules: false }]] + }) as any, + replace({ + '.less': '.css' + }), + { + name: 'replace-scope', + transform(code) { + if (scopeName === '@opentiny') return code + + let modifiedCode = code + while (modifiedCode.match(/@opentiny\/openinula/g)) { + modifiedCode = modifiedCode.replace('@opentiny/openinula', `${scopeName}/openinula`) + } + return { + code: modifiedCode + } + } + } + ], + output: { + strict: false, + manualChunks: {} + }, + external: (source, importer, isResolved) => { + // vite打包入口文件或者没有解析过得包不能排除依赖 + if (isResolved || !importer) { + return false + } + + // 子图标排除周边引用, 这里注意不要排除svg图标 + if (/openinula-icon\/.+\/index/.test(importer)) { + return !/\.svg/.test(source) + } + + // @opentiny/vue 总入口,需要排除所有依赖 + if (/openinula\/(index|pc|mobile|mobile-first)\.ts$/.test(importer)) { + return true + } + + if (['openinula', 'openinula/jsx-runtime'].includes(source)) { + return true + } + + if (source.indexOf(scopeName) === 0) { + return true + } + + return external(source) + } + }, + lib: { + entry, + formats, + fileName: (format, entryName) => `${entryName}.js` + } + } + }) +} + +async function batchBuildAll({ tasks, formats, message, emptyOutDir, dts, npmScope }) { + const rootDir = pathFromPackages('') + const outDir = path.resolve(rootDir, `dist-openinula/${npmScope}`) + await batchBuild({ + tasks, + formats, + message, + emptyOutDir, + dts, + outDir + }) +} + +export async function buildOpenInula( + names: string[] = [], + { buildTarget = '1.0.0', formats = ['es'], clean = false, dts = true, scope = '@opentiny' } +) { + scopeName = scope + buildVersion = buildTarget + // 要构建的模块 + let tasks = getTasks(names) + + // 如果指定了打包icon或者没有传入任何组件 + if (names.some((name) => name.includes('icon')) || !names.length) { + tasks.push(...getAllIcons()) + } + + // 构建 @opentiny/openinula + if (names.some((name) => [`${scopeName}/openinula`, 'openinula'].includes(name))) { + tasks.push(...getEntryTasks()) + } + + const message = `TINY for openinula: ${JSON.stringify(names.length ? names : '全量')}` + + await batchBuildAll({ + tasks, + formats, + message, + emptyOutDir: clean, + dts, + npmScope: scope + }) +} diff --git a/internals/cli/src/commands/build/index.ts b/internals/cli/src/commands/build/index.ts index f06f18494..f23c7dd8f 100644 --- a/internals/cli/src/commands/build/index.ts +++ b/internals/cli/src/commands/build/index.ts @@ -3,4 +3,5 @@ export * from './build-entry' export * from './build-runtime' export * from './build-ui-react' export * from './build-entry-react' -export * from './build-chart-theme' +export * from './build-ui-openinula' +export * from './build-entry-openinula' \ No newline at end of file diff --git a/internals/cli/src/commands/create/common-mapping-openinula.json b/internals/cli/src/commands/create/common-mapping-openinula.json new file mode 100644 index 000000000..197570100 --- /dev/null +++ b/internals/cli/src/commands/create/common-mapping-openinula.json @@ -0,0 +1,12 @@ +{ + "Icon": { + "path": "openinula/icon/index.ts", + "type": "module", + "exclude": true + }, + "Common": { + "path": "openinula/common/src/index.ts", + "type": "module", + "exclude": false + } +} \ No newline at end of file diff --git a/internals/cli/src/commands/create/create-mapping-openinula.ts b/internals/cli/src/commands/create/create-mapping-openinula.ts new file mode 100644 index 000000000..aa16aa0c6 --- /dev/null +++ b/internals/cli/src/commands/create/create-mapping-openinula.ts @@ -0,0 +1,111 @@ +import { capitalize, walkFileTree, pathFromWorkspaceRoot, logGreen, prettierFormat } from '../../shared/utils' +import { quickSort } from '../../shared/module-utils' +import path from 'node:path' +import fs from 'fs-extra' +import commonMappingOpenInula from './common-mapping-openinula.json' + +const getBuildEntryFile = (file, dirs, subPath) => { + // 模板文件(pc|mobile|mobile-first)需要同级目录有index.ts文件才能成为打包入口 + const isTemplatePath = dirs.includes('index.ts') + const isMainEntry = file.includes('index') && dirs.includes('package.json') + const isPcEntry = file.includes('pc.') && subPath.includes(`src${path.sep}pc.`) && isTemplatePath + const isMobileEntry = file.includes('mobile.') && subPath.includes(`src${path.sep}mobile.`) && isTemplatePath + const isMobileFirstEntry = + file.includes('mobile-first.') && subPath.includes(`src${path.sep}mobile-first.`) && isTemplatePath + return { + isBuildEntryFile: isMainEntry || isPcEntry || isMobileEntry || isMobileFirstEntry, + isMainEntry, + isPcEntry, + isMobileEntry, + isMobileFirstEntry + } +} + +const tempMap = { + 'pc.jsx': 'pc', + 'mobile.jsx': 'mobile', + 'mobile-first.jsx': 'mobile-first', + 'pc.tsx': 'pc', + 'mobile.tsx': 'mobile', + 'mobile-first.tsx': 'mobile-first' +} + +const getTemplateName = (currentPaths, entryObj) => { + const entryMaps = { + isPcEntry: 'Pc', + isMobileEntry: 'Mobile', + isMobileFirstEntry: 'MobileFirst', + isMainEntry: '' + } + const mapKey = Object.keys(entryObj).filter((item) => entryObj[item] && item !== 'isBuildEntryFile')[0] + const subFix = entryMaps[mapKey] + return `${currentPaths.split('-').map(capitalize).join('')}${subFix}` +} + +export const writeModuleMap = (moduleMap) => { + fs.writeFileSync( + pathFromWorkspaceRoot('packages/openinula/modules.json'), + prettierFormat({ + str: typeof moduleMap === 'string' ? moduleMap : JSON.stringify(moduleMap), + options: { + parser: 'json', + printWidth: 10 + } + }) + ) +} + +function makeOpenInulaModules() { + const templates = { ...commonMappingOpenInula } + + walkFileTree({ + isDeep: true, + dirPath: pathFromWorkspaceRoot('packages/openinula/src'), + fileFilter({ file }) { + return !/node_modules/.test(file) + }, + callback({ file, subPath, dirs }) { + const entryObj = getBuildEntryFile(file, dirs, subPath) + const mode: string[] = [] + + if (entryObj.isMainEntry && dirs.includes('src')) { + const srcPath = subPath.replace(file, 'src') + const srcFiles = fs.readdirSync(srcPath) || [] + srcFiles.forEach((item) => { + if (tempMap[item]) { + mode.push(tempMap[item]) + } + }) + } + + if (entryObj.isBuildEntryFile) { + const modulePath = subPath.slice(subPath.lastIndexOf(`openinula${path.sep}src`)).replaceAll(path.sep, '/') + const matchArr = modulePath.match(/.+\/(.+?)\/(index\.ts|src\/pc\.|src\/mobile\.|src\/mobile-first\.)/) + if (matchArr?.[1]) { + const compName = getTemplateName(matchArr[1], entryObj) + templates[compName] = { + path: modulePath, + type: entryObj.isMainEntry ? 'component' : 'template', + exclude: false + } + if (mode.length > 0) { + templates[compName].mode = mode + } + } + } + } + }) + + const modulesJson = quickSort({ sortData: templates, returnType: 'object' }) + + writeModuleMap(modulesJson) +} + +try { + makeOpenInulaModules() + + logGreen('npm run create:mapping-openinula done.') +} catch (e) { + // eslint-disable-next-line no-console + console.log(e) +} diff --git a/internals/cli/src/commands/create/create-ui-openinula.ts b/internals/cli/src/commands/create/create-ui-openinula.ts new file mode 100644 index 000000000..8d754f6ae --- /dev/null +++ b/internals/cli/src/commands/create/create-ui-openinula.ts @@ -0,0 +1,236 @@ +/* + * pnpm create:ui-openinula 新建组件,支持格式如下: + * + * pnpm create:ui-openinula alert 新建组件后,请及时测试,如果报错请根据报错内容做局部修改 + */ +import path from 'node:path' +import fs from 'fs-extra' +import semver from 'semver' +import { + walkFileTree, + capitalizeKebabCase, + logGreen, + logYellow, + templatePath, + pathJoinFromCLI +} from '../../shared/utils' +import handlebarsRender from '../build/handlebars.render' +import { parse } from '@vue/compiler-sfc' +import { requireModules } from '../build/build-ui' + +// 从renderless中获取组件API +function getApi(name) { + const { api } = requireModules(`packages/renderless/src/${name}/vue`) + let apiStr = JSON.stringify(api) + apiStr = apiStr.replaceAll('"', '').replace('[', '').replace(']', '') + return apiStr +} +// vue index中获取组件Props,该部分待完成 +// function getProps(name) { +// let path = pathJoinFromCLI('../../packages/vue/src/alert/src/index.ts') +// let fileContent = fs.readFileSync(path, { encoding: 'utf8' }) +// // return props +// } +// 解析vue template部分 +function vueToJSX(templateSource) { + const { descriptor } = parse(templateSource) + if (descriptor.template) { + const ast = descriptor.template.ast + const jsx = JSXFromAST(ast) + return jsx + } else { + throw new Error('No template found in the source.') + } +} +// 遍历Vue AST解析成符合openInula规范的组件模板 E:\tinyvue\tsconfig.vue3.json +function JSXFromAST(node) { + let jsx = '' + // If和For标记 + let tagName = '' + let tagAttr = '' + switch (node.type) { + case 1: // Element + if (node.props) { + node.props.forEach((attr) => { + if (attr.name === 'if') { + tagName = 'If' + tagAttr = ' v-if ={' + attr.exp.content + '}' + } + if (attr.name === 'for') { + tagName = 'For' + let list = attr.exp.content + list = list.slice(list.indexOf('in ') + 3) + tagAttr = ' list ={' + list + '}' + } + // class转换成className + if (attr.name === 'class') { + jsx += ' className="' + attr.value.content + '"' + } + // 含有:的动态属性添加大括号{},其中:class需要转换成className + if (attr.name === 'bind') { + if (attr.arg) { + if (attr.arg.content === 'class') { + jsx += ' className={vc(' + attr.exp.content + ')}' + } else { + jsx += ' ' + attr.arg.content + '={' + attr.exp.content + '}' + } + } + } + // 含有@的动态方法转添加on前缀 + if (attr.name === 'on') { + // 有修饰符的exp可能为undefiend,修饰符待处理 + if (attr.exp) { + jsx += ' on' + capitalizeKebabCase(attr.arg.content) + '={' + attr.exp.content + '}' + } + } + // 含有model的处理 + // if (attr.name === 'model') { + // } + }) + } + jsx = '<' + node.tag + jsx + '>' + + // 添加If和For外层包裹,前后添加换行符更好看 + if (tagName) { + jsx = '\n<' + tagName + tagAttr + '>\n' + jsx + } + if (node.children) { + node.children.forEach((child) => { + jsx += JSXFromAST(child) + }) + } + jsx += '' + // 添加If和For外层包裹 + if (tagName) { + jsx += '\n' + } + break + case 2: // Text,此处多数是换行符 + jsx += node.content + break + case 3: // 注释 + jsx += node.loc.source + break + + case 5: // 插值或组件内变量 + jsx += `{${node.content.content}}` + break + + // 其他节点类型... + default: + // 处理其他节点类型或抛出错误 + logYellow(`======================node.type=${node.type}====================`) + logYellow(node) + // throw new Error('Unsupported node type: ' + node.type); + } + // 单独处理slot + jsx = jsx.replace('', '') + // 单独处理transition + jsx = jsx.replace('transition', 'Transition') + // 单独处理component组件 + jsx = jsx.replace('component', 'Component') + + // 最外层template标签替换成div并添加ref + jsx = jsx.replace('', '') + // windows下调整换行符 + jsx = jsx.replace('\n', '\r\n') + return jsx +} + +export async function createUi(names = []) { + const templateDir = path.join(templatePath, './openinula') + const componetDir = pathJoinFromCLI('../../packages/openinula/src') + const vueComponetDir = pathJoinFromCLI('../../packages/vue/src') + + const { version } = fs.readJSONSync(pathJoinFromCLI('../../packages/openinula/package.json')) + + names.forEach((componentName) => { + let componentPath = path.join(componetDir, componentName) + let vueComponentPath = path.join(vueComponetDir, componentName, 'src') + + if (fs.existsSync(componentPath)) { + logYellow(`The component name : ${componentName} is exist , please enter other name.`) + return + } + // 获取API + let api = getApi(componentName) + // 定义不同模板内容 + let pcTemplate = `` + let mobileTemplate = `` + let mobileFirstTemplate = `` + + walkFileTree({ + isDeep: true, + dirPath: vueComponentPath, + callback({ file, subPath }) { + // 判断是否是.vue文件,否则会出错 + if (file.split('.')[1] === 'vue') { + let fileContent = fs.readFileSync(subPath, { encoding: 'utf8' }) + let content = vueToJSX(fileContent) + + if (file === 'pc.vue') { + pcTemplate = content + } + if (file === 'mobile.vue') { + mobileTemplate = content + } + if (file === 'mobile-first.vue') { + mobileFirstTemplate = content + } + } + } + }) + + walkFileTree({ + isDeep: true, + dirPath: templateDir, + callback({ file, subPath }) { + let fileName = file + // 每次遍历需要重置组件目录,否则会报错 + componentPath = path.join(componetDir, componentName) + const isSrcDir = path.basename(path.dirname(subPath)) === 'src' + if (isSrcDir) { + componentPath = path.join(componentPath, 'src') + } + + if (!fs.existsSync(componentPath)) { + fs.mkdirSync(componentPath) + } + + let template = '' + if (file === 'pc.jsx') { + template = pcTemplate + } + if (file === 'mobile.jsx') { + template = mobileTemplate + } + if (file === 'mobile-first.jsx') { + template = mobileFirstTemplate + } + + let fileContent = fs.readFileSync(subPath, { encoding: 'utf8' }) + const upperComponentName = capitalizeKebabCase(componentName) + + // 编译模板 + fileContent = handlebarsRender({ + template: fileContent, + data: { + NAME: componentName, + UNAME: upperComponentName, + MINOR: semver.minor(version), + SUFFIX: '', + THEME: 'theme', + TEMPLATE: template, + API: api + }, + delimiter: ['\\[\\[', '\\]\\]'], + options: { noEscape: true } + }) + componentPath = path.join(componentPath, fileName) + fs.writeFileSync(componentPath, fileContent) + } + }) + + logGreen('Create ui openinula done for ' + componentName) + }) +} diff --git a/internals/cli/src/commands/create/index.ts b/internals/cli/src/commands/create/index.ts index 47928279f..d53302a97 100644 --- a/internals/cli/src/commands/create/index.ts +++ b/internals/cli/src/commands/create/index.ts @@ -1 +1,2 @@ export * from './create-icon-saas' +export * from './create-ui-openinula' diff --git a/internals/cli/src/index.ts b/internals/cli/src/index.ts index 8bdd5a732..08d757223 100644 --- a/internals/cli/src/index.ts +++ b/internals/cli/src/index.ts @@ -1,9 +1,19 @@ #!/usr/bin/env node import { Command, Option } from 'commander' import { createIconSaas } from './commands/create/index.js' -import { buildUi, buildEntry, buildRuntime, buildReact, buildEntryReact, chartTheme } from './commands/build' +import { + buildUi, + buildEntry, + buildRuntime, + buildReact, + buildEntryReact, + buildOpenInula, + buildEntryOpenInula +} from './commands/build' import { releaseAurora } from './commands/release/releaseAurora' +import { createUi } from './commands/create' + const program = new Command() program.command('release:aurora').description('转换为aurora的包').action(releaseAurora) @@ -12,9 +22,9 @@ program.command('create:icon-saas').description('同步生成 icon-saas').action program.command('build:entry-react').description('生成 react 组件库入口').action(buildEntryReact) -program.command('build:entry').description('生成组件库入口').action(buildEntry) +program.command('build:entry-openinula').description('生成 openinula 组件库入口').action(buildEntryOpenInula) -program.command('build:chartTheme').description('切换chart主题').action(chartTheme) +program.command('build:entry').description('生成组件库入口').action(buildEntry) program .command('build:ui') @@ -23,7 +33,6 @@ program .addOption(new Option('-v --vue-versions ', '目标框架,默认所有').choices(['2', '2.7', '3'])) .addOption(new Option('-f --formats ', '目标格式,默认 ["es"]').choices(['es', 'cjs'])) .addOption(new Option('-t --build-target ', '组件的目标版本')) - .addOption(new Option('-d --design ', '构建的目标设计规范')) .option('-s, --scope ', 'npm scope,默认是 opentiny,会以 @opentiny 发布到 npm') .option('-c, --clean', '清空构建目录') .option('--no-dts', '不生成 dts') @@ -48,4 +57,21 @@ program .option('--no-dts', '不生成 dts') .action(buildReact) +program + .command('build:openinula') + .description('打包 openinula 组件库') + .argument('[names...]', '构建指定组件,如 button alert;不指定则构建全量组件') + .addOption(new Option('-f --formats ', '目标格式,默认 ["es"]').choices(['es', 'cjs'])) + .addOption(new Option('-t --build-target ', '组件的目标版本')) + .option('-s, --scope ', 'npm scope,默认是 opentiny,会以 @opentiny 发布到 npm') + .option('-c, --clean', '清空构建目录') + .option('--no-dts', '不生成 dts') + .action(buildOpenInula) + +program + .command('create:ui-openinula') + .description(' openinula 组件库代码生成') + .argument('[names...]', '构建指定组件,如 button alert;不指定则构建全量组件') + .action(createUi) + program.parse() diff --git a/package.json b/package.json index 718d1bba1..3b46b7609 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "opentiny-vue", - "version": "3.14.0", + "version": "3.7.12", "private": true, "packageManager": "pnpm@8.3.1", "description": "An enterprise-class UI component library, support both Vue.js 2 and Vue.js 3, as well as PC and mobile.", @@ -28,7 +28,7 @@ "main": "packages/index.js", "engines": { "node": ">=16", - "pnpm": ">=6.35" + "pnpm": ">=7" }, "scripts": { "preinstall": "npx only-allow pnpm", @@ -59,28 +59,19 @@ "build:runtime": "pnpm -C internals/cli build:runtime", "// ---------- 构建相关脚本 ----------": "", "build:ui": "pnpm create:icon-saas && pnpm create:mapping && pnpm build:entry && gulp themeConcat && pnpm -C internals/cli build:ui", - "build:chartTheme": "pnpm -C internals/cli build:chartTheme", "build:renderless": "pnpm -C packages/renderless build:fast", "build:theme": "gulp themeConcat && pnpm -C packages/theme build:fast", "build:themeSaas": "pnpm -C packages/theme-saas build:fast", "build:themeMobile": "pnpm -C packages/theme-mobile build:fast", "build:themejson": "gulp themeJson", "build:internals": "pnpm \"--filter=./internals/*\" build", - "build:vite-import": "pnpm --filter @opentiny/vue-vite-import build", - "build:virtual-template": "pnpm --filter @opentiny-internal/unplugin-virtual-template build", - "build:site": "gulp themeConcat && pnpm i -g pnpm && pnpm build:vite-import && pnpm build:virtual-template && pnpm -C examples/sites build", + "build:site": "gulp themeConcat && pnpm -C examples/sites build", "release:aurora": "pnpm -C internals/cli release:aurora", "// ---------- 使用pnpm批量发布npm包 ----------": "", "pub2": "pnpm --filter=\"./packages/dist2/**\" publish --tag v2-latest --no-git-checks --access=public", "pub3": "pnpm --filter=\"./packages/dist3/**\" publish --no-git-checks --access=public", "pub2.7": "pnpm --filter=\"./packages/dist2.7/**\" publish --tag v2.7-latest --no-git-checks --access=public", "pub:aurora": "pnpm --filter=\"./packages/dist2/@aurora/**\" publish --no-git-checks --access=public", - "pub:theme": "pnpm --filter=\"./packages/theme/dist\" publish --no-git-checks --access=public", - "pub:themeMobile": "pnpm --filter=\"./packages/theme-mobile/dist/\" publish --no-git-checks --access=public", - "pub:themeSaas": "pnpm --filter=\"./packages/theme-saas/dist\" publish --no-git-checks --access=public", - "pub:renderless": "pnpm --filter=\"./packages/renderless/dist\" publish --no-git-checks --access=public", - "pub:all": "pnpm pub2 && pnpm pub3 && pnpm pub:theme && pnpm pub:themeMobile && pnpm pub:themeSaas && pnpm pub:renderless", - "pub:site": "pnpm -C examples/sites pub", "// ---------- unit单元测试 ----------": "", "test:unit2": "pnpm -C examples/vue2 test:unit", "test:unit2.7": "pnpm -C examples/vue2.7 test:unit", @@ -89,18 +80,14 @@ "test:e2e2": "pnpm -C examples/vue2 test:e2e --project=chromium", "test:e2e2.7": "pnpm -C examples/vue2.7 test:e2e --project=chromium", "test:e2e3": "pnpm -C examples/vue3 test:e2e --project=chromium", - "test:e2e2:mobile": "pnpm -C examples/vue2 test:e2e --project=android", - "test:e2e2.7:mobile": "pnpm -C examples/vue2.7 test:e2e --project=android", - "test:e2e3:mobile": "pnpm -C examples/vue3 test:e2e --project=android", "// ---------- playwright下载chromium、firefox等浏览器内核 ----------": "", "install:browser": "pnpm -C examples/vue3 install:browser", "// ---------- e2e测试代码生成器 ----------": "", "codegen": "pnpm -C examples/vue3 codegen", - "codegen:mobile": "pnpm -C examples/vue3 codegen --device=\"Pixel 5\" ", "format": "prettier --write --cache packages/**/{*.vue,*.js,*.ts,*.jsx,*.tsx,*.less} examples/**/{*.vue,*.js,*.ts,*.jsx,*.tsx} internals/**/{*.js,*.ts}", "lint": "eslint \"packages/**/{*.vue,*.js,*.ts}\" --quiet --fix", "lint:doc": "eslint \"examples/**/{*.vue,*.js,*.ts}\" --quiet --fix", - "clean:build": "rimraf packages/dist2 packages/dist3 packages/dist2.7 packages/renderless/dist packages/theme/dist packages/theme-saas/dist packages/theme-mobile/dist", + "clean:build": "rimraf packages/dist2 packages/dist3 packages/renderless/dist packages/dist2.7", "clean:dependencies": "rm -rf node_modules /**/node_modules", "// ---------- 构建【mf】版本 ----------": "", "preci:deployMfPatch": "pnpm clean:build && lerna version prepatch --conventional-prerelease --include-merged-tags --preid mf --no-push --yes", @@ -138,17 +125,22 @@ "build:react-site": "pnpm --filter @opentiny/react-site build", "prettier": "prettier --config .prettierrc --write .", "// ---------- openinula 相关脚本命令 ----------": "", - "dev:openinula": "pnpm -C examples/openinula-docs run dev", + "dev:openinula": "pnpm create:mapping-openinula && pnpm build:entry-openinula && pnpm -C examples/openinula-docs run dev", + "build:entry-openinula": "pnpm -C internals/cli build:entry-openinula", + "create:mapping-openinula": "pnpm -C internals/cli create:mapping-openinula", + "build:openinula": "pnpm -C internals/cli build:openinula", + "build:ui-openinula": "pnpm create:mapping-openinula && pnpm build:entry-openinula && pnpm build:openinula", + "create:ui-openinula": "pnpm -C internals/cli create:ui-openinula", "// ---------- solid 相关脚本命令 ----------": "", "dev:solid": "pnpm -C examples/solid-docs run dev" }, "dependencies": { - "@vue/composition-api": "1.7.2", + "@vue/composition-api": "1.2.2", "color": "^4.2.3", "cropperjs": "1.5.12", "crypto-js": "4.2.0", "echarts": "5.4.1", - "echarts-liquidfill": "3.1.0", + "echarts-liquidfill": "3.0.0", "echarts-wordcloud": "2.0.0", "fastdom": "1.0.11", "shepherd.js": "11.0.1", diff --git a/packages/openinula/README.md b/packages/openinula/README.md new file mode 100644 index 000000000..72be04667 --- /dev/null +++ b/packages/openinula/README.md @@ -0,0 +1,239 @@ +# 开发文档 + +## 更新日志 + +#### 2024-04-07 + +1. 修复pnpm build:openinula构建命令 +2. 增加pnpm create:ui-openinula创建openInula组件命令 + 该命令主要使用了/internals/cli/src/create/create-ui-openinula的转换脚本,通过解析vue模板遍历ast,生成inula组件。目前该脚本已经实现了api和template的转换,能减少95%的人工转换工作量。 +3. 修复事件点击报错。该错误来自适配层dom和vnode(对应react的fiber)的关联。 + +## 如何将已经开发好的Vue组件快速转换为openInula组件 + +执行 `pnpm create:ui-openinula +组件名称` 可在`/packages/openinula/src/`目录下生成对应组件,然后进行测试,根据需要进行少量修改即可。 + +## 开发成果 + +1. 基本工具的开发 + +- create-ui-openinula(已完成,通过该转换工具,可以将Vue模板转换成openInula组件模板) +- create-mapping-openinula(已完成,通过该工具,可以生成openinula组件的map文件。新增组件后需要调用) +- build-entry-openinula(已完成,通过该工具,可以生成openinula组件的入口文件。新增组件后需要调用) +- build-ui-openinula(已完成,通过该工具,可以打包openInula组件) +- common-mapping-openinula.json(map文件的公共部分) + +调用顺序为create-ui-openinula(转换组件)=>create-mapping-openinula(创建map)=>build-ui-openinula(创建入口文件) + +实际开发过程中通过命令行,首先执行`pnpm create:ui-openinula+组件名称`,然后执行`pnpm dev:openinula` +其中,`pnpm dev:openinula`命令已经包含了map文件生成和入口文件生成 + +2. openInula-common 适配层的开发,已完成全部API的转换 + +## 下载依赖 + +```bash +pnpm i +``` + +## 将Vue组件转换位 openinula 组件 + +转换后,还需检查转换的代码,将缺少的props和constants补进代码,然后进行测试 + +```bash +pnpm create:ui-openinula +组件名称 +for example: +pnpm create:ui-openinula button +``` + +## 生成 openinula 组件入口,每次生成新组件后需更新组件入口 + +```bash +pnpm build:entry-openinula +``` + +## 本地启动 openinula 调试项目 + +```bash +pnpm dev:openinula +``` + +## 本地启动 openinula 文档项目 + +```bash +pnpm dev:openinula-site +``` + +## 打包 openinula 组件 + +```bash +pnpm build:ui-openinula +``` + +运行此命令后,会在 pacakges-openinula 产生打包产物 +一般是 +packages/dist-openinula/@opention/button +... 单个组件产物 +packages/dist-openinula/@opention/openinula-common +packages/dist-openinula/@opention/openinula +packages/dist-openinula/@opention/openinula-icon + +命令参数:传入字符串参数列表可以指定只打包单个组件或多个特定组件,比如 + +```bash +pnpm build:ui-openinula button +``` + +默认不传的话,会打包所有组件,以及公共任务,比如 openinula-common、openinula-icon + +可以通过 -f 指定目标格式,默认 es,可选 es、cjs +可以通过 -t 指定目标版本,默认 18,现在 openinula 只支持 18 +可以通过 -s 指定发布 npm scope,默认是 opentiny +可以通过 -c 指定是否清空构建目录 +可以通过 --no-dts 指定不生成类型定义文件 + +## 发包 openinula 组件 + +```bash +pnpm pub:openinula +``` + +# 目录结构 + +## 打包 openinula 相关 + +```b +internals/cli + /build + /build-entry-openinula.ts (packages/openinula 目录下生成入口) + /build-ui-openinula.ts (packages/dist-openinula 下生成打包产物) + /create + /create-mapping-openinula.ts (packages 下生成构建任务列表 modules.json) + /common-mapping-openinula.json (定义一些公共的打包任务,如 openinula-common) + /create-ui-openinula.ts (vue到openInula的转换脚本) + +internals/cli + /public/template/openinula(转换脚本使用的模板文件) + +``` + +## 开发 openinula 模版文件相关 + +packages/openinula/src/[compName] 目录 + +一个组件模版的目录结构如下 + +```b +alert + /node_modules + /src + /index.ts + /pc.tsx + /mobile.tsx + /mobile-first.ts + /index.ts + /package.json +``` + +alert/index 是组件入口 +pc、mobile、mobile-first 是三套模版 + +## 开发 openinula-icon 相关 + +packages/openinula-icon/src/[svgName] 目录 + +一个 svg 直接用一个 index.ts 创建 + +如:packages/openinula-icon/src/add/index.ts + +```ts +import { Svg } from '@opentiny/openinula-common' +import { openinulaComponent as AddLoading } from '@opentiny/vue-theme/svgs/add.svg' + +export default Svg({ name: 'AddLoading', component: AddLoading }) +``` + +## 开发 openinula-common openinula 适配层相关 + +openinula-common 的目录如下,主要是适配层的文件 + +```b +packages/openinula-common + /src + /csscls.ts 操作样式类名的一些方法 + /event.ts 模拟 vue 事件系统 + /fiber.ts 对 fiber 的一些读取操作 + /openinulaive.ts 实现数据响应式 + /resolveProps.js 从 openinula 的 props 上解析事件或属性 + /svg-render.jsx 渲染 svg 组件的公共函数 + /utils.ts 工具函数 + /virtual-comp.jsx 虚拟组件,用于实现 vue 的指令系统 + /vm.js 用户模拟 vue 的 vm 对象 + /vue-hooks.js 用户模拟 vue 的钩子函数 +``` + +# 用户使用文档 + +## 在项目中使用所有组件 + +- 1.下载整个组件库 + +```bash +npm i @pe-3/openinula +``` + +- 2. 导入组件 + +```js +import { Button as TinyButton, openinula as Tiny openinula } from '@pe-3/openinula' +``` + +- 3. 使用组件(查看 api 文档) + +```js +function App() { + return ( +
+ 主要按钮 + +
+ ) +} +``` + +## 在项目中使用单个组件 + +- 1. 下载单个组件 + +```bash +npm i @pe-3/openinula-button +npm i @pe-3/openinula-alert +``` + +- 2. 导入单个组件 + +```js +import TintButton from '@pe-3/openinula-button' +import TintAlert from '@pe-3/openinula-alert' +``` + +- 3. 使用单个组件 + +```js +function App() { + return ( +
+ 主要按钮 + +
+ ) +} +``` + +## 组件 api 文档地址: + +https://opentiny.design/ + +## codesandbox + +https://codesandbox.io/s/hungry-bash-tlch6l?file=/src/App.js diff --git a/packages/openinula/index.ts b/packages/openinula/index.ts index 1e3f1a329..0dfe67d4a 100644 --- a/packages/openinula/index.ts +++ b/packages/openinula/index.ts @@ -1,9 +1,19 @@ import Alert from '@opentiny/openinula-alert' +import Badge from '@opentiny/openinula-badge' +import Button from '@opentiny/openinula-button' +import Icon from '@opentiny/openinula-icon' +import Switch from '@opentiny/openinula-switch' + +const components = [Alert, Badge, Button, Icon, Switch] export const version = '1.0.0' -export { Alert } +export { Alert, Badge, Button, Icon, Switch } export default { - Alert + Alert, + Badge, + Button, + Icon, + Switch } as any diff --git a/packages/openinula/mobile-first.ts b/packages/openinula/mobile-first.ts new file mode 100644 index 000000000..88c54d632 --- /dev/null +++ b/packages/openinula/mobile-first.ts @@ -0,0 +1,17 @@ +import Alert from '@opentiny/openinula-alert/src/mobile-first' +import Button from '@opentiny/openinula-button/src/mobile-first' +import Icon from '@opentiny/openinula-icon/src/mobile-first' +import Switch from '@opentiny/openinula-switch/src/mobile-first' + +const components = [Alert, Button, Icon, Switch] + +export const version = '1.0.0' + +export { Alert, Button, Icon, Switch } + +export default { + Alert, + Button, + Icon, + Switch +} as any diff --git a/packages/openinula/mobile.ts b/packages/openinula/mobile.ts new file mode 100644 index 000000000..a8c0a0086 --- /dev/null +++ b/packages/openinula/mobile.ts @@ -0,0 +1,19 @@ +import Alert from '@opentiny/openinula-alert/src/mobile' +import Badge from '@opentiny/openinula-badge/src/mobile' +import Button from '@opentiny/openinula-button/src/mobile' +import Icon from '@opentiny/openinula-icon/src/mobile' +import Switch from '@opentiny/openinula-switch/src/mobile' + +const components = [Alert, Badge, Button, Icon, Switch] + +export const version = '1.0.0' + +export { Alert, Badge, Button, Icon, Switch } + +export default { + Alert, + Badge, + Button, + Icon, + Switch +} as any diff --git a/packages/openinula/modules.json b/packages/openinula/modules.json new file mode 100644 index 000000000..c3aaf14b9 --- /dev/null +++ b/packages/openinula/modules.json @@ -0,0 +1,106 @@ +{ + "Alert": { + "path": "openinula/src/alert/index.ts", + "type": "component", + "exclude": false, + "mode": [ + "mobile-first", + "mobile", + "pc" + ] + }, + "AlertMobile": { + "path": "openinula/src/alert/src/mobile.jsx", + "type": "template", + "exclude": false + }, + "AlertMobileFirst": { + "path": "openinula/src/alert/src/mobile-first.jsx", + "type": "template", + "exclude": false + }, + "AlertPc": { + "path": "openinula/src/alert/src/pc.jsx", + "type": "template", + "exclude": false + }, + "Badge": { + "path": "openinula/src/badge/index.ts", + "type": "component", + "exclude": false, + "mode": [ + "mobile", + "pc" + ] + }, + "BadgeMobile": { + "path": "openinula/src/badge/src/mobile.jsx", + "type": "template", + "exclude": false + }, + "BadgePc": { + "path": "openinula/src/badge/src/pc.jsx", + "type": "template", + "exclude": false + }, + "Button": { + "path": "openinula/src/button/index.ts", + "type": "component", + "exclude": false, + "mode": [ + "mobile-first", + "mobile", + "pc" + ] + }, + "ButtonMobile": { + "path": "openinula/src/button/src/mobile.jsx", + "type": "template", + "exclude": false + }, + "ButtonMobileFirst": { + "path": "openinula/src/button/src/mobile-first.jsx", + "type": "template", + "exclude": false + }, + "ButtonPc": { + "path": "openinula/src/button/src/pc.jsx", + "type": "template", + "exclude": false + }, + "Common": { + "path": "openinula/common/src/index.ts", + "type": "module", + "exclude": false + }, + "Icon": { + "path": "openinula/src/icon/index.ts", + "type": "component", + "exclude": false + }, + "Switch": { + "path": "openinula/src/switch/index.ts", + "type": "component", + "exclude": false, + "mode": [ + "mobile-first", + "mobile", + "pc" + ] + }, + "SwitchMobile": { + "path": "openinula/src/switch/src/mobile.jsx", + "type": "template", + "exclude": false + }, + "SwitchMobileFirst": { + "path": "openinula/src/switch/src/mobile-first.jsx", + "type": "template", + "exclude": false + }, + "SwitchPc": { + "path": "openinula/src/switch/src/pc.jsx", + "type": "template", + "exclude": false + } +} diff --git a/packages/openinula/package.json b/packages/openinula/package.json index d1762dab9..ff6687baa 100644 --- a/packages/openinula/package.json +++ b/packages/openinula/package.json @@ -11,6 +11,10 @@ "license": "ISC", "dependencies": { "@opentiny/openinula-common": "workspace:~", - "@opentiny/openinula-alert": "workspace:~" + "@opentiny/openinula-alert": "workspace:~", + "@opentiny/openinula-badge": "workspace:~", + "@opentiny/openinula-button": "workspace:~", + "@opentiny/openinula-icon": "workspace:~", + "@opentiny/openinula-switch": "workspace:~" } -} +} \ No newline at end of file diff --git a/packages/openinula/pc.ts b/packages/openinula/pc.ts new file mode 100644 index 000000000..185b0bf09 --- /dev/null +++ b/packages/openinula/pc.ts @@ -0,0 +1,19 @@ +import Alert from '@opentiny/openinula-alert/src/pc' +import Badge from '@opentiny/openinula-badge/src/pc' +import Button from '@opentiny/openinula-button/src/pc' +import Icon from '@opentiny/openinula-icon/src/pc' +import Switch from '@opentiny/openinula-switch/src/pc' + +const components = [Alert, Badge, Button, Icon, Switch] + +export const version = '1.0.0' + +export { Alert, Badge, Button, Icon, Switch } + +export default { + Alert, + Badge, + Button, + Icon, + Switch +} as any diff --git a/packages/openinula/src/badge/index.ts b/packages/openinula/src/badge/index.ts new file mode 100644 index 000000000..65e3e5caa --- /dev/null +++ b/packages/openinula/src/badge/index.ts @@ -0,0 +1,3 @@ +import Badge from './src' + +export default Badge diff --git a/packages/openinula/src/badge/package.json b/packages/openinula/src/badge/package.json new file mode 100644 index 000000000..c80497230 --- /dev/null +++ b/packages/openinula/src/badge/package.json @@ -0,0 +1,19 @@ +{ + "name": "@opentiny/openinula-badge", + "version": "1.0.0", + "description": "", + "main": "index.ts", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@opentiny/vue-renderless": "workspace:~", + "@opentiny/openinula-common": "workspace:~", + "@opentiny/openinula-icon": "workspace:~", + "@opentiny/vue-theme": "workspace:~", + "@opentiny/vue-theme-mobile": "workspace:~" + } +} \ No newline at end of file diff --git a/packages/openinula/src/badge/src/index.ts b/packages/openinula/src/badge/src/index.ts new file mode 100644 index 000000000..4a76228b2 --- /dev/null +++ b/packages/openinula/src/badge/src/index.ts @@ -0,0 +1,13 @@ +import pc from './pc' +import mobile from './mobile' + +export default function (props) { + const { tiny_mode = 'pc' } = props + + const S = { + pc, + mobile + }[tiny_mode] + + return S(props) +} diff --git a/packages/openinula/src/badge/src/mobile.jsx b/packages/openinula/src/badge/src/mobile.jsx new file mode 100644 index 000000000..1ab811439 --- /dev/null +++ b/packages/openinula/src/badge/src/mobile.jsx @@ -0,0 +1,70 @@ +import { useSetup, useVm, vc, If, Slot } from '@opentiny/openinula-common' +import { api, renderless } from '@opentiny/vue-renderless/badge/vue' +import '@opentiny/vue-theme-mobile/badge/index.less' + +export default function (props) { + const { + isDot = false, + isFixed = true, + isMini = false, + max, + value, + modelValue, + href, + target, + hidden = false, + type, + badgeClass, + offset = [0, 0] + } = props + + const defaultProps = Object.assign( + { + isDot, + isFixed, + isMini, + hidden, + offset + }, + props + ) + + const { ref, parent, current: vm } = useVm() + + const { state } = useSetup({ + props: defaultProps, + api, + renderless, + vm, + parent + }) + + return ( +
+ + 0 || isDot)}> +
+ + + + + {state.content} + + + + +
+
+
+ ) +} diff --git a/packages/openinula/src/badge/src/pc.jsx b/packages/openinula/src/badge/src/pc.jsx new file mode 100644 index 000000000..0fa1f9b6f --- /dev/null +++ b/packages/openinula/src/badge/src/pc.jsx @@ -0,0 +1,78 @@ +import { useSetup, useVm, vc, If, Slot } from '@opentiny/openinula-common' +import { api, renderless } from '@opentiny/vue-renderless/badge/vue' +import '@opentiny/vue-theme/badge/index.less' + +export default function (props) { + const { + isDot = false, + isFixed = true, + isMini = false, + max, + value, + modelValue, + href, + target, + hidden = false, + type, + badgeClass, + offset = [0, 0], + data + } = props + + const defaultProps = Object.assign( + { + isDot, + isFixed, + isMini, + hidden, + offset + }, + props + ) + + const { ref, parent, current: vm } = useVm() + + const { state } = useSetup({ + props: defaultProps, + api, + renderless, + vm, + parent + }) + + return ( +
+ + {data} + + + +
+ + + + {state.content} + + + + {state.content} + + +
+
+
+ ) +} diff --git a/packages/openinula/src/button/index.ts b/packages/openinula/src/button/index.ts new file mode 100644 index 000000000..6dada6f44 --- /dev/null +++ b/packages/openinula/src/button/index.ts @@ -0,0 +1,3 @@ +import Button from './src' + +export default Button diff --git a/packages/openinula/src/button/package.json b/packages/openinula/src/button/package.json new file mode 100644 index 000000000..db9e972b6 --- /dev/null +++ b/packages/openinula/src/button/package.json @@ -0,0 +1,19 @@ +{ + "name": "@opentiny/openinula-button", + "version": "1.0.0", + "description": "", + "main": "index.ts", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@opentiny/vue-renderless": "workspace:~", + "@opentiny/openinula-common": "workspace:~", + "@opentiny/openinula-icon": "workspace:~", + "@opentiny/vue-theme": "workspace:~", + "@opentiny/vue-theme-mobile": "workspace:~" + } +} \ No newline at end of file diff --git a/packages/openinula/src/button/src/index.ts b/packages/openinula/src/button/src/index.ts new file mode 100644 index 000000000..52ccdeb7f --- /dev/null +++ b/packages/openinula/src/button/src/index.ts @@ -0,0 +1,15 @@ +import pc from './pc' +import mobile from './mobile' +import mobileFirst from './mobile-first' + +export default function (props) { + const { tiny_mode = 'pc' } = props + + const S = { + pc, + mobile, + 'mobile-first': mobileFirst + }[tiny_mode] + + return S(props) +} diff --git a/packages/openinula/src/button/src/mobile-first.jsx b/packages/openinula/src/button/src/mobile-first.jsx new file mode 100644 index 000000000..0eb661caa --- /dev/null +++ b/packages/openinula/src/button/src/mobile-first.jsx @@ -0,0 +1,107 @@ +import { renderless, api } from '@opentiny/vue-renderless/button/vue' +import { useSetup, vc, If, Component, Slot, useVm } from '@opentiny/openinula-common' +import { IconLoading } from '@opentiny/openinula-icon' +import { classes } from './token' + +const define_props = [ + 'children', + 'text', + 'loading', + 'autofocus', + 'plain', + 'round', + 'circle', + 'icon', + 'size', + 'type', + 'nativeType', + 'resetTime', + /^on/ +] + +export default function Button(props) { + const { + loading, + autofocus, + size, + icon, + round, + circle, + href, + buttonClass, + tabindex, + text, + type, + nativeType = 'button', + resetTime = 1000 + } = props + + const defaultProps = Object.assign({ + nativeType, + resetTime, + }, props) + + const { + ref, + parent, + current: vm + } = useVm() + + const { + handleClick, + state, + a, + m, + gcls, + } = useSetup({ + api, + renderless, + props: defaultProps, + classes, + ref, + parent, + vm + }) + + const $attrs = a(props, define_props, false) + + return ( + + ) +} diff --git a/packages/openinula/src/button/src/mobile.jsx b/packages/openinula/src/button/src/mobile.jsx new file mode 100644 index 000000000..938326f02 --- /dev/null +++ b/packages/openinula/src/button/src/mobile.jsx @@ -0,0 +1,88 @@ +import { renderless, api } from '@opentiny/vue-renderless/button/vue' +import { useSetup, vc, If, Component, Slot, useVm } from '@opentiny/openinula-common' +import { IconLoading } from '@opentiny/openinula-icon' +import '@opentiny/vue-theme-mobile/button/index.less' + +const define_props = [ + 'children', + 'text', + 'loading', + 'autofocus', + 'plain', + 'round', + 'circle', + 'icon', + 'size', + 'type', + 'nativeType', + 'resetTime', + /^on/ +] + +export default function Button(props) { + const { + text, + loading, + round, + icon, + size, + type = 'default', + nativeType = 'button', + resetTime = 1000 + } = props + + const defaultProps = Object.assign({ + type, + nativeType, + resetTime + }, props) + + const { + ref, + parent, + current: vm + } = useVm() + + const { + handleClick, + state, + a + } = useSetup({ + props: defaultProps, + renderless, + api, + parent, + vm + }) + + const $attrs = a(props, define_props, false) + + return ( + + ) +} diff --git a/packages/openinula/src/button/src/pc.jsx b/packages/openinula/src/button/src/pc.jsx new file mode 100644 index 000000000..9b535d010 --- /dev/null +++ b/packages/openinula/src/button/src/pc.jsx @@ -0,0 +1,99 @@ +import { renderless, api } from '@opentiny/vue-renderless/button/vue' +import { useSetup, If, Component, vc, useVm } from '@opentiny/openinula-common' +import { IconLoading } from '@opentiny/openinula-icon' +import '@opentiny/vue-theme/button/index.less' + +const define_props = [ + 'children', + 'text', + 'loading', + 'autofocus', + 'plain', + 'round', + 'circle', + 'icon', + 'size', + 'type', + 'nativeType', + 'resetTime', + /^on/ +] + +export default function Button(props) { + const { + children, + text, + loading, + autofocus, + round, + circle, + icon, + size, + tabindex, + type = 'default', + nativeType = 'button', + resetTime = 1000 + } = props + + const defaultProps = Object.assign({ + type, + nativeType, + resetTime + }, props) + + const { + ref, + parent, + current: vm + } = useVm() + + const { + handleClick, + state, + a + } = useSetup({ + props: defaultProps, + renderless, + api, + vm, + parent + }) + + const $attrs = a(props, define_props, false) + + return ( + + ) +} diff --git a/packages/openinula/src/button/src/token.ts b/packages/openinula/src/button/src/token.ts new file mode 100644 index 000000000..31978558d --- /dev/null +++ b/packages/openinula/src/button/src/token.ts @@ -0,0 +1,66 @@ +export const classes = { + 'button': + 'inline-block sm:max-w-[9rem] text-center overflow-hidden overflow-ellipsis whitespace-nowrap transition-button duration-300 delay-[0ms]', + 'size-default': 'h-10 text-sm sm:h-7 sm:text-xs', + 'size-medium': 'h-10 text-sm sm:h-8 sm:text-xs', + 'size-small': 'h-8 text-sm sm:h-7 sm:text-xs', + 'size-mini': 'h-7 sm:h-6 sm:text-xs', + 'type-default': + 'text-black border-color-border hover:border-color-border-hover active:border-color-border-active sm:cursor-pointer', + 'type-primary': + 'text-white border-color-brand bg-color-brand hover:border-color-brand-hover hover:bg-color-brand-hover active:border-color-brand-active active:bg-color-brand-active sm:cursor-pointer', + 'type-success': + 'text-white border-color-success bg-color-success hover:border-color-success-hover hover:bg-color-success-hover active:border-color-success-active active:bg-color-success-active sm:cursor-pointer', + 'type-info': + 'text-white border-color-info-secondary bg-color-info-secondary hover:border-color-info-secondary-hover hover:bg-color-info-secondary-hover active:border-color-info-secondary-active active:bg-color-info-secondary-active sm:cursor-pointer', + 'type-warning': + 'text-white border-color-warning bg-color-warning hover:border-color-warning-hover hover:bg-color-warning-hover active:border-color-warning-active active:bg-color-warning-active sm:cursor-pointer', + 'type-danger': + 'text-white border-color-error bg-color-error hover:border-color-error-hover hover:bg-color-error-hover active:border-color-error-active active:bg-color-error-active sm:cursor-pointer', + 'type-text': + 'border-none bg-transparent cursor-pointer text-color-text-placeholder active:text-color-text-primary sm:hover:text-color-text-primary sm:active:!text-color-brand-active', + 'type-default-disabled': 'text-color-text-disabled bg-color-bg-3 border-transparent hover:cursor-not-allowed', + 'type-primary-disabled': 'text-white bg-color-brand-disabled border-transparent hover:cursor-not-allowed', + 'type-success-disabled': 'text-white bg-color-success-disabled border-transparent hover:cursor-not-allowed', + 'type-info-disabled': 'text-white bg-color-info-secondary-disabled border-transparent hover:cursor-not-allowed', + 'type-warning-disabled': 'text-white bg-color-alert-disabled border-transparent hover:cursor-not-allowed', + 'type-danger-disabled': 'text-white bg-color-error-disabled border-transparent hover:cursor-not-allowed', + 'type-text-disabled': 'text-color-text-disabled hover:cursor-not-allowed', + 'type-default-plain': + 'text-black border-color-border hover:border-color-border-hover active:border-color-border-active sm:cursor-pointer', + 'type-primary-plain': + 'text-color-brand border-color-brand hover:text-color-brand-hover hover:border-color-brand-hover active:text-color-brand-active active:border-color-brand-active bg-white sm:cursor-pointer', + 'type-success-plain': + 'text-color-success border-color-success hover:text-color-success-hover hover:border-color-success-hover active:text-color-success-active active:border-color-success-active bg-white sm:cursor-pointer', + 'type-info-plain': + 'text-color-info-secondary border-color-info-secondary hover:text-color-info-secondary-hover hover:border-color-info-secondary-hover active:text-color-info-secondary-active active:border-color-info-secondary-active bg-white sm:cursor-pointer', + 'type-warning-plain': + 'text-color-warning border-color-warning hover:text-color-warning-hover hover:border-color-warning-hover active:text-color-warning-active active:border-color-warning-active bg-white sm:cursor-pointer', + 'type-danger-plain': + 'text-color-error border-color-error hover:text-color-error-hover hover:border-color-error-hover active:text-color-error-active active:border-color-error-active bg-white sm:cursor-pointer', + 'type-text-plain': 'text-color-brand hover:text-color-brand-hover active:text-color-brand-active', + 'type-default-plain-disabled': + 'text-color-text-disabled bg-white border-color-text-disabled hover:cursor-not-allowed', + 'type-primary-plain-disabled': + 'text-color-brand-disabled bg-white border-color-brand-disabled hover:cursor-not-allowed', + 'type-success-plain-disabled': + 'text-color-success-disabled bg-white border-color-success-disabled hover:cursor-not-allowed', + 'type-info-plain-disabled': + 'text-color-info-secondary-disabled bg-white border-color-info-secondary-disabled hover:cursor-not-allowed', + 'type-warning-plain-disabled': + 'text-color-alert-disabled bg-white border-color-alert-disabled hover:cursor-not-allowed', + 'type-danger-plain-disabled': + 'text-color-error-disabled bg-white border-color-error-disabled hover:cursor-not-allowed', + 'type-text-plain-disabled': 'text-color-text-disabled hover:cursor-not-allowed', + 'no-round': 'rounded-sm', + 'is-round': 'rounded-full', + 'is-border': 'border-0.5 sm:border', + 'no-circle': 'sm:min-w-[4.5rem] pl-3 pr-3', + 'is-circle': 'sm:min-w-[0.4375rem] sm:rounded-full sm:pl-2 sm:pr-2', + 'button-icon': '-mt-0.5 sm:text-base fill-current', + 'button-icon-default': 'text-color-icon-primary hover:text-color-icon-hover active:text-color-icon-active', + 'button-icon-disabled': 'text-color-icon-disabled hover:cursor-not-allowed', + 'loading-svg': 'animate-spin-2 mr-1 fill-current -inset-0.5', + 'button-link': + 'text-color-link hover:text-color-link-hover active:color-link-hover active:hover:text-color-link-hover sm:hover:text-color-link-hover' +} diff --git a/packages/openinula/src/common/src/fiber.ts b/packages/openinula/src/common/src/fiber.ts index 8734c805d..04cff99de 100644 --- a/packages/openinula/src/common/src/fiber.ts +++ b/packages/openinula/src/common/src/fiber.ts @@ -3,10 +3,7 @@ import { compWhiteList } from './virtual-comp' export function getFiberByDom(dom) { const key = Object.keys(dom).find((key) => { - return ( - key.startsWith('__openinulaFiber$') || // openinula 17+ - key.startsWith('__openinulaInternalInstance$') - ) // openinula <17 + return key.startsWith('_inula_VNode_') }) return dom[key] @@ -46,13 +43,13 @@ export function creatFiberCombine(fiber) { const children = [] traverseFiber(fiber.child, [ - (fiber) => { - if (typeof fiber.type === 'string' && fiber.stateNode.getAttribute('v_ref')) { - refs[fiber.stateNode.getAttribute('v_ref')] = fiber.stateNode - } else if (fiber.memoizedProps.v_ref) { - refs[fiber.memoizedProps.v_ref] = fiber - } - }, + // (fiber) => { + // if (typeof fiber.type === 'string' && fiber.stateNode.getAttribute('v_ref')) { + // refs[fiber.stateNode.getAttribute('v_ref')] = fiber.stateNode + // } else if (fiber.memoizedProps.v_ref) { + // refs[fiber.memoizedProps.v_ref] = fiber + // } + // }, (fiber) => { if (fiber.type && typeof fiber.type !== 'string') { children.push(fiber) @@ -74,8 +71,9 @@ export function useFiber() { useEffect(() => { if (ref.current) { const current_fiber = getFiberByDom(ref.current) - setParent(getParentFiber(current_fiber.return)) - setCurrent(current_fiber.return) + + setParent(getParentFiber(current_fiber)) + setCurrent(current_fiber) } }, []) diff --git a/packages/openinula/src/common/src/utils.ts b/packages/openinula/src/common/src/utils.ts index be57dac61..6ec0aa33d 100644 --- a/packages/openinula/src/common/src/utils.ts +++ b/packages/openinula/src/common/src/utils.ts @@ -99,10 +99,10 @@ export const vc = VueClassName export const getElementCssClass = (classes = {}, key) => { if (typeof key === 'object') { - const keys = Array.isArray(key) ? key : Object.keys(key).filter((k) => key[k]) + const keys = Object.keys(key) let cls = '' keys.forEach((k) => { - if (classes[k]) cls += `${classes[k]} ` + if (key[k] && classes[k]) cls += `${classes[k]} ` }) return cls } else { diff --git a/packages/openinula/src/common/src/vm.ts b/packages/openinula/src/common/src/vm.ts index 038b79b7b..dcad20741 100644 --- a/packages/openinula/src/common/src/vm.ts +++ b/packages/openinula/src/common/src/vm.ts @@ -100,7 +100,7 @@ export function useVm() { } return { ref, - current: current.fiber && createVmProxy(current), - parent: parent.fiber && createVmProxy(parent) + current: current && createVmProxy(current), + parent: parent && createVmProxy(parent) } } diff --git a/packages/openinula/src/common/src/vue-instance.ts b/packages/openinula/src/common/src/vue-instance.ts index db7d093e4..3944fc4bd 100644 --- a/packages/openinula/src/common/src/vue-instance.ts +++ b/packages/openinula/src/common/src/vue-instance.ts @@ -11,9 +11,9 @@ const collectRefs = (rootEl, $children) => { const rootFiber = getFiberByDom(rootEl) // 收集普通元素 ref traverseFiber(rootFiber, (fiber) => { - if (typeof fiber.type === 'string' && fiber.stateNode.getAttribute('v-ref')) { - refs[fiber.stateNode.getAttribute('v-ref')] = fiber.stateNode - } + // if (typeof fiber.type === 'string' && fiber.stateNode.getAttribute('v-ref')) { + // refs[fiber.stateNode.getAttribute('v-ref')] = fiber.stateNode + // } }) // 收集组件元素 ref $children.forEach((child) => { diff --git a/packages/openinula/src/switch/index.ts b/packages/openinula/src/switch/index.ts new file mode 100644 index 000000000..0d88d715a --- /dev/null +++ b/packages/openinula/src/switch/index.ts @@ -0,0 +1,7 @@ +import Switch from './src/index' +import '@opentiny/vue-theme/switch/index.less' +import { version } from './package.json' + +Switch.version = version + +export default Switch diff --git a/packages/openinula/src/switch/package.json b/packages/openinula/src/switch/package.json new file mode 100644 index 000000000..265cbb1d3 --- /dev/null +++ b/packages/openinula/src/switch/package.json @@ -0,0 +1,25 @@ +{ + "name": "@opentiny/openinula-switch", + "version": "3.0.0", + "description": "", + "main": "lib/index.js", + "module": "index.ts", + "sideEffects": false, + "type": "module", + "scripts": { + "build": "pnpm -w build:ui-openinula $npm_package_name", + "//postversion": "pnpm build" + }, + "devDependencies": { + "@opentiny-internal/vue-test-utils": "workspace:*", + "vitest": "^0.31.0" + }, + "dependencies": { + "@opentiny/vue-renderless": "workspace:~", + "@opentiny/openinula-common": "workspace:~", + "@opentiny/openinula-icon": "workspace:~", + "@opentiny/vue-theme": "workspace:~", + "@opentiny/vue-theme-mobile": "workspace:~" + }, + "license": "MIT" +} \ No newline at end of file diff --git a/packages/openinula/src/switch/src/index.ts b/packages/openinula/src/switch/src/index.ts new file mode 100644 index 000000000..7c7e40549 --- /dev/null +++ b/packages/openinula/src/switch/src/index.ts @@ -0,0 +1,31 @@ +import pc from './pc' +import mobile from './mobile' + +// import mobileFirst from './mobile-first' + +const $constants = { + PC_PREFIXCLS: 'tiny-switch', + MOBILE_PREFIXCLS: 'tiny-mobile-switch', + Mode: 'pc', + prefixcls(mode) { + return mode === this.Mode ? this.PC_PREFIXCLS : this.MOBILE_PREFIXCLS + } +} + +export default function (props) { + const { tiny_mode = 'pc', _constants = $constants } = props + + const defaultProps = Object.assign( + { + _constants, + tiny_mode + }, + props + ) + const S = { + pc, + mobile + }[tiny_mode] + + return S && S(defaultProps) +} diff --git a/packages/openinula/src/switch/src/mobile.jsx b/packages/openinula/src/switch/src/mobile.jsx new file mode 100644 index 000000000..82f34e505 --- /dev/null +++ b/packages/openinula/src/switch/src/mobile.jsx @@ -0,0 +1,59 @@ +import { renderless, api } from '@opentiny/vue-renderless/switch/vue' +import { vc, useSetup, useVm } from '@opentiny/openinula-common' +import '@opentiny/vue-theme-mobile/switch/index.less' + +const $constants = {} + +export default function Switch(props) { + const { + _constants, + disabled = false, + showText, + falseColor, + falseValue = false, + mini = false, + modelValue = false, + size, + tabindex = '1', + trueColor, + trueValue = true, + beforeChange, + displayOnly = false + } = props + + const defaultProps = Object.assign( + { + disabled, + falseValue, + mini, + modelValue, + tabindex, + trueValue, + displayOnly + }, + props + ) + + const { ref, current: vm, parent } = useVm() + + const { toggle, state } = useSetup({ + props, + renderless, + api, + constants: _constants, + vm, + parent + }) + + return ( +
+ {' '} + +
+ {' '} +
+
+
+
+ ) +} diff --git a/packages/openinula/src/switch/src/pc.jsx b/packages/openinula/src/switch/src/pc.jsx new file mode 100644 index 000000000..5b972fe11 --- /dev/null +++ b/packages/openinula/src/switch/src/pc.jsx @@ -0,0 +1,67 @@ +import { useSetup, useVm, vc, If, Slot } from '@opentiny/openinula-common' +import { api, renderless } from '@opentiny/vue-renderless/switch/vue' +import '@opentiny/vue-theme/switch/index.less' + +export default function (props) { + const { + _constants, + disabled = false, + showText, + falseColor, + falseValue = false, + mini = false, + modelValue = false, + size, + tabindex = '1', + trueColor, + trueValue = true, + beforeChange, + displayOnly = false + } = props + + const defaultProps = Object.assign( + { + disabled, + falseValue, + mini, + modelValue, + tabindex, + trueValue, + displayOnly + }, + props + ) + + const { ref, parent, current: vm } = useVm() + + const { state, toggle } = useSetup({ + props: defaultProps, + api, + renderless, + vm, + parent, + constants: _constants + }) + + return ( + { + e.key === 'Enter' && toggle(e) + }}> + + + + ON + + + OFF + + + + + ) +}