vxrn 1.25.16 → 1.26.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.
Files changed (72) hide show
  1. package/dist/index.js +2 -0
  2. package/dist/index.js.map +1 -1
  3. package/dist/index.mjs +2 -0
  4. package/dist/index.mjs.map +1 -1
  5. package/dist/index.native.js +2 -0
  6. package/dist/index.native.js.map +1 -1
  7. package/dist/patches/builtInDepPatches.mjs +21 -0
  8. package/dist/patches/builtInDepPatches.mjs.map +1 -1
  9. package/dist/patches/builtInDepPatches.native.js +21 -0
  10. package/dist/patches/builtInDepPatches.native.js.map +1 -1
  11. package/dist/plugins/reactNativeDevServer.mjs +5 -0
  12. package/dist/plugins/reactNativeDevServer.mjs.map +1 -1
  13. package/dist/plugins/reactNativeDevServer.native.js +5 -0
  14. package/dist/plugins/reactNativeDevServer.native.js.map +1 -1
  15. package/dist/plugins/rnCodegenPlugin.mjs +16 -0
  16. package/dist/plugins/rnCodegenPlugin.mjs.map +1 -0
  17. package/dist/plugins/rnCodegenPlugin.native.js +16 -0
  18. package/dist/plugins/rnCodegenPlugin.native.js.map +1 -0
  19. package/dist/plugins/rnCodegenPlugin.test.mjs +239 -0
  20. package/dist/plugins/rnCodegenPlugin.test.mjs.map +1 -0
  21. package/dist/plugins/rnCodegenPlugin.test.native.js +242 -0
  22. package/dist/plugins/rnCodegenPlugin.test.native.js.map +1 -0
  23. package/dist/plugins/workletImportsPlugin.mjs +160 -0
  24. package/dist/plugins/workletImportsPlugin.mjs.map +1 -0
  25. package/dist/plugins/workletImportsPlugin.native.js +230 -0
  26. package/dist/plugins/workletImportsPlugin.native.js.map +1 -0
  27. package/dist/utils/createNativeDevEngine.mjs +198 -92
  28. package/dist/utils/createNativeDevEngine.mjs.map +1 -1
  29. package/dist/utils/createNativeDevEngine.native.js +252 -99
  30. package/dist/utils/createNativeDevEngine.native.js.map +1 -1
  31. package/dist/utils/createNativeDevEngine.test.mjs +775 -4
  32. package/dist/utils/createNativeDevEngine.test.mjs.map +1 -1
  33. package/dist/utils/createNativeDevEngine.test.native.js +836 -4
  34. package/dist/utils/createNativeDevEngine.test.native.js.map +1 -1
  35. package/dist/utils/patches.mjs +12 -5
  36. package/dist/utils/patches.mjs.map +1 -1
  37. package/dist/utils/patches.native.js +13 -5
  38. package/dist/utils/patches.native.js.map +1 -1
  39. package/dist/utils/patches.test.mjs +28 -0
  40. package/dist/utils/patches.test.mjs.map +1 -1
  41. package/dist/utils/patches.test.native.js +47 -0
  42. package/dist/utils/patches.test.native.js.map +1 -1
  43. package/dist/utils/scanDepsToOptimize.mjs +5 -1
  44. package/dist/utils/scanDepsToOptimize.mjs.map +1 -1
  45. package/dist/utils/scanDepsToOptimize.native.js +5 -1
  46. package/dist/utils/scanDepsToOptimize.native.js.map +1 -1
  47. package/package.json +19 -12
  48. package/src/index.ts +2 -0
  49. package/src/patches/builtInDepPatches.ts +25 -0
  50. package/src/plugins/reactNativeDevServer.ts +12 -0
  51. package/src/plugins/rnCodegenPlugin.test.ts +313 -0
  52. package/src/plugins/rnCodegenPlugin.ts +20 -0
  53. package/src/plugins/workletImportsPlugin.ts +226 -0
  54. package/src/utils/createNativeDevEngine.test.ts +877 -3
  55. package/src/utils/createNativeDevEngine.ts +259 -107
  56. package/src/utils/patches.test.ts +33 -0
  57. package/src/utils/patches.ts +17 -6
  58. package/src/utils/scanDepsToOptimize.ts +9 -0
  59. package/types/index.d.ts +2 -0
  60. package/types/index.d.ts.map +1 -1
  61. package/types/patches/builtInDepPatches.d.ts.map +1 -1
  62. package/types/plugins/reactNativeDevServer.d.ts.map +1 -1
  63. package/types/plugins/rnCodegenPlugin.d.ts +6 -0
  64. package/types/plugins/rnCodegenPlugin.d.ts.map +1 -0
  65. package/types/plugins/rnCodegenPlugin.test.d.ts +2 -0
  66. package/types/plugins/rnCodegenPlugin.test.d.ts.map +1 -0
  67. package/types/plugins/workletImportsPlugin.d.ts +8 -0
  68. package/types/plugins/workletImportsPlugin.d.ts.map +1 -0
  69. package/types/utils/createNativeDevEngine.d.ts +13 -0
  70. package/types/utils/createNativeDevEngine.d.ts.map +1 -1
  71. package/types/utils/patches.d.ts.map +1 -1
  72. package/types/utils/scanDepsToOptimize.d.ts.map +1 -1
@@ -0,0 +1,313 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { rnCodegenPlugin, transformReactNativeCodegen } from './rnCodegenPlugin'
3
+
4
+ describe('rnCodegenPlugin', () => {
5
+ it('generates view-config replacement for codegenNativeComponent default export', () => {
6
+ const inputCode = `
7
+ import type { ViewProps, HostComponent } from 'react-native';
8
+ import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
9
+
10
+ export interface NativeProps extends ViewProps {
11
+ color?: string;
12
+ }
13
+
14
+ export default codegenNativeComponent<NativeProps>('MyCustomView') as HostComponent<NativeProps>;
15
+ `
16
+
17
+ const result = transformReactNativeCodegen(
18
+ inputCode,
19
+ '/workspace/src/MyCustomViewNativeComponent.ts'
20
+ )
21
+
22
+ expect(result).toBeDefined()
23
+ expect(result?.code).not.toContain('codegenNativeComponent<NativeProps>')
24
+ expect(result?.code).toContain(
25
+ 'export default NativeComponentRegistry.get(nativeComponentName, () => __INTERNAL_VIEW_CONFIG);'
26
+ )
27
+ expect(result?.code).toContain('uiViewClassName: "MyCustomView"')
28
+ expect(result?.code).toContain('export const __INTERNAL_VIEW_CONFIG')
29
+ expect(result?.map).toBeDefined()
30
+ })
31
+
32
+ it('leaves already-generated output alone on a second pass', () => {
33
+ // the transform runs more than once over the same module. its own output
34
+ // keeps the codegenNativeComponent import and turns Commands into a plain
35
+ // object, which the reserved-export check would otherwise reject.
36
+ const inputCode = `
37
+ import type { ViewProps, HostComponent } from 'react-native';
38
+ import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
39
+ import codegenNativeCommands from 'react-native/Libraries/Utilities/codegenNativeCommands';
40
+
41
+ export interface NativeProps extends ViewProps {
42
+ color?: string;
43
+ }
44
+
45
+ interface NativeCommands {
46
+ reload: (viewRef: React.ElementRef<HostComponent<NativeProps>>) => void;
47
+ }
48
+
49
+ export const Commands = codegenNativeCommands<NativeCommands>({
50
+ supportedCommands: ['reload'],
51
+ });
52
+
53
+ export default codegenNativeComponent<NativeProps>('MyCustomView') as HostComponent<NativeProps>;
54
+ `
55
+
56
+ const id = '/workspace/src/MyCustomViewNativeComponent.ts'
57
+ const first = transformReactNativeCodegen(inputCode, id)
58
+ expect(first?.code).toContain('export const Commands')
59
+
60
+ expect(() => transformReactNativeCodegen(first!.code, id)).not.toThrow()
61
+ expect(transformReactNativeCodegen(first!.code, id)).toBeUndefined()
62
+ })
63
+
64
+ it('removes export const Commands = codegenNativeCommands(...) and generates runtime commands', () => {
65
+ const inputCode = `
66
+ import * as React from 'react';
67
+ import type { ViewProps, HostComponent } from 'react-native';
68
+ import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
69
+ import codegenNativeCommands from 'react-native/Libraries/Utilities/codegenNativeCommands';
70
+
71
+ export interface NativeProps extends ViewProps {
72
+ color?: string;
73
+ }
74
+
75
+ type ComponentType = HostComponent<NativeProps>;
76
+
77
+ interface NativeCommands {
78
+ flash: (viewRef: React.ElementRef<ComponentType>, enabled: boolean) => void;
79
+ }
80
+
81
+ export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
82
+ supportedCommands: ['flash'],
83
+ });
84
+
85
+ export default codegenNativeComponent<NativeProps>('MyCustomView') as HostComponent<NativeProps>;
86
+ `
87
+
88
+ const result = transformReactNativeCodegen(
89
+ inputCode,
90
+ '/workspace/src/MyCustomViewNativeComponent.ts'
91
+ )
92
+
93
+ expect(result).toBeDefined()
94
+ // The original codegenNativeCommands export must be removed
95
+ expect(result?.code).not.toContain('codegenNativeCommands<NativeCommands>')
96
+ // The runtime Commands implementation must be generated
97
+ expect(result?.code).toContain('export const Commands = {')
98
+ expect(result?.code).toContain('dispatchCommand(ref, "flash", [enabled]);')
99
+ expect(result?.code).toContain(
100
+ 'export default NativeComponentRegistry.get(nativeComponentName, () => __INTERNAL_VIEW_CONFIG);'
101
+ )
102
+ })
103
+
104
+ it('throws an error if native commands are exported with a name other than Commands', () => {
105
+ const inputCode = `
106
+ import * as React from 'react';
107
+ import type { ViewProps, HostComponent } from 'react-native';
108
+ import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
109
+ import codegenNativeCommands from 'react-native/Libraries/Utilities/codegenNativeCommands';
110
+
111
+ export interface NativeProps extends ViewProps {
112
+ color?: string;
113
+ }
114
+
115
+ type ComponentType = HostComponent<NativeProps>;
116
+
117
+ interface NativeCommands {
118
+ flash: (viewRef: React.ElementRef<ComponentType>, enabled: boolean) => void;
119
+ }
120
+
121
+ export const OtherCommands: NativeCommands = codegenNativeCommands<NativeCommands>({
122
+ supportedCommands: ['flash'],
123
+ });
124
+
125
+ export default codegenNativeComponent<NativeProps>('MyCustomView') as HostComponent<NativeProps>;
126
+ `
127
+
128
+ expect(() => {
129
+ transformReactNativeCodegen(
130
+ inputCode,
131
+ '/workspace/src/MyCustomViewNativeComponent.ts'
132
+ )
133
+ }).toThrow("Native commands must be exported with the name 'Commands'")
134
+ })
135
+
136
+ it("throws an error if 'Commands' is exported without codegenNativeCommands", () => {
137
+ const inputCode = `
138
+ import type { ViewProps, HostComponent } from 'react-native';
139
+ import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
140
+
141
+ export interface NativeProps extends ViewProps {
142
+ color?: string;
143
+ }
144
+
145
+ export const Commands = 123;
146
+
147
+ export default codegenNativeComponent<NativeProps>('MyCustomView') as HostComponent<NativeProps>;
148
+ `
149
+
150
+ expect(() => {
151
+ transformReactNativeCodegen(
152
+ inputCode,
153
+ '/workspace/src/MyCustomViewNativeComponent.ts'
154
+ )
155
+ }).toThrow(
156
+ "'Commands' is a reserved export and may only be used to export the result of codegenNativeCommands."
157
+ )
158
+ })
159
+
160
+ it("throws an error if 'Commands' is exported via export specifier without codegenNativeCommands", () => {
161
+ const inputCode = `
162
+ import type { ViewProps, HostComponent } from 'react-native';
163
+ import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
164
+
165
+ export interface NativeProps extends ViewProps {
166
+ color?: string;
167
+ }
168
+
169
+ const Commands = 123;
170
+ export { Commands };
171
+
172
+ export default codegenNativeComponent<NativeProps>('MyCustomView') as HostComponent<NativeProps>;
173
+ `
174
+
175
+ expect(() => {
176
+ transformReactNativeCodegen(
177
+ inputCode,
178
+ '/workspace/src/MyCustomViewNativeComponent.ts'
179
+ )
180
+ }).toThrow(
181
+ "'Commands' is a reserved export and may only be used to export the result of codegenNativeCommands."
182
+ )
183
+ })
184
+
185
+ it('ignores files that do not match the codegen pattern', () => {
186
+ const inputCode = `
187
+ export default function Button() {
188
+ return null;
189
+ }
190
+ `
191
+ const result = transformReactNativeCodegen(inputCode, '/workspace/src/Button.tsx')
192
+ expect(result).toBeUndefined()
193
+ })
194
+
195
+ it('ignores matching files without codegenNativeComponent', () => {
196
+ const inputCode = `
197
+ export default function MyNativeComponent() {
198
+ return null;
199
+ }
200
+ `
201
+ const result = transformReactNativeCodegen(
202
+ inputCode,
203
+ '/workspace/src/MyNativeComponent.tsx'
204
+ )
205
+ expect(result).toBeUndefined()
206
+ })
207
+
208
+ it('supports files inside specs/ directory', () => {
209
+ const inputCode = `
210
+ import type { ViewProps, HostComponent } from 'react-native';
211
+ import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
212
+
213
+ export interface NativeProps extends ViewProps {
214
+ color?: string;
215
+ }
216
+
217
+ export default codegenNativeComponent<NativeProps>('SpecView') as HostComponent<NativeProps>;
218
+ `
219
+
220
+ const result = transformReactNativeCodegen(inputCode, '/workspace/specs/SpecView.ts')
221
+
222
+ expect(result).toBeDefined()
223
+ expect(result?.code).toContain('uiViewClassName: "SpecView"')
224
+ })
225
+
226
+ it('works through the Vite plugin interface', () => {
227
+ const plugin = rnCodegenPlugin()
228
+ expect(plugin.name).toBe('vxrn:rn-codegen')
229
+ expect(plugin.enforce).toBe('pre')
230
+
231
+ const inputCode = `
232
+ import type { ViewProps, HostComponent } from 'react-native';
233
+ import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
234
+
235
+ export interface NativeProps extends ViewProps {
236
+ color?: string;
237
+ }
238
+
239
+ export default codegenNativeComponent<NativeProps>('PluginView') as HostComponent<NativeProps>;
240
+ `
241
+
242
+ const transformFn =
243
+ typeof plugin.transform === 'function'
244
+ ? plugin.transform
245
+ : (plugin.transform as any)?.handler
246
+
247
+ const result = transformFn.call(
248
+ {} as any,
249
+ inputCode,
250
+ '/workspace/src/PluginViewNativeComponent.ts'
251
+ )
252
+
253
+ expect(result).toBeDefined()
254
+ expect(result?.code).toContain('uiViewClassName: "PluginView"')
255
+ expect(result?.map).toBeDefined()
256
+ })
257
+
258
+ it('removes export const Commands with sequence expression (e.g. coverage instrumentation)', () => {
259
+ const inputCode = `
260
+ import type { ViewProps, HostComponent } from 'react-native';
261
+ import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
262
+ import codegenNativeCommands from 'react-native/Libraries/Utilities/codegenNativeCommands';
263
+
264
+ export interface NativeProps extends ViewProps {
265
+ color?: string;
266
+ }
267
+
268
+ export const Commands = (0, codegenNativeCommands({
269
+ supportedCommands: [],
270
+ }));
271
+
272
+ export default codegenNativeComponent<NativeProps>('SequenceView') as HostComponent<NativeProps>;
273
+ `
274
+
275
+ const result = transformReactNativeCodegen(
276
+ inputCode,
277
+ '/workspace/src/SequenceViewNativeComponent.ts'
278
+ )
279
+
280
+ expect(result).toBeDefined()
281
+ expect(result?.code).not.toContain('export const Commands')
282
+ expect(result?.code).toContain('uiViewClassName: "SequenceView"')
283
+ expect(result?.code).toContain('export default NativeComponentRegistry.get')
284
+ })
285
+
286
+ it('transforms Flow component specs correctly', () => {
287
+ const flowCode = `/**
288
+ * @flow strict-local
289
+ * @format
290
+ */
291
+
292
+ import type {ViewProps} from 'react-native/Libraries/Components/View/ViewPropTypes';
293
+ import type {HostComponent} from 'react-native/Libraries/Renderer/shims/ReactNativeTypes';
294
+ import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
295
+
296
+ type FlowViewProps = $ReadOnly<{
297
+ ...ViewProps,
298
+ title?: string,
299
+ }>;
300
+
301
+ export default codegenNativeComponent<FlowViewProps>('FlowView') as HostComponent<FlowViewProps>;
302
+ `
303
+
304
+ const result = transformReactNativeCodegen(
305
+ flowCode,
306
+ '/workspace/src/FlowViewNativeComponent.js'
307
+ )
308
+
309
+ expect(result).toBeDefined()
310
+ expect(result?.code).toContain('uiViewClassName: "FlowView"')
311
+ expect(result?.code).toContain('export default NativeComponentRegistry.get')
312
+ })
313
+ })
@@ -0,0 +1,20 @@
1
+ import type { Plugin } from 'vite'
2
+ import { transformReactNativeCodegen } from '@vxrn/compiler'
3
+
4
+ // the transform itself lives in @vxrn/compiler so the metro transformer worker
5
+ // can run it too. vxrn depends on @vxrn/vite-plugin-metro, so the worker cannot
6
+ // reach back into this package.
7
+ export { transformReactNativeCodegen } from '@vxrn/compiler'
8
+
9
+ export function rnCodegenPlugin(options?: { projectRoot?: string }): Plugin {
10
+ return {
11
+ name: 'vxrn:rn-codegen',
12
+ enforce: 'pre',
13
+ transform: {
14
+ order: 'pre',
15
+ handler(code: string, id: string) {
16
+ return transformReactNativeCodegen(code, id, options?.projectRoot)
17
+ },
18
+ },
19
+ } as Plugin
20
+ }
@@ -0,0 +1,226 @@
1
+ import { createHash } from 'node:crypto'
2
+ import { isBuiltin } from 'node:module'
3
+ import { dirname, isAbsolute, join } from 'node:path'
4
+ import { statSync } from 'node:fs'
5
+ import { rolldown, type Plugin } from 'rolldown'
6
+ import { parseSync } from 'oxc-parser'
7
+ import { getClosureVariables, JS_GLOBALS, transformHermesLoops } from '@vxrn/compiler'
8
+ import { hermesCompatSWCPlugin, hermesLoopsPlugin } from '../utils/createNativeDevEngine'
9
+
10
+ /**
11
+ * bundles selected, synchronous pure-function exports for normal worklets mode.
12
+ * install in native.bundlerOptions.plugins. the replacement module exposes only
13
+ * the configured exports; each entry owns its complete dependency graph.
14
+ */
15
+ export function workletImportsPlugin(imports: Record<string, readonly string[]>): Plugin {
16
+ const selections = new Map(
17
+ Object.entries(imports).map(([source, names]) => [source, [...new Set(names)]])
18
+ )
19
+
20
+ const rejectRuntimeDependency = (source: string) => {
21
+ const normalized = source.replace(/\\/g, '/')
22
+ if (
23
+ isBuiltin(source) ||
24
+ /(?:^|\/node_modules\/)(?:react|react-native|react-native-worklets|react-native-reanimated|expo-modules-core)(?:\/|$)/.test(
25
+ normalized
26
+ ) ||
27
+ /(?:^|\/node_modules\/)@react-native\//.test(normalized)
28
+ ) {
29
+ throw new Error(
30
+ `[worklet imports] runtime dependency ${source} is not pure JavaScript`
31
+ )
32
+ }
33
+ }
34
+
35
+ return {
36
+ name: 'vxrn:worklet-imports',
37
+ options(nativeOptions) {
38
+ // one config can share this plugin across simultaneous ios/android builds.
39
+ const modules = new Map<string, { entry: string; names: string[]; key: string }>()
40
+ const instance: Plugin = {
41
+ name: 'vxrn:worklet-imports:bundle',
42
+ async resolveId(source, importer, options) {
43
+ const names = selections.get(source)
44
+ if (!names) return
45
+ if (!names.length)
46
+ throw new Error(`[worklet imports] no exports selected for ${source}`)
47
+ if (options.kind !== 'import-statement') {
48
+ throw new Error(`[worklet imports] ${source} requires a static ESM import`)
49
+ }
50
+ rejectRuntimeDependency(source)
51
+ const resolved = await this.resolve(source, importer, { skipSelf: true })
52
+ if (
53
+ !resolved ||
54
+ resolved.external ||
55
+ !isAbsolute(resolved.id) ||
56
+ !statSync(resolved.id, { throwIfNoEntry: false })?.isFile()
57
+ ) {
58
+ throw new Error(
59
+ `[worklet imports] ${source} must resolve to a JavaScript file`
60
+ )
61
+ }
62
+ rejectRuntimeDependency(resolved.id)
63
+ const key = createHash('sha256')
64
+ .update(JSON.stringify([resolved.id, names]))
65
+ .digest('hex')
66
+ .slice(0, 16)
67
+ // a file-shaped id lets the existing native compiler process the directives.
68
+ const id = join(dirname(resolved.id), `__one_worklet_imports_${key}.js`)
69
+ modules.set(id, { entry: resolved.id, names, key })
70
+ return id
71
+ },
72
+ moduleParsed(info) {
73
+ if (
74
+ !info.code ||
75
+ ![...selections.keys()].some((source) => info.code!.includes(source))
76
+ )
77
+ return
78
+ const { program, errors } = parseSync(info.id, info.code, {
79
+ lang: /\.[cm]?ts$/.test(info.id)
80
+ ? 'ts'
81
+ : info.id.endsWith('.tsx')
82
+ ? 'tsx'
83
+ : 'jsx',
84
+ })
85
+ if (errors.length) throw new Error(errors[0].message)
86
+ for (const node of program.body) {
87
+ if (
88
+ node.type !== 'ImportDeclaration' &&
89
+ node.type !== 'ExportNamedDeclaration'
90
+ )
91
+ continue
92
+ const source = node.source?.value
93
+ if (!source) continue
94
+ const names = selections.get(source)
95
+ if (!names) continue
96
+ for (const specifier of node.specifiers) {
97
+ const imported =
98
+ specifier.type === 'ImportDefaultSpecifier'
99
+ ? 'default'
100
+ : specifier.type === 'ImportSpecifier'
101
+ ? specifier.imported.type === 'Identifier'
102
+ ? specifier.imported.name
103
+ : specifier.imported.value
104
+ : specifier.type === 'ExportSpecifier'
105
+ ? specifier.local.type === 'Identifier'
106
+ ? specifier.local.name
107
+ : specifier.local.value
108
+ : undefined
109
+ // native's shimMissingExports would otherwise silently emit undefined.
110
+ if (imported !== undefined && !names.includes(imported)) {
111
+ throw new Error(
112
+ `[worklet imports] ${info.id} imports unselected export ${imported} from ${source}`
113
+ )
114
+ }
115
+ }
116
+ }
117
+ },
118
+ async load(id) {
119
+ const selected = modules.get(id)
120
+ if (!selected) return
121
+ const outerContext = this
122
+ const innerEntry = '\0one-worklet-import-entry'
123
+ const bundle = await rolldown({
124
+ cwd: nativeOptions.cwd,
125
+ input: innerEntry,
126
+ platform: 'neutral',
127
+ resolve: nativeOptions.resolve,
128
+ transform: nativeOptions.transform,
129
+ shimMissingExports: false,
130
+ plugins: [
131
+ {
132
+ name: 'vxrn:pure-worklet-module',
133
+ resolveId(source, _importer, options) {
134
+ if (source === innerEntry) return source
135
+ if (options.kind === 'dynamic-import') {
136
+ throw new Error(
137
+ `[worklet imports] dynamic imports are unsupported in ${selected.entry}`
138
+ )
139
+ }
140
+ rejectRuntimeDependency(source)
141
+ },
142
+ load(source) {
143
+ if (source === innerEntry) {
144
+ return `export { ${selected.names.map((name, index) => `${JSON.stringify(name)} as __export${index}`).join(', ')} } from ${JSON.stringify(selected.entry)};`
145
+ }
146
+ },
147
+ moduleParsed(info) {
148
+ rejectRuntimeDependency(info.id)
149
+ if (isAbsolute(info.id)) outerContext.addWatchFile(info.id)
150
+ if (info.packageJsonPath)
151
+ outerContext.addWatchFile(info.packageJsonPath)
152
+ },
153
+ },
154
+ hermesCompatSWCPlugin(false),
155
+ hermesLoopsPlugin(),
156
+ ],
157
+ })
158
+ let code: string
159
+ try {
160
+ const output = await bundle.generate({
161
+ format: 'iife',
162
+ name: '__oneWorkletBundle',
163
+ })
164
+ const chunk = output.output[0]
165
+ if (
166
+ output.output.length !== 1 ||
167
+ chunk.type !== 'chunk' ||
168
+ chunk.imports.length ||
169
+ chunk.dynamicImports.length
170
+ ) {
171
+ throw new Error(
172
+ `[worklet imports] ${selected.entry} must produce one self-contained JavaScript module`
173
+ )
174
+ }
175
+ // also cover closure-bearing loops in helpers emitted by the bundler.
176
+ code = transformHermesLoops(chunk.code, id)?.code ?? chunk.code
177
+ for (const file of await bundle.watchFiles) {
178
+ if (isAbsolute(file)) outerContext.addWatchFile(file)
179
+ }
180
+ } finally {
181
+ await bundle.close()
182
+ }
183
+
184
+ const loader = `__oneLoadWorkletModule_${selected.key}`
185
+ const loaderCode = `function ${loader}() {
186
+ 'worklet';
187
+ const cache = globalThis.__oneWorkletImportCache || (globalThis.__oneWorkletImportCache = new WeakMap());
188
+ let namespace = cache.get(${loader});
189
+ if (namespace === undefined) {
190
+ ${code}
191
+ namespace = __oneWorkletBundle;
192
+ cache.set(${loader}, namespace);
193
+ }
194
+ return namespace;
195
+ }`
196
+ const parsed = parseSync(id, loaderCode, { lang: 'js' })
197
+ if (parsed.errors.length) throw new Error(parsed.errors[0].message)
198
+ const captures = getClosureVariables(
199
+ parsed.program.body[0],
200
+ new Set([...JS_GLOBALS, 'arguments'])
201
+ )
202
+ if (captures.length) {
203
+ throw new Error(
204
+ `[worklet imports] ${selected.entry} requires unsupported runtime globals: ${captures.join(', ')}`
205
+ )
206
+ }
207
+ return `${loaderCode}\n${selected.names
208
+ .map(
209
+ (name, index) => `
210
+ function __oneExport${index}(...args) {
211
+ 'worklet';
212
+ return (0, ${loader}().__export${index})(...args);
213
+ }
214
+ export { __oneExport${index} as ${JSON.stringify(name)} };
215
+ `
216
+ )
217
+ .join('\n')}`
218
+ },
219
+ }
220
+ return {
221
+ ...nativeOptions,
222
+ plugins: [instance, nativeOptions.plugins],
223
+ }
224
+ },
225
+ }
226
+ }