donar 0.0.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.
Files changed (106) hide show
  1. package/.cspell/custom-dictionary.txt +5 -0
  2. package/.github/actions/base-check/action.yaml +27 -0
  3. package/.github/actions/env-setup/action.yaml +74 -0
  4. package/.github/workflows/pr-check.yaml +33 -0
  5. package/.github/workflows/release.yaml +112 -0
  6. package/.gitignore +41 -0
  7. package/.node-version +1 -0
  8. package/.npmignore +7 -0
  9. package/.vscode/extensions.json +10 -0
  10. package/.vscode/settings.json +44 -0
  11. package/LICENSE +21 -0
  12. package/README.md +160 -0
  13. package/commitlint.config.ts +3 -0
  14. package/cspell.json +27 -0
  15. package/dist/components.esm.js +2 -0
  16. package/dist/css/components.C24nnsjt.css +1 -0
  17. package/dist/hooks.esm.js +185 -0
  18. package/dist/index.esm.js +4 -0
  19. package/dist/js/components.nFDoAkCq.js +198 -0
  20. package/dist/js/utils.CVb1iSAU.js +330 -0
  21. package/dist/types/components.d.ts +1 -0
  22. package/dist/types/hooks.d.ts +1 -0
  23. package/dist/types/index.d.ts +1 -0
  24. package/dist/types/src/components/async-custom-show/index.d.ts +22 -0
  25. package/dist/types/src/components/carousel/hooks.d.ts +74 -0
  26. package/dist/types/src/components/carousel/index.d.ts +88 -0
  27. package/dist/types/src/components/custom-show/index.d.ts +21 -0
  28. package/dist/types/src/components/error-boundary/index.d.ts +31 -0
  29. package/dist/types/src/components/index.d.ts +8 -0
  30. package/dist/types/src/hooks/async-guard.d.ts +9 -0
  31. package/dist/types/src/hooks/index.d.ts +5 -0
  32. package/dist/types/src/hooks/observer.d.ts +70 -0
  33. package/dist/types/src/hooks/state.d.ts +44 -0
  34. package/dist/types/src/hooks/storage.d.ts +25 -0
  35. package/dist/types/src/hooks/timer.d.ts +16 -0
  36. package/dist/types/src/index.d.ts +3 -0
  37. package/dist/types/src/test/emiter-test.d.ts +1 -0
  38. package/dist/types/src/test/index.d.ts +1 -0
  39. package/dist/types/src/test/retry-async-test.d.ts +1 -0
  40. package/dist/types/src/utils/class-singleton.d.ts +6 -0
  41. package/dist/types/src/utils/concurrency.d.ts +17 -0
  42. package/dist/types/src/utils/debounce.d.ts +8 -0
  43. package/dist/types/src/utils/deep-copy.d.ts +11 -0
  44. package/dist/types/src/utils/dev.d.ts +8 -0
  45. package/dist/types/src/utils/download.d.ts +15 -0
  46. package/dist/types/src/utils/event-emitter/helpers.d.ts +36 -0
  47. package/dist/types/src/utils/event-emitter/index.d.ts +65 -0
  48. package/dist/types/src/utils/fetch-xhr/helpers.d.ts +28 -0
  49. package/dist/types/src/utils/fetch-xhr/index.d.ts +25 -0
  50. package/dist/types/src/utils/hash.d.ts +8 -0
  51. package/dist/types/src/utils/index.d.ts +15 -0
  52. package/dist/types/src/utils/is-deep-plain-equal.d.ts +15 -0
  53. package/dist/types/src/utils/json-convert.d.ts +66 -0
  54. package/dist/types/src/utils/micro-queue-scheduler.d.ts +14 -0
  55. package/dist/types/src/utils/raf-interval.d.ts +16 -0
  56. package/dist/types/src/utils/record-typed-map.d.ts +27 -0
  57. package/dist/types/src/utils/retry-async.d.ts +9 -0
  58. package/dist/types/src/utils/thenable.d.ts +15 -0
  59. package/dist/types/utils.d.ts +1 -0
  60. package/dist/utils.esm.js +2 -0
  61. package/eslint.config.ts +48 -0
  62. package/lint-staged.config.ts +13 -0
  63. package/oxfmt.config.ts +14 -0
  64. package/package.json +90 -0
  65. package/pnpm-workspace.yaml +3 -0
  66. package/scripts/sync-node-version/index.js +31 -0
  67. package/simple-git-hooks.js +4 -0
  68. package/src/components/async-custom-show/index.tsx +37 -0
  69. package/src/components/carousel/hooks.ts +312 -0
  70. package/src/components/carousel/index.module.scss +163 -0
  71. package/src/components/carousel/index.tsx +215 -0
  72. package/src/components/custom-show/index.tsx +31 -0
  73. package/src/components/error-boundary/index.tsx +48 -0
  74. package/src/components/index.ts +11 -0
  75. package/src/hooks/async-guard.ts +53 -0
  76. package/src/hooks/index.ts +5 -0
  77. package/src/hooks/observer.ts +236 -0
  78. package/src/hooks/state.ts +140 -0
  79. package/src/hooks/storage.ts +103 -0
  80. package/src/hooks/timer.ts +42 -0
  81. package/src/index.ts +3 -0
  82. package/src/test/emiter-test.ts +19 -0
  83. package/src/test/index.ts +35 -0
  84. package/src/test/retry-async-test.ts +8 -0
  85. package/src/utils/class-singleton.ts +23 -0
  86. package/src/utils/concurrency.ts +49 -0
  87. package/src/utils/debounce.ts +20 -0
  88. package/src/utils/deep-copy.ts +132 -0
  89. package/src/utils/dev.ts +16 -0
  90. package/src/utils/download.ts +39 -0
  91. package/src/utils/event-emitter/helpers.ts +80 -0
  92. package/src/utils/event-emitter/index.ts +171 -0
  93. package/src/utils/fetch-xhr/helpers.ts +85 -0
  94. package/src/utils/fetch-xhr/index.ts +103 -0
  95. package/src/utils/hash.ts +25 -0
  96. package/src/utils/index.ts +18 -0
  97. package/src/utils/is-deep-plain-equal.ts +45 -0
  98. package/src/utils/json-convert.ts +257 -0
  99. package/src/utils/micro-queue-scheduler.ts +38 -0
  100. package/src/utils/raf-interval.ts +42 -0
  101. package/src/utils/record-typed-map.ts +38 -0
  102. package/src/utils/retry-async.ts +30 -0
  103. package/src/utils/thenable.ts +30 -0
  104. package/tsconfig.json +43 -0
  105. package/types/scss.d.ts +10 -0
  106. package/vite.config.ts +51 -0
@@ -0,0 +1,257 @@
1
+ /**
2
+ * @author sonion
3
+ * @description 将小驼峰(camelCase)字符串转换为大驼峰(PascalCase)
4
+ * @param str 小驼峰格式的字符串
5
+ * @returns 大驼峰格式的字符串
6
+ */
7
+ const camel2Pascal = (str: string) => {
8
+ if (!str) {
9
+ return str;
10
+ }
11
+ // 将首字母转为大写,其余保持不变
12
+ return str.charAt(0).toUpperCase() + str.slice(1);
13
+ };
14
+
15
+ /**
16
+ * @author sonion
17
+ * @description 将大驼峰(PascalCase)字符串转换为小驼峰(camelCase)
18
+ * @param str 大驼峰格式的字符串
19
+ * @returns 小驼峰格式的字符串
20
+ */
21
+ const pascal2Camel = (str: string) => {
22
+ if (!str) {
23
+ return str;
24
+ }
25
+ // 将首字母转为小写,其余保持不变
26
+ return str.charAt(0).toLowerCase() + str.slice(1);
27
+ };
28
+
29
+ /**
30
+ * @author sonion
31
+ * @description 将小驼峰命名(camelCase)字符串转换为蛇形命名(snake_case)
32
+ * @param str 小驼峰格式的字符串
33
+ * @returns 蛇形格式的字符串
34
+ */
35
+ const camel2Snake = (str: string) => {
36
+ if (!str) {
37
+ return str;
38
+ }
39
+ return (
40
+ str
41
+ // 在大写字母前加下划线
42
+ .replace(/([A-Z])/g, '_$1')
43
+ // 转成小写
44
+ .toLowerCase()
45
+ // 去掉可能出现在开头的下划线
46
+ .replace(/^_/, '')
47
+ );
48
+ };
49
+
50
+ /**
51
+ * @author sonion
52
+ * @description 将蛇形命名(snake_case)字符串转换为小驼峰(camelCase)
53
+ * @param str 蛇形命名格式的字符串
54
+ * @returns 小驼峰格式的字符串
55
+ */
56
+ const snake2Camel = (str: string) => {
57
+ if (!str) {
58
+ return str;
59
+ }
60
+ // 将下划线后的字母转为大写,并删除下划线
61
+ // 匹配下划线后的字母,将字母转为大写;如果是数字,只删除下划线
62
+ return str
63
+ .replace(/_([a-zA-Z])/g, (_, letter) => letter.toUpperCase())
64
+ .replace(/_(\d+)/g, (_, digit) => digit)
65
+ .replace(/_/g, ''); // 删除所有剩余的下划线
66
+ };
67
+
68
+ /**
69
+ * @author sonion
70
+ * @description 深度遍历 JSON,将所有键名按指定的转换函数处理
71
+ * @param params 参数对象
72
+ * @param params.obj 要处理的 JSON 对象或数组
73
+ * @param params.convertFn 键名转换方法,默认值为 identity(不转换)
74
+ * @param params.ignoreKeys 要忽略的键名集合,默认值为空集合
75
+ * @returns 处理后的 JSON 对象或数组
76
+ */
77
+ const convertKeysDeep = <
78
+ T extends Record<keyof T, unknown> | Record<keyof T, unknown>[],
79
+ R extends string,
80
+ >({
81
+ obj,
82
+ ignoreKeys = new Set(),
83
+ convertFn,
84
+ }: {
85
+ obj: T;
86
+ ignoreKeys?: Set<string>;
87
+ convertFn: (key: string) => R;
88
+ }): T => {
89
+ if (Object(obj) !== obj) {
90
+ return obj;
91
+ }
92
+
93
+ if (Array.isArray(obj)) {
94
+ return obj.map((item) =>
95
+ convertKeysDeep({ obj: item, ignoreKeys, convertFn })
96
+ ) as unknown as T;
97
+ }
98
+
99
+ const converted: Record<string, unknown> = {};
100
+ const entries = Object.entries(obj);
101
+ for (const [key, value] of entries) {
102
+ const newKey = ignoreKeys.has(key) ? key : convertFn(key);
103
+ converted[newKey] = convertKeysDeep({ obj: value, ignoreKeys, convertFn });
104
+ }
105
+ return converted as T;
106
+ };
107
+
108
+ /** 小驼峰命名支持用小划线分割 */
109
+ type CamelToSnakeRaw<S extends string> = S extends `${infer First}${infer Rest}`
110
+ ? First extends Lowercase<First>
111
+ ? `${First}${CamelToSnakeRaw<Rest>}`
112
+ : `_${Lowercase<First>}${CamelToSnakeRaw<Rest>}`
113
+ : S;
114
+
115
+ /** 去掉开头的下划线 */
116
+ type TrimLeadingUnderscore<S extends string> = S extends `_${infer R}` ? R : S;
117
+
118
+ /** 小驼峰命名转换为蛇形命名类型 */
119
+ type CamelToSnake<S extends string> = TrimLeadingUnderscore<CamelToSnakeRaw<S>>;
120
+
121
+ /** 蛇形命名转换为小驼峰命名类型 */
122
+ type SnakeToCamel<S extends string> = S extends `${infer Head}_${infer Tail}`
123
+ ? `${Lowercase<Head>}${Capitalize<SnakeToCamel<Tail>>}`
124
+ : Lowercase<S>;
125
+
126
+ /** 小驼峰命名转换为大驼峰命名类型 */
127
+ type CamelToPascal<S extends string> = Capitalize<S>;
128
+ /** 大驼峰命名转换为小驼峰命名类型 */
129
+ type PascalToCamel<S extends string> = Uncapitalize<S>;
130
+
131
+ /** 转换模式 */
132
+ export const ConvertMode = {
133
+ SnakeToCamel: 'snakeToCamel',
134
+ CamelToSnake: 'camelToSnake',
135
+ CamelToPascal: 'camelToPascal',
136
+ PascalToCamel: 'pascalToCamel',
137
+ } as const;
138
+ /** 转换模式类型 */
139
+ export type ConvertMode = (typeof ConvertMode)[keyof typeof ConvertMode];
140
+ // 类型和值一样,erasableSyntaxOnly 模式下 enum 的替代
141
+
142
+ type TransformMap<K extends string> = {
143
+ snakeToCamel: SnakeToCamel<K>;
144
+ camelToSnake: CamelToSnake<K>;
145
+ camelToPascal: CamelToPascal<K>;
146
+ pascalToCamel: PascalToCamel<K>;
147
+ };
148
+
149
+ /** 通用:深度转换对象所有键名(保留函数与数组)*/
150
+ type ConvertKeys<
151
+ T,
152
+ M extends ConvertMode,
153
+ IGN extends readonly string[] = readonly [],
154
+ > = T extends (...args: never) => unknown
155
+ ? T
156
+ : T extends readonly unknown[]
157
+ ? { [I in keyof T]: ConvertKeys<T[I], M, IGN> }
158
+ : T extends object
159
+ ? {
160
+ [K in keyof T as K extends string
161
+ ? K extends IGN[number]
162
+ ? K
163
+ : TransformMap<K>[M]
164
+ : K]: ConvertKeys<T[K], M, IGN>;
165
+ }
166
+ : T;
167
+
168
+ /**
169
+ * @author sonion
170
+ * @description 深度遍历JSON,将所有键名从蛇形转换为小驼峰
171
+ * @param obj 要处理的JSON对象或数组
172
+ * @param ignoreKeys 忽略转换的键名数组。⚠️ ignoreKeys必须是使用 as const断言的数组,否则类型会失效
173
+ * @example convertSnake2Camel({ a_b: 1, c_d: { e_f: 2 } }, ['c_d'] as const)
174
+ */
175
+ const convertSnake2Camel = <
176
+ T extends Record<keyof T, unknown> | Record<keyof T, unknown>[],
177
+ K extends readonly string[] = readonly [],
178
+ >(
179
+ obj: T,
180
+ ignoreKeys?: K
181
+ ): ConvertKeys<T, typeof ConvertMode.SnakeToCamel, K> => {
182
+ return convertKeysDeep({
183
+ obj,
184
+ ignoreKeys: new Set(ignoreKeys ?? []),
185
+ convertFn: snake2Camel,
186
+ }) as ConvertKeys<T, typeof ConvertMode.SnakeToCamel, K>;
187
+ };
188
+
189
+ /**
190
+ * @author sonion
191
+ * @description 深度遍历JSON,将所有键名从小驼峰转换为蛇形
192
+ * @param obj 要处理的JSON对象或数组
193
+ * @param ignoreKeys 忽略转换的键名数组。⚠️ ignoreKeys必须是使用 as const断言的数组,否则类型会失效
194
+ * @example convertCamel2Snake({ aB: 1, cD: { eF: 2 } }, ['cD'] as const)
195
+ */
196
+ const convertCamel2Snake = <
197
+ T extends Record<keyof T, unknown> | Record<keyof T, unknown>[],
198
+ K extends readonly string[] = readonly [],
199
+ >(
200
+ obj: T,
201
+ ignoreKeys?: K
202
+ ): ConvertKeys<T, typeof ConvertMode.CamelToSnake, K> => {
203
+ return convertKeysDeep({
204
+ obj,
205
+ ignoreKeys: new Set(ignoreKeys ?? []),
206
+ convertFn: camel2Snake,
207
+ }) as ConvertKeys<T, typeof ConvertMode.CamelToSnake, K>;
208
+ };
209
+
210
+ /**
211
+ * @author sonion
212
+ * @description 深度遍历JSON,将所有键名从小驼峰转换为大驼峰
213
+ * @param obj 要处理的JSON对象或数组
214
+ * @param ignoreKeys 忽略转换的键名数组。⚠️ ignoreKeys必须是使用 as const断言的数组,否则类型会失效
215
+ * @example convertCamel2Pascal({ aB: 1, cD: { eF: 2 } }, ['cD'] as const)
216
+ */
217
+ const convertCamel2Pascal = <
218
+ T extends Record<keyof T, unknown> | Record<keyof T, unknown>[],
219
+ K extends readonly string[] = readonly [],
220
+ >(
221
+ obj: T,
222
+ ignoreKeys?: K
223
+ ): ConvertKeys<T, typeof ConvertMode.CamelToPascal, K> => {
224
+ return convertKeysDeep({
225
+ obj,
226
+ ignoreKeys: new Set(ignoreKeys ?? []),
227
+ convertFn: camel2Pascal,
228
+ }) as ConvertKeys<T, typeof ConvertMode.CamelToPascal, K>;
229
+ };
230
+
231
+ /**
232
+ * @author sonion
233
+ * @description 深度遍历JSON,将所有键名从大驼峰转换为小驼峰
234
+ * @param obj 要处理的JSON对象或数组
235
+ * @param ignoreKeys 忽略转换的键名数组。⚠️ ignoreKeys必须是使用 as const断言的数组,否则类型会失效
236
+ * @example convertPascal2Camel({ AB: 1, CD: { EF: 2 } }, ['CD'] as const)
237
+ */
238
+ const convertPascal2Camel = <
239
+ T extends Record<keyof T, unknown> | Record<keyof T, unknown>[],
240
+ K extends readonly string[] = readonly [],
241
+ >(
242
+ obj: T,
243
+ ignoreKeys?: K
244
+ ): ConvertKeys<T, typeof ConvertMode.PascalToCamel, K> => {
245
+ return convertKeysDeep({
246
+ obj,
247
+ ignoreKeys: new Set(ignoreKeys ?? []),
248
+ convertFn: pascal2Camel,
249
+ }) as ConvertKeys<T, typeof ConvertMode.PascalToCamel, K>;
250
+ };
251
+
252
+ export {
253
+ convertSnake2Camel,
254
+ convertCamel2Snake,
255
+ convertCamel2Pascal,
256
+ convertPascal2Camel,
257
+ };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @author sonion
3
+ * @description 创建微队列调度器 - 将同一个同步执行阶段中的所有任务合并到一个微任务中执行。
4
+ * @returns 调度器函数
5
+ *
6
+ * 使用场景:
7
+ * - 批量合并异步任务,保证它们在同一个微任务中执行
8
+ * - 避免频繁创建多个 Promise 微任务,提高性能
9
+ * @example
10
+ * const scheduler = createMicroQueueScheduler();
11
+ * scheduler(() => console.log("task1"));
12
+ * scheduler(() => console.log("task2"));
13
+ */
14
+ export const createMicroQueueScheduler = (): ((
15
+ fn: () => void
16
+ ) => Promise<void>) => {
17
+ const queue: Array<() => void> = []; // 任务队列
18
+ let currentPromise: Promise<void> | null = null; // 这一个队列集合
19
+
20
+ const scheduler = (fn: () => void) => {
21
+ queue.push(fn);
22
+
23
+ if (!currentPromise) {
24
+ currentPromise = Promise.resolve()
25
+ .then(() => {
26
+ let task: (() => void) | undefined;
27
+ while ((task = queue.shift())) task(); // while遍历过程中加入的任务也能被遍历到。就直接清空对列了
28
+ })
29
+ .finally(() => {
30
+ currentPromise = null;
31
+ // queue.length = 0; // 冗余,queue.shift() 会清空队列
32
+ });
33
+ }
34
+
35
+ return currentPromise;
36
+ };
37
+ return scheduler;
38
+ };
@@ -0,0 +1,42 @@
1
+ export type RAfIntervalReturn = Record<'stop', () => void>;
2
+
3
+ /**
4
+ * @author sonion
5
+ * @description rAf版setInterval。可利用rAf页面隐藏暂停的特性,实现页面隐藏时暂停,页面显示时继续
6
+ * @param {() => void} fn - 回调函数
7
+ * @param {number} wait - 间隔时间 毫秒
8
+ * @returns {RAfIntervalReturn} - 返回取消函数
9
+ */
10
+ export const rAfInterval = (
11
+ fn: () => void,
12
+ wait: number
13
+ ): RAfIntervalReturn => {
14
+ let startTime = Date.now();
15
+ let isStop = false;
16
+ const stop = () => (isStop = true);
17
+ const _interval = () => {
18
+ requestAnimationFrame(() => {
19
+ const currentTime = Date.now();
20
+ if (currentTime - startTime >= wait) {
21
+ fn();
22
+ startTime = currentTime;
23
+ }
24
+ isStop || _interval();
25
+ });
26
+ };
27
+ requestAnimationFrame(_interval);
28
+ return { stop };
29
+ };
30
+
31
+ /**
32
+ * @author sonion
33
+ * @description 清除rAf版setInterval定时器
34
+ * @param {RAfIntervalReturn} intervalId - 手写定时器的返回值
35
+ * @returns {void}
36
+ */
37
+ export const clearRAfInterval = (
38
+ intervalId: RAfIntervalReturn | (() => void) | undefined
39
+ ) => {
40
+ typeof intervalId === 'object' && intervalId.stop && intervalId.stop();
41
+ typeof intervalId === 'function' && intervalId();
42
+ };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @author sonion
3
+ * @description 一个带有精确类型约束的 Map。
4
+ *
5
+ * 与普通 `Map<keyof T, T[keyof T]>` 不同,
6
+ * 它保证键和值的类型一一对应:
7
+ * - 键只能是对象 T 的属性名
8
+ * - 值必须是该属性对应的类型
9
+ * - 因使用 object 约束泛型,键只能是 string|number|symbol
10
+ *
11
+ * 这样可以避免宽泛的联合类型,提供类似对象的强类型体验。
12
+ * @example
13
+ * interface Config {
14
+ * port: number;
15
+ * host: string;
16
+ * }
17
+ *
18
+ * const map = new RecordTypedMap<Config>();
19
+ * map.set("port", 3000); // ✅ 正确
20
+ * map.set("host", "localhost"); // ✅ 正确
21
+ * map.set("port", "oops"); // ❌ 类型错误
22
+ */
23
+ export class RecordTypedMap<T extends object> extends Map<keyof T, T[keyof T]> {
24
+ constructor(obj?: T) {
25
+ super(obj ? (Object.entries(obj) as [[keyof T, T[keyof T]]]) : []);
26
+ // 创建 Proxy 实例,拦截属性/方法访问
27
+ }
28
+
29
+ // 自定义的强类型 get 方法
30
+ get<K extends keyof T>(key: K): T[K] | undefined {
31
+ return super.get(key) as T[K];
32
+ }
33
+
34
+ // 自定义的强类型 set 方法
35
+ set<K extends keyof T>(key: K, value: T[K]) {
36
+ return super.set(key, value);
37
+ }
38
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @author sonion
3
+ * @description 重试任务,直到成功或达到最大重试次数
4
+ * @param task - 要重试的任务函数,需返回 Promise
5
+ * @param count - 最大重试次数,默认 5 次
6
+ * @param delay - 每次重试间隔时间,默认 0 毫秒。可传函数,根据重试次数、错误信息动态计算。
7
+ * @returns Promise<R> - 任务成功时的返回值
8
+ */
9
+ export const retryAsync = <R>(
10
+ task: () => Promise<R>,
11
+ count = 5,
12
+ delay: number | ((attemptIndex?: number, beforeError?: Error) => number) = 0
13
+ ) => {
14
+ return new Promise<R>((resolve, reject) => {
15
+ const attempt = (remainingCount: number) => {
16
+ task()
17
+ .then(resolve)
18
+ .catch((error) => {
19
+ if (remainingCount <= 0) {
20
+ return reject(error);
21
+ }
22
+ const attemptIndex = count - remainingCount; // 第几次重试
23
+ const wait =
24
+ typeof delay === 'function' ? delay(attemptIndex, error) : delay;
25
+ setTimeout(() => attempt(remainingCount - 1), wait ?? 0);
26
+ });
27
+ };
28
+ attempt(count);
29
+ });
30
+ };
@@ -0,0 +1,30 @@
1
+ type SafeAwaitResult<T> = [true, null, T] | [false, Error, null];
2
+
3
+ /**
4
+ * @author sonion
5
+ * @description 安全的 await 函数。
6
+ * @param {Promise<T>} promise 要执行的 Promise 函数
7
+ * @returns {Promise<SafeAwaitResult<T>>} - 返回一个元组,第一个元素为是否成功,第二个元素为错误信息,第三个元素为数据
8
+ */
9
+ export const safeAwait = async <T>(
10
+ promise: Promise<T>
11
+ ): Promise<SafeAwaitResult<T>> => {
12
+ try {
13
+ const data = await Promise.resolve(promise);
14
+ return [true, null, data] as const;
15
+ } catch (err) {
16
+ const error = err instanceof Error ? err : new Error(String(err));
17
+ return [false, error, null] as const;
18
+ }
19
+ };
20
+
21
+ /**
22
+ * @author sonion
23
+ * @description Promise.try 的polyfill实现
24
+ * @param p - promise 或 一个返回 promise 的函数
25
+ */
26
+ export const promiseTry = <T>(p: PromiseLike<T> | (() => PromiseLike<T>)) => {
27
+ return new Promise<T>((resolve) => {
28
+ resolve(typeof p === 'function' ? p() : p);
29
+ });
30
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "compilerOptions": {
3
+ "types": ["vite/client"],
4
+
5
+ /* 目标与模块 */
6
+ "target": "ESNext",
7
+ "module": "ESNext",
8
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
9
+
10
+ /* JSX */
11
+ "jsx": "react-jsx",
12
+
13
+ /* 模块解析:现代 bundler 模式 */
14
+ "moduleResolution": "bundler", // 更适合 Vite/Rollup
15
+ "verbatimModuleSyntax": true,
16
+ "moduleDetection": "force",
17
+ "noEmit": true, // 不生成类型文件,库开发不需要
18
+ "allowImportingTsExtensions": true, // 仅onEmit有效
19
+
20
+ /* 严格检查与 lint */
21
+ "strict": true,
22
+ "noUnusedLocals": true,
23
+ "noUnusedParameters": true,
24
+ "noFallthroughCasesInSwitch": true,
25
+ "skipLibCheck": true,
26
+ "noUncheckedSideEffectImports": true,
27
+ "useDefineForClassFields": true,
28
+ "erasableSyntaxOnly": true,
29
+
30
+ /* 互操作与工具支持 */
31
+ "esModuleInterop": true,
32
+ "resolveJsonModule": true,
33
+ "isolatedModules": true,
34
+
35
+ // "baseUrl": ".", // 已废弃,使用 paths 替代
36
+ /* 路径别名 */
37
+ "paths": {
38
+ "@/*": ["./src/*"]
39
+ }
40
+ },
41
+ "include": ["src", "types"],
42
+ "exclude": ["node_modules", "dist"]
43
+ }
@@ -0,0 +1,10 @@
1
+ declare module '*.module.scss' {
2
+ const classes: { [key: string]: string };
3
+ export default classes;
4
+ }
5
+
6
+ // 如果你还使用其他样式文件,可以一并声明
7
+ declare module '*.scss' {
8
+ const content: { [key: string]: string };
9
+ export default content;
10
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,51 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+ import dts from 'vite-plugin-dts';
4
+ import path from 'path';
5
+ import { libInjectCss } from 'vite-plugin-lib-inject-css';
6
+
7
+ export default defineConfig({
8
+ plugins: [
9
+ react(),
10
+ libInjectCss(), // 注入 CSS 到每个生成的 chunk 文件
11
+ dts({
12
+ insertTypesEntry: true,
13
+ outDir: 'dist/types',
14
+ include: ['src/**/*'],
15
+ }), // 生成类型声明文件
16
+ ],
17
+ resolve: {
18
+ // tsconfigPaths: true, // 启用 tsconfig 路径解析
19
+ alias: {
20
+ '@': path.resolve(__dirname, 'src'),
21
+ },
22
+ },
23
+ // CSS 配置
24
+ css: {
25
+ modules: {
26
+ localsConvention: 'camelCaseOnly', // 推荐使用驼峰命名
27
+ },
28
+ },
29
+ build: {
30
+ target: 'es2020',
31
+ lib: {
32
+ name: 'Tonar',
33
+ entry: {
34
+ index: 'src/index.ts',
35
+ hooks: 'src/hooks/index.ts',
36
+ utils: 'src/utils/index.ts',
37
+ components: 'src/components/index.ts',
38
+ },
39
+ formats: ['es'], // 只输出 ESM
40
+ fileName: '[name].[format]', // 不用后缀
41
+ },
42
+ rolldownOptions: {
43
+ external: ['react', 'react-dom', 'react/jsx-runtime'],
44
+ output: {
45
+ assetFileNames: ({ names: [name] }) =>
46
+ `${name.endsWith('.css') ? 'css' : 'assets'}/[name].[hash][extname]`,
47
+ chunkFileNames: 'js/[name].[hash].js', // 除入口外的 chunk 文件放js文件夹
48
+ },
49
+ },
50
+ },
51
+ });