@melt-js/libs-react-leva 0.0.0-stage → 2026.11.13
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 +115 -2
- package/hel_dist/assets/index-DMdT4ab7.js +80 -0
- package/hel_dist/assets/index-DMdT4ab7.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 +52 -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,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"app": {
|
|
3
|
+
"name": "libs-react-leva",
|
|
4
|
+
"app_group_name": "libs-react-leva",
|
|
5
|
+
"git_repo_url": "",
|
|
6
|
+
"online_version": "2026.11.13",
|
|
7
|
+
"build_version": "2026.11.13",
|
|
8
|
+
"platform": "unpkg",
|
|
9
|
+
"create_at": "2026-11-13T07:51:56.018Z"
|
|
10
|
+
},
|
|
11
|
+
"version": {
|
|
12
|
+
"plugin_ver": "5.4.3",
|
|
13
|
+
"extract_mode": "build",
|
|
14
|
+
"sub_app_name": "libs-react-leva",
|
|
15
|
+
"sub_app_version": "libs-react-leva@2026.11.13",
|
|
16
|
+
"version_tag": "2026.11.13",
|
|
17
|
+
"src_map": {
|
|
18
|
+
"webDirPath": "https://unpkg.com/@melt-js/libs-react-leva@2026.11.13/hel_dist",
|
|
19
|
+
"htmlIndexSrc": "https://unpkg.com/@melt-js/libs-react-leva@2026.11.13/hel_dist/index.html",
|
|
20
|
+
"extractMode": "all",
|
|
21
|
+
"iframeSrc": "",
|
|
22
|
+
"chunkCssSrcList": [],
|
|
23
|
+
"chunkJsSrcList": [
|
|
24
|
+
"https://unpkg.com/@melt-js/libs-react-leva@2026.11.13/hel_dist/assets/index-DMdT4ab7.js"
|
|
25
|
+
],
|
|
26
|
+
"staticCssSrcList": [],
|
|
27
|
+
"staticJsSrcList": [
|
|
28
|
+
"https://unpkg.com/react@18.3.1/umd/react.production.min.js",
|
|
29
|
+
"https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js"
|
|
30
|
+
],
|
|
31
|
+
"relativeCssSrcList": [],
|
|
32
|
+
"relativeJsSrcList": [],
|
|
33
|
+
"headAssetList": [
|
|
34
|
+
{
|
|
35
|
+
"tag": "staticScript",
|
|
36
|
+
"append": false,
|
|
37
|
+
"attrs": {
|
|
38
|
+
"src": "https://unpkg.com/react@18.3.1/umd/react.production.min.js"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"tag": "staticScript",
|
|
43
|
+
"append": false,
|
|
44
|
+
"attrs": {
|
|
45
|
+
"src": "https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"tag": "script",
|
|
50
|
+
"append": true,
|
|
51
|
+
"attrs": {
|
|
52
|
+
"src": "https://unpkg.com/@melt-js/libs-react-leva@2026.11.13/hel_dist/assets/index-DMdT4ab7.js",
|
|
53
|
+
"type": "module",
|
|
54
|
+
"crossorigin": ""
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
"bodyAssetList": [],
|
|
59
|
+
"otherSrcList": [
|
|
60
|
+
"https://unpkg.com/@melt-js/libs-react-leva@2026.11.13/hel_dist/assets/index-DMdT4ab7.js.map",
|
|
61
|
+
"https://unpkg.com/@melt-js/libs-react-leva@2026.11.13/hel_dist/index.html"
|
|
62
|
+
],
|
|
63
|
+
"srvModSrcList": [],
|
|
64
|
+
"srvModSrcIndex": ""
|
|
65
|
+
},
|
|
66
|
+
"html_content": "<!doctype html>\n<html lang=\"zh-CN\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>libs-react-leva</title>\n <!--\n react / react-dom 被 external 成全局,独立访问本页时由这里提供运行时。\n data-helappend=\"0\":宿主通过 hel 解析本页时跳过该脚本,改用宿主注入的那一份 react。\n -->\n <script\n data-helappend=\"0\"\n src=\"https://unpkg.com/react@18.3.1/umd/react.production.min.js\"\n ></script>\n <script\n data-helappend=\"0\"\n src=\"https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js\"\n ></script>\n <script type=\"module\" crossorigin src=\"https://unpkg.com/@melt-js/libs-react-leva@2026.11.13/hel_dist/assets/index-DMdT4ab7.js\"></script>\n </head>\n <body>\n <div id=\"container\"></div>\n </body>\n</html>\n",
|
|
67
|
+
"create_at": "2026-11-13T07:51:56.018Z",
|
|
68
|
+
"desc": "this version meta is created by hel-dev-utils@5.4.3"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>libs-react-leva</title>
|
|
7
|
+
<!--
|
|
8
|
+
react / react-dom 被 external 成全局,独立访问本页时由这里提供运行时。
|
|
9
|
+
data-helappend="0":宿主通过 hel 解析本页时跳过该脚本,改用宿主注入的那一份 react。
|
|
10
|
+
-->
|
|
11
|
+
<script
|
|
12
|
+
data-helappend="0"
|
|
13
|
+
src="https://unpkg.com/react@18.3.1/umd/react.production.min.js"
|
|
14
|
+
></script>
|
|
15
|
+
<script
|
|
16
|
+
data-helappend="0"
|
|
17
|
+
src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js"
|
|
18
|
+
></script>
|
|
19
|
+
<script type="module" crossorigin src="https://unpkg.com/@melt-js/libs-react-leva@2026.11.13/hel_dist/assets/index-DMdT4ab7.js"></script>
|
|
20
|
+
</head>
|
|
21
|
+
<body>
|
|
22
|
+
<div id="container"></div>
|
|
23
|
+
</body>
|
|
24
|
+
</html>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
(function (global, factory) {
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('hel-lib-proxy')) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['exports', 'hel-lib-proxy'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["libs-react-leva"] = {}, global.HelLibProxy));
|
|
5
|
+
})(this, (function (exports, helLibProxy) { 'use strict';
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
|--------------------------------------------------------------------------
|
|
9
|
+
|
|
|
10
|
+
| 对应 package.json/appGroupName
|
|
11
|
+
|
|
|
12
|
+
|--------------------------------------------------------------------------
|
|
13
|
+
*/
|
|
14
|
+
const LIB_NAME = 'libs-react-leva';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 组件类型导出文件,同时作为 rollup 打包入口:打包后是仅含 hel-lib-proxy 的空壳,
|
|
18
|
+
* 真正的组件在运行时由 hel 通过 hel_dist 载入。
|
|
19
|
+
*/
|
|
20
|
+
const lib = helLibProxy.exposeLib(LIB_NAME);
|
|
21
|
+
const PropsPanel = lib.PropsPanel;
|
|
22
|
+
const PropsPlayground = lib.PropsPlayground;
|
|
23
|
+
|
|
24
|
+
exports.PropsPanel = PropsPanel;
|
|
25
|
+
exports.PropsPlayground = PropsPlayground;
|
|
26
|
+
exports.default = lib;
|
|
27
|
+
exports.lib = lib;
|
|
28
|
+
|
|
29
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
30
|
+
|
|
31
|
+
}));
|
|
32
|
+
//# sourceMappingURL=entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.js","sources":["../src/configs/file:/E:/work-microhel/libs-react-leva/src/configs/subApp.ts","../src/entrance/file:/E:/work-microhel/libs-react-leva/src/entrance/libTypes.ts"],"sourcesContent":[null,null],"names":["exposeLib"],"mappings":";;;;;;CAAA;;;;;;CAME;CACK,MAAM,QAAQ,GAAG,iBAAiB;;CCHzC;;;CAGG;OACU,GAAG,GAAGA,qBAAS,CAAgB,QAAQ;AAE7C,OAAM,UAAU,GAAG,GAAG,CAAC;AACvB,OAAM,eAAe,GAAG,GAAG,CAAC;;;;;;;;;"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { exposeLib } from 'hel-lib-proxy';
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
|--------------------------------------------------------------------------
|
|
5
|
+
|
|
|
6
|
+
| 对应 package.json/appGroupName
|
|
7
|
+
|
|
|
8
|
+
|--------------------------------------------------------------------------
|
|
9
|
+
*/
|
|
10
|
+
const LIB_NAME = 'libs-react-leva';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 组件类型导出文件,同时作为 rollup 打包入口:打包后是仅含 hel-lib-proxy 的空壳,
|
|
14
|
+
* 真正的组件在运行时由 hel 通过 hel_dist 载入。
|
|
15
|
+
*/
|
|
16
|
+
const lib = exposeLib(LIB_NAME);
|
|
17
|
+
const PropsPanel = lib.PropsPanel;
|
|
18
|
+
const PropsPlayground = lib.PropsPlayground;
|
|
19
|
+
|
|
20
|
+
export { PropsPanel, PropsPlayground, lib as default, lib };
|
|
21
|
+
//# sourceMappingURL=entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.js","sources":["../src/configs/file:/E:/work-microhel/libs-react-leva/src/configs/subApp.ts","../src/entrance/file:/E:/work-microhel/libs-react-leva/src/entrance/libTypes.ts"],"sourcesContent":[null,null],"names":[],"mappings":";;AAAA;;;;;;AAME;AACK,MAAM,QAAQ,GAAG,iBAAiB;;ACHzC;;;AAGG;MACU,GAAG,GAAG,SAAS,CAAgB,QAAQ;AAE7C,MAAM,UAAU,GAAG,GAAG,CAAC;AACvB,MAAM,eAAe,GAAG,GAAG,CAAC;;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,79 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@melt-js/libs-react-leva",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
3
|
+
"appGroupName": "libs-react-leva",
|
|
4
|
+
"version": "2026.11.13",
|
|
5
|
+
"description": "React 属性面板(Leva):PropsPanel / PropsPlayground 远程包 + react-docgen-typescript schema 生成 CLI",
|
|
6
|
+
"main": "hel_proxy/entry.js",
|
|
7
|
+
"module": "hel_proxy_es/entry.js",
|
|
8
|
+
"types": "src/entrance/libTypes.ts",
|
|
9
|
+
"unpkg": "hel_dist/index.html",
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public",
|
|
12
|
+
"registry": "https://registry.npmjs.org",
|
|
13
|
+
"tag": "latest"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"hel_dist",
|
|
17
|
+
"hel_proxy",
|
|
18
|
+
"hel_proxy_es",
|
|
19
|
+
"scripts",
|
|
20
|
+
"src",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"bin": {
|
|
24
|
+
"react-leva-gen-schema": "./scripts/gen-schema.mjs"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"hel-iso": "4.4.0",
|
|
28
|
+
"hel-lib-proxy": "4.12.3",
|
|
29
|
+
"leva": "0.10.1",
|
|
30
|
+
"react-docgen-typescript": "2.4.0"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"hel-lib-proxy": "4.12.3",
|
|
34
|
+
"react": "18.3.1",
|
|
35
|
+
"react-dom": "18.3.1",
|
|
36
|
+
"react-is": "18.3.1",
|
|
37
|
+
"typescript": ">=4.3.x"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@rollup/plugin-typescript": "12.3.1",
|
|
41
|
+
"@types/node": "20.19.43",
|
|
42
|
+
"@types/react": "18.3.31",
|
|
43
|
+
"@types/react-dom": "18.3.7",
|
|
44
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
45
|
+
"cross-env": "7.0.3",
|
|
46
|
+
"hel-dev-utils": "5.4.3",
|
|
47
|
+
"rollup": "4.67.5",
|
|
48
|
+
"tslib": "2.9.1",
|
|
49
|
+
"typescript": "5.9.3",
|
|
50
|
+
"vite": "6.4.5",
|
|
51
|
+
"vitest": "5.1.0"
|
|
52
|
+
},
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=18.12.1"
|
|
55
|
+
},
|
|
56
|
+
"browserslist": [
|
|
57
|
+
"defaults",
|
|
58
|
+
"not dead",
|
|
59
|
+
"not IE 11",
|
|
60
|
+
"chrome 72"
|
|
61
|
+
],
|
|
62
|
+
"scripts": {
|
|
63
|
+
"dev": "vite --host 127.0.0.1 --port 10050",
|
|
64
|
+
"start": "vite --host 127.0.0.1 --port 10050",
|
|
65
|
+
"build": "pnpm run check_name && pnpm run build_dist && pnpm run build_meta && pnpm run build_proxy",
|
|
66
|
+
"publish:stage": "pnpm run build && node ../scripts/publish-stage.mjs",
|
|
67
|
+
"build:local": "cross-env HEL_APP_HOME_PAGE=http://localhost:10050 pnpm run build",
|
|
68
|
+
"build_dist": "vite build",
|
|
69
|
+
"build_meta": "node scripts/meta.mjs",
|
|
70
|
+
"build_proxy": "pnpm run build_proxy_umd && pnpm run build_proxy_es",
|
|
71
|
+
"build_proxy_umd": "rollup -c",
|
|
72
|
+
"build_proxy_es": "cross-env BUILD_ENV=es rollup -c",
|
|
73
|
+
"check_name": "node scripts/check.mjs",
|
|
74
|
+
"preview": "vite preview --host 127.0.0.1 --port 10050",
|
|
75
|
+
"gen:schema": "react-leva-gen-schema",
|
|
76
|
+
"test": "vitest run",
|
|
77
|
+
"typecheck": "tsc --noEmit"
|
|
78
|
+
}
|
|
6
79
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
|--------------------------------------------------------------------------
|
|
3
|
+
|
|
|
4
|
+
| 此脚本在流水线上被触发,校验组名是否和应用里的组名保持一致
|
|
5
|
+
|
|
|
6
|
+
|--------------------------------------------------------------------------
|
|
7
|
+
*/
|
|
8
|
+
import path from 'node:path'
|
|
9
|
+
import { fileURLToPath } from 'node:url'
|
|
10
|
+
import { createRequire } from 'node:module'
|
|
11
|
+
import helDevUtils from 'hel-dev-utils'
|
|
12
|
+
|
|
13
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
14
|
+
const require = createRequire(import.meta.url)
|
|
15
|
+
const pkg = require('../package.json')
|
|
16
|
+
|
|
17
|
+
const fileFullPath = path.join(__dirname, '../src/configs/subApp')
|
|
18
|
+
helDevUtils.check(pkg, { fileFullPath, checkEnv: false })
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/*
|
|
3
|
+
|--------------------------------------------------------------------------
|
|
4
|
+
|
|
|
5
|
+
| 用 react-docgen-typescript 解析 React 组件的 TS props,生成和组件放在一起的
|
|
6
|
+
| propsSchema.json(形状与 @melt-js/libs-vue3-leva 产出的 schema 同构)。
|
|
7
|
+
|
|
|
8
|
+
| 用法:
|
|
9
|
+
| react-leva-gen-schema 读取当前目录的 playground.config.mjs
|
|
10
|
+
| react-leva-gen-schema --check 只校验,schema 与源码不一致时退出码 1
|
|
11
|
+
| react-leva-gen-schema --config a.mjs --root ../comps-react
|
|
12
|
+
|
|
|
13
|
+
| 约定:
|
|
14
|
+
| - 组件 props 用 interface / type 声明,JSDoc 写在属性上方作为 description
|
|
15
|
+
| - index.tsx 的 schema 写到同目录 propsSchema.json,单文件组件写 Foo.propsSchema.json
|
|
16
|
+
| - 字符串字面量联合('a' | 'b')生成下拉 options;min / max / step 等走 hints 覆盖
|
|
17
|
+
|
|
|
18
|
+
|--------------------------------------------------------------------------
|
|
19
|
+
*/
|
|
20
|
+
import fs from 'node:fs'
|
|
21
|
+
import os from 'node:os'
|
|
22
|
+
import path from 'node:path'
|
|
23
|
+
import { createRequire } from 'node:module'
|
|
24
|
+
import { pathToFileURL } from 'node:url'
|
|
25
|
+
|
|
26
|
+
const require = createRequire(import.meta.url)
|
|
27
|
+
const docgen = require('react-docgen-typescript')
|
|
28
|
+
|
|
29
|
+
const SCALAR_TYPES = ['boolean', 'number', 'string']
|
|
30
|
+
const DEFAULT_CONFIG = 'playground.config.mjs'
|
|
31
|
+
const COMPONENT_DIR = 'src/components'
|
|
32
|
+
|
|
33
|
+
const HELP = `
|
|
34
|
+
用法: react-leva-gen-schema [options]
|
|
35
|
+
|
|
36
|
+
--config <file> 配置文件,默认 playground.config.mjs
|
|
37
|
+
--root <dir> 组件源码根目录,默认当前目录
|
|
38
|
+
--check 只校验 schema 是否和源码一致(CI 用),不写文件
|
|
39
|
+
--silence 不打印每个组件的生成结果
|
|
40
|
+
-h, --help 显示帮助
|
|
41
|
+
|
|
42
|
+
配置文件(ESM)示例:
|
|
43
|
+
export default {
|
|
44
|
+
components: [
|
|
45
|
+
{ name: 'HelloButton', file: 'src/components/HelloButton.tsx' },
|
|
46
|
+
],
|
|
47
|
+
hints: { HelloButton: { size: { min: 12, max: 48, step: 1 } } },
|
|
48
|
+
}
|
|
49
|
+
`
|
|
50
|
+
|
|
51
|
+
function parseArgv(argv) {
|
|
52
|
+
const options = { check: false, silence: false, config: DEFAULT_CONFIG, root: '' }
|
|
53
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
54
|
+
const arg = argv[i]
|
|
55
|
+
if (arg === '--check') options.check = true
|
|
56
|
+
else if (arg === '--silence') options.silence = true
|
|
57
|
+
else if (arg === '--config') options.config = argv[(i += 1)]
|
|
58
|
+
else if (arg === '--root') options.root = argv[(i += 1)]
|
|
59
|
+
else if (arg === '-h' || arg === '--help') options.help = true
|
|
60
|
+
else throw new Error(`[props-schema] 未知参数: ${arg}`)
|
|
61
|
+
}
|
|
62
|
+
return options
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function toPosix(file) {
|
|
66
|
+
return file.split(path.sep).join('/')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** 归一出可绑定的标量类型;拿不到标量返回空串 */
|
|
70
|
+
function baseType(type) {
|
|
71
|
+
if (!type) return ''
|
|
72
|
+
const name = type.name || ''
|
|
73
|
+
const raw = type.raw || ''
|
|
74
|
+
if (SCALAR_TYPES.includes(name)) return name
|
|
75
|
+
if (name === 'union') {
|
|
76
|
+
const parts = (type.value || [])
|
|
77
|
+
.map((v) => String(v?.value ?? v?.name ?? '').trim())
|
|
78
|
+
.filter(Boolean)
|
|
79
|
+
// 字面量联合('wheel' | 'fan')当成 string,面板上用下拉
|
|
80
|
+
if (parts.length > 1 && parts.every((p) => /^(['"]).*\1$/.test(p))) return 'string'
|
|
81
|
+
const scalar = SCALAR_TYPES.find((s) => parts.includes(s))
|
|
82
|
+
return scalar || ''
|
|
83
|
+
}
|
|
84
|
+
if (name === 'enum') return 'string'
|
|
85
|
+
if (name === 'arrayOf' || /\[\]$/.test(raw)) return 'array'
|
|
86
|
+
if (/^object$/i.test(name) || /^record</i.test(raw)) return 'object'
|
|
87
|
+
return ''
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 把 'wheel' | 'fan' 拆成 Leva 的 options: { wheel: 'wheel', fan: 'fan' }
|
|
91
|
+
// docgen 里字面量联合的 type.name 可能是 'union' 或 'enum',两种都兼容
|
|
92
|
+
function literalOptions(type) {
|
|
93
|
+
if (!type || (type.name !== 'union' && type.name !== 'enum')) return null
|
|
94
|
+
const parts = (type.value || [])
|
|
95
|
+
.map((v) => String(v?.value ?? v?.name ?? '').trim())
|
|
96
|
+
.filter(Boolean)
|
|
97
|
+
if (parts.length < 2 || !parts.every((p) => /^(['"]).*\1$/.test(p))) return null
|
|
98
|
+
return parts.reduce((options, part) => {
|
|
99
|
+
const value = part.slice(1, -1)
|
|
100
|
+
options[value] = value
|
|
101
|
+
return options
|
|
102
|
+
}, {})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// docgen 的 default 是源码表达式(字符串会带引号),这里转成面板初始值
|
|
106
|
+
function parseString(raw) {
|
|
107
|
+
if (!raw) return ''
|
|
108
|
+
try {
|
|
109
|
+
return JSON.parse(raw)
|
|
110
|
+
} catch (error) {
|
|
111
|
+
const quoted =
|
|
112
|
+
raw.length > 1 && (raw[0] === '"' || raw[0] === "'") && raw[raw.length - 1] === raw[0]
|
|
113
|
+
return quoted ? raw.slice(1, -1) : raw
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function defaultValue(type, raw) {
|
|
118
|
+
if (raw === undefined || raw === null || raw === '') return undefined
|
|
119
|
+
if (type === 'boolean') return raw === 'true'
|
|
120
|
+
if (type === 'number') {
|
|
121
|
+
const value = Number(raw)
|
|
122
|
+
return Number.isFinite(value) ? value : 0
|
|
123
|
+
}
|
|
124
|
+
if (type === 'string') return parseString(raw)
|
|
125
|
+
return undefined
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// index.tsx 放同目录的 propsSchema.json;单文件组件放 Foo.propsSchema.json
|
|
129
|
+
function schemaRelPath(file) {
|
|
130
|
+
if (/\/index\.(tsx|ts|jsx|js)$/.test(file)) return file.replace(/index\.(tsx|ts|jsx|js)$/, 'propsSchema.json')
|
|
131
|
+
return file.replace(/\.(tsx|ts|jsx|js)$/, '.propsSchema.json')
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 沿用已有文件的换行符(Windows 检出是 CRLF),避免只因为 EOL 产生 diff
|
|
135
|
+
function eolOf(filePath) {
|
|
136
|
+
if (!fs.existsSync(filePath)) return os.EOL
|
|
137
|
+
const text = fs.readFileSync(filePath, 'utf8')
|
|
138
|
+
return text.includes('\r\n') ? '\r\n' : '\n'
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function toSchemaProp(prop, hint) {
|
|
142
|
+
const type = baseType(prop.type)
|
|
143
|
+
const rawDefault = prop.defaultValue && !prop.defaultValue.computed ? prop.defaultValue.value : ''
|
|
144
|
+
const typeName = (prop.type && prop.type.name) || ''
|
|
145
|
+
// 事件回调:onXxx 命名,或类型是函数签名(docgen 会把箭头函数类型串进 name)
|
|
146
|
+
const isFunction = typeName === 'function' || /=>/.test(typeName)
|
|
147
|
+
const item = {
|
|
148
|
+
name: prop.name,
|
|
149
|
+
type: (prop.type && (prop.type.raw || prop.type.name)) || type,
|
|
150
|
+
description: prop.description || prop.name,
|
|
151
|
+
default: rawDefault || '',
|
|
152
|
+
required: Boolean(prop.required),
|
|
153
|
+
version: '',
|
|
154
|
+
isEvent: /^on[A-Z]/.test(prop.name) || isFunction,
|
|
155
|
+
bindable: SCALAR_TYPES.includes(type),
|
|
156
|
+
}
|
|
157
|
+
if (item.bindable) item.value = defaultValue(type, rawDefault)
|
|
158
|
+
if (hint) {
|
|
159
|
+
item.control = { ...hint }
|
|
160
|
+
} else if (item.bindable) {
|
|
161
|
+
// 字面量联合默认做成下拉,config 里的 hints 可以覆盖
|
|
162
|
+
const options = literalOptions(prop.type)
|
|
163
|
+
if (options) item.control = { options }
|
|
164
|
+
}
|
|
165
|
+
return item
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** 没配 components 时,按 src/components 的约定自动发现 */
|
|
169
|
+
function discoverComponents(root) {
|
|
170
|
+
const dir = path.join(root, COMPONENT_DIR)
|
|
171
|
+
if (!fs.existsSync(dir)) return []
|
|
172
|
+
const components = []
|
|
173
|
+
fs.readdirSync(dir, { withFileTypes: true }).forEach((entry) => {
|
|
174
|
+
const full = path.join(dir, entry.name)
|
|
175
|
+
if (entry.isDirectory()) {
|
|
176
|
+
const indexFile = ['index.tsx', 'index.ts', 'index.jsx', 'index.js']
|
|
177
|
+
.map((name) => path.join(full, name))
|
|
178
|
+
.find((candidate) => fs.existsSync(candidate))
|
|
179
|
+
if (indexFile) {
|
|
180
|
+
components.push({ name: entry.name, file: toPosix(path.relative(root, indexFile)) })
|
|
181
|
+
}
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
if (entry.isFile() && /\.(tsx|ts|jsx|js)$/.test(entry.name) && entry.name !== 'App.tsx') {
|
|
185
|
+
components.push({
|
|
186
|
+
name: entry.name.replace(/\.(tsx|ts|jsx|js)$/, ''),
|
|
187
|
+
file: toPosix(path.relative(root, full)),
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
return components.sort((a, b) => a.name.localeCompare(b.name))
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function loadConfig(configPath) {
|
|
195
|
+
if (!fs.existsSync(configPath)) {
|
|
196
|
+
throw new Error(`[props-schema] 找不到配置文件: ${configPath}`)
|
|
197
|
+
}
|
|
198
|
+
const mod = await import(pathToFileURL(configPath).href)
|
|
199
|
+
return mod.default || {}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function parseComponentProps(fullPath, name) {
|
|
203
|
+
// 排除继承自 node_modules 原生 DOM 类型带入的属性,只保留组件自身声明的 props
|
|
204
|
+
const docs = docgen.parse(fullPath, {
|
|
205
|
+
savePropValueAsString: true,
|
|
206
|
+
shouldExtractLiteralValuesFromEnum: true,
|
|
207
|
+
propFilter: (prop) => {
|
|
208
|
+
const parent = prop.parent
|
|
209
|
+
if (parent && parent.fileName && /node_modules/.test(parent.fileName)) return false
|
|
210
|
+
return true
|
|
211
|
+
},
|
|
212
|
+
})
|
|
213
|
+
// props 是 { [propName]: PropItem } 映射,这里转成数组;优先按组件名匹配文档
|
|
214
|
+
const doc = docs.find((item) => item.displayName === name) || docs[0]
|
|
215
|
+
return doc ? Object.values(doc.props || {}) : []
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function run(options) {
|
|
219
|
+
if (options.help) {
|
|
220
|
+
console.log(HELP.trim())
|
|
221
|
+
return 0
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const cwd = process.cwd()
|
|
225
|
+
const root = options.root ? path.resolve(cwd, options.root) : cwd
|
|
226
|
+
|
|
227
|
+
return loadConfig(path.resolve(root, options.config)).then((config) => {
|
|
228
|
+
const components =
|
|
229
|
+
Array.isArray(config.components) && config.components.length
|
|
230
|
+
? config.components
|
|
231
|
+
: discoverComponents(root)
|
|
232
|
+
const hints = config.hints || {}
|
|
233
|
+
|
|
234
|
+
if (!components.length) {
|
|
235
|
+
throw new Error(`[props-schema] 没有找到组件,检查 ${COMPONENT_DIR} 或配置里的 components`)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const stale = []
|
|
239
|
+
|
|
240
|
+
components.forEach(({ name, file }) => {
|
|
241
|
+
if (!name || !file) throw new Error('[props-schema] components 的每一项都需要 name 和 file')
|
|
242
|
+
|
|
243
|
+
const fullPath = path.join(root, file)
|
|
244
|
+
if (!fs.existsSync(fullPath)) {
|
|
245
|
+
throw new Error(`[props-schema] 组件文件不存在: ${file}`)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const props = parseComponentProps(fullPath, name).map((prop) =>
|
|
249
|
+
toSchemaProp(prop, hints[name] && hints[name][prop.name]),
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
if (!props.length) {
|
|
253
|
+
console.warn(
|
|
254
|
+
`[props-schema] ${name}: 没有解析到 props,检查 props interface / type 上方的 JSDoc`,
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
props.forEach((prop) => {
|
|
258
|
+
if (prop.bindable && !SCALAR_TYPES.includes(typeof prop.value)) {
|
|
259
|
+
throw new Error(`[props-schema] ${name}.${prop.name}: 默认值类型不对(${prop.type})`)
|
|
260
|
+
}
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
const schemaFile = schemaRelPath(file)
|
|
264
|
+
const schemaFullPath = path.join(root, schemaFile)
|
|
265
|
+
const eol = eolOf(schemaFullPath)
|
|
266
|
+
const output = JSON.stringify(props, null, 2).split('\n').join(eol) + eol
|
|
267
|
+
|
|
268
|
+
if (options.check) {
|
|
269
|
+
const current = fs.existsSync(schemaFullPath)
|
|
270
|
+
? fs.readFileSync(schemaFullPath, 'utf8')
|
|
271
|
+
: ''
|
|
272
|
+
if (current !== output) stale.push(schemaFile)
|
|
273
|
+
return
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
fs.writeFileSync(schemaFullPath, output)
|
|
277
|
+
if (!options.silence) {
|
|
278
|
+
console.log(`[props-schema] ${name}: ${props.length} props -> ${schemaFile}`)
|
|
279
|
+
}
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
if (options.check) {
|
|
283
|
+
if (stale.length) {
|
|
284
|
+
console.error(
|
|
285
|
+
`[props-schema] schema 不是最新的: ${stale.join(', ')},请执行 react-leva-gen-schema`,
|
|
286
|
+
)
|
|
287
|
+
return 1
|
|
288
|
+
}
|
|
289
|
+
console.log(`[props-schema] check passed: ${components.length} components`)
|
|
290
|
+
return 0
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (!options.silence) console.log(`[props-schema] done: ${components.length} components`)
|
|
294
|
+
return 0
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
run(parseArgv(process.argv.slice(2)))
|
|
299
|
+
.then((code) => {
|
|
300
|
+
process.exitCode = code
|
|
301
|
+
})
|
|
302
|
+
.catch((error) => {
|
|
303
|
+
console.error(error.message || error)
|
|
304
|
+
process.exit(1)
|
|
305
|
+
})
|
package/scripts/meta.mjs
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*
|
|
2
|
+
|--------------------------------------------------------------------------
|
|
3
|
+
|
|
|
4
|
+
| 生成 hel-meta.json(写入 hel_dist)
|
|
5
|
+
|
|
|
6
|
+
|--------------------------------------------------------------------------
|
|
7
|
+
*/
|
|
8
|
+
import path from 'node:path'
|
|
9
|
+
import fs from 'node:fs'
|
|
10
|
+
import { fileURLToPath } from 'node:url'
|
|
11
|
+
import { createRequire } from 'node:module'
|
|
12
|
+
import helDevUtils from 'hel-dev-utils'
|
|
13
|
+
import appInfo from '../appInfo.mjs'
|
|
14
|
+
|
|
15
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
16
|
+
const require = createRequire(import.meta.url)
|
|
17
|
+
const pkg = require('../package.json')
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
await helDevUtils.extractHelMetaJson({
|
|
21
|
+
appInfo,
|
|
22
|
+
buildDirFullPath: path.join(__dirname, '../hel_dist'),
|
|
23
|
+
packageJson: pkg,
|
|
24
|
+
})
|
|
25
|
+
// hel-dev-utils 在 Windows 遍历产物时会把路径分隔符带入资源 URL。
|
|
26
|
+
const metaPath = path.join(__dirname, '../hel_dist/hel-meta.json')
|
|
27
|
+
const meta = JSON.parse(fs.readFileSync(metaPath, 'utf8'))
|
|
28
|
+
function normalizeAssetUrls(value) {
|
|
29
|
+
if (typeof value === 'string' && /^https?:\/\//i.test(value)) {
|
|
30
|
+
const url = new URL(value.replace(/\\/g, '/'))
|
|
31
|
+
url.pathname = url.pathname.replace(/\/{2,}/g, '/')
|
|
32
|
+
return url.toString()
|
|
33
|
+
}
|
|
34
|
+
if (Array.isArray(value)) return [...new Set(value.map(normalizeAssetUrls))]
|
|
35
|
+
if (value && typeof value === 'object') {
|
|
36
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, normalizeAssetUrls(entry)]))
|
|
37
|
+
}
|
|
38
|
+
return value
|
|
39
|
+
}
|
|
40
|
+
meta.version.src_map = normalizeAssetUrls(meta.version.src_map)
|
|
41
|
+
fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2) + '\n')
|
|
42
|
+
console.log('[libs-react-leva] hel-meta.json 生成完成')
|
|
43
|
+
} catch (err) {
|
|
44
|
+
console.error('[libs-react-leva] hel-meta.json 生成失败:', err)
|
|
45
|
+
process.exit(-1)
|
|
46
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/*
|
|
2
|
+
|--------------------------------------------------------------------------
|
|
3
|
+
|
|
|
4
|
+
| npm publish 前检查 package.json 版本号是否和 hel_dist/hel-meta.json 一致
|
|
5
|
+
|
|
|
6
|
+
|--------------------------------------------------------------------------
|
|
7
|
+
*/
|
|
8
|
+
import fs from 'node:fs'
|
|
9
|
+
import path from 'node:path'
|
|
10
|
+
import { fileURLToPath } from 'node:url'
|
|
11
|
+
import { createRequire } from 'node:module'
|
|
12
|
+
|
|
13
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
14
|
+
const require = createRequire(import.meta.url)
|
|
15
|
+
const pkg = require('../package.json')
|
|
16
|
+
const helDistPath = path.resolve(__dirname, '../hel_dist/hel-meta.json')
|
|
17
|
+
|
|
18
|
+
// 检查是否执行过 pnpm run build 打包命令
|
|
19
|
+
if (!fs.existsSync(helDistPath)) {
|
|
20
|
+
console.log("\x1b[31mRun the 'pnpm run build' command first.\n\x1b[0m")
|
|
21
|
+
process.exit(1)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { app, version } = JSON.parse(fs.readFileSync(helDistPath, 'utf-8'))
|
|
25
|
+
if (app?.build_version !== pkg.version) {
|
|
26
|
+
console.log(
|
|
27
|
+
'\x1b[31mThe package.json version number and the hel_dist/hel-meta.json version number must be the same.\n\x1b[0m',
|
|
28
|
+
)
|
|
29
|
+
process.exit(1)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const webDirPath = version?.src_map?.webDirPath || ''
|
|
33
|
+
if (!webDirPath || /^https?:\/\/(localhost|127\.0\.0\.1|\[::1\])(:\d+)?(\/|$)/i.test(webDirPath)) {
|
|
34
|
+
console.error('HEL 资源地址为空或指向本地,请执行 pnpm run build 生成 unpkg 发布产物。')
|
|
35
|
+
process.exit(1)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (const entry of [pkg.main, pkg.module, pkg.unpkg, pkg.types, ...Object.values(pkg.bin || {})]) {
|
|
39
|
+
if (!entry || !fs.existsSync(path.resolve(__dirname, '..', entry))) {
|
|
40
|
+
console.error(`缺少发布入口 ${entry},请重新执行 pnpm run build。`)
|
|
41
|
+
process.exit(1)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
console.log(`[prepublishOnly] ${pkg.name}@${pkg.version} HEL 发布校验通过。`)
|