feat: 适配 solidjs (#1566) (#1682)

* feat:同步代码

* chore: 优化本地 svg 加载

Co-authored-by: however <102494131+river-xiu@users.noreply.github.com>
This commit is contained in:
ajaxzheng 2024-06-27 16:11:48 +08:00 committed by GitHub
parent 196ab84bee
commit f7ca5794d3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 1654 additions and 60 deletions

View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Opentiny Solid 组件调试</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,24 @@
{
"name": "@opentiny/docs-solid",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"solid-js": "^1.7.8",
"@opensolidtiny/solid": "~3.14.0",
"@opensolidtiny/solid-icon": "~3.14.0",
"@opensolidtiny/solid-common": "~3.14.0",
"@opentiny/vue-theme": "~3.14.0",
"@opentiny/vue-renderless": "~3.14.0"
},
"devDependencies": {
"vite": "^4.4.5",
"vite-plugin-solid": "^2.7.0",
"vite-plugin-inspect": "^0.7.10"
}
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,35 @@
import { createMutable } from 'solid-js/store'
import { Button, Alert } from '@opensolidtiny/solid'
import { IconActivation } from '@opensolidtiny/solid-icon'
// 在这里导入组件,进行 api 调试
function App() {
const state = createMutable({
value: 1
})
const hanleClick = () => {
state.value++
}
const alertSlots = {
title: <span></span>
}
return (
<div className="app">
<h1>{state.value}</h1>
<Button onClick={hanleClick}></Button>
<Button type="primary"></Button>
<Button type="success"> </Button>
<Button type="danger"></Button>
<Button type="danger" text={'Text'}></Button>
<Alert description="type 为默认值 info"></Alert>
<Alert type="warning" description="type 为默认值 info" size="large" slots={alertSlots}></Alert>
<br />
<br />
<IconActivation style={{ width: '48px', height: '48px' }}></IconActivation>
</div>
)
}
export default App

View File

@ -0,0 +1,4 @@
.app {
margin: 10px;
width: 500px;
}

View File

@ -0,0 +1,7 @@
import { render } from 'solid-js/web'
import App from './App'
import './main.css'
const root = document.getElementById('root')
render(() => <App />, root)

View File

@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

View File

@ -0,0 +1,10 @@
import { defineConfig } from 'vite'
import solid from 'vite-plugin-solid'
import inspectPlugin from 'vite-plugin-inspect'
export default defineConfig({
plugins: [inspectPlugin(), solid()],
define: {
'process.env': {}
}
})

View File

@ -10,11 +10,20 @@
},
"dependencies": {
"solid-js": "^1.7.8",
"@opentiny/solid": "workspace:~"
"@opentiny/solid": "workspace:~",
"@opentiny/solid-common": "workspace:~",
"@opentiny/vue-button": "workspace:~",
"@opentiny/vue-alert": "workspace:~",
"@opentiny/vue-badge": "workspace:~",
"@opentiny/vue-icon": "workspace:~",
"@opentiny/vue-switch": "workspace:~",
"@opentiny/vue-theme": "workspace:~"
},
"devDependencies": {
"fs-extra": "^11.2.0",
"vite": "^4.4.5",
"vite-plugin-solid": "^2.7.0",
"vite-plugin-svgr": "^3.2.0"
"vite-plugin-inspect": "^0.7.10",
"@opentiny/vue-vite-template2jsx": "workspace:~"
}
}
}

View File

@ -1,10 +1,54 @@
import { Button } from '@opentiny/solid'
import { createMutable } from 'solid-js/store'
import Button from './views/button'
import Alert from './views/alert'
import Icon from './views/icon'
import Switch from './views/switch'
import { Dynamic } from 'solid-js/web'
import './main.less'
// 在这里导入组件,进行 api 调试
function App() {
const allViews = {
Button,
Alert,
Icon,
Switch
}
const state = createMutable({
value: null,
active: '',
menus: ['Button', 'Alert', 'Icon', 'Switch']
})
const getViewComponent = (name: string) => {
return allViews[name] || Button
}
const hashchange = () => {
state.active = window.location.hash.replace('#/', '') || 'Button'
state.value = getViewComponent(state.active)
}
window.addEventListener('hashchange', hashchange)
hashchange()
return (
<div className="app">
<Button></Button>
<div class="app">
<p>
<For each={state.menus}>
{(item) => (
<>
<a classList={{ 'title': true, 'active': state.active === item }} href={`#/${item}`}>
{item}
</a>
<span>/</span>
</>
)}
</For>
</p>
<br />
<Dynamic component={state.value} />
</div>
)
}

View File

@ -0,0 +1,17 @@
.app {
margin: 40px auto;
width: 640px;
a.title {
color: #39f;
cursor: pointer;
display: inline-block;
margin:0 10px;
text-decoration: none;
&.active {
font-weight: 600;
text-decoration: underline;
}
}
}

View File

@ -1,6 +1,5 @@
import { render } from 'solid-js/web'
import App from './App'
import './main.css'
const root = document.getElementById('root')

View File

@ -0,0 +1,61 @@
import TinyAlert from '@opentiny/vue-alert'
export default function () {
const alertSlots = {
title: <span></span>
}
const alertSlots2 = {
description: (
<span>
<p style="color:red;"></p>
</span>
)
}
const alertSlots3 = {
close: <span></span>
}
return (
<div className="alert">
<TinyAlert description="type 为默认值 info"></TinyAlert>
<TinyAlert size="large" title="slot 自定义内容">
<span></span>
</TinyAlert>
<br />
<TinyAlert size="large" title="slot 自定义交互操作">
<a href="javascript: void(0);"></a>
<a href="javascript: void(0);"></a>
</TinyAlert>
<br />
<TinyAlert type="success" title="成功" size="large" description="提交结果页用于反馈一系列操作任务的处理结果。">
<a href="javascript: void(0);"></a>
<a href="javascript: void(0);"></a>
</TinyAlert>
<br />
<TinyAlert
type="error"
title="错误"
size="large"
description="提交结果页用于反馈一系列操作任务的处理结果。"></TinyAlert>
<br />
<TinyAlert type="warning" title="警告" size="large" description="提交结果页用于反馈一系列操作任务的处理结果。">
<a href="javascript: void(0);"></a>
<a href="javascript: void(0);"></a>
</TinyAlert>
<br />
<TinyAlert type="warning" description="type 为默认值 info" size="large" slots={alertSlots}></TinyAlert>
<br />
<TinyAlert type="warning" title="警告" size="large" slots={alertSlots2}></TinyAlert>
<br />
<TinyAlert
type="error"
title="错误"
size="large"
description="关闭按钮自定义文本"
closable={false}
slots={alertSlots3}></TinyAlert>
</div>
)
}

View File

@ -0,0 +1,60 @@
import { createMutable } from 'solid-js/store'
import TinyButton from '@opentiny/vue-button'
import TinyIconSearch from '@opentiny/vue-icon/search/index.ts'
export default function () {
const state = createMutable({
value: 1
})
const hanleClick = () => {
state.value++
}
return (
<div className="button">
<TinyButton></TinyButton>
<TinyButton type="primary"></TinyButton>
<TinyButton type="success"></TinyButton>
<br />
<br />
<TinyButton type="info"></TinyButton>
<TinyButton type="warning"></TinyButton>
<TinyButton type="danger"> </TinyButton>
<br />
<br />
<TinyButton plain> </TinyButton>
<TinyButton type="danger" text={'自定义 Text'}></TinyButton>
<TinyButton type="primary" loading>
</TinyButton>
<br />
<br />
<TinyButton icon={TinyIconSearch}></TinyButton>
<TinyButton type="danger" icon={TinyIconSearch} circle></TinyButton>
<TinyButton type="text"></TinyButton>
<br />
<br />
<TinyButton type="primary" size="large" plain>
{' '}
{' '}
</TinyButton>
<TinyButton type="primary" size="medium" plain>
{' '}
{' '}
</TinyButton>
<TinyButton type="primary" size="small" plain>
{' '}
{' '}
</TinyButton>
<TinyButton type="primary" size="mini" plain>
{' '}
{' '}
</TinyButton>
<br />
<br />
<TinyButton onClick={hanleClick}></TinyButton> {state.value}
<br />
<br />
</div>
)
}

View File

@ -0,0 +1,6 @@
.icon-demo .tiny-svg {
fill: #8994aa;
margin: 20px 50px;
vertical-align: middle;
font-size: 48px;
}

View File

@ -0,0 +1,20 @@
import TinyIconActivation from '@opentiny/vue-icon/activation/index.ts'
import TinyIconShare from '@opentiny/vue-icon/share/index.ts'
import TinyIconDel from '@opentiny/vue-icon/del/index.ts'
import TinyIconWriting from '@opentiny/vue-icon/writing/index.ts'
import TinyIconAscending from '@opentiny/vue-icon/ascending/index.ts'
import TinyIconClockWork from '@opentiny/vue-icon/clock-work/index.ts'
import './icon.less'
export default function () {
return (
<div className="icon-demo">
<TinyIconActivation></TinyIconActivation>
<TinyIconShare></TinyIconShare>
<TinyIconDel></TinyIconDel>
<TinyIconWriting></TinyIconWriting>
<TinyIconAscending></TinyIconAscending>
<TinyIconClockWork></TinyIconClockWork>
</div>
)
}

View File

@ -0,0 +1,25 @@
import TinySwitch from '@opentiny/vue-switch'
import { createMutable } from 'solid-js/store'
export default function () {
const state = createMutable({
value: true
})
const switchSlots = {
open: <span></span>,
close: <span></span>
}
return (
<div className="switch">
<TinySwitch></TinySwitch>
<br />
<br />
<TinySwitch modelValue={state.value}></TinySwitch>
<br />
<br />
<TinySwitch modelValue={state.value} slots={switchSlots}></TinySwitch>
</div>
)
}

View File

@ -1,7 +1,18 @@
import { defineConfig } from 'vite'
import solid from 'vite-plugin-solid'
import svgr from 'vite-plugin-svgr'
import path from 'node:path'
import inspectPlugin from 'vite-plugin-inspect'
import vueTemplate2jsx from '@opentiny/vue-vite-template2jsx'
export default defineConfig({
plugins: [solid(), svgr()]
plugins: [inspectPlugin(), vueTemplate2jsx(), solid({ extensions: ['.js', '.ts', '.tsx', '.jsx', '.vue'] })],
define: {
'process.env': {}
},
resolve: {
alias: {
'@': path.resolve('./src'),
'@opentiny/solid-common': path.resolve('../../packages/solid/src/common/src/index.ts')
}
}
})

View File

@ -0,0 +1,278 @@
import { getBabelOutputPlugin } from '@rollup/plugin-babel'
import { createRequire } from 'node:module'
import path from 'node:path'
import { build, defineConfig } from 'vite'
import { getAlias, pathFromWorkspaceRoot } from '../../config/vite'
import { external } from '../../shared/config'
import type { Module } from '../../shared/module-utils'
import { getAllIcons, getAllModules, getByName } from '../../shared/module-utils'
import { logGreen, kebabCase, capitalizeKebabCase, getPatchVersion, isValidVersion } from '../../shared/utils'
import generatePackageJsonPlugin from './rollup/generate-package-json'
import inlineChunksPlugin from './rollup/inline-chunks'
import replaceModuleNamePlugin from './rollup/replace-module-name'
import solid from 'vite-plugin-solid'
import vueTemplate2jsx from '@opentiny/vue-vite-template2jsx'
export const pathFromPackages = (...args) => pathFromWorkspaceRoot('packages', ...args)
export const require = createRequire(import.meta.url)
export const requireModules = (id: string) => require(require.resolve(pathFromWorkspaceRoot(id)))
// 需要打包的solid组件
const buildComponents = ['alert', 'button']
export interface BaseConfig {
buildTarget: string
npmScope?: string
}
export const getBaseConfig = ({ buildTarget }: BaseConfig) => {
const versionTarget = isValidVersion(buildTarget) ? buildTarget : `3.${buildTarget}`
const themeAndRenderlessVersion = isValidVersion(buildTarget) ? buildTarget : `3.${buildTarget}`
const isThemeOrRenderless = (key) => key.includes('@opentiny/vue-theme') || key.includes('@opentiny/vue-renderless')
return defineConfig({
publicDir: false,
plugins: [
inlineChunksPlugin({ deleteInlinedFiles: true }),
generatePackageJsonPlugin({
beforeWriteFile: (filePath, content) => {
const dependencies = {}
Object.entries(content.dependencies).forEach(([key, value]) => {
// dependencies里的@opentiny,统一使用:~x.x.0
if (isThemeOrRenderless(key)) {
dependencies[key] = getPatchVersion(themeAndRenderlessVersion)
} else if ((value as string).includes('workspace:~')) {
dependencies[
key.replace('@opentiny/vue', '@opensolidtiny/solid').replace('@opentiny/solid', '@opensolidtiny/solid')
] = getPatchVersion(versionTarget)
} else {
dependencies[key] = value
}
})
const matchList = ['solid-icon', 'vue-icon-saas', 'solid']
// 如果是主入口、svg图标或者主题规范包则直接指向相同路径
if (matchList.includes(filePath)) {
content.main = './index.js'
content.module = './index.js'
} else {
content.main = './lib/index.js'
content.module = './lib/index.js'
}
content.name = content.name
.replace('@opentiny/vue', '@opensolidtiny/solid')
.replace('@opentiny/solid', '@opensolidtiny/solid')
content.version = versionTarget
content.dependencies = dependencies
delete content.devDependencies
delete content.private
delete content.exports
return {
filePath: filePath.replace(/[\\/]lib$/, ''),
content
}
}
}),
replaceModuleNamePlugin(versionTarget),
vueTemplate2jsx(),
solid({ extensions: ['.js', '.ts', '.tsx', '.jsx', '.vue'] })
],
resolve: {
extensions: ['.js', '.ts', '.tsx', '.vue'],
alias: {
...getAlias(3, '', '')
}
},
define: {
'process.env.BUILD_TARGET': JSON.stringify('component')
}
})
}
async function batchBuildAll({ tasks, formats, message, emptyOutDir, buildTarget, npmScope }) {
const rootDir = pathFromPackages('')
const outDir = path.resolve(rootDir, `dist-solid/${npmScope}`)
await batchBuild({
tasks,
formats,
message,
emptyOutDir
})
function toEntry(libs) {
return libs.reduce((result, { libPath, path: file }) => {
const tLibPath = libPath.replace('-lib/', '/lib/')
result[tLibPath] = pathFromPackages(file)
return result
}, {})
}
async function batchBuild({ tasks, formats, message, emptyOutDir }) {
if (tasks.length === 0) return
logGreen(`====== 开始构建 ${message} ======`)
const entry = toEntry(tasks)
await build({
configFile: false,
...getBaseConfig({ buildTarget }),
build: {
emptyOutDir,
minify: false,
rollupOptions: {
plugins: [
getBabelOutputPlugin({
presets: [['@babel/preset-env', { loose: true, modules: false }]]
}) as any
],
external: (source, importer, isResolved) => {
// vite打包入口文件或者没有解析过得包不能排除依赖
if (isResolved || !importer) {
return false
}
if (/vue-icon(-saas)?\/index/.test(importer)) {
// 图标入口排除子图标
return /^\.\//.test(source)
}
// 子图标排除周边引用, 这里注意不要排除svg图标
if (/vue-icon(-saas)?\/.+\/index/.test(importer)) {
return !/\.svg/.test(source)
}
if (/src\/index/.test(importer)) {
// 模块入口pc/mobile 文件要分离,同时排除 node_modules 依赖
return /^\.\/(pc|mobile|mobile-first)/.test(source) || external(source)
}
// @opentiny/vue 总入口,需要排除所有依赖
if (/vue\/(index|pc|mobile|mobile-first)\.ts$/.test(importer)) {
return true
}
return external(source)
},
output: {
strict: false,
manualChunks: {}
}
},
lib: {
// 这里可以多入口打包,也可以单入口打包
entry,
formats,
fileName: (format, entryName) => `${entryName}.js`
},
outDir
}
})
}
}
export interface BuildUiOption {
buildTarget: string // 目标版本,必填, 不需要major位因为需要同时打出vue2和vue3的包
formats: string[] // 打包的格式
clean: boolean // 是否清空build产物
scope?: string // npm的组织名称
min?: boolean // 是否压缩产物
}
function getEntryTasks(): Module[] {
// 读取TinyVue组件库入口文件
return [
{
path: `solid/index.ts`,
libPath: `solid/index`,
type: 'module',
name: kebabCase({ str: '@opensolidtiny/solid' }),
global: capitalizeKebabCase('opentinySolid'),
importName: '@opensolidtiny/solid'
}
]
}
function getSolidCommonTasks(): Module[] {
// 读取TinyVue组件库入口文件
return [
{
path: `solid/src/common/src/index.ts`,
libPath: `common/lib/index`,
type: 'module',
name: kebabCase({ str: '@opensolidtiny/solid-common' }),
global: capitalizeKebabCase('opentinySolidCommon'),
importName: '@opensolidtiny/solid-common'
}
]
}
function getTasks(names: string[]): Module[] {
// 没有指定组件,则全量构建
if (names.length === 0) {
return [...getAllModules(false)]
}
return names
.map((name) =>
getByName({
name: kebabCase({ str: name.replace('@opentiny/vue-', '') }),
isSort: false
})
)
.flat()
}
/**
* TinyVue组件打包主入口
* @private
* @param {string[]} names alert和button两个组件 pnpm build:ui alert button
* @param {BuildUiOption} buildUiOption BuildUiOption接口
*/
export async function buildSolid(
names: string[] = [],
{ buildTarget = '3.14.0', formats = ['es'], clean = false, scope = '@opensolidtiny' }: BuildUiOption
) {
// 是否清空构建目录
let emptyOutDir = clean
// 要构建的模块
let tasks = getTasks(names).filter((item) => !item.path.includes('mobile'))
// 如果指定了打包icon或者没有传入任何组件
if (names.some((name) => name.includes('icon')) || !names.length) {
const icons = getAllIcons()
icons.forEach((item) => {
item.libPath = item.libPath.replace('vue-icon', 'solid-icon')
})
tasks.push(...icons)
}
// 过虑出需要打包的solid组件入口
tasks = tasks
.filter((item) =>
buildComponents.some((value) => item.path.includes(`/${value}/`) || item.path.includes('vue-icon'))
)
.filter((item) => !item.path.includes('icon-saas'))
tasks.forEach((item) => {
if (item.libPath.includes('vue-icon')) {
item.libPath = item.libPath.replace('vue-icon', 'solid-icon')
}
})
// 打包入口文件
if (names.length === 0 || names.some((name) => ['@opensolidtiny/solid', 'solid'].includes(name))) {
tasks.push(...getEntryTasks(), ...getSolidCommonTasks())
}
// 要构建的vue框架版本
const message = `TINY for solid: ${JSON.stringify(names.length ? names : '全量')}`
await batchBuildAll({ tasks, formats, message, emptyOutDir, buildTarget, npmScope: scope })
// 确保只运行一次
emptyOutDir = false
}

View File

@ -4,3 +4,4 @@ export * from './build-runtime'
export * from './build-ui-react'
export * from './build-entry-react'
export * from './build-chart-theme'
export * from './build-ui-solid'

View File

@ -0,0 +1,3 @@
# vue-vite-template2jsx
## 一个可以将vue模板转化成jsx语法的vite插件

View File

@ -0,0 +1,143 @@
import { createRequire } from 'node:module'
import { readFileSync, existsSync } from 'node:fs'
import { transformVueTemplateToSolid } from './src/plugin.js'
import { dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import path from 'node:path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
const require = createRequire(import.meta.url)
const indexTemplatePath = path.resolve(__dirname, './template/src/index.tss')
const pcTemplatePath = path.resolve(__dirname, './template/src/pc.jsxs')
// 多模板入口模板
const indexTemplate = readFileSync(indexTemplatePath, { encoding: 'utf-8' })
// PC模板入口
const pcTemplate = readFileSync(pcTemplatePath, { encoding: 'utf-8' })
const matchComponentRegx = /vue\/src\/([a-z]+)\/src/
const fundProps = (code, findStr) => {
let findIndex = code.indexOf(findStr)
let props = ''
if (~findIndex) {
const endStr = 'export '
let constantsPart = code.substring(findIndex + findStr.length, code.length)
props = constantsPart.substring(0, constantsPart.indexOf(endStr))
}
return props
}
const getSvgContent = (svg) => {
return svg.substring(svg.indexOf('>') + 1, svg.length)
}
const getDefaultValueFromEntry = (id) => {
const componentMatchs = id.match(matchComponentRegx)
const entryPath = id.replace('pc.vue', 'index.ts')
if (existsSync(entryPath)) {
if (componentMatchs?.[1]) {
const code = readFileSync(entryPath, { encoding: 'utf-8' })
let constants = fundProps(code, '$constants =') || '{}'
let propsContent = fundProps(code, 'Props =') || fundProps(code, 'props :')
let props = {}
if (propsContent) {
const propsContentParts = propsContent.split(/\n/)
propsContentParts.forEach((item, index) => {
if (item.indexOf('type:') && ['Boolean', 'String', 'Number'].some((type) => item.includes(type))) {
const name = propsContentParts[index - 1].replace(/\W/g, '')
let value = propsContentParts[index + 1]
if (name && value.includes('default:')) {
const type = /['"]/.test(value) ? 'Char' : 'Other'
value = value.replace('default:', '').replace(/["|'|\s,]/g, '')
props[name] = { type, value }
}
}
})
}
return {
$constants: constants,
$props: props
}
}
}
return {
$constants: '{}',
$props: {}
}
}
export default function vueTemplate2Jsx() {
return {
name: '@opentiny/vue-vite-template2jsx',
enforce: 'pre',
transform(code, id) {
if (id.includes('vue-icon/src')) {
const svgName = code.match(/([a-z1-9-]+)\.svg/)
if (svgName && svgName[0]) {
let svgPath
try {
svgPath = require.resolve(`@opentiny/vue-theme/svgs/${svgName[0]}`)
} catch (e) {
svgPath = path.resolve(__dirname, `../../packages/theme/src/svgs/${svgName[0]}`)
}
if (existsSync(svgPath)) {
let svgContent = readFileSync(svgPath, { encoding: 'utf-8' })
if (svgContent.includes('<!--')) {
svgContent = svgContent.replace(/<!--[\s\S]+-->/, '')
}
if (svgContent.includes('<?xml')) {
svgContent = getSvgContent(svgContent)
}
if (svgContent.includes('<!DOCTYPE')) {
svgContent = getSvgContent(svgContent)
}
if (svgContent.includes('<style')) {
svgContent = svgContent.replace(/<style[\s\S]+<\/style>/, '')
}
const viewBox = svgContent.match(/viewBox="[\d|\s]+"/)?.[0] || `viewBox="0 0 24 24"`
return `export default function(props) {
return (<svg style={props.style} xmlns="http://www.w3.org/2000/svg" onClick={props.onClick} ${viewBox} xml:space="preserve" class={'tiny-svg svgs-icon ' + props.class}>
${getSvgContent(svgContent)}
)
}`
}
}
return code
}
if (id.includes(`src/index.ts`) && !id.includes(`common/src/index.ts`)) {
return indexTemplate
}
if (id.includes('pc.vue')) {
const componentMatchs = id.match(matchComponentRegx)
if (componentMatchs?.[1]) {
const mapData = getDefaultValueFromEntry(id)
const pcComponent = transformVueTemplateToSolid(pcTemplate, code, componentMatchs[1], mapData)
return pcComponent
}
}
}
}
}

View File

@ -0,0 +1,28 @@
{
"name": "@opentiny/vue-vite-template2jsx",
"version": "1.1.5",
"description": "A TinyVue vite import plugin",
"main": "index.js",
"module": "index.js",
"author": "Tiny Vue Team",
"license": "MIT",
"type": "module",
"repository": {
"type": "git",
"url": "git@github.com:opentiny/tiny-vue.git"
},
"keywords": [
"vite-plugin",
"TinyVue",
"vite"
],
"dependencies": {
"lodash-es": "^4.17.21",
"fs-extra": "^11.2.0",
"vue-template-compiler": "2.6.14",
"html-dom-parser": "~5.0.8"
},
"peerDependencies": {
"vite": ">=4"
}
}

View File

@ -0,0 +1,222 @@
import path from 'node:path'
import { fileURLToPath } from 'node:url'
export const builtinDirective = ['v-if', 'v-for', 'v-model', 'v-slot', 'v-else', 'v-else-if']
export const builtinDirectivesWithoutVModel = builtinDirective.filter((x) => x !== 'v-model')
export const isNamedSlot = function (dObj) {
return Object.keys(dObj.attribs).find(isSlotAttribute)
}
export function getSlotName(dObj) {
if (!dObj.attribs) return 'default'
const attr = Object.keys(dObj.attribs).find(isSlotAttribute)
if (!isNamedSlot(dObj)) return 'default'
const prefix = attr.startsWith('#') ? '#' : 'v-slot:'
return attr.split(prefix)[1]
}
export function getSlotProps(dObj) {
const attr = Object.keys(dObj.attribs).find(isSlotAttribute)
return dObj.attribs[attr]
}
export const getCondition = function (dObj) {
return dObj.attribs['v-if'] || dObj.attribs['v-else-if']
}
export function isConditional(dObj) {
return isVIf(dObj) || isVElse(dObj) || isVElseIf(dObj)
}
export function isVIf(dObj) {
return dObj.type !== 'text' && Object.prototype.hasOwnProperty.call(dObj.attribs, 'v-if')
}
export function isVElseIf(dObj) {
return dObj.type !== 'text' && Object.prototype.hasOwnProperty.call(dObj.attribs, 'v-else-if')
}
export function isVElse(dObj) {
return dObj.type !== 'text' && Object.prototype.hasOwnProperty.call(dObj.attribs, 'v-else')
}
export function isVifOrVElseIf(dObj) {
return isVIf(dObj) || isVElseIf(dObj)
}
export const isVFor = function (dObj) {
return dObj.attribs && Object.prototype.hasOwnProperty.call(dObj.attribs, 'v-for')
}
export const isSlotAttribute = function (attr) {
return attr.startsWith('#') || attr.startsWith('v-slot')
}
export function isValueAttribute(attr) {
return !['v-if', 'v-else', 'v-else-if', 'v-for', 'v-slot'].includes(attr)
}
export function isTextBlank(txt) {
for (let i of txt) {
if (i !== ' ' && i !== '\n') {
return false
}
}
return true
}
export function getTag(dObj) {
return dObj.name
}
export function getFirstChild(dObj) {
return dObj.children.length && dObj.children[0]
}
export function isEmptyObject(dObj) {
return dObj.type === 'text' && isTextBlank(dObj.data)
}
export function isText(dObj) {
return dObj.type === 'text'
}
export function isComponent(dObj) {
return dObj.name === 'component'
}
export function isTransition(dObj) {
return ['transition-group', 'transition'].includes(dObj.name)
}
export function getText(dObj) {
return dObj.data
}
export function convertText(text) {
return text.replace(/{{/g, '{').replace(/}}/g, '}').replace(/\$t/g, 't')
}
export function setDynamicComponent(dObj) {
let component = dObj.attribs[':is']
dObj.attribs[':component'] = `resolveComponent(${component}, useIcons)`
delete dObj.attribs[':is']
dObj.name = 'Dynamic'
}
export function isBindClass(attrName) {
return attrName === ':class'
}
export function isBindAll(attrName) {
return attrName === 'v-bind'
}
export function getLoopCommand(dObj) {
const command = dObj.attribs['v-for']
const separator = command.includes(' in ') ? ' in ' : ' of '
const s = command.split(separator)
return `${s[1].trim()}.map(${s[0].trim()} `
}
export function isBindingAttrs(attrName) {
return attrName.startsWith(':')
}
export const toCamelCase = function (s) {
let res = ''
for (let i = 0; i < s.length; i++) {
if (i > 0 && s[i - 1] === '-') {
res += s[i].toUpperCase()
} else if (s[i] !== '-') res += s[i]
}
return res
}
export function getAttrName(attrName, prefix) {
return toCamelCase(attrName.split(prefix)[1])
}
export function isEventListener(attrName) {
return attrName.startsWith('@')
}
export function getListenerName(attrName) {
const idx = attrName.indexOf('.')
if (idx === -1) return attrName.substr(1)
else return attrName.substr(1, idx - 1)
}
export function getModifiers(attrName) {
let res = attrName.split('.')
res.shift()
return res
}
export const convertEventListenerName = function (attrName) {
const name = getListenerName(attrName)
return toCamelCase(`on-${name}`).replace(/modelvalue/g, 'modelValue')
}
export const convertListener = function (listener) {
const isFunction = listener.includes('=>') || (!listener.includes('(') && !listener.includes('='))
return isFunction
? listener
: listener.includes('$event')
? `(v) => {${listener.replace(/\$event/g, 'v')}}`
: `() => ${listener}`
}
export const convertEventListener = function (listener, modifiers = []) {
const eventName = convertListener(listener)
return modifiers.length === 0
? `${eventName}`
: `(event) => withModifiers(event, ${eventName}, [${modifiers.map((m) => `'${m}'`).join(', ')}])`
}
export const isVModel = function (attrName) {
return attrName.startsWith('v-model')
}
export const isBooleanAttrs = function (attrName) {
return attrName === ''
}
export const isRef = function (attrName) {
return attrName === 'ref'
}
export const isDynamic = function (attrName) {
return attrName === ':is'
}
export const convertRefName = function (name) {
return toCamelCase(name)
}
const filename = fileURLToPath(import.meta.url)
export const dirname = path.dirname(filename)
export const capitalize = function (str) {
return typeof str === 'string' ? str.slice(0, 1).toUpperCase() + str.slice(1) : str
}
export const capitalizeKebabCase = function (str, splitChar = '-') {
return typeof str === 'string' ? str.split(splitChar).map(capitalize).join('') : str
}
export const kebabCase = (str, splitChar = '-') => {
if (!str || typeof str !== 'string') return str
return str
.split('')
.map((char, index) => {
const charCod = char.charCodeAt(0)
if (charCod < 65 || charCod > 122) return char
return charCod >= 65 && charCod <= 90 ? (index !== 0 ? splitChar : '') + char.toLowerCase() : char
})
.join('')
}

View File

@ -0,0 +1,95 @@
import parse from 'html-dom-parser'
import renderVueDomObject from './render.js'
import { parseComponent } from 'vue-template-compiler'
import { template } from 'lodash-es'
// 处理computed操作如果想要具有响应式需要特殊处理
const computedMap = {
button: ['buttonDisabled', 'plain', 'formDisabled'],
alert: ['getIcon', 'getTitle', 'alertClass', 'alertStyle'],
switch: ['wrapClasses', 'isDisplayOnly', 'innerClasses']
}
export const transformVueTemplateToSolid = (jsxTemplate, vueCode, componentName, mapData) => {
const component = parseComponent(vueCode)
if (component.template && component.script) {
const useCommons = [
'useSetup',
'getClassList',
'mergeProps',
'resolveComponent',
'$prefix',
'TransitionGroup',
'Transition',
't',
'withModifiers'
]
const script = component.script.content
const startStr = 'props: ['
const sliceStr = script.substring(script.indexOf(startStr) + startStr.length)
const props = sliceStr
.substring(0, sliceStr.indexOf(']'))
.replace('...props,', '')
.replace(/[\n\s']/g, '')
.split(',')
const specialVars = ['useIcons']
const node = parse(component.template.content, { recognizeSelfClosing: true })[0]
const renderNode = renderVueDomObject(node)
const useApi = renderNode.useAttrs.realAttrs.filter(
(item) => !props.includes(item) && !useCommons.includes(item) && !specialVars.includes(item)
)
let useProps = props.filter((item) => renderNode.useAttrs.realAttrs.includes(item))
let jsxContent = renderNode.default.content.replace(/slots\.default/g, 'children')
let defaultProps = []
let importIcons = script.match(/import\s?{\s?ico.+/)
let useIcons = '{}'
if (computedMap[componentName]) {
computedMap[componentName].forEach((item) => {
jsxContent = jsxContent.replaceAll(`state.${item}`, `state.${item}()`)
})
}
if (mapData.$props) {
const getKeyValue = (key, props, char = '=') => {
return `${key} ${char} ${props.type === 'Char' ? `'${props.value}'` : props.value}`
}
useProps = useProps.map((item) => {
const props = mapData.$props[item]
if (props) {
return getKeyValue(item, props)
}
return item
})
for (let key in mapData.$props) {
const props = mapData.$props[key]
defaultProps.push(getKeyValue(key, props, ':'))
}
}
importIcons = importIcons?.[0]
if (importIcons) {
importIcons = importIcons.replace(/icon/g, 'Icon')
useIcons = importIcons.match(/\{(.+)\}/)[0] || '{}'
}
const realComponet = template(jsxTemplate)({
USEAPI: useApi.join(','),
USEPROPS: useProps.join(','),
JSX: jsxContent,
NAME: componentName,
CONSTANTS: mapData.$constants || '{}',
DEFAULTPROPS: defaultProps.join(','),
IMPORTICONS: importIcons,
USEICONS: useIcons,
USECOMMONS: useCommons.join(',')
})
return realComponet
}
}

View File

@ -0,0 +1,276 @@
import { cloneDeep } from 'lodash-es'
import {
builtinDirectivesWithoutVModel,
isVModel,
convertText,
isSlotAttribute,
isBooleanAttrs,
isVIf,
isVElse,
isVFor,
isTextBlank,
isRef,
convertRefName,
getCondition,
getTag,
getFirstChild,
isEmptyObject,
isText,
getText,
isVElseIf,
getLoopCommand,
getSlotProps,
getSlotName,
isBindingAttrs,
getAttrName,
isEventListener,
getModifiers,
convertEventListenerName,
convertEventListener,
isBindClass,
isBindAll,
isComponent,
setDynamicComponent,
capitalizeKebabCase,
isTransition
} from './helpers.js'
let renderDomAttrs = []
const renderSlots = (slots) => {
if (Object.keys(slots).length === 0) return ''
let res = ` v-slots={{ `
Object.keys(slots).forEach((slotName) => {
let content = slots[slotName].content
if (!content.startsWith('<>')) content = `<> ${content} </>\n`
if (!isTextBlank(slots[slotName].content))
res = res + `'${slotName}': (${slots[slotName].slotProps}) => ${content}, \n`
})
res += ` }}`
return res
}
const convertAttrs = function (attrs) {
const res = {}
Object.keys(attrs).forEach((attrName) => {
if (isSlotAttribute(attrName) || isBindAll(attrName)) return
if (isBindingAttrs(attrName)) {
if (isBindClass(attrName)) {
res.classList = `{ getClassList(${attrs[attrName]}) }`
} else {
res[getAttrName(attrName, ':')] = `{ ${attrs[attrName]} }`
}
} else if (isEventListener(attrName)) {
const eventListenerName = convertEventListenerName(attrName)
const modifiers = getModifiers(attrName)
const listener = convertEventListener(attrs[attrName], modifiers)
res[eventListenerName] = `{${listener}}`
} else if (isVModel(attrName)) {
res[attrName] = `{${attrs[attrName]}}`
} else if (isRef(attrName)) {
res[attrName] = `{${convertRefName(attrs[attrName])}}`
} else if (isBooleanAttrs(attrs[attrName])) {
res[attrName] = ''
} else {
res[attrName] = `"${attrs[attrName]}"`
}
})
return res
}
const renderAttrs = function (attrs) {
let res = ''
Object.keys(attrs).forEach((attrName) => {
res += attrs[attrName] === '' ? `${attrName} ` : `${attrName}=${attrs[attrName]} ` // boolean attrs
})
return res === '' ? '' : ` ${res}`
}
const renderDomObj = function (dObj, needWrap = true) {
if (isEmptyObject(dObj)) {
return ''
}
if (isText(dObj)) return convertText(getText(dObj))
if (isComponent(dObj)) {
setDynamicComponent(dObj)
}
const tagName = getTag(dObj)
const slots = renderVueDomObject(getFirstChild(dObj))
const attrs = convertAttrs(cloneDeep(dObj.attribs))
let tag = tagName === 'template' ? '' : tagName
builtinDirectivesWithoutVModel.forEach((attr) => delete attrs[attr])
if (Object.keys(dObj.attribs).length) {
renderDomAttrs.push(dObj.attribs)
if (dObj.name === 'slot') {
renderDomAttrs.push({ 'v-slot': `slots.${dObj.attribs.name}` })
}
}
if (tagName.indexOf('icon-') === 0) {
tag = capitalizeKebabCase(tag)
} else if (isTransition(dObj)) {
tag = capitalizeKebabCase(tag)
attrs.onExit = '{ (el, done) => { setTimeout(done, 300) } }'
const getRealClassName = (part) => `"${(attrs.name + part).replace(/"/g, '')}"`
attrs.enterToClass = getRealClassName('-enter-from')
attrs.exitActiveClass = getRealClassName('-leave-active')
}
if (Object.keys(slots).length < 2) {
let content = slots.default?.content || ''
if (!tag) {
let slotName = 'children'
if (attrs.name) {
slotName = `slots.${attrs.name.replace(/["']/g, '')}`
}
if (!content) {
return `\n { ${slotName} } \n`
}
if (!/<[^>]+>/.test(content)) {
content = `<span>${content}</span>`
}
if (needWrap) {
return `{ ${slotName} || ${content} }`
}
return `(${slotName} || ${content})`
}
return `<${tag}${renderAttrs(attrs)}> \n ${content}</${tag}>\n`
}
return `<${tag}${renderAttrs(attrs)}${renderSlots(slots)}></${tag}>\n`
}
const renderVIfSegment = function (first, last, nodes, isRoot) {
const open = isRoot ? '{' : '('
const close = isRoot ? '}' : ')'
const domContent = renderDomObj(nodes[first], false)
if (first + 1 === last) {
return `${open} \n (${getCondition(nodes[first])}) && \n ${domContent} ${close} \n`
} else {
if (isVElse(nodes[first + 1])) {
return `${open} \n (${getCondition(nodes[first])}) ? \n ${domContent} : \n
${renderDomObj(nodes[first + 1])} ${close} \n`
} else {
return `${open} \n (${getCondition(nodes[first])}) ? \n ${domContent} : \n
${renderVIfSegment(first + 1, last, nodes)} ${close} \n`
}
}
}
const renderVFor = function (dObj) {
return `{${getLoopCommand(dObj)} => \n ${renderDomObj(dObj)} )}`
}
const renderVueDomObject = function (dObj) {
if (!dObj) return ''
let _dObj = cloneDeep(dObj)
let slots = {}
slots.default = { slotProps: '', content: '', nodes: [] }
function addNode(dObj) {
if (isEmptyObject(_dObj)) return
const slotName = getSlotName(dObj)
if (dObj.name === 'slot') {
dObj.name = 'template'
}
slots[slotName] = slots[slotName] || { slotProps: getSlotProps(dObj), nodes: [] }
slots[slotName].nodes.push(dObj)
}
addNode(_dObj)
while (_dObj.next) {
_dObj = _dObj.next
addNode(_dObj)
}
function renderNodes(nodes) {
let res = ''
for (let i = 0; i < nodes.length; i++) {
if (isVIf(nodes[i])) {
let j = i + 1
while (j < nodes.length && isVElseIf(nodes[j])) j++
if (j < nodes.length) {
if (isVElse(nodes[j])) j++
}
res += renderVIfSegment(i, j, nodes, true)
i = j - 1
} else if (isVFor(nodes[i])) {
res = res + renderVFor(nodes[i])
} else {
res = res + renderDomObj(nodes[i])
}
}
return res
}
Object.keys(slots).forEach((slotName) => {
let nodes = slots[slotName].nodes
let content = renderNodes(nodes)
slots[slotName].content = content
})
return slots
}
const vueOperators = ':,v-,@'.split(',')
const globalProps = 'true,false'.split(',')
const charReg = /[\n\+\[\&\]!=\{\},\?\:\|]/g
const specialProps = 'a,$attrs,state,children,slots'.split(',')
const getUseAttrs = function () {
let attrs = renderDomAttrs
.map((item) => {
return Object.entries(item)
.filter(([key, _]) => vueOperators.some((val) => key.indexOf(val) === 0))
.map((arr) => arr[1])
.map((key) =>
key
.replace(charReg, ' ')
.split(' ')
.filter((name) => name && !name.includes('-') && !name.includes("'"))
)
.flat()
})
.flat()
.map((item) => {
return item.split(/[\(\)]/)
})
.flat()
.filter((name) => name.length && !globalProps.includes(name))
attrs = [...new Set(attrs)]
const realAttrs = [...new Set(attrs.map((attr) => attr.split('.')[0]))].filter((name) => !specialProps.includes(name))
return {
attrs,
realAttrs
}
}
export default function (dObj) {
renderDomAttrs = []
const component = renderVueDomObject(dObj)
const useAttrs = getUseAttrs()
return {
...component,
useAttrs
}
}

View File

@ -0,0 +1,3 @@
import <%=UPPERNAME%> from './src'
export default <%=UPPERNAME%>

View File

@ -0,0 +1,17 @@
{
"name": "@opentiny/solid-<%=NAME%>",
"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/solid-common": "workspace:~",
"@opentiny/vue-theme": "workspace:~"
}
}

View File

@ -0,0 +1,11 @@
import pc from './pc.vue'
export default function (props) {
const { tiny_mode = 'pc' } = props
const S = {
pc
}[tiny_mode]
return S(props)
}

View File

@ -0,0 +1,15 @@
import { renderless } from '@opentiny/vue-renderless/<%=NAME%>/vue'
import { <%=USECOMMONS%> } from '@opentiny/solid-common'
import '@opentiny/vue-theme/<%=NAME%>/index.less'
<%=IMPORTICONS%>
const useIcons = <%=USEICONS%>
const $constants = <%=CONSTANTS%>
export default function (props) {
const { children, slots = {}, <%=USEPROPS%> } = props
const { state, <%=USEAPI%> } = useSetup({
props: mergeProps({<%=DEFAULTPROPS%>}, props),
renderless,
constants: $constants,
})
return (<><%=JSX%></>)
}

View File

@ -143,8 +143,10 @@
"prettier": "prettier --config .prettierrc --write .",
"// ---------- openinula 相关脚本命令 ----------": "",
"dev:openinula": "pnpm -C examples/openinula-docs run dev",
"// ---------- solid 相关脚本命令 ----------": "",
"dev:solid": "pnpm -C examples/solid-docs run dev"
"// ---------- 预览发布后的solid组件 ----------": "",
"preview:solid": "pnpm -C examples/solid-demo run dev",
"build:solid": "pnpm -C internals/cli build:solid",
"pub:solid": "pnpm --filter=\"./packages/dist-solid/**\" publish --no-git-checks --access=public --registry=https://registry.npmjs.org"
},
"dependencies": {
"@vue/composition-api": "1.7.2",

View File

@ -1,9 +1,11 @@
import Button from '@opentiny/solid-button'
import Alert from '@opentiny/solid-alert'
export const version = '1.0.0'
export { Button }
export { Button, Alert }
export default {
Button
Button,
Alert
} as any

View File

@ -11,6 +11,7 @@
"license": "ISC",
"dependencies": {
"@opentiny/solid-common": "workspace:~",
"@opentiny/solid-button": "workspace:~"
"@opentiny/vue-button": "workspace:~",
"@opentiny/vue-alert": "workspace:~"
}
}
}

View File

@ -12,7 +12,7 @@
"dependencies": {
"@opentiny/vue-renderless": "workspace:~",
"@opentiny/vue-theme": "workspace:~",
"classnames": "^2.3.2",
"solid-transition-group": "0.2.3",
"solid-js": "^1.7.8"
}
}

View File

@ -1,5 +1,7 @@
import * as hooks from 'solid-js'
import { createSignal, onCleanup, createMemo } from 'solid-js'
import { onCleanup, onMount, createResource, createEffect, on, mergeProps } from 'solid-js'
import { createMutable } from 'solid-js/store'
import { TransitionGroup, Transition } from 'solid-transition-group'
import '@opentiny/vue-theme/base/index.less'
const EVENTS_PREFIX = 'on'
@ -7,41 +9,12 @@ const EVENTS_PREFIX = 'on'
// 处理solid事件触发机制
export const emit =
(props) =>
(evName, ...args) => {
const eventsName = `${EVENTS_PREFIX}${evName[0].toLocaleUpperCase()}${evName.slice(1)}`
if (props[eventsName] && typeof props[eventsName] === 'function') {
props[eventsName](...args)
}
}
export const useSetState = (initialState) => {
const [state, setState] = createSignal(initialState, { equals: false })
return [state, setState]
}
// props 应该不用做处理, props 都是 . 访问。
export const reactive = (staticObject) => {
const [state, setState] = useSetState(staticObject)
return new Proxy(state(), {
get(target, property) {
if (property === 'solidState') {
return state
(evName, ...args) => {
const eventsName = `${EVENTS_PREFIX}${evName[0].toLocaleUpperCase()}${evName.slice(1)}`
if (props[eventsName] && typeof props[eventsName] === 'function') {
props[eventsName](...args)
}
if (typeof target[property] === 'function') {
return target[property](target)
} else {
return target[property]
}
},
set(target, property, value) {
Reflect.set(target, property, value)
setState((val) => val)
return true
}
})
}
// nextTick 等待 dom 更新后触发回调
export const useNextTick = (callback) => {
@ -62,30 +35,142 @@ export const emitEvent = () => {
}
}
const computed = (callback) => {
try {
return createMemo(callback)
} catch (error) {
return []
}
const watch = (valueFn, callback, options) => {
createEffect(on(valueFn, callback, { defer: !options.immediate }))
}
export const useSetup = ({ props, renderless, extendOptions = { framework: 'Solid' } }) => {
export const t = (str) => str
const reactive = (state) => {
const proxy = createMutable(state)
// 暂时解决嵌套computed导致禁用问题后期再完善
if (proxy.formDisabled) {
proxy.formDisabled = false
}
if (proxy.disabled) {
proxy.disabled = false
}
return proxy
}
export const useSetup = ({ props, renderless, extendOptions = { framework: 'Solid' }, constants }) => {
const render = typeof props.tiny_renderless === 'function' ? props.tiny_renderless : renderless
const utils = {
parent: {},
emit: emit(props)
emit: emit(props),
constants,
nextTick: useNextTick,
t,
mode: 'pc'
}
const sdk = render(
props,
{ ...hooks, reactive, computed, useNextTick, inject: () => {}, watch: () => {}, onBeforeUnmount: onCleanup },
{
...hooks,
reactive,
computed: (callback) => {
const [computedValue, { mutate }] = createResource(() => {
return Promise.resolve().then(() => {
return callback()
})
})
Promise.resolve().then(() => {
createEffect(() => {
mutate(callback())
})
})
return computedValue
},
inject: () => { },
watch,
watchEffect: createEffect,
onMounted: onMount,
onBeforeUnmount: onCleanup
},
utils,
extendOptions
)
return {
...sdk,
state: sdk.state.solidState,
type: props.type ?? 'default'
}
}
export const getType = (str, type = 'object') => {
return (
{
'[object Number]': 'number',
'[object String]': 'string',
'[object Boolean]': 'boolean',
'[object Undefined]': 'undefined',
'[object Null]': 'null',
'[object Array]': 'array',
'[object Arguments]': 'arguments',
'[object Function]': 'function',
'[object Error]': 'error',
'[object Date]': 'date',
'[object RegExp]': 'regexp',
'[object Object]': 'object'
}[Object.prototype.toString.call(str)] === type
)
}
export const getClassList = (cls) => {
if (getType(cls)) {
return cls
}
if (Array.isArray(cls)) {
const classList = {}
cls.forEach((cls) => {
if (!cls) return
if (typeof cls === 'string') {
classList[cls] = true
} else if (typeof cls === 'object') {
if (Array.isArray(cls)) {
Object.assign(classList, getClassList(cls))
} else {
Object.assign(classList, cls)
}
}
})
return classList
}
return {
[cls]: true
}
}
export const capitalize = function (str) {
return typeof str === 'string' ? str.slice(0, 1).toUpperCase() + str.slice(1) : str
}
export const capitalizeKebabCase = function (str, splitChar = '-') {
return typeof str === 'string' ? str.split(splitChar).map(capitalize).join('') : str
}
export const resolveComponent = (component, useComponents) => {
if (typeof component === 'string' && component.indexOf('icon-') === 0) {
const componentName = capitalizeKebabCase(component)
return useComponents[componentName] || componentName
}
return component
}
export const $prefix = 'Tiny'
export const withModifiers = function (event, eventCallback, keys) {
if (typeof eventCallback === 'function') {
eventCallback(event);
}
}
export { mergeProps, TransitionGroup, Transition }

View File

@ -2,3 +2,4 @@ packages:
- packages/**
- examples/*
- internals/*
- solid-demo/*

30
tsconfig.solid.json Normal file
View File

@ -0,0 +1,30 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": {
"allowJs": true,
"noImplicitAny": false,
"baseUrl": ".",
"paths": {
"@opentiny/vue-autonavi-map": ["packages/vue/src/chart/autonavi-map"],
"@opentiny/vue-baidu-map": ["packages/vue/src/chart/baidu-map"],
"@opentiny/vue-chart-*": ["packages/vue/src/chart/chart-*"],
"@opentiny/vue-*": ["packages/vue-*", "packages/vue/src/*"],
"@opentiny/vue-renderless/types*": ["packages/renderless/types*"],
"@opentiny/vue-renderless*": ["packages/renderless/src*"],
"virtual:common/adapter/vue": ["packages/vue-common/src/adapter/vue3/index.ts"],
"virtual:locale/vue": ["packages/vue-locale/src/vue3/index.ts"]
},
"types": ["node", "vite/client"]
},
"vueCompilerOptions": {
"target": 3
},
"include": [
"packages/**/*.ts",
"packages/**/*.tsx",
"packages/**/*.vue",
"examples/vue3/shims-app.d.ts",
"examples/vue3/shims-vue.d.ts"
],
"exclude": ["**/node_modules", "**/dist*", "**/*.md"]
}