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.
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -1
- package/dist/index.native.js +2 -0
- package/dist/index.native.js.map +1 -1
- package/dist/patches/builtInDepPatches.mjs +21 -0
- package/dist/patches/builtInDepPatches.mjs.map +1 -1
- package/dist/patches/builtInDepPatches.native.js +21 -0
- package/dist/patches/builtInDepPatches.native.js.map +1 -1
- package/dist/plugins/reactNativeDevServer.mjs +5 -0
- package/dist/plugins/reactNativeDevServer.mjs.map +1 -1
- package/dist/plugins/reactNativeDevServer.native.js +5 -0
- package/dist/plugins/reactNativeDevServer.native.js.map +1 -1
- package/dist/plugins/rnCodegenPlugin.mjs +16 -0
- package/dist/plugins/rnCodegenPlugin.mjs.map +1 -0
- package/dist/plugins/rnCodegenPlugin.native.js +16 -0
- package/dist/plugins/rnCodegenPlugin.native.js.map +1 -0
- package/dist/plugins/rnCodegenPlugin.test.mjs +239 -0
- package/dist/plugins/rnCodegenPlugin.test.mjs.map +1 -0
- package/dist/plugins/rnCodegenPlugin.test.native.js +242 -0
- package/dist/plugins/rnCodegenPlugin.test.native.js.map +1 -0
- package/dist/plugins/workletImportsPlugin.mjs +160 -0
- package/dist/plugins/workletImportsPlugin.mjs.map +1 -0
- package/dist/plugins/workletImportsPlugin.native.js +230 -0
- package/dist/plugins/workletImportsPlugin.native.js.map +1 -0
- package/dist/utils/createNativeDevEngine.mjs +198 -92
- package/dist/utils/createNativeDevEngine.mjs.map +1 -1
- package/dist/utils/createNativeDevEngine.native.js +252 -99
- package/dist/utils/createNativeDevEngine.native.js.map +1 -1
- package/dist/utils/createNativeDevEngine.test.mjs +775 -4
- package/dist/utils/createNativeDevEngine.test.mjs.map +1 -1
- package/dist/utils/createNativeDevEngine.test.native.js +836 -4
- package/dist/utils/createNativeDevEngine.test.native.js.map +1 -1
- package/dist/utils/patches.mjs +12 -5
- package/dist/utils/patches.mjs.map +1 -1
- package/dist/utils/patches.native.js +13 -5
- package/dist/utils/patches.native.js.map +1 -1
- package/dist/utils/patches.test.mjs +28 -0
- package/dist/utils/patches.test.mjs.map +1 -1
- package/dist/utils/patches.test.native.js +47 -0
- package/dist/utils/patches.test.native.js.map +1 -1
- package/dist/utils/scanDepsToOptimize.mjs +5 -1
- package/dist/utils/scanDepsToOptimize.mjs.map +1 -1
- package/dist/utils/scanDepsToOptimize.native.js +5 -1
- package/dist/utils/scanDepsToOptimize.native.js.map +1 -1
- package/package.json +19 -12
- package/src/index.ts +2 -0
- package/src/patches/builtInDepPatches.ts +25 -0
- package/src/plugins/reactNativeDevServer.ts +12 -0
- package/src/plugins/rnCodegenPlugin.test.ts +313 -0
- package/src/plugins/rnCodegenPlugin.ts +20 -0
- package/src/plugins/workletImportsPlugin.ts +226 -0
- package/src/utils/createNativeDevEngine.test.ts +877 -3
- package/src/utils/createNativeDevEngine.ts +259 -107
- package/src/utils/patches.test.ts +33 -0
- package/src/utils/patches.ts +17 -6
- package/src/utils/scanDepsToOptimize.ts +9 -0
- package/types/index.d.ts +2 -0
- package/types/index.d.ts.map +1 -1
- package/types/patches/builtInDepPatches.d.ts.map +1 -1
- package/types/plugins/reactNativeDevServer.d.ts.map +1 -1
- package/types/plugins/rnCodegenPlugin.d.ts +6 -0
- package/types/plugins/rnCodegenPlugin.d.ts.map +1 -0
- package/types/plugins/rnCodegenPlugin.test.d.ts +2 -0
- package/types/plugins/rnCodegenPlugin.test.d.ts.map +1 -0
- package/types/plugins/workletImportsPlugin.d.ts +8 -0
- package/types/plugins/workletImportsPlugin.d.ts.map +1 -0
- package/types/utils/createNativeDevEngine.d.ts +13 -0
- package/types/utils/createNativeDevEngine.d.ts.map +1 -1
- package/types/utils/patches.d.ts.map +1 -1
- package/types/utils/scanDepsToOptimize.d.ts.map +1 -1
|
@@ -2,18 +2,234 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import { runInNewContext } from "node:vm";
|
|
5
|
+
import { createContext, runInContext, runInNewContext } from "node:vm";
|
|
6
6
|
import { rolldown } from "rolldown";
|
|
7
7
|
import { dev } from "rolldown/experimental";
|
|
8
|
-
import { describe, expect, it } from "vitest";
|
|
8
|
+
import { describe, expect, it, vi } from "vitest";
|
|
9
9
|
import { getNativePrelude } from "../runtime/native-prelude.mjs";
|
|
10
|
-
import {
|
|
10
|
+
import { workletImportsPlugin } from "../plugins/workletImportsPlugin.mjs";
|
|
11
|
+
import { buildNativeBundle, createNativeDevAssetRegistry, createNativeDevEngine, getHermesSWCIncludes, getHmrRuntimeSource, getNativeAssetData, getNativeTransformConfig, hermesCompatSWCPlugin, hmrClientNoopPlugin, nativeAnimatedGuardPlugin, normalizeNativeCommonJSInterop, vxrnCompilerPlugin, wrapNativeBundleModuleScope } from "./createNativeDevEngine.mjs";
|
|
11
12
|
const nativeTransformProbe = `
|
|
12
13
|
export const transformProbe = () => {
|
|
13
14
|
'worklet'
|
|
14
15
|
return 'transformed'
|
|
15
16
|
}
|
|
16
17
|
`;
|
|
18
|
+
describe.each(["rolldown-dev", "rolldown-build", "metro-module", "metro-script"])("native async through %s", pipeline => {
|
|
19
|
+
it("preserves defaults, lexical bindings, bigint, rejection, and iterator cleanup", async () => {
|
|
20
|
+
const id = "/project/native-async.js";
|
|
21
|
+
const source = `
|
|
22
|
+
globalThis.result = (function(parentArgument) {
|
|
23
|
+
const events = [];
|
|
24
|
+
const run = async(value = 2n, fail = false) => {
|
|
25
|
+
try {
|
|
26
|
+
const awaited = await (fail ? Promise.reject(new Error('rejected')) : Promise.resolve(3n));
|
|
27
|
+
events.push('after-await');
|
|
28
|
+
return [String(value + awaited), this.base, arguments[0]];
|
|
29
|
+
} catch (error) { return error.message; }
|
|
30
|
+
finally { events.push('finally'); }
|
|
31
|
+
};
|
|
32
|
+
const badDefault = async(value = (() => { throw new Error('default'); })()) => value;
|
|
33
|
+
let synchronousThrow = false;
|
|
34
|
+
let rejectedDefault;
|
|
35
|
+
try { rejectedDefault = badDefault().catch(error => error.message); }
|
|
36
|
+
catch { synchronousThrow = true; }
|
|
37
|
+
async function* values() {
|
|
38
|
+
try { yield 1n; yield 2n; }
|
|
39
|
+
finally { events.push('closed'); }
|
|
40
|
+
}
|
|
41
|
+
const first = async() => { for await (const value of values()) return String(value); };
|
|
42
|
+
return Promise.all([run(), run(4n), run(undefined, true), rejectedDefault, first()])
|
|
43
|
+
.then(values => ({ values, events, synchronousThrow }));
|
|
44
|
+
}).call({ base: 7 }, 'lexical');
|
|
45
|
+
`;
|
|
46
|
+
let output;
|
|
47
|
+
if (pipeline.startsWith("metro")) {
|
|
48
|
+
const {
|
|
49
|
+
transform
|
|
50
|
+
} = await import("@vxrn/vite-plugin-metro/metroNativeWorker");
|
|
51
|
+
const data = (await transform({}, "/project", id, Buffer.from(source), {
|
|
52
|
+
dev: false,
|
|
53
|
+
platform: "ios",
|
|
54
|
+
type: pipeline === "metro-script" ? "script" : "module"
|
|
55
|
+
})).output[0].data;
|
|
56
|
+
const {
|
|
57
|
+
fromRawMappings
|
|
58
|
+
} = await import("metro-source-map");
|
|
59
|
+
output = {
|
|
60
|
+
code: data.code,
|
|
61
|
+
map: fromRawMappings([{
|
|
62
|
+
code: data.code,
|
|
63
|
+
path: id,
|
|
64
|
+
source,
|
|
65
|
+
map: data.map
|
|
66
|
+
}]).toMap()
|
|
67
|
+
};
|
|
68
|
+
} else {
|
|
69
|
+
const plugin = hermesCompatSWCPlugin(pipeline === "rolldown-dev", true);
|
|
70
|
+
output = await Reflect.apply(plugin.transform, void 0, [source, id]);
|
|
71
|
+
}
|
|
72
|
+
const context = {};
|
|
73
|
+
context.__d = factory => {
|
|
74
|
+
const module = {
|
|
75
|
+
exports: {}
|
|
76
|
+
};
|
|
77
|
+
factory(context, name => {
|
|
78
|
+
throw new Error(`unexpected dependency ${name}`);
|
|
79
|
+
}, () => {}, () => {}, module, module.exports, []);
|
|
80
|
+
};
|
|
81
|
+
runInNewContext(output.code, context);
|
|
82
|
+
const result = await context.result;
|
|
83
|
+
expect(result.values).toEqual([["5", 7, "lexical"], ["7", 7, "lexical"], "rejected", "default", "1"]);
|
|
84
|
+
expect(result.synchronousThrow).toBe(false);
|
|
85
|
+
expect(result.events.filter(value => value === "finally")).toHaveLength(3);
|
|
86
|
+
expect(result.events.filter(value => value === "closed")).toHaveLength(1);
|
|
87
|
+
const {
|
|
88
|
+
originalPositionFor,
|
|
89
|
+
TraceMap
|
|
90
|
+
} = await import("@jridgewell/trace-mapping");
|
|
91
|
+
const lines = output.code.split("\n");
|
|
92
|
+
const line = lines.findIndex(value => value.includes("after-await"));
|
|
93
|
+
const position = originalPositionFor(new TraceMap(output.map), {
|
|
94
|
+
line: line + 1,
|
|
95
|
+
column: lines[line].indexOf("events.push")
|
|
96
|
+
});
|
|
97
|
+
expect(position.source).toBe(id);
|
|
98
|
+
expect(position.line).toBe(source.split("\n").findIndex(value => value.includes("after-await")) + 1);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
describe.each(["shared", "rolldown", "metro"])("React Compiler worklets through %s", pipeline => {
|
|
102
|
+
it.each([["block", "useDerivedValue(() => { return value + 8 }, [value])", 18], ["expression", "useDerivedValue(() => value + 8, [value])", 18], ["object", "useDerivedValue(() => ({ lift: value + 8 }), [value])", {
|
|
103
|
+
lift: 18
|
|
104
|
+
}], ["nested", "useDerivedValue(() => runOnUI(() => value + 8), [value])", 18], ["gesture", "useDerivedValue(() => Gesture.Pan().onUpdate(() => value + 8), [value])", 18]])("preserves %s callbacks on the UI runtime and memoizes stable inputs", async (_name, callback, expected) => {
|
|
105
|
+
const compiler = await import("@vxrn/compiler");
|
|
106
|
+
const {
|
|
107
|
+
parseSync
|
|
108
|
+
} = await import("oxc-parser");
|
|
109
|
+
const {
|
|
110
|
+
default: MagicString
|
|
111
|
+
} = await import("magic-string");
|
|
112
|
+
const {
|
|
113
|
+
TraceMap,
|
|
114
|
+
originalPositionFor
|
|
115
|
+
} = await import("@jridgewell/trace-mapping");
|
|
116
|
+
const projectRoot = await createWorkletsProject(false);
|
|
117
|
+
const sourceMaps = process.env.VXRN_ENABLE_SOURCE_MAP;
|
|
118
|
+
process.env.VXRN_ENABLE_SOURCE_MAP = "1";
|
|
119
|
+
compiler.configureVXRNCompilerPlugin({
|
|
120
|
+
enableCompiler: true,
|
|
121
|
+
enableReanimated: true,
|
|
122
|
+
enableNativeWorklets: true
|
|
123
|
+
});
|
|
124
|
+
try {
|
|
125
|
+
const id = join(projectRoot, "useProbe.ts");
|
|
126
|
+
const source = `export function useProbe(value) {
|
|
127
|
+
return ${callback};
|
|
128
|
+
}
|
|
129
|
+
`;
|
|
130
|
+
await writeFile(id, source);
|
|
131
|
+
let result;
|
|
132
|
+
if (pipeline === "shared") {
|
|
133
|
+
const plugin = (await compiler.createVXRNCompilerPlugin()).find(p => p.name === "one:compiler");
|
|
134
|
+
await plugin.configResolved({
|
|
135
|
+
root: projectRoot,
|
|
136
|
+
build: {}
|
|
137
|
+
});
|
|
138
|
+
result = await (plugin.transform.handler || plugin.transform).call({
|
|
139
|
+
environment: {
|
|
140
|
+
name: "ios"
|
|
141
|
+
}
|
|
142
|
+
}, source, id);
|
|
143
|
+
} else if (pipeline === "metro") {
|
|
144
|
+
const {
|
|
145
|
+
transform
|
|
146
|
+
} = await import("@vxrn/vite-plugin-metro/metroNativeWorker");
|
|
147
|
+
const data = (await transform({}, projectRoot, id, Buffer.from(source), {
|
|
148
|
+
dev: false,
|
|
149
|
+
platform: "ios",
|
|
150
|
+
type: "module",
|
|
151
|
+
customTransformOptions: {
|
|
152
|
+
reactCompiler: true,
|
|
153
|
+
worklets: true
|
|
154
|
+
}
|
|
155
|
+
})).output[0].data;
|
|
156
|
+
const msm = await import("metro-source-map");
|
|
157
|
+
result = {
|
|
158
|
+
code: data.code,
|
|
159
|
+
map: msm.fromRawMappings([{
|
|
160
|
+
code: data.code,
|
|
161
|
+
path: id,
|
|
162
|
+
source,
|
|
163
|
+
map: data.map
|
|
164
|
+
}]).toMap()
|
|
165
|
+
};
|
|
166
|
+
} else {
|
|
167
|
+
const plugin = vxrnCompilerPlugin("ios", false, projectRoot, true);
|
|
168
|
+
result = await Reflect.apply(plugin.transform, void 0, [source, id]);
|
|
169
|
+
}
|
|
170
|
+
const runnable = new MagicString(result.code);
|
|
171
|
+
for (const node of parseSync("probe.js", result.code).program.body) {
|
|
172
|
+
if (node.type === "ImportDeclaration") runnable.remove(node.start, node.end);
|
|
173
|
+
if (node.type === "ExportNamedDeclaration" && node.declaration) runnable.remove(node.start, node.declaration.start);
|
|
174
|
+
}
|
|
175
|
+
let cache;
|
|
176
|
+
let cacheCalls = 0;
|
|
177
|
+
const memo = size => {
|
|
178
|
+
cacheCalls++;
|
|
179
|
+
return cache ??= Array(size).fill(/* @__PURE__ */Symbol.for("react.memo_cache_sentinel"));
|
|
180
|
+
};
|
|
181
|
+
const module = {
|
|
182
|
+
exports: {}
|
|
183
|
+
};
|
|
184
|
+
const probe = new Function("_c", "useDerivedValue", "runOnUI", "Gesture", "__d", "module", `${runnable}
|
|
185
|
+
return ${pipeline === "metro" ? "module.exports.useProbe" : "useProbe"}`)(memo, fn => fn, fn => fn, {
|
|
186
|
+
Pan: () => ({
|
|
187
|
+
onUpdate: fn => fn
|
|
188
|
+
})
|
|
189
|
+
}, factory => factory(globalThis, () => ({
|
|
190
|
+
c: memo
|
|
191
|
+
}), () => {}, () => {}, module, module.exports, []), module);
|
|
192
|
+
const first = probe(10);
|
|
193
|
+
expect(first.__initData?.code).toBeTypeOf("string");
|
|
194
|
+
for (const onUI of [false, true]) {
|
|
195
|
+
let fn = first;
|
|
196
|
+
let value;
|
|
197
|
+
for (let depth = 0; depth < 2; depth++) {
|
|
198
|
+
expect(fn.__initData?.code).toBeTypeOf("string");
|
|
199
|
+
value = onUI ? new Function("runOnUI", `return (${fn.__initData.code})`)(fn2 => fn2).call({
|
|
200
|
+
__closure: fn.__closure
|
|
201
|
+
}) : fn();
|
|
202
|
+
if (typeof value !== "function") break;
|
|
203
|
+
fn = value;
|
|
204
|
+
}
|
|
205
|
+
expect(value).toEqual(expected);
|
|
206
|
+
}
|
|
207
|
+
expect(probe(10)).toBe(first);
|
|
208
|
+
expect(cacheCalls).toBe(2);
|
|
209
|
+
expect(probe(20)).not.toBe(first);
|
|
210
|
+
const lines = result.code.split("\n");
|
|
211
|
+
const line = lines.findIndex(text => text.includes("function useProbe("));
|
|
212
|
+
expect(line).toBeGreaterThanOrEqual(0);
|
|
213
|
+
const position = originalPositionFor(new TraceMap(result.map), {
|
|
214
|
+
line: line + 1,
|
|
215
|
+
column: lines[line].indexOf("function")
|
|
216
|
+
});
|
|
217
|
+
expect(position.source).toBe(id);
|
|
218
|
+
expect(position.line).toBe(1);
|
|
219
|
+
} finally {
|
|
220
|
+
if (sourceMaps === void 0) delete process.env.VXRN_ENABLE_SOURCE_MAP;else process.env.VXRN_ENABLE_SOURCE_MAP = sourceMaps;
|
|
221
|
+
compiler.configureVXRNCompilerPlugin({
|
|
222
|
+
enableCompiler: false,
|
|
223
|
+
enableReanimated: false,
|
|
224
|
+
enableNativeWorklets: false
|
|
225
|
+
});
|
|
226
|
+
await rm(projectRoot, {
|
|
227
|
+
recursive: true,
|
|
228
|
+
force: true
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
});
|
|
17
233
|
async function createWorkletsProject(throwOnTransform = true) {
|
|
18
234
|
const testRoot = await mkdtemp(join(tmpdir(), "vxrn-native-transform-failure-"));
|
|
19
235
|
const packageRoot = join(testRoot, "node_modules/react-native-worklets");
|
|
@@ -21,7 +237,8 @@ async function createWorkletsProject(throwOnTransform = true) {
|
|
|
21
237
|
recursive: true
|
|
22
238
|
});
|
|
23
239
|
await writeFile(join(packageRoot, "package.json"), JSON.stringify({
|
|
24
|
-
name: "react-native-worklets"
|
|
240
|
+
name: "react-native-worklets",
|
|
241
|
+
version: "0.10.1"
|
|
25
242
|
}));
|
|
26
243
|
await writeFile(join(packageRoot, "plugin.js"), throwOnTransform ? `module.exports = () => ({
|
|
27
244
|
visitor: {
|
|
@@ -59,7 +276,249 @@ describe("native prelude", () => {
|
|
|
59
276
|
expect(context.removeEventListener).toBe(removeEventListener);
|
|
60
277
|
});
|
|
61
278
|
});
|
|
279
|
+
describe("native pure-function worklet imports", () => {
|
|
280
|
+
it("keeps live module versions isolated across a transitive HMR update", async () => {
|
|
281
|
+
const root2 = await createWorkletsProject(false);
|
|
282
|
+
const packageRoot = join(root2, "node_modules/pure-math");
|
|
283
|
+
await mkdir(packageRoot, {
|
|
284
|
+
recursive: true
|
|
285
|
+
});
|
|
286
|
+
await writeFile(join(packageRoot, "package.json"), JSON.stringify({
|
|
287
|
+
type: "module",
|
|
288
|
+
exports: "./index.js"
|
|
289
|
+
}));
|
|
290
|
+
const dependency = join(packageRoot, "amount.ios.js");
|
|
291
|
+
await writeFile(dependency, "export const amount = 2");
|
|
292
|
+
await writeFile(join(packageRoot, "amount.android.js"), "export const amount = 99");
|
|
293
|
+
await writeFile(join(packageRoot, "index.js"), `
|
|
294
|
+
import { amount } from './amount'
|
|
295
|
+
const instance = globalThis.moduleInitializations = (globalThis.moduleInitializations || 0) + 1
|
|
296
|
+
export default function calculate(value) { return value + amount }
|
|
297
|
+
export function reads() { return instance }
|
|
298
|
+
`);
|
|
299
|
+
await writeFile(join(root2, "entry.mjs"), `
|
|
300
|
+
import calculate, { reads } from 'pure-math'
|
|
301
|
+
globalThis.calculate = calculate
|
|
302
|
+
globalThis.reads = reads
|
|
303
|
+
if (import.meta.hot) import.meta.hot.accept(() => {})
|
|
304
|
+
`);
|
|
305
|
+
const compiler = await import("@vxrn/compiler");
|
|
306
|
+
compiler.configureVXRNCompilerPlugin({
|
|
307
|
+
enableReanimated: true,
|
|
308
|
+
enableNativeWorklets: true
|
|
309
|
+
});
|
|
310
|
+
let resolveUpdate;
|
|
311
|
+
const updated = new Promise(resolve => {
|
|
312
|
+
resolveUpdate = resolve;
|
|
313
|
+
});
|
|
314
|
+
const native = await createNativeDevEngine({
|
|
315
|
+
root: root2,
|
|
316
|
+
port: 0,
|
|
317
|
+
platform: "ios",
|
|
318
|
+
plugins: [workletImportsPlugin({
|
|
319
|
+
"pure-math": ["default", "reads"]
|
|
320
|
+
}), {
|
|
321
|
+
name: "worklet-import-fixture",
|
|
322
|
+
transform(_code, id) {
|
|
323
|
+
if (id.endsWith("/__virtual-native-entry.tsx")) return `import './entry.mjs'`;
|
|
324
|
+
}
|
|
325
|
+
}],
|
|
326
|
+
onHmrUpdate: resolveUpdate
|
|
327
|
+
});
|
|
328
|
+
try {
|
|
329
|
+
const initial = await native.getBundle();
|
|
330
|
+
const rn = {
|
|
331
|
+
console,
|
|
332
|
+
setTimeout,
|
|
333
|
+
clearTimeout
|
|
334
|
+
};
|
|
335
|
+
runInNewContext(initial.code, rn);
|
|
336
|
+
expect(rn.calculate(10)).toBe(12);
|
|
337
|
+
expect(rn.reads()).toBe(1);
|
|
338
|
+
const ui = createContext({});
|
|
339
|
+
const compiled = /* @__PURE__ */new Map();
|
|
340
|
+
const reconstruct = worklet => {
|
|
341
|
+
if (!worklet.__initData || !worklet.__workletHash) throw new Error("remote function");
|
|
342
|
+
let fn = compiled.get(worklet.__workletHash);
|
|
343
|
+
if (!fn) {
|
|
344
|
+
fn = runInContext(`(${worklet.__initData.code})`, ui);
|
|
345
|
+
compiled.set(worklet.__workletHash, fn);
|
|
346
|
+
}
|
|
347
|
+
const closure = Object.fromEntries(Object.entries(worklet.__closure).map(([name, value]) => [name, typeof value === "function" ? reconstruct(value) : value]));
|
|
348
|
+
return fn.bind({
|
|
349
|
+
__closure: closure
|
|
350
|
+
});
|
|
351
|
+
};
|
|
352
|
+
expect(() => reconstruct(value => value + 2)).toThrow("remote function");
|
|
353
|
+
const old = reconstruct(rn.calculate);
|
|
354
|
+
const oldReads = reconstruct(rn.reads);
|
|
355
|
+
expect(old(10)).toBe(12);
|
|
356
|
+
expect(reconstruct(rn.calculate)(11)).toBe(13);
|
|
357
|
+
expect(oldReads()).toBe(1);
|
|
358
|
+
expect(rn.reads()).toBe(1);
|
|
359
|
+
const runtime = rn.__rolldown_runtime__;
|
|
360
|
+
await native.engine.registerClient(runtime.clientId);
|
|
361
|
+
await writeFile(dependency, "export const amount = 3");
|
|
362
|
+
const update = await updated;
|
|
363
|
+
expect(update.type).toBe("hmr:update");
|
|
364
|
+
expect(runtime.applyHmrUpdate(update.code, update.changedIds, update.seq)).toBe(true);
|
|
365
|
+
expect(rn.calculate(10)).toBe(13);
|
|
366
|
+
const next = reconstruct(rn.calculate);
|
|
367
|
+
expect([old(10), next(10), old(10), next(10)]).toEqual([12, 13, 12, 13]);
|
|
368
|
+
const nextReads = reconstruct(rn.reads);
|
|
369
|
+
expect([oldReads(), nextReads(), oldReads(), nextReads()]).toEqual([1, 2, 1, 2]);
|
|
370
|
+
expect(ui.moduleInitializations).toBe(2);
|
|
371
|
+
expect(rn.reads()).toBe(2);
|
|
372
|
+
await writeFile(join(root2, "production.mjs"), `import calculate, { reads } from 'pure-math'; globalThis.calculate = calculate; globalThis.reads = reads`);
|
|
373
|
+
const production = await buildNativeBundle({
|
|
374
|
+
root: root2,
|
|
375
|
+
platform: "android",
|
|
376
|
+
entryFile: "production.mjs",
|
|
377
|
+
plugins: [workletImportsPlugin({
|
|
378
|
+
"pure-math": ["default", "reads"]
|
|
379
|
+
})]
|
|
380
|
+
});
|
|
381
|
+
const productionRN = {
|
|
382
|
+
console
|
|
383
|
+
};
|
|
384
|
+
runInNewContext(production.code, productionRN);
|
|
385
|
+
expect(productionRN.calculate(10)).toBe(109);
|
|
386
|
+
expect(reconstruct(productionRN.calculate)(10)).toBe(109);
|
|
387
|
+
expect(reconstruct(productionRN.reads)()).toBe(3);
|
|
388
|
+
expect(productionRN.reads()).toBe(1);
|
|
389
|
+
const sharedPlugin = workletImportsPlugin({
|
|
390
|
+
"pure-math": ["default", "reads"]
|
|
391
|
+
});
|
|
392
|
+
expect(await Promise.all(["ios", "android"].map(async platform => {
|
|
393
|
+
const output = await buildNativeBundle({
|
|
394
|
+
root: root2,
|
|
395
|
+
platform,
|
|
396
|
+
entryFile: "production.mjs",
|
|
397
|
+
plugins: [sharedPlugin]
|
|
398
|
+
});
|
|
399
|
+
const context = {
|
|
400
|
+
console
|
|
401
|
+
};
|
|
402
|
+
runInNewContext(output.code, context);
|
|
403
|
+
return context.calculate(10);
|
|
404
|
+
}))).toEqual([13, 109]);
|
|
405
|
+
} finally {
|
|
406
|
+
await native.close();
|
|
407
|
+
compiler.configureVXRNCompilerPlugin({
|
|
408
|
+
enableReanimated: false,
|
|
409
|
+
enableNativeWorklets: false
|
|
410
|
+
});
|
|
411
|
+
await rm(root2, {
|
|
412
|
+
recursive: true,
|
|
413
|
+
force: true
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
it.each([["unselected", "export default function value() { return 1 }; export const other = 2", `import { other } from 'pure-math'; globalThis.value = other`, /unselected export other/], ["missing", "export const other = 2", `import value from 'pure-math'; globalThis.value = value`, /default.*not exported|MISSING_EXPORT/], ["builtin", `import fs from 'node:fs'; export default function value() { return fs.readFileSync('x') }`, `import value from 'pure-math'; globalThis.value = value`, /runtime dependency node:fs/], ["global", "export default function value() { return process.pid }", `import value from 'pure-math'; globalThis.value = value`, /unsupported runtime globals: process/], ["dynamic", `export default function value() { return import('./other.js') }`, `import value from 'pure-math'; globalThis.value = value`, /dynamic imports are unsupported/]])("rejects %s through the native engine", async (_name, source, entry, expected) => {
|
|
418
|
+
const root2 = await createWorkletsProject(false);
|
|
419
|
+
const packageRoot = join(root2, "node_modules/pure-math");
|
|
420
|
+
await mkdir(packageRoot, {
|
|
421
|
+
recursive: true
|
|
422
|
+
});
|
|
423
|
+
await writeFile(join(packageRoot, "package.json"), JSON.stringify({
|
|
424
|
+
type: "module",
|
|
425
|
+
exports: "./index.js"
|
|
426
|
+
}));
|
|
427
|
+
await writeFile(join(packageRoot, "index.js"), source);
|
|
428
|
+
await writeFile(join(root2, "entry.mjs"), entry);
|
|
429
|
+
const compiler = await import("@vxrn/compiler");
|
|
430
|
+
compiler.configureVXRNCompilerPlugin({
|
|
431
|
+
enableReanimated: true,
|
|
432
|
+
enableNativeWorklets: true
|
|
433
|
+
});
|
|
434
|
+
const native = await createNativeDevEngine({
|
|
435
|
+
root: root2,
|
|
436
|
+
port: 0,
|
|
437
|
+
platform: "ios",
|
|
438
|
+
plugins: [workletImportsPlugin({
|
|
439
|
+
"pure-math": ["default"]
|
|
440
|
+
}), {
|
|
441
|
+
name: "worklet-import-negative-fixture",
|
|
442
|
+
transform(_code, id) {
|
|
443
|
+
if (id.endsWith("/__virtual-native-entry.tsx")) return `import './entry.mjs'`;
|
|
444
|
+
}
|
|
445
|
+
}]
|
|
446
|
+
});
|
|
447
|
+
try {
|
|
448
|
+
await expect(native.getBundle()).rejects.toThrow(expected);
|
|
449
|
+
} finally {
|
|
450
|
+
await native.close();
|
|
451
|
+
compiler.configureVXRNCompilerPlugin({
|
|
452
|
+
enableReanimated: false,
|
|
453
|
+
enableNativeWorklets: false
|
|
454
|
+
});
|
|
455
|
+
await rm(root2, {
|
|
456
|
+
recursive: true,
|
|
457
|
+
force: true
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
});
|
|
62
462
|
describe("native Rolldown HMR runtime", () => {
|
|
463
|
+
it.each([true, false])("keeps CommonJS default components callable through the native HMR wire path (Babel=%s)", async babel => {
|
|
464
|
+
const root2 = await mkdtemp(join(tmpdir(), "vxrn-native-hmr-interop-"));
|
|
465
|
+
const entry = join(root2, "entry.mjs");
|
|
466
|
+
const packageRoot = join(root2, "node_modules/component");
|
|
467
|
+
await mkdir(packageRoot, {
|
|
468
|
+
recursive: true
|
|
469
|
+
});
|
|
470
|
+
await writeFile(join(packageRoot, "package.json"), JSON.stringify({
|
|
471
|
+
main: "./component(view).js"
|
|
472
|
+
}));
|
|
473
|
+
await writeFile(join(packageRoot, "component(view).js"), babel ? `Object.defineProperty(exports, '__esModule', { value: true }); exports.default = function Component() { return 42 }` : `module.exports = function Component() { return 42 }`);
|
|
474
|
+
const source = version => `
|
|
475
|
+
import Component from 'component'
|
|
476
|
+
globalThis.renderComponent = Component
|
|
477
|
+
export const version = ${version}
|
|
478
|
+
if (import.meta.hot) import.meta.hot.accept(() => {})
|
|
479
|
+
`;
|
|
480
|
+
await writeFile(entry, source(1));
|
|
481
|
+
let resolveUpdate;
|
|
482
|
+
const updateReceived = new Promise(resolve => {
|
|
483
|
+
resolveUpdate = resolve;
|
|
484
|
+
});
|
|
485
|
+
const native = await createNativeDevEngine({
|
|
486
|
+
root: root2,
|
|
487
|
+
port: 0,
|
|
488
|
+
platform: "ios",
|
|
489
|
+
plugins: [{
|
|
490
|
+
name: "native-hmr-fixture-entry",
|
|
491
|
+
transform(_code, id) {
|
|
492
|
+
if (id.endsWith("/__virtual-native-entry.tsx")) return `import './entry.mjs'`;
|
|
493
|
+
}
|
|
494
|
+
}],
|
|
495
|
+
onHmrUpdate: resolveUpdate
|
|
496
|
+
});
|
|
497
|
+
try {
|
|
498
|
+
const initial = await native.getBundle();
|
|
499
|
+
const context = {
|
|
500
|
+
console,
|
|
501
|
+
setTimeout,
|
|
502
|
+
clearTimeout
|
|
503
|
+
};
|
|
504
|
+
runInNewContext(initial.code, context);
|
|
505
|
+
expect(context.renderComponent()).toBe(42);
|
|
506
|
+
const runtime = context.__rolldown_runtime__;
|
|
507
|
+
await native.engine.registerClient(runtime.clientId);
|
|
508
|
+
await writeFile(entry, source(2));
|
|
509
|
+
const update = await updateReceived;
|
|
510
|
+
expect(update.type).toBe("hmr:update");
|
|
511
|
+
expect(update.clientId).toBe(runtime.clientId);
|
|
512
|
+
expect(runtime.applyHmrUpdate(update.code, update.changedIds, update.seq)).toBe(true);
|
|
513
|
+
expect(context.renderComponent()).toBe(42);
|
|
514
|
+
} finally {
|
|
515
|
+
await native.close();
|
|
516
|
+
await rm(root2, {
|
|
517
|
+
recursive: true,
|
|
518
|
+
force: true
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
});
|
|
63
522
|
it("registers a Rolldown 1.2 client and applies a self-accepted patch", {
|
|
64
523
|
timeout: 3e4
|
|
65
524
|
}, async () => {
|
|
@@ -347,6 +806,27 @@ describe("getHermesSWCIncludes", () => {
|
|
|
347
806
|
expect(result.code).not.toContain("async function*");
|
|
348
807
|
expect(result.code).not.toContain("async function *");
|
|
349
808
|
});
|
|
809
|
+
it("downlevels private fields, public class fields, and static blocks for Hermes", async () => {
|
|
810
|
+
const plugin = hermesCompatSWCPlugin(true);
|
|
811
|
+
if (typeof plugin.transform !== "function") throw new Error("Hermes compatibility transform hook is not callable");
|
|
812
|
+
const result = await Reflect.apply(plugin.transform, void 0, [`
|
|
813
|
+
class TransformProbe {
|
|
814
|
+
#privateField = 1
|
|
815
|
+
publicField = 2
|
|
816
|
+
static {
|
|
817
|
+
TransformProbe.initialized = true
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
getSecret() {
|
|
821
|
+
return this.#privateField
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
`, "/project/TransformProbe.ts"]);
|
|
825
|
+
expect(result.code).not.toContain("#privateField");
|
|
826
|
+
expect(result.code).not.toMatch(/^\s*publicField\s*=/m);
|
|
827
|
+
expect(result.code).not.toContain("static {");
|
|
828
|
+
expect(result.code).toContain("this.publicField = 2");
|
|
829
|
+
});
|
|
350
830
|
it("preserves per-iteration bindings used by lazy method getters", async () => {
|
|
351
831
|
const plugin = hermesCompatSWCPlugin(true);
|
|
352
832
|
if (typeof plugin.transform !== "function") throw new Error("Hermes compatibility transform hook is not callable");
|
|
@@ -457,6 +937,7 @@ describe("native required transform failures", () => {
|
|
|
457
937
|
it.each([true, false])("rejects valid worklet source when its required compiler transform fails (dev=%s)", async dev2 => {
|
|
458
938
|
const testRoot = await createWorkletsProject();
|
|
459
939
|
const compiler = await import("@vxrn/compiler");
|
|
940
|
+
const spy = vi.spyOn(compiler, "transformWorklets").mockRejectedValue(/* @__PURE__ */new Error("NATIVE_TRANSFORM_NEGATIVE_CONTROL"));
|
|
460
941
|
compiler.configureVXRNCompilerPlugin({
|
|
461
942
|
enableReanimated: true
|
|
462
943
|
});
|
|
@@ -465,6 +946,7 @@ describe("native required transform failures", () => {
|
|
|
465
946
|
if (typeof plugin.transform !== "function") throw new Error("vxrn compiler transform hook is not callable");
|
|
466
947
|
await expect(Reflect.apply(plugin.transform, void 0, [nativeTransformProbe, join(testRoot, "entry.ts")])).rejects.toThrow("NATIVE_TRANSFORM_NEGATIVE_CONTROL");
|
|
467
948
|
} finally {
|
|
949
|
+
spy.mockRestore();
|
|
468
950
|
compiler.configureVXRNCompilerPlugin({
|
|
469
951
|
enableReanimated: false
|
|
470
952
|
});
|
|
@@ -477,6 +959,7 @@ describe("native required transform failures", () => {
|
|
|
477
959
|
it("returns a dev build error and no output when the required compiler transform fails", async () => {
|
|
478
960
|
const testRoot = await createWorkletsProject();
|
|
479
961
|
const compiler = await import("@vxrn/compiler");
|
|
962
|
+
const spy = vi.spyOn(compiler, "transformWorklets").mockRejectedValue(/* @__PURE__ */new Error("NATIVE_TRANSFORM_NEGATIVE_CONTROL"));
|
|
480
963
|
compiler.configureVXRNCompilerPlugin({
|
|
481
964
|
enableReanimated: true
|
|
482
965
|
});
|
|
@@ -499,6 +982,7 @@ describe("native required transform failures", () => {
|
|
|
499
982
|
expect(result).toBeInstanceOf(Error);
|
|
500
983
|
expect(String(result)).toContain("NATIVE_TRANSFORM_NEGATIVE_CONTROL");
|
|
501
984
|
} finally {
|
|
985
|
+
spy.mockRestore();
|
|
502
986
|
await engine.close();
|
|
503
987
|
compiler.configureVXRNCompilerPlugin({
|
|
504
988
|
enableReanimated: false
|
|
@@ -512,6 +996,7 @@ describe("native required transform failures", () => {
|
|
|
512
996
|
it("emits no production bundle when the required compiler transform fails", async () => {
|
|
513
997
|
const testRoot = await createWorkletsProject();
|
|
514
998
|
const compiler = await import("@vxrn/compiler");
|
|
999
|
+
const spy = vi.spyOn(compiler, "transformWorklets").mockRejectedValue(/* @__PURE__ */new Error("NATIVE_TRANSFORM_NEGATIVE_CONTROL"));
|
|
515
1000
|
compiler.configureVXRNCompilerPlugin({
|
|
516
1001
|
enableReanimated: true
|
|
517
1002
|
});
|
|
@@ -522,6 +1007,7 @@ describe("native required transform failures", () => {
|
|
|
522
1007
|
entryFile: "entry.ts"
|
|
523
1008
|
})).rejects.toThrow("NATIVE_TRANSFORM_NEGATIVE_CONTROL");
|
|
524
1009
|
} finally {
|
|
1010
|
+
spy.mockRestore();
|
|
525
1011
|
compiler.configureVXRNCompilerPlugin({
|
|
526
1012
|
enableReanimated: false
|
|
527
1013
|
});
|
|
@@ -559,6 +1045,230 @@ describe("native required transform failures", () => {
|
|
|
559
1045
|
});
|
|
560
1046
|
}
|
|
561
1047
|
});
|
|
1048
|
+
it("composes source maps across React Compiler, Fast Refresh, and module wrapper back to original source lines", async () => {
|
|
1049
|
+
const {
|
|
1050
|
+
TraceMap,
|
|
1051
|
+
originalPositionFor
|
|
1052
|
+
} = await import("@jridgewell/trace-mapping");
|
|
1053
|
+
const compiler = await import("@vxrn/compiler");
|
|
1054
|
+
compiler.configureVXRNCompilerPlugin({
|
|
1055
|
+
enableCompiler: true
|
|
1056
|
+
});
|
|
1057
|
+
try {
|
|
1058
|
+
const plugin = vxrnCompilerPlugin("ios", true, "/project", true);
|
|
1059
|
+
if (typeof plugin.transform !== "function") throw new Error("transform hook not callable");
|
|
1060
|
+
const inputCode = `import { useState } from 'react'
|
|
1061
|
+
|
|
1062
|
+
export function Counter() {
|
|
1063
|
+
const [count, setCount] = useState(0)
|
|
1064
|
+
return <button onClick={() => setCount(count + 1)}>{count}</button>
|
|
1065
|
+
}
|
|
1066
|
+
`;
|
|
1067
|
+
const id = "/project/Counter.tsx";
|
|
1068
|
+
const result = await Reflect.apply(plugin.transform, void 0, [inputCode, id]);
|
|
1069
|
+
expect(result).toBeDefined();
|
|
1070
|
+
expect(result.map).toBeDefined();
|
|
1071
|
+
const tracer = new TraceMap(result.map);
|
|
1072
|
+
const counterLineIndex = result.code.split("\n").findIndex(l => l.includes("function Counter"));
|
|
1073
|
+
expect(counterLineIndex).toBeGreaterThan(0);
|
|
1074
|
+
const pos = originalPositionFor(tracer, {
|
|
1075
|
+
line: counterLineIndex + 1,
|
|
1076
|
+
column: 16
|
|
1077
|
+
});
|
|
1078
|
+
expect(pos.source).toBe(id);
|
|
1079
|
+
expect(pos.line).toBe(3);
|
|
1080
|
+
} finally {
|
|
1081
|
+
compiler.configureVXRNCompilerPlugin({
|
|
1082
|
+
enableCompiler: false
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
});
|
|
1086
|
+
it("composes source maps accurately across mixed path (React Compiler + Babel + Fast Refresh + module wrapper)", async () => {
|
|
1087
|
+
const {
|
|
1088
|
+
TraceMap,
|
|
1089
|
+
originalPositionFor
|
|
1090
|
+
} = await import("@jridgewell/trace-mapping");
|
|
1091
|
+
const compiler = await import("@vxrn/compiler");
|
|
1092
|
+
const origEnv = process.env.VXRN_USE_BABEL_FOR_GENERATORS;
|
|
1093
|
+
process.env.VXRN_USE_BABEL_FOR_GENERATORS = "1";
|
|
1094
|
+
compiler.configureVXRNCompilerPlugin({
|
|
1095
|
+
enableCompiler: true,
|
|
1096
|
+
enableReanimated: false
|
|
1097
|
+
});
|
|
1098
|
+
try {
|
|
1099
|
+
const plugin = vxrnCompilerPlugin("ios", true, "/project", true);
|
|
1100
|
+
if (typeof plugin.transform !== "function") throw new Error("transform hook not callable");
|
|
1101
|
+
const inputCode = `export function Counter() { return <View />; }
|
|
1102
|
+
|
|
1103
|
+
|
|
1104
|
+
|
|
1105
|
+
|
|
1106
|
+
|
|
1107
|
+
|
|
1108
|
+
|
|
1109
|
+
export async function* probe() { throw new Error("MARKER"); }
|
|
1110
|
+
`;
|
|
1111
|
+
const id = "/project/Counter.tsx";
|
|
1112
|
+
const result = await Reflect.apply(plugin.transform, void 0, [inputCode, id]);
|
|
1113
|
+
expect(result).toBeDefined();
|
|
1114
|
+
expect(result.map).toBeDefined();
|
|
1115
|
+
expect(result.code.includes("_wrapAsyncGenerator") || result.code.includes("regeneratorRuntime")).toBe(true);
|
|
1116
|
+
const tracer = new TraceMap(result.map);
|
|
1117
|
+
const lines = result.code.split("\n");
|
|
1118
|
+
const markerLineIndex = lines.findIndex(l => l.includes("MARKER"));
|
|
1119
|
+
expect(markerLineIndex).toBeGreaterThan(0);
|
|
1120
|
+
const markerCol = lines[markerLineIndex].indexOf("throw");
|
|
1121
|
+
const pos = originalPositionFor(tracer, {
|
|
1122
|
+
line: markerLineIndex + 1,
|
|
1123
|
+
column: markerCol
|
|
1124
|
+
});
|
|
1125
|
+
expect(pos.source).toBe(id);
|
|
1126
|
+
expect(pos.line).toBe(9);
|
|
1127
|
+
expect(pos.column).toBe(33);
|
|
1128
|
+
const compilerOut = await compiler.transformOxcReactCompiler(id, inputCode, "19", true);
|
|
1129
|
+
const remapping = (await import("@jridgewell/remapping")).default;
|
|
1130
|
+
const badPos = originalPositionFor(new TraceMap(remapping([result.map, compilerOut.map], () => null)), {
|
|
1131
|
+
line: markerLineIndex + 1,
|
|
1132
|
+
column: markerCol
|
|
1133
|
+
});
|
|
1134
|
+
expect(badPos.source).toBeNull();
|
|
1135
|
+
expect(badPos.line).toBeNull();
|
|
1136
|
+
} finally {
|
|
1137
|
+
compiler.configureVXRNCompilerPlugin({
|
|
1138
|
+
enableCompiler: false
|
|
1139
|
+
});
|
|
1140
|
+
if (origEnv !== void 0) process.env.VXRN_USE_BABEL_FOR_GENERATORS = origEnv;else delete process.env.VXRN_USE_BABEL_FOR_GENERATORS;
|
|
1141
|
+
}
|
|
1142
|
+
});
|
|
1143
|
+
it("transforms worklets via native Rust/Wasm SWC with zero Babel execution and accurate sourcemaps", async () => {
|
|
1144
|
+
const {
|
|
1145
|
+
TraceMap,
|
|
1146
|
+
originalPositionFor
|
|
1147
|
+
} = await import("@jridgewell/trace-mapping");
|
|
1148
|
+
const compiler = await import("@vxrn/compiler");
|
|
1149
|
+
const {
|
|
1150
|
+
vi: vi2
|
|
1151
|
+
} = await import("vitest");
|
|
1152
|
+
const babelSpy = vi2.spyOn(compiler, "transformBabel");
|
|
1153
|
+
compiler.configureVXRNCompilerPlugin({
|
|
1154
|
+
enableReanimated: true,
|
|
1155
|
+
enableNativeWorklets: true,
|
|
1156
|
+
enableCompiler: false
|
|
1157
|
+
});
|
|
1158
|
+
try {
|
|
1159
|
+
const projectRoot = process.cwd();
|
|
1160
|
+
const plugin = vxrnCompilerPlugin("ios", true, projectRoot, true);
|
|
1161
|
+
if (typeof plugin.transform !== "function") throw new Error("transform hook not callable");
|
|
1162
|
+
const inputCode = `import { useAnimatedStyle } from 'react-native-reanimated'
|
|
1163
|
+
|
|
1164
|
+
export function Box() {
|
|
1165
|
+
const animatedStyle = useAnimatedStyle(() => {
|
|
1166
|
+
return { opacity: 1 }
|
|
1167
|
+
})
|
|
1168
|
+
return <div style={animatedStyle} />
|
|
1169
|
+
}
|
|
1170
|
+
`;
|
|
1171
|
+
const id = join(projectRoot, "Box.tsx");
|
|
1172
|
+
const result = await Reflect.apply(plugin.transform, void 0, [inputCode, id]);
|
|
1173
|
+
expect(result).toBeDefined();
|
|
1174
|
+
expect(result.code).toContain("__workletHash");
|
|
1175
|
+
expect(result.code).toContain("__closure");
|
|
1176
|
+
expect(babelSpy).not.toHaveBeenCalled();
|
|
1177
|
+
expect(result.map).toBeDefined();
|
|
1178
|
+
const tracer = new TraceMap(result.map);
|
|
1179
|
+
const boxLineIndex = result.code.split("\n").findIndex(l => l.includes("export function Box"));
|
|
1180
|
+
expect(boxLineIndex).toBeGreaterThan(0);
|
|
1181
|
+
const pos = originalPositionFor(tracer, {
|
|
1182
|
+
line: boxLineIndex + 1,
|
|
1183
|
+
column: 16
|
|
1184
|
+
});
|
|
1185
|
+
expect(pos.source).toBe(id);
|
|
1186
|
+
expect(pos.line).toBe(3);
|
|
1187
|
+
} finally {
|
|
1188
|
+
babelSpy.mockRestore();
|
|
1189
|
+
compiler.configureVXRNCompilerPlugin({
|
|
1190
|
+
enableReanimated: false,
|
|
1191
|
+
enableNativeWorklets: false
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
});
|
|
1195
|
+
it("retains the existing Babel backend by default when enableNativeWorklets is false for auto-detected Reanimated and ordinary configured worklets plugin", async () => {
|
|
1196
|
+
const compiler = await import("@vxrn/compiler");
|
|
1197
|
+
const {
|
|
1198
|
+
vi: vi2
|
|
1199
|
+
} = await import("vitest");
|
|
1200
|
+
const babelSpy = vi2.spyOn(compiler, "transformBabel").mockResolvedValue({
|
|
1201
|
+
code: "/* babel transformed */"
|
|
1202
|
+
});
|
|
1203
|
+
const workletSpy = vi2.spyOn(compiler, "transformWorklets");
|
|
1204
|
+
compiler.configureVXRNCompilerPlugin({
|
|
1205
|
+
enableReanimated: true,
|
|
1206
|
+
enableNativeWorklets: false
|
|
1207
|
+
});
|
|
1208
|
+
const getBabelOptionsSpy = vi2.spyOn(compiler, "getBabelOptions").mockReturnValue({
|
|
1209
|
+
plugins: ["react-native-reanimated/plugin"]
|
|
1210
|
+
});
|
|
1211
|
+
try {
|
|
1212
|
+
const projectRoot = process.cwd();
|
|
1213
|
+
const plugin = vxrnCompilerPlugin("ios", true, projectRoot, false);
|
|
1214
|
+
if (typeof plugin.transform !== "function") throw new Error("transform hook not callable");
|
|
1215
|
+
const inputCode = `export function fn() { 'worklet'; return 1 }`;
|
|
1216
|
+
const id = join(projectRoot, "DefaultWorklet.tsx");
|
|
1217
|
+
expect(await Reflect.apply(plugin.transform, void 0, [inputCode, id])).toBeDefined();
|
|
1218
|
+
expect(babelSpy).toHaveBeenCalledWith(id, inputCode, expect.objectContaining({
|
|
1219
|
+
plugins: expect.arrayContaining(["react-native-reanimated/plugin"])
|
|
1220
|
+
}));
|
|
1221
|
+
expect(workletSpy).not.toHaveBeenCalled();
|
|
1222
|
+
} finally {
|
|
1223
|
+
babelSpy.mockRestore();
|
|
1224
|
+
workletSpy.mockRestore();
|
|
1225
|
+
getBabelOptionsSpy.mockRestore();
|
|
1226
|
+
compiler.configureVXRNCompilerPlugin({
|
|
1227
|
+
enableReanimated: false,
|
|
1228
|
+
enableNativeWorklets: false
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
});
|
|
1232
|
+
it("intercepts tuple worklet plugins with options, executes native SWC with options, and skips Babel when enableNativeWorklets is true", async () => {
|
|
1233
|
+
const compiler = await import("@vxrn/compiler");
|
|
1234
|
+
const {
|
|
1235
|
+
vi: vi2
|
|
1236
|
+
} = await import("vitest");
|
|
1237
|
+
const babelSpy = vi2.spyOn(compiler, "transformBabel");
|
|
1238
|
+
const workletSpy = vi2.spyOn(compiler, "transformWorklets");
|
|
1239
|
+
compiler.configureVXRNCompilerPlugin({
|
|
1240
|
+
enableReanimated: true,
|
|
1241
|
+
enableNativeWorklets: true
|
|
1242
|
+
});
|
|
1243
|
+
const getBabelOptionsSpy = vi2.spyOn(compiler, "getBabelOptions").mockReturnValue({
|
|
1244
|
+
plugins: [["react-native-reanimated/plugin", {
|
|
1245
|
+
globals: ["customGlobal"]
|
|
1246
|
+
}]]
|
|
1247
|
+
});
|
|
1248
|
+
try {
|
|
1249
|
+
const projectRoot = process.cwd();
|
|
1250
|
+
const plugin = vxrnCompilerPlugin("ios", true, projectRoot, false);
|
|
1251
|
+
if (typeof plugin.transform !== "function") throw new Error("transform hook not callable");
|
|
1252
|
+
const inputCode = `export function fn() { 'worklet'; return customGlobal * 2 }`;
|
|
1253
|
+
const id = join(projectRoot, "TupleWorklet.tsx");
|
|
1254
|
+
const result = await Reflect.apply(plugin.transform, void 0, [inputCode, id]);
|
|
1255
|
+
expect(result).toBeDefined();
|
|
1256
|
+
expect(result.code).toContain("__workletHash");
|
|
1257
|
+
expect(workletSpy).toHaveBeenCalledWith(id, expect.any(String), false, expect.objectContaining({
|
|
1258
|
+
globals: ["customGlobal"],
|
|
1259
|
+
projectRoot
|
|
1260
|
+
}));
|
|
1261
|
+
expect(babelSpy).not.toHaveBeenCalled();
|
|
1262
|
+
} finally {
|
|
1263
|
+
babelSpy.mockRestore();
|
|
1264
|
+
workletSpy.mockRestore();
|
|
1265
|
+
getBabelOptionsSpy.mockRestore();
|
|
1266
|
+
compiler.configureVXRNCompilerPlugin({
|
|
1267
|
+
enableReanimated: false,
|
|
1268
|
+
enableNativeWorklets: false
|
|
1269
|
+
});
|
|
1270
|
+
}
|
|
1271
|
+
});
|
|
562
1272
|
});
|
|
563
1273
|
describe("native production assets", () => {
|
|
564
1274
|
it("registers scale siblings and keeps monorepo assets inside assetsDest", async () => {
|
|
@@ -752,4 +1462,65 @@ globalThis.__vxrnConditionalExportProbe = helper()
|
|
|
752
1462
|
}
|
|
753
1463
|
});
|
|
754
1464
|
});
|
|
1465
|
+
describe("native Flow sources", () => {
|
|
1466
|
+
it.each(["", `/* ${"license text ".repeat(150)} */
|
|
1467
|
+
`])("strips third-party Flow following a license header (%#)", async license => {
|
|
1468
|
+
const testRoot = await mkdtemp(join(tmpdir(), "vxrn-native-flow-"));
|
|
1469
|
+
const packageRoot = join(testRoot, "node_modules/@flowy-scope/flowy-lib");
|
|
1470
|
+
await mkdir(packageRoot, {
|
|
1471
|
+
recursive: true
|
|
1472
|
+
});
|
|
1473
|
+
await writeFile(join(packageRoot, "package.json"), JSON.stringify({
|
|
1474
|
+
name: "@flowy-scope/flowy-lib",
|
|
1475
|
+
main: "./js/Flowy.js"
|
|
1476
|
+
}));
|
|
1477
|
+
await mkdir(join(packageRoot, "js"), {
|
|
1478
|
+
recursive: true
|
|
1479
|
+
});
|
|
1480
|
+
await writeFile(join(packageRoot, "js/Flowy.js"), `${license}/**
|
|
1481
|
+
* @flow
|
|
1482
|
+
* @format
|
|
1483
|
+
*/
|
|
1484
|
+
|
|
1485
|
+
type FlowyProps = {|
|
|
1486
|
+
+value: number,
|
|
1487
|
+
|}
|
|
1488
|
+
|
|
1489
|
+
export default class Flowy {
|
|
1490
|
+
_seen: boolean = false
|
|
1491
|
+
|
|
1492
|
+
measure(props: FlowyProps): number {
|
|
1493
|
+
const { value, ...rest }: FlowyProps = props
|
|
1494
|
+
this._seen = true
|
|
1495
|
+
return ((value: any): number) + Object.keys(rest).length
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
`);
|
|
1499
|
+
await writeFile(join(testRoot, "entry.js"), `import Flowy from '@flowy-scope/flowy-lib'
|
|
1500
|
+
globalThis.__vxrnFlowProbe = new Flowy().measure({ value: 41 }) + 1
|
|
1501
|
+
`);
|
|
1502
|
+
try {
|
|
1503
|
+
const result = await buildNativeBundle({
|
|
1504
|
+
root: testRoot,
|
|
1505
|
+
platform: "ios",
|
|
1506
|
+
entryFile: "entry.js"
|
|
1507
|
+
});
|
|
1508
|
+
const context = {
|
|
1509
|
+
clearTimeout,
|
|
1510
|
+
console,
|
|
1511
|
+
process: {
|
|
1512
|
+
env: {}
|
|
1513
|
+
},
|
|
1514
|
+
setTimeout
|
|
1515
|
+
};
|
|
1516
|
+
runInNewContext(result.code, context);
|
|
1517
|
+
expect(Reflect.get(context, "__vxrnFlowProbe")).toBe(42);
|
|
1518
|
+
} finally {
|
|
1519
|
+
await rm(testRoot, {
|
|
1520
|
+
recursive: true,
|
|
1521
|
+
force: true
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
});
|
|
1525
|
+
});
|
|
755
1526
|
//# sourceMappingURL=createNativeDevEngine.test.mjs.map
|