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.
- package/dist/config/getViteServerConfig.mjs +6 -0
- package/dist/config/getViteServerConfig.mjs.map +1 -1
- package/dist/config/getViteServerConfig.native.js +6 -0
- package/dist/config/getViteServerConfig.native.js.map +1 -1
- package/dist/config/getViteServerConfig.test.mjs +26 -0
- package/dist/config/getViteServerConfig.test.mjs.map +1 -0
- package/dist/config/getViteServerConfig.test.native.js +26 -0
- package/dist/config/getViteServerConfig.test.native.js.map +1 -0
- package/dist/exports/dev.mjs +26 -1
- package/dist/exports/dev.mjs.map +1 -1
- package/dist/exports/dev.native.js +26 -1
- package/dist/exports/dev.native.js.map +1 -1
- package/dist/plugins/reactNativeDevServer.mjs +80 -7
- package/dist/plugins/reactNativeDevServer.mjs.map +1 -1
- package/dist/plugins/reactNativeDevServer.native.js +80 -7
- package/dist/plugins/reactNativeDevServer.native.js.map +1 -1
- package/dist/runtime/native-prelude.mjs +7 -0
- package/dist/runtime/native-prelude.mjs.map +1 -1
- package/dist/runtime/native-prelude.native.js +7 -0
- package/dist/runtime/native-prelude.native.js.map +1 -1
- package/dist/utils/createNativeDevEngine.mjs +302 -197
- package/dist/utils/createNativeDevEngine.mjs.map +1 -1
- package/dist/utils/createNativeDevEngine.native.js +410 -221
- package/dist/utils/createNativeDevEngine.native.js.map +1 -1
- package/dist/utils/createNativeDevEngine.test.mjs +536 -6
- package/dist/utils/createNativeDevEngine.test.mjs.map +1 -1
- package/dist/utils/createNativeDevEngine.test.native.js +559 -6
- package/dist/utils/createNativeDevEngine.test.native.js.map +1 -1
- package/package.json +11 -11
- package/src/config/getViteServerConfig.test.ts +29 -0
- package/src/config/getViteServerConfig.ts +11 -0
- package/src/exports/dev.ts +34 -3
- package/src/plugins/reactNativeDevServer.ts +89 -12
- package/src/runtime/native-prelude.ts +7 -0
- package/src/utils/createNativeDevEngine.test.ts +682 -5
- package/src/utils/createNativeDevEngine.ts +516 -229
- package/types/config/getViteServerConfig.d.ts.map +1 -1
- package/types/config/getViteServerConfig.test.d.ts +2 -0
- package/types/config/getViteServerConfig.test.d.ts.map +1 -0
- package/types/exports/dev.d.ts.map +1 -1
- package/types/plugins/reactNativeDevServer.d.ts +1 -0
- package/types/plugins/reactNativeDevServer.d.ts.map +1 -1
- package/types/runtime/native-prelude.d.ts.map +1 -1
- package/types/utils/createNativeDevEngine.d.ts +40 -2
- package/types/utils/createNativeDevEngine.d.ts.map +1 -1
|
@@ -1,10 +1,64 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
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
|
-
|
|
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
|
|
125
|
-
expect(getHermesSWCIncludes(true)).
|
|
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
|