@rn-org/react-native-thread 0.4.0 → 0.6.0
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 +143 -22
- package/lib/module/babel-plugin.js +328 -2
- package/lib/module/babel-plugin.js.map +1 -1
- package/lib/module/index.js +77 -8
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/index.d.ts +5 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/babel-plugin.js +411 -2
- package/src/index.tsx +102 -12
package/src/index.tsx
CHANGED
|
@@ -8,12 +8,18 @@ export type ThreadInfo = {
|
|
|
8
8
|
readonly name: string;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
export type ThreadError = {
|
|
12
|
+
message: string;
|
|
13
|
+
stack: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
11
16
|
export type ThreadHandle = {
|
|
12
17
|
readonly id: number;
|
|
13
18
|
readonly name: string;
|
|
14
19
|
run(task: ThreadTask, params?: unknown): void;
|
|
15
20
|
onMessage(handler: (data: unknown) => void): () => void;
|
|
16
21
|
onMessage(): Promise<unknown>;
|
|
22
|
+
onError(handler: (error: ThreadError) => void): () => void;
|
|
17
23
|
destroy(): void;
|
|
18
24
|
};
|
|
19
25
|
|
|
@@ -22,6 +28,13 @@ const RN_THREAD_MESSAGE_EVENT = 'RNThreadMessage';
|
|
|
22
28
|
|
|
23
29
|
const _registry = new Map<number, ThreadInfo>();
|
|
24
30
|
|
|
31
|
+
function _findByName(name: string): ThreadInfo | undefined {
|
|
32
|
+
for (const info of _registry.values()) {
|
|
33
|
+
if (info.name === name) return info;
|
|
34
|
+
}
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
25
38
|
function _register(id: number, name: string): void {
|
|
26
39
|
_registry.set(id, { id, name });
|
|
27
40
|
}
|
|
@@ -30,18 +43,60 @@ function _unregister(id: number): void {
|
|
|
30
43
|
_registry.delete(id);
|
|
31
44
|
}
|
|
32
45
|
|
|
46
|
+
function _isBytecode(src: string): boolean {
|
|
47
|
+
return (
|
|
48
|
+
src.includes('[bytecode]') ||
|
|
49
|
+
/^\s*function\s*\(\)\s*\{\s*bytecode\s*\}/.test(src)
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function _serializeValue(value: unknown): string {
|
|
54
|
+
if (value === undefined) return 'undefined';
|
|
55
|
+
if (value === null) return 'null';
|
|
56
|
+
if (typeof value === 'function') {
|
|
57
|
+
const src = value.toString();
|
|
58
|
+
if (_isBytecode(src)) {
|
|
59
|
+
if (__DEV__) {
|
|
60
|
+
console.warn(
|
|
61
|
+
'[react-native-thread] A function param returned a Hermes bytecode placeholder. ' +
|
|
62
|
+
"Add the Babel plugin: plugins: ['@rn-org/react-native-thread/babel-plugin']"
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
return 'undefined';
|
|
66
|
+
}
|
|
67
|
+
return src;
|
|
68
|
+
}
|
|
69
|
+
if (typeof value === 'string') return JSON.stringify(value);
|
|
70
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
71
|
+
return String(value);
|
|
72
|
+
if (Array.isArray(value))
|
|
73
|
+
return '[' + value.map(_serializeValue).join(',') + ']';
|
|
74
|
+
if (typeof value === 'object') {
|
|
75
|
+
const rec = value as Record<string, unknown>;
|
|
76
|
+
if (typeof rec.__rnThreadFn === 'string') {
|
|
77
|
+
return rec.__rnThreadFn;
|
|
78
|
+
}
|
|
79
|
+
const entries = Object.entries(rec)
|
|
80
|
+
.map(([k, v]) => `${JSON.stringify(k)}:${_serializeValue(v)}`)
|
|
81
|
+
.join(',');
|
|
82
|
+
return '{' + entries + '}';
|
|
83
|
+
}
|
|
84
|
+
return String(value);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function _wrapWithErrorHandler(code: string): string {
|
|
88
|
+
return `try { ${code} } catch(__e__) { resolveThreadMessage(JSON.stringify({ __rnThreadError: true, message: __e__.message || String(__e__), stack: __e__.stack || '' })); }`;
|
|
89
|
+
}
|
|
90
|
+
|
|
33
91
|
function toCode(task: ThreadTask, params?: unknown): string {
|
|
34
92
|
if (typeof task === 'string') {
|
|
35
|
-
const
|
|
36
|
-
params !== undefined ?
|
|
37
|
-
return `var __params__ = ${
|
|
93
|
+
const paramsCode =
|
|
94
|
+
params !== undefined ? _serializeValue(params) : 'undefined';
|
|
95
|
+
return _wrapWithErrorHandler(`var __params__ = ${paramsCode};\n${task}`);
|
|
38
96
|
}
|
|
39
97
|
|
|
40
98
|
const src = task.toString();
|
|
41
|
-
if (
|
|
42
|
-
src.includes('[bytecode]') ||
|
|
43
|
-
/^\s*function\s*\(\)\s*\{\s*bytecode\s*\}/.test(src)
|
|
44
|
-
) {
|
|
99
|
+
if (_isBytecode(src)) {
|
|
45
100
|
if (__DEV__) {
|
|
46
101
|
console.warn(
|
|
47
102
|
'[react-native-thread] fn.toString() returned a Hermes bytecode placeholder. ' +
|
|
@@ -51,8 +106,8 @@ function toCode(task: ThreadTask, params?: unknown): string {
|
|
|
51
106
|
return '/* bytecode: no-op */';
|
|
52
107
|
}
|
|
53
108
|
|
|
54
|
-
const argsStr = params !== undefined ?
|
|
55
|
-
return `(${src})(${argsStr})
|
|
109
|
+
const argsStr = params !== undefined ? _serializeValue(params) : '';
|
|
110
|
+
return _wrapWithErrorHandler(`(${src})(${argsStr})`);
|
|
56
111
|
}
|
|
57
112
|
|
|
58
113
|
const SHARED_THREAD_NAME = 'RNOrgThread';
|
|
@@ -75,6 +130,12 @@ export function runOnNewJS(
|
|
|
75
130
|
params?: unknown,
|
|
76
131
|
name?: string
|
|
77
132
|
): ThreadHandle {
|
|
133
|
+
if (name != null) {
|
|
134
|
+
const existing = _findByName(name);
|
|
135
|
+
if (existing) {
|
|
136
|
+
return createThreadHandle(existing.id, existing.name);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
78
139
|
const id = ReactNativeThread.createThread();
|
|
79
140
|
const resolvedName = name ?? `RNThread-${id}`;
|
|
80
141
|
_register(id, resolvedName);
|
|
@@ -83,6 +144,12 @@ export function runOnNewJS(
|
|
|
83
144
|
}
|
|
84
145
|
|
|
85
146
|
export function createThread(name?: string): ThreadHandle {
|
|
147
|
+
if (name != null) {
|
|
148
|
+
const existing = _findByName(name);
|
|
149
|
+
if (existing) {
|
|
150
|
+
return createThreadHandle(existing.id, existing.name);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
86
153
|
const id = ReactNativeThread.createThread();
|
|
87
154
|
const resolvedName = name ?? `RNThread-${id}`;
|
|
88
155
|
_register(id, resolvedName);
|
|
@@ -151,6 +218,16 @@ export function onMessage(
|
|
|
151
218
|
});
|
|
152
219
|
}
|
|
153
220
|
|
|
221
|
+
function _isThreadError(
|
|
222
|
+
data: unknown
|
|
223
|
+
): data is { __rnThreadError: true; message: string; stack: string } {
|
|
224
|
+
return (
|
|
225
|
+
typeof data === 'object' &&
|
|
226
|
+
data !== null &&
|
|
227
|
+
(data as any).__rnThreadError === true
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
154
231
|
function createThreadHandle(id: number, name: string): ThreadHandle {
|
|
155
232
|
return {
|
|
156
233
|
id,
|
|
@@ -161,18 +238,31 @@ function createThreadHandle(id: number, name: string): ThreadHandle {
|
|
|
161
238
|
onMessage: function (handler?: (data: unknown) => void) {
|
|
162
239
|
if (handler) {
|
|
163
240
|
return onMessage((data, threadId) => {
|
|
164
|
-
if (threadId === id) handler(data);
|
|
241
|
+
if (threadId === id && !_isThreadError(data)) handler(data);
|
|
165
242
|
});
|
|
166
243
|
}
|
|
167
|
-
return new Promise<unknown>((resolve) => {
|
|
244
|
+
return new Promise<unknown>((resolve, reject) => {
|
|
168
245
|
const unsub = onMessage((data, threadId) => {
|
|
169
246
|
if (threadId === id) {
|
|
170
247
|
unsub();
|
|
171
|
-
|
|
248
|
+
if (_isThreadError(data)) {
|
|
249
|
+
const err = new Error(data.message);
|
|
250
|
+
err.stack = data.stack;
|
|
251
|
+
reject(err);
|
|
252
|
+
} else {
|
|
253
|
+
resolve(data);
|
|
254
|
+
}
|
|
172
255
|
}
|
|
173
256
|
});
|
|
174
257
|
});
|
|
175
258
|
} as ThreadHandle['onMessage'],
|
|
259
|
+
onError(handler: (error: ThreadError) => void) {
|
|
260
|
+
return onMessage((data, threadId) => {
|
|
261
|
+
if (threadId === id && _isThreadError(data)) {
|
|
262
|
+
handler({ message: data.message, stack: data.stack });
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
},
|
|
176
266
|
destroy() {
|
|
177
267
|
destroyThread(id);
|
|
178
268
|
},
|