@melt-js/libs-react-leva 0.0.0-stage → 2026.11.13-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +116 -2
- package/hel_dist/assets/index-TzYRLzs7.js +80 -0
- package/hel_dist/assets/index-TzYRLzs7.js.map +1 -0
- package/hel_dist/hel-meta.json +70 -0
- package/hel_dist/index.html +24 -0
- package/hel_proxy/entry.js +32 -0
- package/hel_proxy/entry.js.map +1 -0
- package/hel_proxy_es/entry.js +21 -0
- package/hel_proxy_es/entry.js.map +1 -0
- package/package.json +76 -3
- package/scripts/check.mjs +18 -0
- package/scripts/gen-schema.mjs +305 -0
- package/scripts/meta.mjs +46 -0
- package/scripts/prepublishOnly.mjs +45 -0
- package/src/PropsPanel.tsx +60 -0
- package/src/PropsPlayground.tsx +152 -0
- package/src/configs/subApp.ts +8 -0
- package/src/core/__tests__/pure.test.ts +47 -0
- package/src/core/leva.ts +40 -0
- package/src/core/schema.ts +57 -0
- package/src/entrance/libProperties.ts +16 -0
- package/src/entrance/libTypes.ts +16 -0
- package/src/index.ts +19 -0
- package/src/loadApp.tsx +44 -0
- package/src/shims/jsx-runtime.mjs +18 -0
- package/src/shims/react-dom-client.mjs +8 -0
- package/src/shims/react-dom.mjs +8 -0
- package/src/shims/react-is.mjs +2 -0
- package/src/shims/react.mjs +72 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React, { useEffect, useMemo } from 'react'
|
|
2
|
+
import { LevaPanel, useControls, useCreateStore } from 'leva'
|
|
3
|
+
import type { PropSchema } from './core/schema'
|
|
4
|
+
import { complexPropNames } from './core/schema'
|
|
5
|
+
import { toLevaSchema } from './core/leva'
|
|
6
|
+
|
|
7
|
+
export interface PropsPanelProps {
|
|
8
|
+
/** 面板分组名,一般传组件名 */
|
|
9
|
+
compName?: string
|
|
10
|
+
/** 组件的 propsSchema.json 内容 */
|
|
11
|
+
schema: PropSchema[]
|
|
12
|
+
/** 受控用法:外部持有的值对象(仅作为初始值,面板改动通过 onChange 回传) */
|
|
13
|
+
value?: Record<string, unknown>
|
|
14
|
+
/** 控件改动回调,回传整份最新值 */
|
|
15
|
+
onChange?: (next: Record<string, unknown>) => void
|
|
16
|
+
/** 根节点附加 class */
|
|
17
|
+
className?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 通用的 props 属性面板:只认 schema,不关心是哪个组件。
|
|
22
|
+
*
|
|
23
|
+
* 与 Vue 版 PropsPanel 同构:schema 决定生成哪些控件,控件值由 Leva store 持有,
|
|
24
|
+
* 改完通过 onChange 通知父级;复杂类型(对象 / 数组 / 事件)不生成控件,只给提示。
|
|
25
|
+
*/
|
|
26
|
+
export default function PropsPanel({
|
|
27
|
+
compName,
|
|
28
|
+
schema,
|
|
29
|
+
value,
|
|
30
|
+
onChange,
|
|
31
|
+
className,
|
|
32
|
+
}: PropsPanelProps) {
|
|
33
|
+
const levaSchema = useMemo(() => toLevaSchema(schema, value), [schema, value])
|
|
34
|
+
const complex = useMemo(() => complexPropNames(schema), [schema])
|
|
35
|
+
|
|
36
|
+
// 每个面板持有独立 store,组件切换或多个面板共存时,同名属性不会串值。
|
|
37
|
+
const store = useCreateStore()
|
|
38
|
+
const levaValues = useControls(levaSchema, { store })
|
|
39
|
+
|
|
40
|
+
// 面板值变化 -> 通知父级
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
onChange?.(levaValues as Record<string, unknown>)
|
|
43
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
44
|
+
}, [levaValues])
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div className={`props-panel${className ? ` ${className}` : ''}`}>
|
|
48
|
+
<LevaPanel
|
|
49
|
+
store={store}
|
|
50
|
+
fill
|
|
51
|
+
flat
|
|
52
|
+
hideCopyButton
|
|
53
|
+
titleBar={{ title: compName || '属性面板', drag: false, filter: false }}
|
|
54
|
+
/>
|
|
55
|
+
{complex.length > 0 && (
|
|
56
|
+
<p className="props-panel__hint">复杂类型暂未生成控件:{complex.join('、')}</p>
|
|
57
|
+
)}
|
|
58
|
+
</div>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import React, { useEffect, useMemo, useState } from 'react'
|
|
2
|
+
import PropsPanel from './PropsPanel'
|
|
3
|
+
import type { PropSchema } from './core/schema'
|
|
4
|
+
import { createValues } from './core/schema'
|
|
5
|
+
|
|
6
|
+
type Listener = (...args: unknown[]) => void
|
|
7
|
+
|
|
8
|
+
export interface PropsPlaygroundProps {
|
|
9
|
+
/** { 组件名: 组件 } */
|
|
10
|
+
components: Record<string, React.ComponentType<Record<string, unknown>>>
|
|
11
|
+
/** { 组件名: propsSchema 数组 } */
|
|
12
|
+
schemas: Record<string, PropSchema[]>
|
|
13
|
+
/** 可选:{ 组件名: { items: [...] } },面板不生成控件的复杂 props / 配套数据,同时覆盖初始值 */
|
|
14
|
+
presets?: Record<string, Record<string, unknown>>
|
|
15
|
+
/** 可选:{ 组件名: { select: (item) => ... } },组件事件监听 */
|
|
16
|
+
listeners?: Record<string, Record<string, Listener>>
|
|
17
|
+
/** 可选:{ 组件名: 'is-dark' },预览舞台附加 class */
|
|
18
|
+
stageClasses?: Record<string, string>
|
|
19
|
+
/** 可选:{ 组件名: { height: '480px' } },预览舞台内联样式 */
|
|
20
|
+
stageStyles?: Record<string, React.CSSProperties>
|
|
21
|
+
/** 可选:{ 组件名: { default: '<p>内容</p>' } },插槽用 HTML 字符串填充(当前支持 default 作为 children) */
|
|
22
|
+
slotHtml?: Record<string, Record<string, string>>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** listeners 的 key 映射成 React 回调 prop:select -> onSelect,onSelect 原样保留 */
|
|
26
|
+
function toReactListeners(listeners: Record<string, Listener> | undefined) {
|
|
27
|
+
if (!listeners) return {}
|
|
28
|
+
return Object.entries(listeners).reduce<Record<string, Listener>>((acc, [key, fn]) => {
|
|
29
|
+
const propName = /^on[A-Z]/.test(key)
|
|
30
|
+
? key
|
|
31
|
+
: `on${key.charAt(0).toUpperCase()}${key.slice(1)}`
|
|
32
|
+
acc[propName] = fn
|
|
33
|
+
return acc
|
|
34
|
+
}, {})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 纯文本当文本节点,带标签的才走 dangerouslySetInnerHTML */
|
|
38
|
+
function renderSlot(html: string | undefined) {
|
|
39
|
+
if (!html) return null
|
|
40
|
+
if (/<[a-z][\s\S]*>/i.test(html)) {
|
|
41
|
+
return <span dangerouslySetInnerHTML={{ __html: html }} />
|
|
42
|
+
}
|
|
43
|
+
return html
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 通用的组件属性实验台:组件、schema 都由宿主通过 props 传入。
|
|
48
|
+
* 不 import 任何具体组件,也不认识组件包的 schema 目录。
|
|
49
|
+
*
|
|
50
|
+
* 与 Vue 版 PropsPlayground 同构:tabs 切换组件、舞台预览、右侧属性面板;
|
|
51
|
+
* presets 固定复杂 props,其余可绑定 props 交给面板读写。
|
|
52
|
+
*/
|
|
53
|
+
export default function PropsPlayground({
|
|
54
|
+
components,
|
|
55
|
+
schemas,
|
|
56
|
+
presets = {},
|
|
57
|
+
listeners = {},
|
|
58
|
+
stageClasses = {},
|
|
59
|
+
stageStyles = {},
|
|
60
|
+
slotHtml = {},
|
|
61
|
+
}: PropsPlaygroundProps) {
|
|
62
|
+
const names = useMemo(
|
|
63
|
+
() => Object.keys(schemas).filter((name) => components[name]),
|
|
64
|
+
[schemas, components],
|
|
65
|
+
)
|
|
66
|
+
const [selected, setSelected] = useState<string>('')
|
|
67
|
+
const [values, setValues] = useState<Record<string, unknown>>({})
|
|
68
|
+
const [mountKey, setMountKey] = useState(0)
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (!names.includes(selected) && names.length) {
|
|
72
|
+
select(names[0])
|
|
73
|
+
}
|
|
74
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
75
|
+
}, [names])
|
|
76
|
+
|
|
77
|
+
const schema = schemas[selected] || []
|
|
78
|
+
const ActiveComp = components[selected]
|
|
79
|
+
const activePreset = presets[selected] || {}
|
|
80
|
+
const activeListeners = toReactListeners(listeners[selected])
|
|
81
|
+
const activeSlot = slotHtml[selected] || {}
|
|
82
|
+
|
|
83
|
+
function select(name: string) {
|
|
84
|
+
if (name === selected) return
|
|
85
|
+
setSelected(name)
|
|
86
|
+
// presets 既承载复杂 props,也可以覆盖面板初始值
|
|
87
|
+
const preset = presets[name] || {}
|
|
88
|
+
setValues({ ...createValues(schemas[name] || []), ...preset })
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const remount = () => setMountKey((key) => key + 1)
|
|
92
|
+
|
|
93
|
+
if (!names.length) {
|
|
94
|
+
return (
|
|
95
|
+
<p className="props-playground__empty">
|
|
96
|
+
PropsPlayground 需要 components(组件映射)和 schemas(propsSchema 映射)两个 props
|
|
97
|
+
</p>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const activeProps = { ...activePreset, ...values }
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<div className="props-playground">
|
|
105
|
+
<div className="props-playground__bar">
|
|
106
|
+
<div className="props-playground__tabs">
|
|
107
|
+
{names.map((name) => (
|
|
108
|
+
<button
|
|
109
|
+
key={name}
|
|
110
|
+
type="button"
|
|
111
|
+
className={`props-playground__tab${name === selected ? ' is-active' : ''}`}
|
|
112
|
+
onClick={() => select(name)}
|
|
113
|
+
>
|
|
114
|
+
{name}
|
|
115
|
+
</button>
|
|
116
|
+
))}
|
|
117
|
+
</div>
|
|
118
|
+
<button type="button" className="props-playground__action" onClick={remount}>
|
|
119
|
+
重新挂载
|
|
120
|
+
</button>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<div className="props-playground__body">
|
|
124
|
+
<div
|
|
125
|
+
className={`props-playground__stage${stageClasses[selected] ? ` ${stageClasses[selected]}` : ''}`}
|
|
126
|
+
style={stageStyles[selected]}
|
|
127
|
+
>
|
|
128
|
+
{ActiveComp ? (
|
|
129
|
+
<ActiveComp
|
|
130
|
+
key={`${selected}-${mountKey}`}
|
|
131
|
+
{...activeProps}
|
|
132
|
+
{...activeListeners}
|
|
133
|
+
>
|
|
134
|
+
{renderSlot(activeSlot.default)}
|
|
135
|
+
</ActiveComp>
|
|
136
|
+
) : (
|
|
137
|
+
<p className="props-playground__empty">组件 {selected} 未导出</p>
|
|
138
|
+
)}
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<PropsPanel
|
|
142
|
+
key={selected}
|
|
143
|
+
className="props-playground__panel"
|
|
144
|
+
compName={selected}
|
|
145
|
+
schema={schema}
|
|
146
|
+
value={values}
|
|
147
|
+
onChange={setValues}
|
|
148
|
+
/>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
)
|
|
152
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { describe, it, assert } from 'vitest'
|
|
2
|
+
import { bindableProps, complexPropNames, createValues, isBindable } from '../schema'
|
|
3
|
+
import type { PropSchema } from '../schema'
|
|
4
|
+
import { toLevaSchema } from '../leva'
|
|
5
|
+
|
|
6
|
+
const schema: PropSchema[] = [
|
|
7
|
+
{ name: 'active', type: 'boolean', description: '', default: '', required: false, version: '', isEvent: false, bindable: true, value: false },
|
|
8
|
+
{ name: 'scale', type: 'number', description: '', default: '', required: false, version: '', isEvent: false, bindable: true, value: 0, control: { min: 0, max: 2 } },
|
|
9
|
+
{ name: 'items', type: 'Array', description: '', default: '', required: false, version: '', isEvent: false, bindable: false },
|
|
10
|
+
{ name: 'onChange', type: 'function', description: '', default: '', required: false, version: '', isEvent: true, bindable: false },
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
describe('schema 纯函数', () => {
|
|
14
|
+
it('isBindable 只认 bindable 标记', () => {
|
|
15
|
+
assert.equal(isBindable(schema[0]), true)
|
|
16
|
+
assert.equal(isBindable(schema[2]), false)
|
|
17
|
+
assert.equal(isBindable(null), false)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('createValues 只收集可绑定 prop 的初始值', () => {
|
|
21
|
+
assert.deepEqual(createValues(schema), { active: false, scale: 0 })
|
|
22
|
+
assert.deepEqual(createValues(undefined), {})
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('bindableProps / complexPropNames 分类正确', () => {
|
|
26
|
+
assert.deepEqual(
|
|
27
|
+
bindableProps(schema).map((prop) => prop.name),
|
|
28
|
+
['active', 'scale'],
|
|
29
|
+
)
|
|
30
|
+
assert.deepEqual(complexPropNames(schema), ['items'])
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
describe('toLevaSchema', () => {
|
|
35
|
+
it('只转可绑定 prop,合并 control 配置', () => {
|
|
36
|
+
const leva = toLevaSchema(schema)
|
|
37
|
+
assert.equal(leva.active.value, false)
|
|
38
|
+
assert.equal(leva.scale.min, 0)
|
|
39
|
+
assert.equal(leva.scale.max, 2)
|
|
40
|
+
assert.ok(!('items' in leva))
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('受控值优先于 schema 初始值', () => {
|
|
44
|
+
const leva = toLevaSchema(schema, { scale: 1.5 })
|
|
45
|
+
assert.equal(leva.scale.value, 1.5)
|
|
46
|
+
})
|
|
47
|
+
})
|
package/src/core/leva.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/*
|
|
2
|
+
|--------------------------------------------------------------------------
|
|
3
|
+
|
|
|
4
|
+
| 把与框架无关的 propsSchema 转成 Leva 的 controls schema 纯函数
|
|
5
|
+
|
|
|
6
|
+
| 面板组件只关心:schema 转控件配置、值从哪来;控件库本身在 PropsPanel 里挂载
|
|
7
|
+
|
|
|
8
|
+
|--------------------------------------------------------------------------
|
|
9
|
+
*/
|
|
10
|
+
import type { PropSchema } from './schema'
|
|
11
|
+
import { bindableProps } from './schema'
|
|
12
|
+
|
|
13
|
+
/** Leva 单个控件配置:value + label,其余 min / max / step / options 等原样透传 */
|
|
14
|
+
export type LevaControl = Record<string, any>
|
|
15
|
+
|
|
16
|
+
/** Leva controls schema:{ [propName]: LevaControl } */
|
|
17
|
+
export type LevaSchema = Record<string, any>
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 把 propsSchema.json 内容转成 Leva 的 schema 对象。
|
|
21
|
+
*
|
|
22
|
+
* @param schema 组件的 propsSchema 数组
|
|
23
|
+
* @param values 已有的面板值(受控用法下优先取这里,缺省回落到 schema 里的初始值)
|
|
24
|
+
*/
|
|
25
|
+
export function toLevaSchema(
|
|
26
|
+
schema: PropSchema[] | undefined | null,
|
|
27
|
+
values?: Record<string, unknown>,
|
|
28
|
+
): LevaSchema {
|
|
29
|
+
const merged = values || {}
|
|
30
|
+
return bindableProps(schema).reduce<LevaSchema>((acc, prop) => {
|
|
31
|
+
acc[prop.name] = {
|
|
32
|
+
value: Object.prototype.hasOwnProperty.call(merged, prop.name)
|
|
33
|
+
? merged[prop.name]
|
|
34
|
+
: prop.value,
|
|
35
|
+
label: prop.description || prop.name,
|
|
36
|
+
...(prop.control || {}),
|
|
37
|
+
}
|
|
38
|
+
return acc
|
|
39
|
+
}, {})
|
|
40
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/*
|
|
2
|
+
|--------------------------------------------------------------------------
|
|
3
|
+
|
|
|
4
|
+
| schema 的纯函数工具:与框架无关,形状与 @melt-js/libs-vue3-leva 保持一致
|
|
5
|
+
|
|
|
6
|
+
|--------------------------------------------------------------------------
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** 透传给面板控件库(Leva)的额外配置:min / max / step / options ... */
|
|
10
|
+
export type PropControl = Record<string, unknown>
|
|
11
|
+
|
|
12
|
+
/** propsSchema.json 里单个 prop 的形状(与 Vue 版同构) */
|
|
13
|
+
export interface PropSchema {
|
|
14
|
+
/** 属性名 */
|
|
15
|
+
name: string
|
|
16
|
+
/** 原始类型串,如 number / string / 'wheel' | 'fan' / Items[] */
|
|
17
|
+
type: string
|
|
18
|
+
/** JSDoc 描述 */
|
|
19
|
+
description: string
|
|
20
|
+
/** 源码里的默认值表达式(字面量或 computed 文本) */
|
|
21
|
+
default: string
|
|
22
|
+
required: boolean
|
|
23
|
+
version: string
|
|
24
|
+
/** 是否为事件回调(onXxx / function) */
|
|
25
|
+
isEvent: boolean
|
|
26
|
+
/** 是否能在面板上生成控件(boolean / number / string) */
|
|
27
|
+
bindable: boolean
|
|
28
|
+
/** 可绑定 prop 的面板初始值(已转成字面量) */
|
|
29
|
+
value?: string | number | boolean
|
|
30
|
+
/** 控件配置,原样交给 Leva */
|
|
31
|
+
control?: PropControl
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** 该 prop 是否能在面板上生成控件(boolean / number / string) */
|
|
35
|
+
export function isBindable(prop: PropSchema | null | undefined): boolean {
|
|
36
|
+
return Boolean(prop && prop.bindable)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** 按 schema 生成面板的初始值,只包含可绑定(bindable)的 prop */
|
|
40
|
+
export function createValues(schema: PropSchema[] | undefined | null): Record<string, unknown> {
|
|
41
|
+
return (schema || []).reduce<Record<string, unknown>>((values, prop) => {
|
|
42
|
+
if (isBindable(prop)) values[prop.name] = prop.value
|
|
43
|
+
return values
|
|
44
|
+
}, {})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** 没有生成控件的 prop 名(复杂对象 / 数组 / 事件等),面板上给个提示 */
|
|
48
|
+
export function complexPropNames(schema: PropSchema[] | undefined | null): string[] {
|
|
49
|
+
return (schema || [])
|
|
50
|
+
.filter((prop) => !prop.isEvent && !isBindable(prop))
|
|
51
|
+
.map((prop) => prop.name)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** 可生成控件的 prop 列表 */
|
|
55
|
+
export function bindableProps(schema: PropSchema[] | undefined | null): PropSchema[] {
|
|
56
|
+
return (schema || []).filter(isBindable)
|
|
57
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import PropsPanel from '../PropsPanel'
|
|
2
|
+
import PropsPlayground from '../PropsPlayground'
|
|
3
|
+
|
|
4
|
+
export { PropsPanel, PropsPlayground }
|
|
5
|
+
export * from '../core/schema'
|
|
6
|
+
export * from '../core/leva'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 这个远程包自身持有面板实现(PropsPanel / PropsPlayground),并把它暴露给使用方。
|
|
10
|
+
* 默认导出对象同时挂在 hel 模块上,供 preFetchLib 后按具名取值。
|
|
11
|
+
*/
|
|
12
|
+
const libProperties = { PropsPanel, PropsPlayground }
|
|
13
|
+
|
|
14
|
+
export type LibProperties = typeof libProperties
|
|
15
|
+
|
|
16
|
+
export default libProperties
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { exposeLib } from 'hel-lib-proxy'
|
|
2
|
+
import { LIB_NAME } from '../configs/subApp'
|
|
3
|
+
import type { LibProperties } from './libProperties'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 组件类型导出文件,同时作为 rollup 打包入口:打包后是仅含 hel-lib-proxy 的空壳,
|
|
7
|
+
* 真正的组件在运行时由 hel 通过 hel_dist 载入。
|
|
8
|
+
*/
|
|
9
|
+
export const lib = exposeLib<LibProperties>(LIB_NAME)
|
|
10
|
+
|
|
11
|
+
export const PropsPanel = lib.PropsPanel
|
|
12
|
+
export const PropsPlayground = lib.PropsPlayground
|
|
13
|
+
|
|
14
|
+
export type Lib = LibProperties
|
|
15
|
+
|
|
16
|
+
export default lib
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
async function main() {
|
|
2
|
+
const { isMasterApp } = await import('hel-iso')
|
|
3
|
+
const { libReady } = await import('hel-lib-proxy')
|
|
4
|
+
const { LIB_NAME } = await import('./configs/subApp')
|
|
5
|
+
|
|
6
|
+
const libProperties = await import('./entrance/libProperties')
|
|
7
|
+
// !!!注意这句话不能删掉,否则使用方无法获取到模块
|
|
8
|
+
libReady(LIB_NAME, libProperties.default)
|
|
9
|
+
|
|
10
|
+
// 非子应用时自己挂载调试页,方便本地独立验证(被宿主载入时不会触发)
|
|
11
|
+
if (isMasterApp()) {
|
|
12
|
+
await import('./loadApp')
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
main().catch(console.error)
|
|
17
|
+
|
|
18
|
+
// avoid isolatedModules warning
|
|
19
|
+
export default 'HEL REMOTE MOD'
|
package/src/loadApp.tsx
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 仅本地调试用:直接访问本工程(dev / preview)时渲染一个玩具组件演示面板,
|
|
3
|
+
* 作为远程库被宿主加载时不会执行(见 src/index.ts 的 isMasterApp 判断)。
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react'
|
|
6
|
+
import { createRoot } from 'react-dom/client'
|
|
7
|
+
import PropsPlayground from './PropsPlayground'
|
|
8
|
+
|
|
9
|
+
const Toy: React.FC<Record<string, unknown>> = (props) => (
|
|
10
|
+
<div style={{ padding: 24, fontSize: `${props.size}px` }}>{props.label as string}</div>
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
const SCHEMA = [
|
|
14
|
+
{
|
|
15
|
+
name: 'label',
|
|
16
|
+
type: 'string',
|
|
17
|
+
description: '文案',
|
|
18
|
+
default: '"hello"',
|
|
19
|
+
required: false,
|
|
20
|
+
version: '',
|
|
21
|
+
isEvent: false,
|
|
22
|
+
bindable: true,
|
|
23
|
+
value: 'hello',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'size',
|
|
27
|
+
type: 'number',
|
|
28
|
+
description: '字号',
|
|
29
|
+
default: '16',
|
|
30
|
+
required: false,
|
|
31
|
+
version: '',
|
|
32
|
+
isEvent: false,
|
|
33
|
+
bindable: true,
|
|
34
|
+
value: 16,
|
|
35
|
+
control: { min: 12, max: 48, step: 1 },
|
|
36
|
+
},
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
const node = document.getElementById('container') || document.getElementById('root')
|
|
40
|
+
if (node) {
|
|
41
|
+
createRoot(node).render(
|
|
42
|
+
<PropsPlayground components={{ Toy }} schemas={{ Toy: SCHEMA }} />,
|
|
43
|
+
)
|
|
44
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dev 专用 shim:react/jsx-runtime 与 react/jsx-dev-runtime 映射到全局 React。
|
|
3
|
+
* 全局 React UMD 不暴露 jsx/jsxs,这里基于 React.createElement 自行包装实现。
|
|
4
|
+
*/
|
|
5
|
+
const React = globalThis.React
|
|
6
|
+
|
|
7
|
+
const { Fragment } = React
|
|
8
|
+
|
|
9
|
+
function jsx(type, props, key) {
|
|
10
|
+
const next = props ? { ...props } : {}
|
|
11
|
+
if (key != null) next.key = key
|
|
12
|
+
return React.createElement(type, next)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const jsxs = jsx
|
|
16
|
+
const jsxDEV = jsx
|
|
17
|
+
|
|
18
|
+
export { Fragment, jsx, jsxs, jsxDEV }
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dev 专用 shim:把 react 解析到宿主(或本地 index.html UMD)注入的全局 React,
|
|
3
|
+
* 避免 vite dep optimizer 内联出第二份 React 实例而触发 invalid hook call。
|
|
4
|
+
* 生产 build 走 rollup external + UMD globals,不经过此文件。
|
|
5
|
+
*/
|
|
6
|
+
const React = globalThis.React
|
|
7
|
+
|
|
8
|
+
export default React
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
useState,
|
|
12
|
+
useEffect,
|
|
13
|
+
useMemo,
|
|
14
|
+
useCallback,
|
|
15
|
+
useRef,
|
|
16
|
+
useContext,
|
|
17
|
+
useReducer,
|
|
18
|
+
useImperativeHandle,
|
|
19
|
+
useLayoutEffect,
|
|
20
|
+
useDebugValue,
|
|
21
|
+
useSyncExternalStore,
|
|
22
|
+
useTransition,
|
|
23
|
+
useDeferredValue,
|
|
24
|
+
useId,
|
|
25
|
+
useInsertionEffect,
|
|
26
|
+
forwardRef,
|
|
27
|
+
createElement,
|
|
28
|
+
Fragment,
|
|
29
|
+
isValidElement,
|
|
30
|
+
cloneElement,
|
|
31
|
+
Children,
|
|
32
|
+
createContext,
|
|
33
|
+
memo,
|
|
34
|
+
lazy,
|
|
35
|
+
Suspense,
|
|
36
|
+
Profiler,
|
|
37
|
+
StrictMode,
|
|
38
|
+
startTransition,
|
|
39
|
+
version,
|
|
40
|
+
} = React
|
|
41
|
+
|
|
42
|
+
export {
|
|
43
|
+
useState,
|
|
44
|
+
useEffect,
|
|
45
|
+
useMemo,
|
|
46
|
+
useCallback,
|
|
47
|
+
useRef,
|
|
48
|
+
useContext,
|
|
49
|
+
useReducer,
|
|
50
|
+
useImperativeHandle,
|
|
51
|
+
useLayoutEffect,
|
|
52
|
+
useDebugValue,
|
|
53
|
+
useSyncExternalStore,
|
|
54
|
+
useTransition,
|
|
55
|
+
useDeferredValue,
|
|
56
|
+
useId,
|
|
57
|
+
useInsertionEffect,
|
|
58
|
+
forwardRef,
|
|
59
|
+
createElement,
|
|
60
|
+
Fragment,
|
|
61
|
+
isValidElement,
|
|
62
|
+
cloneElement,
|
|
63
|
+
Children,
|
|
64
|
+
createContext,
|
|
65
|
+
memo,
|
|
66
|
+
lazy,
|
|
67
|
+
Suspense,
|
|
68
|
+
Profiler,
|
|
69
|
+
StrictMode,
|
|
70
|
+
startTransition,
|
|
71
|
+
version,
|
|
72
|
+
}
|