vxrn 1.25.8 → 1.25.9

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 (45) hide show
  1. package/dist/config/getViteServerConfig.mjs +6 -0
  2. package/dist/config/getViteServerConfig.mjs.map +1 -1
  3. package/dist/config/getViteServerConfig.native.js +6 -0
  4. package/dist/config/getViteServerConfig.native.js.map +1 -1
  5. package/dist/config/getViteServerConfig.test.mjs +26 -0
  6. package/dist/config/getViteServerConfig.test.mjs.map +1 -0
  7. package/dist/config/getViteServerConfig.test.native.js +26 -0
  8. package/dist/config/getViteServerConfig.test.native.js.map +1 -0
  9. package/dist/exports/dev.mjs +26 -1
  10. package/dist/exports/dev.mjs.map +1 -1
  11. package/dist/exports/dev.native.js +26 -1
  12. package/dist/exports/dev.native.js.map +1 -1
  13. package/dist/plugins/reactNativeDevServer.mjs +80 -7
  14. package/dist/plugins/reactNativeDevServer.mjs.map +1 -1
  15. package/dist/plugins/reactNativeDevServer.native.js +80 -7
  16. package/dist/plugins/reactNativeDevServer.native.js.map +1 -1
  17. package/dist/runtime/native-prelude.mjs +7 -0
  18. package/dist/runtime/native-prelude.mjs.map +1 -1
  19. package/dist/runtime/native-prelude.native.js +7 -0
  20. package/dist/runtime/native-prelude.native.js.map +1 -1
  21. package/dist/utils/createNativeDevEngine.mjs +302 -197
  22. package/dist/utils/createNativeDevEngine.mjs.map +1 -1
  23. package/dist/utils/createNativeDevEngine.native.js +410 -221
  24. package/dist/utils/createNativeDevEngine.native.js.map +1 -1
  25. package/dist/utils/createNativeDevEngine.test.mjs +536 -6
  26. package/dist/utils/createNativeDevEngine.test.mjs.map +1 -1
  27. package/dist/utils/createNativeDevEngine.test.native.js +559 -6
  28. package/dist/utils/createNativeDevEngine.test.native.js.map +1 -1
  29. package/package.json +11 -11
  30. package/src/config/getViteServerConfig.test.ts +29 -0
  31. package/src/config/getViteServerConfig.ts +11 -0
  32. package/src/exports/dev.ts +34 -3
  33. package/src/plugins/reactNativeDevServer.ts +89 -12
  34. package/src/runtime/native-prelude.ts +7 -0
  35. package/src/utils/createNativeDevEngine.test.ts +682 -5
  36. package/src/utils/createNativeDevEngine.ts +516 -229
  37. package/types/config/getViteServerConfig.d.ts.map +1 -1
  38. package/types/config/getViteServerConfig.test.d.ts +2 -0
  39. package/types/config/getViteServerConfig.test.d.ts.map +1 -0
  40. package/types/exports/dev.d.ts.map +1 -1
  41. package/types/plugins/reactNativeDevServer.d.ts +1 -0
  42. package/types/plugins/reactNativeDevServer.d.ts.map +1 -1
  43. package/types/runtime/native-prelude.d.ts.map +1 -1
  44. package/types/utils/createNativeDevEngine.d.ts +40 -2
  45. package/types/utils/createNativeDevEngine.d.ts.map +1 -1
@@ -1,10 +1,64 @@
1
- import { mkdtemp, rm, writeFile } from "node:fs/promises";
1
+ import { existsSync } from "node:fs";
2
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
2
3
  import { tmpdir } from "node:os";
3
4
  import { join } from "node:path";
5
+ import { runInNewContext } from "node:vm";
4
6
  import { rolldown } from "rolldown";
5
7
  import { dev } from "rolldown/experimental";
6
8
  import { describe, expect, it } from "vitest";
7
- import { getHermesSWCIncludes, getHmrRuntimeSource, getNativeTransformConfig, hermesCompatSWCPlugin, hmrClientNoopPlugin, vxrnCompilerPlugin, wrapNativeBundleModuleScope } from "./createNativeDevEngine.mjs";
9
+ import { getNativePrelude } from "../runtime/native-prelude.mjs";
10
+ import { buildNativeBundle, createNativeDevAssetRegistry, getHermesSWCIncludes, getHmrRuntimeSource, getNativeAssetData, getNativeTransformConfig, hermesCompatSWCPlugin, hmrClientNoopPlugin, nativeAnimatedGuardPlugin, normalizeNativeCommonJSInterop, vxrnCompilerPlugin, wrapNativeBundleModuleScope } from "./createNativeDevEngine.mjs";
11
+ const nativeTransformProbe = `
12
+ export const transformProbe = () => {
13
+ 'worklet'
14
+ return 'transformed'
15
+ }
16
+ `;
17
+ async function createWorkletsProject(throwOnTransform = true) {
18
+ const testRoot = await mkdtemp(join(tmpdir(), "vxrn-native-transform-failure-"));
19
+ const packageRoot = join(testRoot, "node_modules/react-native-worklets");
20
+ await mkdir(packageRoot, {
21
+ recursive: true
22
+ });
23
+ await writeFile(join(packageRoot, "package.json"), JSON.stringify({
24
+ name: "react-native-worklets"
25
+ }));
26
+ await writeFile(join(packageRoot, "plugin.js"), throwOnTransform ? `module.exports = () => ({
27
+ visitor: {
28
+ Program() {
29
+ throw new Error('NATIVE_TRANSFORM_NEGATIVE_CONTROL')
30
+ }
31
+ }
32
+ })` : `module.exports = () => ({ visitor: {} })`);
33
+ await writeFile(join(testRoot, "entry.ts"), nativeTransformProbe);
34
+ return testRoot;
35
+ }
36
+ describe("native prelude", () => {
37
+ it("does not advertise a host event API that cannot remove listeners", () => {
38
+ const context = {
39
+ addEventListener() {}
40
+ };
41
+ runInNewContext(getNativePrelude({
42
+ dev: false,
43
+ platform: "ios"
44
+ }), context);
45
+ expect(Reflect.get(context, "addEventListener")).toBeUndefined();
46
+ });
47
+ it("preserves a complete host event API", () => {
48
+ const addEventListener = () => {};
49
+ const removeEventListener = () => {};
50
+ const context = {
51
+ addEventListener,
52
+ removeEventListener
53
+ };
54
+ runInNewContext(getNativePrelude({
55
+ dev: false,
56
+ platform: "ios"
57
+ }), context);
58
+ expect(context.addEventListener).toBe(addEventListener);
59
+ expect(context.removeEventListener).toBe(removeEventListener);
60
+ });
61
+ });
8
62
  describe("native Rolldown HMR runtime", () => {
9
63
  it("registers a Rolldown 1.2 client and applies a self-accepted patch", {
10
64
  timeout: 3e4
@@ -63,7 +117,8 @@ if (import.meta.hot) {
63
117
  await writeFile(entry, source("v2"));
64
118
  const patch = (await Promise.race([hmrUpdate, new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */new Error("timed out waiting for HMR patch")), 1e4))])).updates.find(item => item.clientId === runtime.clientId && item.update.type === "Patch")?.update;
65
119
  expect(patch).toBeTruthy();
66
- expect(runtime.applyHmrUpdate(patch.code, patch.changedIds, patch.seq)).toBe(true);
120
+ const applyHmrUpdate = Reflect.get(runtime, "applyHmrUpdate");
121
+ expect(Reflect.apply(applyHmrUpdate, runtime, [patch.code, patch.changedIds, patch.seq])).toBe(true);
67
122
  expect(globalThis.__vxrnHmrBody).toBe("v2");
68
123
  expect(globalThis.__vxrnHmrAccepted).toBe("v2");
69
124
  } finally {
@@ -77,6 +132,130 @@ if (import.meta.hot) {
77
132
  delete globalThis.__vxrnHmrAccepted;
78
133
  }
79
134
  });
135
+ it("propagates a non-component update to a React Refresh boundary", {
136
+ timeout: 3e4
137
+ }, async () => {
138
+ const testRoot = await mkdtemp(join(tmpdir(), "vxrn-native-refresh-boundary-"));
139
+ const entry = join(testRoot, "entry.js");
140
+ const leaf = join(testRoot, "leaf.js");
141
+ await writeFile(entry, `
142
+ import { value } from './leaf.js'
143
+ globalThis.__vxrnRefreshBoundaryValue = value
144
+ export function App() {}
145
+ if (import.meta.hot) {
146
+ import.meta.hot.acceptReactRefresh(() => {
147
+ globalThis.__vxrnRefreshBoundaryAccepted = true
148
+ })
149
+ }
150
+ `);
151
+ await writeFile(leaf, `export const value = 'v1'`);
152
+ let resolveInitialOutput;
153
+ const initialOutput = new Promise(resolve => {
154
+ resolveInitialOutput = resolve;
155
+ });
156
+ let resolveHmrUpdate;
157
+ const hmrUpdate = new Promise(resolve => {
158
+ resolveHmrUpdate = resolve;
159
+ });
160
+ let registeredClientId;
161
+ const engine = await dev({
162
+ cwd: testRoot,
163
+ input: entry,
164
+ experimental: {
165
+ devMode: {
166
+ implement: getHmrRuntimeSource()
167
+ }
168
+ }
169
+ }, {
170
+ format: "esm"
171
+ }, {
172
+ onOutput(result) {
173
+ resolveInitialOutput(result);
174
+ },
175
+ onHmrUpdates(result) {
176
+ if (!(result instanceof Error) && result.updates.some(item => item.clientId === registeredClientId && item.update.type === "Patch")) resolveHmrUpdate(result);
177
+ }
178
+ });
179
+ const previousRefreshRuntime = Reflect.get(globalThis, "__ReactRefresh");
180
+ Reflect.set(globalThis, "__ReactRefresh", {
181
+ isLikelyComponentType(value) {
182
+ return typeof value === "function";
183
+ }
184
+ });
185
+ try {
186
+ await engine.run();
187
+ const initial = await initialOutput;
188
+ if (initial instanceof Error) throw initial;
189
+ const chunk = initial.output.find(item => item.type === "chunk" && item.isEntry);
190
+ expect(chunk).toBeTruthy();
191
+ Reflect.deleteProperty(globalThis, "__rolldown_runtime__");
192
+ await import(`data:text/javascript;base64,${Buffer.from(chunk.code).toString("base64")}`);
193
+ const runtime = Reflect.get(globalThis, "__rolldown_runtime__");
194
+ registeredClientId = runtime.clientId;
195
+ await engine.registerClient(runtime.clientId);
196
+ await writeFile(leaf, `export const value = 'v2'`);
197
+ const patch = (await Promise.race([hmrUpdate, new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */new Error("timed out waiting for HMR patch")), 1e4))])).updates.find(item => item.clientId === runtime.clientId && item.update.type === "Patch")?.update;
198
+ expect(patch).toBeTruthy();
199
+ const applyHmrUpdate = Reflect.get(runtime, "applyHmrUpdate");
200
+ expect(Reflect.apply(applyHmrUpdate, runtime, [patch.code, patch.changedIds, patch.seq])).toBe(true);
201
+ expect(Reflect.get(globalThis, "__vxrnRefreshBoundaryValue")).toBe("v2");
202
+ expect(Reflect.get(globalThis, "__vxrnRefreshBoundaryAccepted")).toBe(true);
203
+ } finally {
204
+ await engine.close();
205
+ await rm(testRoot, {
206
+ recursive: true,
207
+ force: true
208
+ });
209
+ Reflect.deleteProperty(globalThis, "__rolldown_runtime__");
210
+ Reflect.deleteProperty(globalThis, "__vxrnRefreshBoundaryValue");
211
+ Reflect.deleteProperty(globalThis, "__vxrnRefreshBoundaryAccepted");
212
+ if (previousRefreshRuntime === void 0) Reflect.deleteProperty(globalThis, "__ReactRefresh");else Reflect.set(globalThis, "__ReactRefresh", previousRefreshRuntime);
213
+ }
214
+ });
215
+ });
216
+ describe("native production import.meta lowering", () => {
217
+ it("emits a Hermes-compatible bundle for guarded import.meta.env reads", async () => {
218
+ const testRoot = await mkdtemp(join(tmpdir(), "vxrn-native-import-meta-"));
219
+ await writeFile(join(testRoot, "entry.js"), `globalThis.__vxrnNativeImportMetaProbe = typeof import.meta !== 'undefined' && import.meta.env.DEV`);
220
+ try {
221
+ const result = await buildNativeBundle({
222
+ root: testRoot,
223
+ platform: "ios",
224
+ entryFile: "entry.js"
225
+ });
226
+ expect(result.code).not.toContain("typeof import.meta");
227
+ const context = {
228
+ globalThis: {},
229
+ process: {
230
+ env: {}
231
+ }
232
+ };
233
+ runInNewContext(result.code, context);
234
+ expect(Reflect.get(context.globalThis, "__vxrnNativeImportMetaProbe")).toBe(false);
235
+ } finally {
236
+ await rm(testRoot, {
237
+ recursive: true,
238
+ force: true
239
+ });
240
+ }
241
+ });
242
+ });
243
+ describe("native animated guard transform", () => {
244
+ it("preserves line count and returns a composable source map", async () => {
245
+ const plugin = nativeAnimatedGuardPlugin();
246
+ if (typeof plugin.transform !== "function") throw new Error("native animated guard transform hook is not callable");
247
+ const source = ["function call(methodName) {", " const method = nullthrows(NativeAnimatedModule)[methodName];", " method();", "}"].join("\n");
248
+ const result = await Reflect.apply(plugin.transform, void 0, [source, "/project/node_modules/react-native/src/private/animated/NativeAnimatedHelper.js"]);
249
+ expect(result.code.split("\n")).toHaveLength(source.split("\n").length);
250
+ expect(result.code).toContain("if (typeof method !== 'function') return");
251
+ expect(result.map).toEqual({
252
+ version: 3,
253
+ sources: ["/project/node_modules/react-native/src/private/animated/NativeAnimatedHelper.js"],
254
+ sourcesContent: [source],
255
+ names: [],
256
+ mappings: "AAAA;AACA;AACA;AACA"
257
+ });
258
+ });
80
259
  });
81
260
  const root = "/tmp/vxrn-native-env-define-test-nonexistent";
82
261
  describe("getNativeTransformConfig platform env defines", () => {
@@ -94,6 +273,21 @@ describe("getNativeTransformConfig platform env defines", () => {
94
273
  expect(envObject.TAMAGUI_TARGET).toBe("native");
95
274
  expect(envObject.TAMAGUI_ENVIRONMENT).toBe(platform);
96
275
  });
276
+ it("inlines EXPO_PUBLIC values supplied by the native build environment", () => {
277
+ const key = "EXPO_PUBLIC_VXRN_NATIVE_ENV_PROBE";
278
+ const previous = process.env[key];
279
+ process.env[key] = "native-env-value";
280
+ try {
281
+ const {
282
+ define
283
+ } = getNativeTransformConfig("ios", false, root);
284
+ expect(define[`process.env.${key}`]).toBe("\"native-env-value\"");
285
+ expect(define[`import.meta.env.${key}`]).toBe("\"native-env-value\"");
286
+ expect(JSON.parse(define["import.meta.env"])[key]).toBe("native-env-value");
287
+ } finally {
288
+ if (previous === void 0) delete process.env[key];else process.env[key] = previous;
289
+ }
290
+ });
97
291
  });
98
292
  describe("wrapNativeBundleModuleScope", () => {
99
293
  const RUNTIME_MARKER = "//#region \\0rolldown/runtime.js";
@@ -116,15 +310,51 @@ globalThis.__rolldown_runtime__ = {};
116
310
  });
117
311
  });
118
312
  describe("getHermesSWCIncludes", () => {
119
- const CLASS_SET = ["transform-classes", "transform-parameters", "transform-class-properties", "transform-class-static-block", "transform-private-methods", "transform-private-property-in-object"];
313
+ const CLASS_SET = ["transform-classes", "transform-parameters", "transform-block-scoping", "transform-class-properties", "transform-class-static-block", "transform-private-methods", "transform-private-property-in-object"];
120
314
  it("always includes the full Hermes class-transform set (dev and prod)", () => {
121
315
  expect(getHermesSWCIncludes(true)).toEqual(expect.arrayContaining(CLASS_SET));
122
316
  expect(getHermesSWCIncludes(false)).toEqual(expect.arrayContaining(CLASS_SET));
123
317
  });
124
- it("adds transform-async-to-generator only in production", () => {
125
- expect(getHermesSWCIncludes(true)).not.toContain("transform-async-to-generator");
318
+ it("adds transform-async-to-generator in development and production", () => {
319
+ expect(getHermesSWCIncludes(true)).toContain("transform-async-to-generator");
126
320
  expect(getHermesSWCIncludes(false)).toContain("transform-async-to-generator");
127
321
  });
322
+ it("lowers async generators for the Hermes development interpreter", async () => {
323
+ const plugin = hermesCompatSWCPlugin(true);
324
+ if (typeof plugin.transform !== "function") throw new Error("Hermes compatibility transform hook is not callable");
325
+ const result = await Reflect.apply(plugin.transform, void 0, ["export async function* values() { yield await Promise.resolve(1) }", "/project/async-generator.ts"]);
326
+ expect(result.code).not.toContain("async function*");
327
+ expect(result.code).not.toContain("async function *");
328
+ });
329
+ it("preserves per-iteration bindings used by lazy method getters", async () => {
330
+ const plugin = hermesCompatSWCPlugin(true);
331
+ if (typeof plugin.transform !== "function") throw new Error("Hermes compatibility transform hook is not callable");
332
+ const result = await Reflect.apply(plugin.transform, void 0, [`
333
+ const installedGroups = new WeakMap()
334
+ function install(inst, methods) {
335
+ const proto = Object.getPrototypeOf(inst)
336
+ for (const key in methods) {
337
+ const fn = methods[key]
338
+ Object.defineProperty(proto, key, {
339
+ get() { return fn.bind(this) }
340
+ })
341
+ }
342
+ }
343
+ function Schema() {
344
+ install(this, {
345
+ nullish() { return 'nullish' },
346
+ apply(fn) { return fn(this) }
347
+ })
348
+ }
349
+ globalThis.__vxrnBlockScopeProbe = new Schema().nullish()
350
+ `, "/project/block-scope-loop.ts"]);
351
+ try {
352
+ new Function(result.code)();
353
+ expect(Reflect.get(globalThis, "__vxrnBlockScopeProbe")).toBe("nullish");
354
+ } finally {
355
+ Reflect.deleteProperty(globalThis, "__vxrnBlockScopeProbe");
356
+ }
357
+ });
128
358
  it("bundles lowered classes whose constructors use default and rest parameters", async () => {
129
359
  const testRoot = await mkdtemp(join(tmpdir(), "vxrn-hermes-parameters-"));
130
360
  const entry = join(testRoot, "entry.js");
@@ -196,9 +426,309 @@ describe("vxrnCompilerPlugin React Refresh registration", () => {
196
426
  expect(code).toContain("var __vxrnRefreshReg = globalThis.$RefreshReg$");
197
427
  expect(code).toContain("__vxrnRefreshReg(");
198
428
  expect(code).toContain("\"$RefreshReg$(\"");
429
+ expect(code).toContain("import.meta.hot.acceptReactRefresh(");
199
430
  } finally {
200
431
  process.env.NODE_ENV = previousNodeEnv;
201
432
  }
202
433
  });
203
434
  });
435
+ describe("native required transform failures", () => {
436
+ it.each([true, false])("rejects valid worklet source when its required compiler transform fails (dev=%s)", async dev2 => {
437
+ const testRoot = await createWorkletsProject();
438
+ const compiler = await import("@vxrn/compiler");
439
+ compiler.configureVXRNCompilerPlugin({
440
+ enableReanimated: true
441
+ });
442
+ try {
443
+ const plugin = vxrnCompilerPlugin("ios", dev2, testRoot);
444
+ if (typeof plugin.transform !== "function") throw new Error("vxrn compiler transform hook is not callable");
445
+ await expect(Reflect.apply(plugin.transform, void 0, [nativeTransformProbe, join(testRoot, "entry.ts")])).rejects.toThrow("NATIVE_TRANSFORM_NEGATIVE_CONTROL");
446
+ } finally {
447
+ compiler.configureVXRNCompilerPlugin({
448
+ enableReanimated: false
449
+ });
450
+ await rm(testRoot, {
451
+ recursive: true,
452
+ force: true
453
+ });
454
+ }
455
+ });
456
+ it("returns a dev build error and no output when the required compiler transform fails", async () => {
457
+ const testRoot = await createWorkletsProject();
458
+ const compiler = await import("@vxrn/compiler");
459
+ compiler.configureVXRNCompilerPlugin({
460
+ enableReanimated: true
461
+ });
462
+ let resolveOutput;
463
+ const output = new Promise(resolve => {
464
+ resolveOutput = resolve;
465
+ });
466
+ const engine = await dev({
467
+ cwd: testRoot,
468
+ input: join(testRoot, "entry.ts"),
469
+ plugins: [vxrnCompilerPlugin("ios", true, testRoot)]
470
+ }, {
471
+ format: "esm"
472
+ }, {
473
+ onOutput: resolveOutput
474
+ });
475
+ try {
476
+ await engine.run();
477
+ const result = await output;
478
+ expect(result).toBeInstanceOf(Error);
479
+ expect(String(result)).toContain("NATIVE_TRANSFORM_NEGATIVE_CONTROL");
480
+ } finally {
481
+ await engine.close();
482
+ compiler.configureVXRNCompilerPlugin({
483
+ enableReanimated: false
484
+ });
485
+ await rm(testRoot, {
486
+ recursive: true,
487
+ force: true
488
+ });
489
+ }
490
+ });
491
+ it("emits no production bundle when the required compiler transform fails", async () => {
492
+ const testRoot = await createWorkletsProject();
493
+ const compiler = await import("@vxrn/compiler");
494
+ compiler.configureVXRNCompilerPlugin({
495
+ enableReanimated: true
496
+ });
497
+ try {
498
+ await expect(buildNativeBundle({
499
+ root: testRoot,
500
+ platform: "ios",
501
+ entryFile: "entry.ts"
502
+ })).rejects.toThrow("NATIVE_TRANSFORM_NEGATIVE_CONTROL");
503
+ } finally {
504
+ compiler.configureVXRNCompilerPlugin({
505
+ enableReanimated: false
506
+ });
507
+ await rm(testRoot, {
508
+ recursive: true,
509
+ force: true
510
+ });
511
+ }
512
+ });
513
+ it("rejects Hermes compatibility transform errors instead of returning source", async () => {
514
+ const plugin = hermesCompatSWCPlugin(true);
515
+ if (typeof plugin.transform !== "function") throw new Error("Hermes compatibility transform hook is not callable");
516
+ await expect(Reflect.apply(plugin.transform, void 0, ["class TransformProbe { value = ; }", "/project/TransformProbe.ts"])).rejects.toBeTruthy();
517
+ });
518
+ it("returns maps for every required production transform", async () => {
519
+ const testRoot = await createWorkletsProject(false);
520
+ const compiler = await import("@vxrn/compiler");
521
+ compiler.configureVXRNCompilerPlugin({
522
+ enableReanimated: true
523
+ });
524
+ try {
525
+ const compilerPlugin = vxrnCompilerPlugin("ios", false, testRoot, true);
526
+ if (typeof compilerPlugin.transform !== "function") throw new Error("vxrn compiler transform hook is not callable");
527
+ expect((await Reflect.apply(compilerPlugin.transform, void 0, [nativeTransformProbe, join(testRoot, "entry.ts")])).map).toBeTruthy();
528
+ const hermesPlugin = hermesCompatSWCPlugin(false, true);
529
+ if (typeof hermesPlugin.transform !== "function") throw new Error("Hermes compatibility transform hook is not callable");
530
+ expect((await Reflect.apply(hermesPlugin.transform, void 0, ["export class TransformProbe { value = 1 }", join(testRoot, "TransformProbe.ts")])).map).toBeTruthy();
531
+ } finally {
532
+ compiler.configureVXRNCompilerPlugin({
533
+ enableReanimated: false
534
+ });
535
+ await rm(testRoot, {
536
+ recursive: true,
537
+ force: true
538
+ });
539
+ }
540
+ });
541
+ });
542
+ describe("native production assets", () => {
543
+ it("registers scale siblings and keeps monorepo assets inside assetsDest", async () => {
544
+ const testRoot = await mkdtemp(join(tmpdir(), "vxrn-native-assets-"));
545
+ const appRoot = join(testRoot, "workspace/apps/native-app");
546
+ const appAssets = join(appRoot, "assets");
547
+ const packageAssets = join(testRoot, "workspace/node_modules/example/assets");
548
+ const assetsDest = join(testRoot, "output");
549
+ await mkdir(appAssets, {
550
+ recursive: true
551
+ });
552
+ await mkdir(packageAssets, {
553
+ recursive: true
554
+ });
555
+ await writeFile(join(appAssets, "icon.png"), "icon-1x");
556
+ await writeFile(join(appAssets, "icon@2x.png"), "icon-2x");
557
+ await writeFile(join(appAssets, "icon@3x.png"), "icon-3x");
558
+ await writeFile(join(packageAssets, "back.png"), "back-1x");
559
+ await writeFile(join(packageAssets, "back@2x.png"), "back-2x");
560
+ await writeFile(join(appRoot, "entry.js"), `
561
+ import icon from './assets/icon.png'
562
+ import back from '../../node_modules/example/assets/back.png'
563
+ globalThis.__nativeAssetProbe = [icon, back]
564
+ `);
565
+ try {
566
+ const assetData = await getNativeAssetData(join(appAssets, "icon.png"), appRoot, "ios");
567
+ expect(assetData.scales).toEqual([1, 2, 3]);
568
+ expect(assetData.files.map(file => file.slice(appAssets.length + 1))).toEqual(["icon.png", "icon@2x.png", "icon@3x.png"]);
569
+ expect(assetData.hash).not.toBe("");
570
+ const registry = createNativeDevAssetRegistry();
571
+ registry.register(assetData);
572
+ expect(registry.resolve("/assets/assets/icon@2x.png", assetData.hash)?.filePath).toBe(join(appAssets, "icon@2x.png"));
573
+ expect(registry.resolve("/assets/assets/icon.png", "stale-content-hash")).toBeUndefined();
574
+ expect(registry.resolve("/assets/../../package.json", assetData.hash)).toBeUndefined();
575
+ expect((await buildNativeBundle({
576
+ root: appRoot,
577
+ platform: "ios",
578
+ entryFile: "entry.js",
579
+ assetsDest
580
+ })).code).toMatch(/"scales":\s*\[\s*1,\s*2,\s*3\s*\]/);
581
+ for (const file of ["icon.png", "icon@2x.png", "icon@3x.png"]) expect(existsSync(join(assetsDest, "assets/assets", file))).toBe(true);
582
+ for (const file of ["back.png", "back@2x.png"]) expect(existsSync(join(assetsDest, "assets/_/_/node_modules/example/assets", file))).toBe(true);
583
+ expect(existsSync(join(testRoot, "node_modules/example/assets/back.png"))).toBe(false);
584
+ } finally {
585
+ await rm(testRoot, {
586
+ recursive: true,
587
+ force: true
588
+ });
589
+ }
590
+ });
591
+ });
592
+ describe("native conditional exports", () => {
593
+ it("uses the require condition for CommonJS calls", async () => {
594
+ const testRoot = await mkdtemp(join(tmpdir(), "vxrn-native-conditions-"));
595
+ const packageRoot = join(testRoot, "node_modules/conditional-helper");
596
+ await mkdir(packageRoot, {
597
+ recursive: true
598
+ });
599
+ await writeFile(join(packageRoot, "package.json"), JSON.stringify({
600
+ name: "conditional-helper",
601
+ exports: {
602
+ ".": {
603
+ import: "./esm.js",
604
+ require: "./cjs.cjs"
605
+ }
606
+ },
607
+ type: "module"
608
+ }));
609
+ await writeFile(join(packageRoot, "esm.js"), `export default function helper() { return 'import' }`);
610
+ await writeFile(join(packageRoot, "cjs.cjs"), `module.exports = function helper() { return 'require' }`);
611
+ await writeFile(join(testRoot, "entry.cjs"), `
612
+ const helper = require('conditional-helper')
613
+ globalThis.__vxrnConditionalExportProbe = helper()
614
+ `);
615
+ try {
616
+ const result = await buildNativeBundle({
617
+ root: testRoot,
618
+ platform: "ios",
619
+ entryFile: "entry.cjs",
620
+ dev: true
621
+ });
622
+ const context = {
623
+ clearTimeout,
624
+ console,
625
+ process: {
626
+ env: {}
627
+ },
628
+ setTimeout
629
+ };
630
+ runInNewContext(result.code, context);
631
+ expect(Reflect.get(context, "__vxrnConditionalExportProbe")).toBe("require");
632
+ } finally {
633
+ await rm(testRoot, {
634
+ recursive: true,
635
+ force: true
636
+ });
637
+ }
638
+ });
639
+ it("unwraps the default export of Babel CommonJS modules in dev output", {
640
+ timeout: 3e4
641
+ }, async () => {
642
+ const testRoot = await mkdtemp(join(tmpdir(), "vxrn-native-cjs-default-"));
643
+ const packageRoot = join(testRoot, "node_modules/default-export-helper");
644
+ await mkdir(packageRoot, {
645
+ recursive: true
646
+ });
647
+ await writeFile(join(packageRoot, "package.json"), JSON.stringify({
648
+ name: "default-export-helper",
649
+ main: "./index.js"
650
+ }));
651
+ await writeFile(join(packageRoot, "index.js"), `Object.defineProperty(exports, '__esModule', { value: true }); exports.default = function Component() {}`);
652
+ await writeFile(join(testRoot, "entry.js"), `import Component from 'default-export-helper'; globalThis.__vxrnCjsDefaultProbe = typeof Component`);
653
+ let resolveOutput;
654
+ const output = new Promise(resolve => {
655
+ resolveOutput = resolve;
656
+ });
657
+ const engine = await dev({
658
+ cwd: testRoot,
659
+ input: join(testRoot, "entry.js"),
660
+ experimental: {
661
+ devMode: {
662
+ implement: getHmrRuntimeSource()
663
+ }
664
+ }
665
+ }, {
666
+ format: "esm",
667
+ codeSplitting: false,
668
+ strictExecutionOrder: true
669
+ }, {
670
+ onOutput: resolveOutput
671
+ });
672
+ try {
673
+ await engine.run();
674
+ const result = await output;
675
+ if (result instanceof Error) throw result;
676
+ const chunk = result.output.find(item => item.type === "chunk" && item.isEntry);
677
+ if (!chunk || chunk.type !== "chunk") throw new Error("Rolldown did not emit a native dev entry chunk");
678
+ const nodeModeCode = chunk.code.replace(/(\b__toESM(?:\$\d+)?\(\s*require[\w$]*\(\)\s*)\)/, "$1, 1)");
679
+ expect(nodeModeCode).not.toBe(chunk.code);
680
+ Reflect.deleteProperty(globalThis, "__rolldown_runtime__");
681
+ await import(`data:text/javascript;base64,${Buffer.from(nodeModeCode).toString("base64")}`);
682
+ expect(Reflect.get(globalThis, "__vxrnCjsDefaultProbe")).toBe("object");
683
+ Reflect.deleteProperty(globalThis, "__rolldown_runtime__");
684
+ Reflect.deleteProperty(globalThis, "__vxrnCjsDefaultProbe");
685
+ const normalized = normalizeNativeCommonJSInterop(nodeModeCode);
686
+ await import(`data:text/javascript;base64,${Buffer.from(normalized).toString("base64")}`);
687
+ expect(Reflect.get(globalThis, "__vxrnCjsDefaultProbe")).toBe("function");
688
+ } finally {
689
+ await engine.close();
690
+ Reflect.deleteProperty(globalThis, "__rolldown_runtime__");
691
+ Reflect.deleteProperty(globalThis, "__vxrnCjsDefaultProbe");
692
+ await rm(testRoot, {
693
+ recursive: true,
694
+ force: true
695
+ });
696
+ }
697
+ });
698
+ it("unwraps the default export of Babel CommonJS modules in production output", async () => {
699
+ const testRoot = await mkdtemp(join(tmpdir(), "vxrn-native-prod-cjs-default-"));
700
+ const packageRoot = join(testRoot, "node_modules/default-export-helper");
701
+ await mkdir(packageRoot, {
702
+ recursive: true
703
+ });
704
+ await writeFile(join(packageRoot, "package.json"), JSON.stringify({
705
+ name: "default-export-helper",
706
+ main: "./index.js"
707
+ }));
708
+ await writeFile(join(packageRoot, "index.js"), `Object.defineProperty(exports, '__esModule', { value: true }); exports.default = function Component() {}`);
709
+ await writeFile(join(testRoot, "entry.js"), `import Component from 'default-export-helper'; globalThis.__vxrnProdCjsDefaultProbe = typeof Component`);
710
+ try {
711
+ const result = await buildNativeBundle({
712
+ root: testRoot,
713
+ platform: "ios",
714
+ entryFile: "entry.js"
715
+ });
716
+ const context = {
717
+ clearTimeout,
718
+ console,
719
+ process: {
720
+ env: {}
721
+ },
722
+ setTimeout
723
+ };
724
+ runInNewContext(result.code, context);
725
+ expect(Reflect.get(context, "__vxrnProdCjsDefaultProbe")).toBe("function");
726
+ } finally {
727
+ await rm(testRoot, {
728
+ recursive: true,
729
+ force: true
730
+ });
731
+ }
732
+ });
733
+ });
204
734
  //# sourceMappingURL=createNativeDevEngine.test.mjs.map