@vxrn/compiler 2.13.3 → 2.13.5
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/dist/cjs/transformWorklets.cjs +1 -0
- package/dist/cjs/worklets/classes.cjs +198 -0
- package/dist/cjs/worklets/scope.cjs +23 -1
- package/dist/cjs/worklets/serialize.cjs +4 -2
- package/dist/cjs/worklets/transform.cjs +58 -4
- package/dist/esm/transformWorklets.mjs +1 -0
- package/dist/esm/transformWorklets.mjs.map +1 -1
- package/dist/esm/worklets/classes.mjs +170 -0
- package/dist/esm/worklets/classes.mjs.map +1 -0
- package/dist/esm/worklets/scope.mjs +23 -1
- package/dist/esm/worklets/scope.mjs.map +1 -1
- package/dist/esm/worklets/serialize.mjs +4 -2
- package/dist/esm/worklets/serialize.mjs.map +1 -1
- package/dist/esm/worklets/transform.mjs +58 -4
- package/dist/esm/worklets/transform.mjs.map +1 -1
- package/package.json +5 -5
- package/src/transformWorklets.test.ts +196 -1
- package/src/transformWorklets.ts +4 -0
- package/src/worklets/classes.ts +238 -0
- package/src/worklets/scope.ts +27 -1
- package/src/worklets/serialize.ts +14 -2
- package/src/worklets/transform.ts +99 -5
- package/src/worklets/types.ts +7 -0
- package/types/transformWorklets.d.ts.map +1 -1
- package/types/worklets/classes.d.ts +22 -0
- package/types/worklets/classes.d.ts.map +1 -0
- package/types/worklets/scope.d.ts.map +1 -1
- package/types/worklets/serialize.d.ts +5 -1
- package/types/worklets/serialize.d.ts.map +1 -1
- package/types/worklets/transform.d.ts.map +1 -1
- package/types/worklets/types.d.ts +6 -0
- package/types/worklets/types.d.ts.map +1 -1
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import type { WorkletClassCandidate } from './types'
|
|
2
|
+
|
|
3
|
+
export const WORKLET_CLASS_MARKER = '__workletClass'
|
|
4
|
+
export const WORKLET_CLASS_FACTORY_SUFFIX = '__classFactory'
|
|
5
|
+
|
|
6
|
+
const MARKER_FIELD_TYPES = new Set([
|
|
7
|
+
'PropertyDefinition',
|
|
8
|
+
'AccessorProperty',
|
|
9
|
+
'TSAbstractPropertyDefinition',
|
|
10
|
+
])
|
|
11
|
+
|
|
12
|
+
function isMarkerField(element: any): boolean {
|
|
13
|
+
return (
|
|
14
|
+
MARKER_FIELD_TYPES.has(element?.type) &&
|
|
15
|
+
!element.computed &&
|
|
16
|
+
element.key?.type === 'Identifier' &&
|
|
17
|
+
element.key.name === WORKLET_CLASS_MARKER
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function findWorkletClasses(program: any): WorkletClassCandidate[] {
|
|
22
|
+
const candidates: WorkletClassCandidate[] = []
|
|
23
|
+
|
|
24
|
+
function walk(node: any, parent: any) {
|
|
25
|
+
if (!node || typeof node !== 'object') return
|
|
26
|
+
|
|
27
|
+
if (
|
|
28
|
+
node.type === 'ClassDeclaration' &&
|
|
29
|
+
node.id?.type === 'Identifier' &&
|
|
30
|
+
!node.declare &&
|
|
31
|
+
// class-level decorators sit outside the class range upstream's factory
|
|
32
|
+
// shape can carry, so decorated classes keep today's raw capture.
|
|
33
|
+
!(node.decorators?.length > 0)
|
|
34
|
+
) {
|
|
35
|
+
const marker = node.body?.body?.find(isMarkerField)
|
|
36
|
+
if (marker) {
|
|
37
|
+
candidates.push({ node, name: node.id.name, parent, marker })
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
for (const key of Object.keys(node)) {
|
|
42
|
+
if (key === 'start' || key === 'end' || key === 'loc' || key === 'range') continue
|
|
43
|
+
const child = node[key]
|
|
44
|
+
if (Array.isArray(child)) {
|
|
45
|
+
for (const item of child) walk(item, node)
|
|
46
|
+
} else if (child && typeof child === 'object') {
|
|
47
|
+
walk(child, node)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
walk(program, null)
|
|
53
|
+
// a marked class nested inside another marked class shares one rewrite
|
|
54
|
+
// range with it, so only the outermost of an overlap is rewritten.
|
|
55
|
+
return candidates.filter(
|
|
56
|
+
(candidate) =>
|
|
57
|
+
!candidates.some(
|
|
58
|
+
(other) =>
|
|
59
|
+
other !== candidate &&
|
|
60
|
+
other.node.start <= candidate.node.start &&
|
|
61
|
+
other.node.end >= candidate.node.end
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function classFactoryName(className: string): string {
|
|
67
|
+
return `${className}${WORKLET_CLASS_FACTORY_SUFFIX}`
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function collectPatternNames(pattern: any, count: (name: string) => void) {
|
|
71
|
+
if (!pattern || typeof pattern !== 'object') return
|
|
72
|
+
switch (pattern.type) {
|
|
73
|
+
case 'Identifier':
|
|
74
|
+
count(pattern.name)
|
|
75
|
+
return
|
|
76
|
+
case 'AssignmentPattern':
|
|
77
|
+
collectPatternNames(pattern.left, count)
|
|
78
|
+
return
|
|
79
|
+
case 'RestElement':
|
|
80
|
+
collectPatternNames(pattern.argument, count)
|
|
81
|
+
return
|
|
82
|
+
case 'ArrayPattern':
|
|
83
|
+
for (const element of pattern.elements ?? []) collectPatternNames(element, count)
|
|
84
|
+
return
|
|
85
|
+
case 'ObjectPattern':
|
|
86
|
+
for (const property of pattern.properties ?? []) {
|
|
87
|
+
if (property?.type === 'Property') collectPatternNames(property.value, count)
|
|
88
|
+
else if (property?.type === 'RestElement')
|
|
89
|
+
collectPatternNames(property.argument, count)
|
|
90
|
+
}
|
|
91
|
+
return
|
|
92
|
+
case 'TSParameterProperty':
|
|
93
|
+
collectPatternNames(pattern.parameter, count)
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* marked class names that are the sole value binding of that name in the file.
|
|
100
|
+
* every free reference to one necessarily resolves to the marked class, so a
|
|
101
|
+
* worklet capturing it (a superclass, a static access, an instanceof) carries
|
|
102
|
+
* the factory instead of the raw class. any other binding of the same name
|
|
103
|
+
* keeps the previous capture, since the reference may resolve to that one.
|
|
104
|
+
*/
|
|
105
|
+
export function findUnambiguousClassNames(
|
|
106
|
+
program: any,
|
|
107
|
+
candidates: WorkletClassCandidate[]
|
|
108
|
+
): Set<string> {
|
|
109
|
+
const wanted = new Set(candidates.map((candidate) => candidate.name))
|
|
110
|
+
const counts = new Map<string, number>()
|
|
111
|
+
const count = (name: string) => {
|
|
112
|
+
if (wanted.has(name)) counts.set(name, (counts.get(name) ?? 0) + 1)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function walk(node: any) {
|
|
116
|
+
if (!node || typeof node !== 'object') return
|
|
117
|
+
if (Array.isArray(node)) {
|
|
118
|
+
for (const item of node) walk(item)
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
switch (node.type) {
|
|
122
|
+
case 'VariableDeclarator':
|
|
123
|
+
collectPatternNames(node.id, count)
|
|
124
|
+
break
|
|
125
|
+
case 'FunctionDeclaration':
|
|
126
|
+
case 'FunctionExpression':
|
|
127
|
+
if (node.id?.type === 'Identifier') count(node.id.name)
|
|
128
|
+
for (const param of node.params ?? []) collectPatternNames(param, count)
|
|
129
|
+
break
|
|
130
|
+
case 'ArrowFunctionExpression':
|
|
131
|
+
for (const param of node.params ?? []) collectPatternNames(param, count)
|
|
132
|
+
break
|
|
133
|
+
case 'ClassDeclaration':
|
|
134
|
+
case 'ClassExpression':
|
|
135
|
+
if (node.id?.type === 'Identifier') count(node.id.name)
|
|
136
|
+
break
|
|
137
|
+
case 'CatchClause':
|
|
138
|
+
collectPatternNames(node.param, count)
|
|
139
|
+
break
|
|
140
|
+
case 'ImportSpecifier':
|
|
141
|
+
case 'ImportDefaultSpecifier':
|
|
142
|
+
case 'ImportNamespaceSpecifier':
|
|
143
|
+
if (node.local?.type === 'Identifier') count(node.local.name)
|
|
144
|
+
break
|
|
145
|
+
case 'TSEnumDeclaration':
|
|
146
|
+
case 'TSModuleDeclaration':
|
|
147
|
+
if (node.id?.type === 'Identifier') count(node.id.name)
|
|
148
|
+
break
|
|
149
|
+
}
|
|
150
|
+
for (const key of Object.keys(node)) {
|
|
151
|
+
if (key === 'start' || key === 'end' || key === 'loc' || key === 'range') continue
|
|
152
|
+
walk(node[key])
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
walk(program)
|
|
157
|
+
return new Set(
|
|
158
|
+
candidates.filter((candidate) => counts.get(candidate.name) === 1).map((c) => c.name)
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const ABSTRACT_MEMBER_TYPES = new Set([
|
|
163
|
+
'TSAbstractMethodDefinition',
|
|
164
|
+
'TSAbstractPropertyDefinition',
|
|
165
|
+
])
|
|
166
|
+
|
|
167
|
+
export function buildClassFactoryDeclaration(
|
|
168
|
+
candidate: WorkletClassCandidate,
|
|
169
|
+
code: string
|
|
170
|
+
): string {
|
|
171
|
+
const { node, name, marker } = candidate
|
|
172
|
+
const factory = classFactoryName(name)
|
|
173
|
+
|
|
174
|
+
// the marker plus any bodiless abstract member, which only compiles inside
|
|
175
|
+
// an abstract class declaration and a factory holds a class expression.
|
|
176
|
+
const removals: Array<[number, number]> = [
|
|
177
|
+
[marker.decorators?.[0]?.start ?? marker.start, marker.end],
|
|
178
|
+
]
|
|
179
|
+
for (const element of node.body?.body ?? []) {
|
|
180
|
+
if (ABSTRACT_MEMBER_TYPES.has(element?.type)) {
|
|
181
|
+
removals.push([element.start, element.end])
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
removals.sort((a, b) => a[0] - b[0])
|
|
185
|
+
let classSrc = ''
|
|
186
|
+
let cursor = node.start
|
|
187
|
+
for (const [start, end] of removals) {
|
|
188
|
+
if (start < cursor) continue
|
|
189
|
+
classSrc += code.slice(cursor, start)
|
|
190
|
+
cursor = end
|
|
191
|
+
}
|
|
192
|
+
classSrc += code.slice(cursor, node.end)
|
|
193
|
+
const classExpr = classSrc.replace(/^abstract\s+/, '')
|
|
194
|
+
|
|
195
|
+
return [
|
|
196
|
+
`function ${factory}() {`,
|
|
197
|
+
`'worklet';`,
|
|
198
|
+
`const ${name} = ${classExpr};`,
|
|
199
|
+
`${name}.${factory} = ${factory};`,
|
|
200
|
+
`return ${name};`,
|
|
201
|
+
`}`,
|
|
202
|
+
].join('\n')
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* names constructed with `new Name()` anywhere under the worklet, in traversal
|
|
207
|
+
* order. like upstream's plugin, callers rewrite one whose name is also a
|
|
208
|
+
* captured variable to its class factory, without checking where the class
|
|
209
|
+
* was defined, so imported worklet classes resolve through their own file.
|
|
210
|
+
*/
|
|
211
|
+
export function findConstructedNames(fnNode: any): string[] {
|
|
212
|
+
const names: string[] = []
|
|
213
|
+
const seen = new Set<string>()
|
|
214
|
+
|
|
215
|
+
function walk(node: any) {
|
|
216
|
+
if (!node || typeof node !== 'object') return
|
|
217
|
+
if (Array.isArray(node)) {
|
|
218
|
+
for (const item of node) walk(item)
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
if (
|
|
222
|
+
node.type === 'NewExpression' &&
|
|
223
|
+
node.callee?.type === 'Identifier' &&
|
|
224
|
+
!seen.has(node.callee.name)
|
|
225
|
+
) {
|
|
226
|
+
seen.add(node.callee.name)
|
|
227
|
+
names.push(node.callee.name)
|
|
228
|
+
}
|
|
229
|
+
for (const key of Object.keys(node)) {
|
|
230
|
+
if (key === 'start' || key === 'end' || key === 'loc' || key === 'range') continue
|
|
231
|
+
walk(node[key])
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
walk(fnNode.body)
|
|
236
|
+
for (const param of fnNode.params ?? []) walk(param)
|
|
237
|
+
return names
|
|
238
|
+
}
|
package/src/worklets/scope.ts
CHANGED
|
@@ -296,6 +296,9 @@ export function getClosureVariables(fnNode: any, globals: Set<string>): string[]
|
|
|
296
296
|
if (node.id && node.id.name) {
|
|
297
297
|
currentScope.bindings.add(node.id.name)
|
|
298
298
|
}
|
|
299
|
+
if (node.decorators) {
|
|
300
|
+
for (const decorator of node.decorators) walk(decorator)
|
|
301
|
+
}
|
|
299
302
|
if (node.superClass) walk(node.superClass)
|
|
300
303
|
pushScope(false)
|
|
301
304
|
walk(node.body)
|
|
@@ -319,14 +322,37 @@ export function getClosureVariables(fnNode: any, globals: Set<string>): string[]
|
|
|
319
322
|
return
|
|
320
323
|
}
|
|
321
324
|
|
|
322
|
-
case 'MethodDefinition':
|
|
325
|
+
case 'MethodDefinition':
|
|
326
|
+
case 'TSAbstractMethodDefinition': {
|
|
323
327
|
if (node.computed) {
|
|
324
328
|
walk(node.key)
|
|
325
329
|
}
|
|
330
|
+
if (node.decorators) {
|
|
331
|
+
for (const decorator of node.decorators) walk(decorator)
|
|
332
|
+
}
|
|
326
333
|
walk(node.value)
|
|
327
334
|
return
|
|
328
335
|
}
|
|
329
336
|
|
|
337
|
+
case 'PropertyDefinition':
|
|
338
|
+
case 'AccessorProperty':
|
|
339
|
+
case 'TSAbstractPropertyDefinition': {
|
|
340
|
+
// a plain field name is not a value reference. without this case the
|
|
341
|
+
// default traversal captures every field name as a closure variable.
|
|
342
|
+
if (node.computed) {
|
|
343
|
+
walk(node.key)
|
|
344
|
+
}
|
|
345
|
+
if (node.decorators) {
|
|
346
|
+
for (const decorator of node.decorators) walk(decorator)
|
|
347
|
+
}
|
|
348
|
+
if (node.value) walk(node.value)
|
|
349
|
+
return
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
case 'TSIndexSignature': {
|
|
353
|
+
return
|
|
354
|
+
}
|
|
355
|
+
|
|
330
356
|
case 'Identifier': {
|
|
331
357
|
resolveIdentifier(node.name)
|
|
332
358
|
return
|
|
@@ -24,6 +24,11 @@ function assertTransformed(
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
export type ClassReconstruction = {
|
|
28
|
+
name: string
|
|
29
|
+
factory: string
|
|
30
|
+
}
|
|
31
|
+
|
|
27
32
|
/**
|
|
28
33
|
* serializes a worklet function into a string for the hermes ui thread (__initData.code).
|
|
29
34
|
* includes injected unpacker: `const [var1, var2] = this.__closure;`
|
|
@@ -33,7 +38,8 @@ export function serializeWorkletForUI(
|
|
|
33
38
|
fnNode: any,
|
|
34
39
|
code: string,
|
|
35
40
|
name: string | undefined,
|
|
36
|
-
closureVars: string[]
|
|
41
|
+
closureVars: string[],
|
|
42
|
+
reconstructions: ClassReconstruction[] = []
|
|
37
43
|
): string {
|
|
38
44
|
let rawParams = ''
|
|
39
45
|
if (fnNode.params && fnNode.params.length > 0) {
|
|
@@ -53,12 +59,18 @@ export function serializeWorkletForUI(
|
|
|
53
59
|
|
|
54
60
|
const unpacker =
|
|
55
61
|
closureVars.length > 0 ? `const [${closureVars.join(', ')}] = this.__closure;\n` : ''
|
|
62
|
+
const rebuilt =
|
|
63
|
+
reconstructions.length > 0
|
|
64
|
+
? reconstructions
|
|
65
|
+
.map(({ name, factory }) => `const ${name} = ${factory}();\n`)
|
|
66
|
+
.join('')
|
|
67
|
+
: ''
|
|
56
68
|
|
|
57
69
|
const asyncPrefix = fnNode.async ? 'async ' : ''
|
|
58
70
|
const genPrefix = fnNode.generator ? '*' : ''
|
|
59
71
|
const fnName = name || '_worklet'
|
|
60
72
|
|
|
61
|
-
const fullFn = `${asyncPrefix}function${genPrefix} ${fnName}(${rawParams}) {\n${unpacker}${rawBody}\n}`
|
|
73
|
+
const fullFn = `${asyncPrefix}function${genPrefix} ${fnName}(${rawParams}) {\n${unpacker}${rebuilt}${rawBody}\n}`
|
|
62
74
|
|
|
63
75
|
const transformed = transformSync('worklet.ts', fullFn)
|
|
64
76
|
assertTransformed(transformed, name, 'serialized')
|
|
@@ -7,10 +7,21 @@ import {
|
|
|
7
7
|
findWorkletCandidates,
|
|
8
8
|
hasDirective,
|
|
9
9
|
} from './autoworklet'
|
|
10
|
+
import {
|
|
11
|
+
buildClassFactoryDeclaration,
|
|
12
|
+
classFactoryName,
|
|
13
|
+
findConstructedNames,
|
|
14
|
+
findUnambiguousClassNames,
|
|
15
|
+
findWorkletClasses,
|
|
16
|
+
} from './classes'
|
|
10
17
|
import { createGlobalsSet } from './globals'
|
|
11
18
|
import { calculateWorkletHash } from './hash'
|
|
12
19
|
import { getClosureVariables } from './scope'
|
|
13
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
buildLocalFunction,
|
|
22
|
+
serializeWorkletForUI,
|
|
23
|
+
type ClassReconstruction,
|
|
24
|
+
} from './serialize'
|
|
14
25
|
import type { TransformWorkletsOptions } from './types'
|
|
15
26
|
|
|
16
27
|
const FUNCTION_TYPES = new Set([
|
|
@@ -124,7 +135,17 @@ export function executeWorkletTransform(
|
|
|
124
135
|
}
|
|
125
136
|
|
|
126
137
|
const initialCandidates = findWorkletCandidates(initialParse.program)
|
|
127
|
-
|
|
138
|
+
// upstream's plugin skips class work under either flag, and this port has
|
|
139
|
+
// no bundle-mode support, so both leave classes and their captures raw.
|
|
140
|
+
const workletClassesEnabled = !options?.disableWorkletClasses && !options?.bundleMode
|
|
141
|
+
const initialClasses = workletClassesEnabled
|
|
142
|
+
? findWorkletClasses(initialParse.program)
|
|
143
|
+
: []
|
|
144
|
+
const resolvableClassNames =
|
|
145
|
+
initialClasses.length > 0
|
|
146
|
+
? findUnambiguousClassNames(initialParse.program, initialClasses)
|
|
147
|
+
: new Set<string>()
|
|
148
|
+
if (initialCandidates.length === 0 && initialClasses.length === 0) {
|
|
128
149
|
let map: any = undefined
|
|
129
150
|
if (sourceMaps) {
|
|
130
151
|
const ms = new MagicString(code)
|
|
@@ -154,6 +175,48 @@ export function executeWorkletTransform(
|
|
|
154
175
|
const initDataDefs = new Map<string, string>()
|
|
155
176
|
const passMaps: any[] = []
|
|
156
177
|
|
|
178
|
+
// marked classes become serializable factories before the worklet passes, so
|
|
179
|
+
// the factories themselves are workletized like any other candidate and a
|
|
180
|
+
// worklet capturing the class reconstructs it from the factory on the ui
|
|
181
|
+
// thread instead of capturing the raw class.
|
|
182
|
+
if (initialClasses.length > 0) {
|
|
183
|
+
const ms = new MagicString(currentCode)
|
|
184
|
+
for (const candidate of initialClasses) {
|
|
185
|
+
const factoryDecl = buildClassFactoryDeclaration(candidate, currentCode)
|
|
186
|
+
const rebound = `const ${candidate.name} = ${classFactoryName(candidate.name)}();`
|
|
187
|
+
if (candidate.parent?.type === 'ExportNamedDeclaration') {
|
|
188
|
+
ms.overwrite(
|
|
189
|
+
candidate.parent.start,
|
|
190
|
+
candidate.parent.end,
|
|
191
|
+
`${factoryDecl}\nexport ${rebound}`
|
|
192
|
+
)
|
|
193
|
+
} else if (candidate.parent?.type === 'ExportDefaultDeclaration') {
|
|
194
|
+
ms.overwrite(
|
|
195
|
+
candidate.parent.start,
|
|
196
|
+
candidate.parent.end,
|
|
197
|
+
`${factoryDecl}\n${rebound}\nexport default ${candidate.name};`
|
|
198
|
+
)
|
|
199
|
+
} else {
|
|
200
|
+
ms.overwrite(
|
|
201
|
+
candidate.node.start,
|
|
202
|
+
candidate.node.end,
|
|
203
|
+
`${factoryDecl}\n${rebound}`
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (sourceMaps) {
|
|
208
|
+
passMaps.push(
|
|
209
|
+
ms.generateMap({
|
|
210
|
+
source: cleanId,
|
|
211
|
+
file: cleanId,
|
|
212
|
+
hires: true,
|
|
213
|
+
includeContent: true,
|
|
214
|
+
})
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
currentCode = ms.toString()
|
|
218
|
+
}
|
|
219
|
+
|
|
157
220
|
// 3. Iterative bottom-up transformation
|
|
158
221
|
// In each pass, we transform all innermost candidates (candidates that contain NO other candidates in their body).
|
|
159
222
|
// All innermost candidates are disjoint, so they can be rewritten simultaneously in a single MagicString.
|
|
@@ -202,16 +265,42 @@ export function executeWorkletTransform(
|
|
|
202
265
|
// `no-worklet-closure` says the worklet captures nothing, so it gets no
|
|
203
266
|
// unpacker line and an empty `__closure`. it is what lets the worklets
|
|
204
267
|
// runtime evaluate the serialized code with no closure to bind.
|
|
205
|
-
|
|
268
|
+
let closureVars = hasDirective(candidate.fnNode, 'no-worklet-closure')
|
|
206
269
|
? []
|
|
207
270
|
: getClosureVariables(candidate.fnNode, globals)
|
|
271
|
+
// a capture that is known to be a worklet class travels as its
|
|
272
|
+
// factory and is rebuilt from it on the ui thread, so a factory
|
|
273
|
+
// closing over its superclass stays serializable. anything else that
|
|
274
|
+
// is constructed resolves through its own file like upstream.
|
|
275
|
+
const reconstructions: ClassReconstruction[] = []
|
|
276
|
+
if (workletClassesEnabled && closureVars.length > 0) {
|
|
277
|
+
const captured = new Set(closureVars)
|
|
278
|
+
const rewriteToFactory = (name: string) => {
|
|
279
|
+
if (!captured.has(name)) return
|
|
280
|
+
const factory = classFactoryName(name)
|
|
281
|
+
captured.delete(name)
|
|
282
|
+
captured.add(factory)
|
|
283
|
+
if (!reconstructions.some((r) => r.factory === factory)) {
|
|
284
|
+
reconstructions.push({ name, factory })
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
const knownCaptures = [...captured].filter((name) =>
|
|
288
|
+
resolvableClassNames.has(name)
|
|
289
|
+
)
|
|
290
|
+
for (const name of knownCaptures) rewriteToFactory(name)
|
|
291
|
+
for (const constructed of findConstructedNames(candidate.fnNode)) {
|
|
292
|
+
rewriteToFactory(constructed)
|
|
293
|
+
}
|
|
294
|
+
closureVars = [...captured].sort()
|
|
295
|
+
}
|
|
208
296
|
const fnName = candidate.name
|
|
209
297
|
|
|
210
298
|
const serializedCode = serializeWorkletForUI(
|
|
211
299
|
candidate.fnNode,
|
|
212
300
|
currentCode,
|
|
213
301
|
fnName,
|
|
214
|
-
closureVars
|
|
302
|
+
closureVars,
|
|
303
|
+
reconstructions
|
|
215
304
|
)
|
|
216
305
|
const workletHash = calculateWorkletHash(serializedCode)
|
|
217
306
|
const initDataVar = `_worklet_${workletHash}_init_data`
|
|
@@ -242,7 +331,12 @@ export function executeWorkletTransform(
|
|
|
242
331
|
const localFnCode = buildLocalFunction(candidate.fnNode, currentCode, fnName)
|
|
243
332
|
const localName = fnName || '_worklet'
|
|
244
333
|
// worklets 0.13 captures values in the same order the ui function unpacks them.
|
|
245
|
-
|
|
334
|
+
// a class factory travels as a member of its class, which the defining
|
|
335
|
+
// file attached when it built the factory.
|
|
336
|
+
const factoryOwners = new Map(reconstructions.map((r) => [r.factory, r.name]))
|
|
337
|
+
const closureValues = closureVars
|
|
338
|
+
.map((v) => (factoryOwners.has(v) ? `${factoryOwners.get(v)}.${v}` : v))
|
|
339
|
+
.join(', ')
|
|
246
340
|
|
|
247
341
|
const iife = `(function () {
|
|
248
342
|
var _e = [new (typeof global !== 'undefined' ? global : globalThis).Error(), 1, -27];
|
package/src/worklets/types.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transformWorklets.d.ts","sourceRoot":"","sources":["../src/transformWorklets.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,sCAAsC,
|
|
1
|
+
{"version":3,"file":"transformWorklets.d.ts","sourceRoot":"","sources":["../src/transformWorklets.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,sCAAsC,UAqBlD,CAAA;AAiCD,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,WAWjF;AAED,wBAAgB,kBAAkB,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CA8DlF;AAED,MAAM,MAAM,wBAAwB,GAAG;IACrC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,0BAA0B,CAAC,EAAE,OAAO,CAAA;IACpC,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,sBAAsB,CAAC,EAAE,OAAO,CAAA;IAChC,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAOD,OAAO,EAAE,+BAA+B,EAAE,MAAM,sBAAsB,CAAA;AAEtE,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,UAAU,UAAQ,EAClB,OAAO,CAAC,EAAE,wBAAwB,GACjC,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,GAAG,CAAA;CAAE,CAAC,CAqBtC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { WorkletClassCandidate } from './types';
|
|
2
|
+
export declare const WORKLET_CLASS_MARKER = "__workletClass";
|
|
3
|
+
export declare const WORKLET_CLASS_FACTORY_SUFFIX = "__classFactory";
|
|
4
|
+
export declare function findWorkletClasses(program: any): WorkletClassCandidate[];
|
|
5
|
+
export declare function classFactoryName(className: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* marked class names that are the sole value binding of that name in the file.
|
|
8
|
+
* every free reference to one necessarily resolves to the marked class, so a
|
|
9
|
+
* worklet capturing it (a superclass, a static access, an instanceof) carries
|
|
10
|
+
* the factory instead of the raw class. any other binding of the same name
|
|
11
|
+
* keeps the previous capture, since the reference may resolve to that one.
|
|
12
|
+
*/
|
|
13
|
+
export declare function findUnambiguousClassNames(program: any, candidates: WorkletClassCandidate[]): Set<string>;
|
|
14
|
+
export declare function buildClassFactoryDeclaration(candidate: WorkletClassCandidate, code: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* names constructed with `new Name()` anywhere under the worklet, in traversal
|
|
17
|
+
* order. like upstream's plugin, callers rewrite one whose name is also a
|
|
18
|
+
* captured variable to its class factory, without checking where the class
|
|
19
|
+
* was defined, so imported worklet classes resolve through their own file.
|
|
20
|
+
*/
|
|
21
|
+
export declare function findConstructedNames(fnNode: any): string[];
|
|
22
|
+
//# sourceMappingURL=classes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classes.d.ts","sourceRoot":"","sources":["../../src/worklets/classes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAA;AAEpD,eAAO,MAAM,oBAAoB,mBAAmB,CAAA;AACpD,eAAO,MAAM,4BAA4B,mBAAmB,CAAA;AAiB5D,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,GAAG,GAAG,qBAAqB,EAAE,CA2CxE;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE1D;AA8BD;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,GAAG,EACZ,UAAU,EAAE,qBAAqB,EAAE,GAClC,GAAG,CAAC,MAAM,CAAC,CAoDb;AAOD,wBAAgB,4BAA4B,CAC1C,SAAS,EAAE,qBAAqB,EAChC,IAAI,EAAE,MAAM,GACX,MAAM,CAiCR;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,GAAG,GAAG,MAAM,EAAE,CA2B1D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../../src/worklets/scope.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../../src/worklets/scope.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CA8Z/E"}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
export type ClassReconstruction = {
|
|
2
|
+
name: string;
|
|
3
|
+
factory: string;
|
|
4
|
+
};
|
|
1
5
|
/**
|
|
2
6
|
* serializes a worklet function into a string for the hermes ui thread (__initData.code).
|
|
3
7
|
* includes injected unpacker: `const [var1, var2] = this.__closure;`
|
|
4
8
|
* strips typescript types and formats via oxc-transform.
|
|
5
9
|
*/
|
|
6
|
-
export declare function serializeWorkletForUI(fnNode: any, code: string, name: string | undefined, closureVars: string[]): string;
|
|
10
|
+
export declare function serializeWorkletForUI(fnNode: any, code: string, name: string | undefined, closureVars: string[], reconstructions?: ClassReconstruction[]): string;
|
|
7
11
|
/**
|
|
8
12
|
* Builds the local JavaScript function to run on the JS thread inside the worklet IIFE.
|
|
9
13
|
* Strips TypeScript types and the 'worklet' directive.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../../src/worklets/serialize.ts"],"names":[],"mappings":"AA0BA;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,GAAG,EACX,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,WAAW,EAAE,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../../src/worklets/serialize.ts"],"names":[],"mappings":"AA0BA,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,GAAG,EACX,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,WAAW,EAAE,MAAM,EAAE,EACrB,eAAe,GAAE,mBAAmB,EAAO,GAC1C,MAAM,CAmCR;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,GAAG,EACX,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GAAG,SAAS,GACvB,MAAM,CAqCR"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../src/worklets/transform.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../src/worklets/transform.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAA;AAQvD,wBAAgB,+BAA+B,CAC7C,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,UAAU,UAAQ,GACjB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,GAAG,CAAA;CAAE,GAAG,IAAI,CAiCpC;AAsCD,wBAAgB,uBAAuB,CACrC,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,UAAU,UAAQ,EAClB,OAAO,CAAC,EAAE,wBAAwB,EAClC,gBAAgB,CAAC,EAAE,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,MAAM,GACrE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,GAAG,CAAA;CAAE,CAuU7B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/worklets/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,wBAAwB,GAAG;IACrC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,0BAA0B,CAAC,EAAE,OAAO,CAAA;IACpC,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,sBAAsB,CAAC,EAAE,OAAO,CAAA;IAChC,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,oBAAoB,GAC5B,sBAAsB,GACtB,qBAAqB,GACrB,gBAAgB,GAChB,eAAe,CAAA;AAEnB,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,GAAG,CAAA;IACT,MAAM,EAAE,GAAG,CAAA;IACX,IAAI,EAAE,oBAAoB,CAAA;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,GAAG,CAAA;IACZ,aAAa,EAAE,OAAO,CAAA;CACvB"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/worklets/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,wBAAwB,GAAG;IACrC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,0BAA0B,CAAC,EAAE,OAAO,CAAA;IACpC,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,sBAAsB,CAAC,EAAE,OAAO,CAAA;IAChC,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,oBAAoB,GAC5B,sBAAsB,GACtB,qBAAqB,GACrB,gBAAgB,GAChB,eAAe,CAAA;AAEnB,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,GAAG,CAAA;IACT,MAAM,EAAE,GAAG,CAAA;IACX,IAAI,EAAE,oBAAoB,CAAA;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,GAAG,CAAA;IACZ,aAAa,EAAE,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,GAAG,CAAA;IACT,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,GAAG,CAAA;IACZ,MAAM,EAAE,GAAG,CAAA;CACZ"}
|