vxrn 1.26.0 → 1.26.1
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/plugins/reactNativeDevServer.mjs +123 -42
- package/dist/plugins/reactNativeDevServer.mjs.map +1 -1
- package/dist/plugins/reactNativeDevServer.native.js +209 -44
- package/dist/plugins/reactNativeDevServer.native.js.map +1 -1
- package/dist/rn-commands/bundle/buildBundle.mjs +29 -10
- package/dist/rn-commands/bundle/buildBundle.mjs.map +1 -1
- package/dist/rn-commands/bundle/buildBundle.native.js +29 -10
- package/dist/rn-commands/bundle/buildBundle.native.js.map +1 -1
- package/dist/utils/createNativeDevEngine.mjs +220 -83
- package/dist/utils/createNativeDevEngine.mjs.map +1 -1
- package/dist/utils/createNativeDevEngine.native.js +257 -84
- package/dist/utils/createNativeDevEngine.native.js.map +1 -1
- package/dist/utils/createNativeDevEngine.test.mjs +285 -23
- package/dist/utils/createNativeDevEngine.test.mjs.map +1 -1
- package/dist/utils/createNativeDevEngine.test.native.js +319 -23
- package/dist/utils/createNativeDevEngine.test.native.js.map +1 -1
- package/dist/utils/patches.test.mjs.map +1 -1
- package/dist/utils/patches.test.native.js.map +1 -1
- package/dist/utils/symbolicateNativeStack.mjs +66 -0
- package/dist/utils/symbolicateNativeStack.mjs.map +1 -0
- package/dist/utils/symbolicateNativeStack.native.js +70 -0
- package/dist/utils/symbolicateNativeStack.native.js.map +1 -0
- package/dist/utils/symbolicateNativeStack.test.mjs +96 -0
- package/dist/utils/symbolicateNativeStack.test.mjs.map +1 -0
- package/dist/utils/symbolicateNativeStack.test.native.js +96 -0
- package/dist/utils/symbolicateNativeStack.test.native.js.map +1 -0
- package/package.json +12 -11
- package/src/plugins/reactNativeDevServer.ts +162 -50
- package/src/rn-commands/bundle/buildBundle.ts +28 -9
- package/src/utils/createNativeDevEngine.test.ts +581 -193
- package/src/utils/createNativeDevEngine.ts +434 -115
- package/src/utils/patches.test.ts +1 -4
- package/src/utils/symbolicateNativeStack.test.ts +78 -0
- package/src/utils/symbolicateNativeStack.ts +129 -0
- package/types/plugins/reactNativeDevServer.d.ts.map +1 -1
- package/types/rn-commands/bundle/buildBundle.d.ts.map +1 -1
- package/types/utils/createNativeDevEngine.d.ts +23 -4
- package/types/utils/createNativeDevEngine.d.ts.map +1 -1
- package/types/utils/symbolicateNativeStack.d.ts +46 -0
- package/types/utils/symbolicateNativeStack.d.ts.map +1 -0
- package/types/utils/symbolicateNativeStack.test.d.ts +2 -0
- package/types/utils/symbolicateNativeStack.test.d.ts.map +1 -0
|
@@ -11,6 +11,12 @@ import { readFile } from 'node:fs/promises'
|
|
|
11
11
|
import { createDevMiddleware } from '@react-native/dev-middleware'
|
|
12
12
|
import { createNativeDevEngine } from '../utils/createNativeDevEngine'
|
|
13
13
|
import { getBoundPort } from '../utils/getBoundPort'
|
|
14
|
+
import {
|
|
15
|
+
getNativeFramePlatform,
|
|
16
|
+
isNativeBundleFrame,
|
|
17
|
+
symbolicateNativeStack,
|
|
18
|
+
type NativeStackFrame,
|
|
19
|
+
} from '../utils/symbolicateNativeStack'
|
|
14
20
|
|
|
15
21
|
type ClientMessage = {
|
|
16
22
|
type: 'client-log'
|
|
@@ -205,7 +211,7 @@ export function createReactNativeDevServerPlugin(
|
|
|
205
211
|
}
|
|
206
212
|
addConnectedNativeClient()
|
|
207
213
|
|
|
208
|
-
socket.on('message',
|
|
214
|
+
socket.on('message', (message) => {
|
|
209
215
|
const value = message.toString()
|
|
210
216
|
if (value === 'ping') {
|
|
211
217
|
socket.send('pong')
|
|
@@ -219,8 +225,9 @@ export function createReactNativeDevServerPlugin(
|
|
|
219
225
|
return
|
|
220
226
|
}
|
|
221
227
|
if (update.type === 'hmr:invalidate') {
|
|
222
|
-
|
|
223
|
-
|
|
228
|
+
// the runtime could not accept an update, so restart it. the bundle
|
|
229
|
+
// it fetches on the way back settles the engine's pending work
|
|
230
|
+
// first, so it lands on the current output.
|
|
224
231
|
socket.send(JSON.stringify({ type: 'hmr:reload' }))
|
|
225
232
|
}
|
|
226
233
|
})
|
|
@@ -237,6 +244,66 @@ export function createReactNativeDevServerPlugin(
|
|
|
237
244
|
})
|
|
238
245
|
})
|
|
239
246
|
|
|
247
|
+
const reloadNativeClients = (platform: string) => {
|
|
248
|
+
const reload = JSON.stringify({ type: 'hmr:reload' })
|
|
249
|
+
hmrWSS.clients.forEach((client) => {
|
|
250
|
+
const nativeClient = client as NativeHmrSocket
|
|
251
|
+
if (nativeClient.readyState === 1 && nativeClient.vxrnPlatform === platform) {
|
|
252
|
+
nativeClient.send(reload)
|
|
253
|
+
}
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// a created route file: rolldown's addWatchFile on the route directories
|
|
258
|
+
// is supposed to report it, and the engine rebuilds only then. github's
|
|
259
|
+
// macos runners do not always surface that, so vite's watcher is the
|
|
260
|
+
// backup, still scoped to route files inside handleAddedFile.
|
|
261
|
+
//
|
|
262
|
+
// a deletion it cannot survive. rolldown's dev engine panics its worker
|
|
263
|
+
// when it retires a module that left the disk ("index out of bounds" out
|
|
264
|
+
// of oxc_index), and from then on every bundle request answers 500 even
|
|
265
|
+
// once the file comes back. that engine cannot be repaired, so drop it
|
|
266
|
+
// and let the next bundle request build a fresh one.
|
|
267
|
+
let deletionTimer: ReturnType<typeof setTimeout> | undefined
|
|
268
|
+
let addTimer: ReturnType<typeof setTimeout> | undefined
|
|
269
|
+
const addedFiles = new Set<string>()
|
|
270
|
+
server.watcher.on('add', (file) => {
|
|
271
|
+
if (file.split('/').some((segment) => segment.startsWith('.'))) return
|
|
272
|
+
addedFiles.add(file)
|
|
273
|
+
clearTimeout(addTimer)
|
|
274
|
+
addTimer = setTimeout(() => {
|
|
275
|
+
const files = [...addedFiles]
|
|
276
|
+
addedFiles.clear()
|
|
277
|
+
for (const platform of Object.keys(devEngines)) {
|
|
278
|
+
const devEngine = devEngines[platform]
|
|
279
|
+
if (!devEngine) continue
|
|
280
|
+
for (const added of files) {
|
|
281
|
+
devEngine.handleAddedFile(added).catch((error) => {
|
|
282
|
+
console.error(`[vxrn] handling added ${added} for ${platform} failed`, error)
|
|
283
|
+
})
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}, 50)
|
|
287
|
+
})
|
|
288
|
+
server.watcher.on('unlink', (file) => {
|
|
289
|
+
// vite's watcher already skips node_modules and .git. keep out the rest
|
|
290
|
+
// of what a dev server writes into a project while it runs: caches and
|
|
291
|
+
// build output under a dot directory, editor swap files.
|
|
292
|
+
if (file.split('/').some((segment) => segment.startsWith('.'))) return
|
|
293
|
+
// one pass for a burst (a branch switch, a generator, a rename)
|
|
294
|
+
clearTimeout(deletionTimer)
|
|
295
|
+
deletionTimer = setTimeout(() => {
|
|
296
|
+
for (const platform of Object.keys(devEngines)) {
|
|
297
|
+
const devEngine = devEngines[platform]
|
|
298
|
+
if (!devEngine) continue
|
|
299
|
+
devEngines[platform] = null
|
|
300
|
+
devEngineCreating[platform] = null
|
|
301
|
+
devEngine.close().catch(() => {})
|
|
302
|
+
reloadNativeClients(platform)
|
|
303
|
+
}
|
|
304
|
+
}, 500)
|
|
305
|
+
})
|
|
306
|
+
|
|
240
307
|
clientWSS.on('connection', (socket) => {
|
|
241
308
|
socket.on('message', (messageRaw) => {
|
|
242
309
|
const message = JSON.parse(messageRaw.toString()) as any as ClientMessage
|
|
@@ -267,6 +334,49 @@ export function createReactNativeDevServerPlugin(
|
|
|
267
334
|
})
|
|
268
335
|
})
|
|
269
336
|
|
|
337
|
+
const getDevEngine = async (platform: 'ios' | 'android') => {
|
|
338
|
+
if (!devEngines[platform]) {
|
|
339
|
+
// prevent duplicate creation from concurrent requests
|
|
340
|
+
if (!devEngineCreating[platform]) {
|
|
341
|
+
devEngineCreating[platform] = (async () => {
|
|
342
|
+
try {
|
|
343
|
+
console.info(`[vxrn] creating rolldown DevEngine for ${platform}...`)
|
|
344
|
+
devEngines[platform] = await createNativeDevEngine({
|
|
345
|
+
root,
|
|
346
|
+
port: getBoundPort(server),
|
|
347
|
+
host: typeof host === 'string' ? host : 'localhost',
|
|
348
|
+
platform,
|
|
349
|
+
serverUrl: `http://${typeof host === 'string' && host !== '0.0.0.0' ? host : 'localhost'}:${getBoundPort(server)}`,
|
|
350
|
+
onHmrUpdate: (update) => {
|
|
351
|
+
const msg = JSON.stringify(update)
|
|
352
|
+
// an update with no clientId is for every client on the
|
|
353
|
+
// platform: an error, or a reload after a full rebuild
|
|
354
|
+
const target = 'clientId' in update ? update.clientId : undefined
|
|
355
|
+
hmrWSS.clients.forEach((client) => {
|
|
356
|
+
const nativeClient = client as NativeHmrSocket
|
|
357
|
+
if (
|
|
358
|
+
nativeClient.readyState === 1 &&
|
|
359
|
+
nativeClient.vxrnPlatform === platform &&
|
|
360
|
+
(!target || nativeClient.vxrnClientId === target)
|
|
361
|
+
) {
|
|
362
|
+
client.send(msg)
|
|
363
|
+
}
|
|
364
|
+
})
|
|
365
|
+
},
|
|
366
|
+
})
|
|
367
|
+
console.info(`[vxrn] rolldown DevEngine ready for ${platform}`)
|
|
368
|
+
} catch (err) {
|
|
369
|
+
// clear so next request retries instead of permanently failing
|
|
370
|
+
devEngineCreating[platform] = null
|
|
371
|
+
throw err
|
|
372
|
+
}
|
|
373
|
+
})()
|
|
374
|
+
}
|
|
375
|
+
await devEngineCreating[platform]
|
|
376
|
+
}
|
|
377
|
+
return devEngines[platform]!
|
|
378
|
+
}
|
|
379
|
+
|
|
270
380
|
// React Native bundle handler
|
|
271
381
|
const handleRNBundle: Connect.NextHandleFunction = async (req, res) => {
|
|
272
382
|
const url = new URL(req.url!, `http://${req.headers.host}`)
|
|
@@ -281,7 +391,10 @@ export function createReactNativeDevServerPlugin(
|
|
|
281
391
|
// here would mean a second engine per platform. production native bundles
|
|
282
392
|
// come from the build command instead. say so rather than quietly serving
|
|
283
393
|
// dev bytes to something that asked for production ones.
|
|
284
|
-
if (
|
|
394
|
+
if (
|
|
395
|
+
url.searchParams.get('dev') === 'false' &&
|
|
396
|
+
!warnedProdBundleRequest.has(platform)
|
|
397
|
+
) {
|
|
285
398
|
warnedProdBundleRequest.add(platform)
|
|
286
399
|
console.warn(
|
|
287
400
|
`[vxrn] ${platform} bundle requested with dev=false, but the dev server only builds dev bundles. Serving a dev bundle. Use the build command for a production one.`
|
|
@@ -289,50 +402,10 @@ export function createReactNativeDevServerPlugin(
|
|
|
289
402
|
}
|
|
290
403
|
|
|
291
404
|
try {
|
|
292
|
-
const bundle = await (
|
|
293
|
-
if (!devEngines[platform]) {
|
|
294
|
-
// prevent duplicate creation from concurrent requests
|
|
295
|
-
if (!devEngineCreating[platform]) {
|
|
296
|
-
devEngineCreating[platform] = (async () => {
|
|
297
|
-
try {
|
|
298
|
-
console.info(`[vxrn] creating rolldown DevEngine for ${platform}...`)
|
|
299
|
-
devEngines[platform] = await createNativeDevEngine({
|
|
300
|
-
root,
|
|
301
|
-
port: getBoundPort(server),
|
|
302
|
-
host: typeof host === 'string' ? host : 'localhost',
|
|
303
|
-
platform,
|
|
304
|
-
serverUrl: `http://${typeof host === 'string' && host !== '0.0.0.0' ? host : 'localhost'}:${getBoundPort(server)}`,
|
|
305
|
-
onHmrUpdate: (update) => {
|
|
306
|
-
const msg = JSON.stringify(update)
|
|
307
|
-
hmrWSS.clients.forEach((client) => {
|
|
308
|
-
const nativeClient = client as NativeHmrSocket
|
|
309
|
-
if (
|
|
310
|
-
nativeClient.readyState === 1 &&
|
|
311
|
-
nativeClient.vxrnPlatform === platform &&
|
|
312
|
-
(update.type === 'hmr:error' ||
|
|
313
|
-
nativeClient.vxrnClientId === update.clientId)
|
|
314
|
-
) {
|
|
315
|
-
client.send(msg)
|
|
316
|
-
}
|
|
317
|
-
})
|
|
318
|
-
},
|
|
319
|
-
})
|
|
320
|
-
console.info(`[vxrn] rolldown DevEngine ready for ${platform}`)
|
|
321
|
-
} catch (err) {
|
|
322
|
-
// clear so next request retries instead of permanently failing
|
|
323
|
-
devEngineCreating[platform] = null
|
|
324
|
-
throw err
|
|
325
|
-
}
|
|
326
|
-
})()
|
|
327
|
-
}
|
|
328
|
-
await devEngineCreating[platform]
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
return await devEngines[platform]!.getBundle().then((r) => r.code)
|
|
332
|
-
})()
|
|
405
|
+
const bundle = await (await getDevEngine(platform)).getBundle()
|
|
333
406
|
|
|
334
407
|
res.writeHead(200, { 'Content-Type': 'text/javascript' })
|
|
335
|
-
res.end(bundle)
|
|
408
|
+
res.end(bundle.code)
|
|
336
409
|
} catch (err) {
|
|
337
410
|
console.error(` Error building React Native bundle`)
|
|
338
411
|
console.error(err)
|
|
@@ -353,10 +426,49 @@ export function createReactNativeDevServerPlugin(
|
|
|
353
426
|
}
|
|
354
427
|
})
|
|
355
428
|
|
|
356
|
-
//
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
429
|
+
// resolve a device stack back to authored files through the dev bundle's
|
|
430
|
+
// source map. react native posts here from LogBox and from console traces.
|
|
431
|
+
server.middlewares.use('/symbolicate', async (req, res) => {
|
|
432
|
+
try {
|
|
433
|
+
const chunks: Buffer[] = []
|
|
434
|
+
for await (const chunk of req) chunks.push(chunk as Buffer)
|
|
435
|
+
const body: { stack?: NativeStackFrame[] } = JSON.parse(
|
|
436
|
+
Buffer.concat(chunks).toString('utf8')
|
|
437
|
+
)
|
|
438
|
+
const stack = body.stack
|
|
439
|
+
if (!Array.isArray(stack)) {
|
|
440
|
+
throw new Error('expected a `stack` array in the request body')
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const bundleFrame = stack.find((frame) => isNativeBundleFrame(frame.file))
|
|
444
|
+
if (!bundleFrame) {
|
|
445
|
+
// nothing in this stack came from the bundle, so there is nothing to
|
|
446
|
+
// resolve. hand it straight back rather than inventing frames.
|
|
447
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
448
|
+
res.end(JSON.stringify({ stack, codeFrame: null }))
|
|
449
|
+
return
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const framePlatform = getNativeFramePlatform(bundleFrame.file)
|
|
453
|
+
const platform = validPlatforms[framePlatform ?? '']
|
|
454
|
+
if (!platform) {
|
|
455
|
+
throw new Error(
|
|
456
|
+
`could not tell which platform "${bundleFrame.file}" was bundled for`
|
|
457
|
+
)
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const bundle = await (await getDevEngine(platform)).getBundle()
|
|
461
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
462
|
+
res.end(JSON.stringify(symbolicateNativeStack(stack, bundle.map)))
|
|
463
|
+
} catch (err) {
|
|
464
|
+
console.error(`[vxrn] symbolicate failed`, err)
|
|
465
|
+
res.writeHead(500, { 'Content-Type': 'application/json' })
|
|
466
|
+
res.end(
|
|
467
|
+
JSON.stringify({
|
|
468
|
+
error: err instanceof Error ? err.message : String(err),
|
|
469
|
+
})
|
|
470
|
+
)
|
|
471
|
+
}
|
|
360
472
|
})
|
|
361
473
|
},
|
|
362
474
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import path from 'node:path'
|
|
2
1
|
import FSExtra from 'fs-extra'
|
|
2
|
+
import { clearTransformCache } from '@vxrn/compiler'
|
|
3
3
|
import {
|
|
4
4
|
bundle as metroBundle,
|
|
5
5
|
type BundleCommandArgs,
|
|
@@ -18,15 +18,29 @@ export async function buildBundle(
|
|
|
18
18
|
const {
|
|
19
19
|
platform, // Android is not supported yet.
|
|
20
20
|
dev,
|
|
21
|
-
// bundleEncoding, // Not supported, we are using utf8.
|
|
22
|
-
// sourcemapUseAbsolutePath,
|
|
23
|
-
// unstableTransformProfile,
|
|
24
|
-
// resetCache, // Currently we are not using cache for production builds.
|
|
25
|
-
// readGlobalCache,
|
|
26
|
-
// entryFile, // Not supported. With VxRN, we are using a static entry file.
|
|
27
21
|
bundleOutput,
|
|
22
|
+
// React Native's `--bundle-encoding` names a Node Buffer encoding.
|
|
23
|
+
bundleEncoding = 'utf8',
|
|
28
24
|
assetsDest,
|
|
29
|
-
// minify
|
|
25
|
+
// `--minify` overrides React Native's default of "minify unless this is a
|
|
26
|
+
// dev bundle". undefined means the flag was not passed.
|
|
27
|
+
minify,
|
|
28
|
+
// `--reset-cache` drops the compiler's transform cache, the only cache a
|
|
29
|
+
// rolldown production build reads.
|
|
30
|
+
resetCache,
|
|
31
|
+
// `--entry-file` names the React Native root module. One generates its own
|
|
32
|
+
// native entry from the route tree instead, and __vxrnNativeEntryFile is
|
|
33
|
+
// the supported way to point the build somewhere else.
|
|
34
|
+
// `--read-global-cache` / `--max-workers` / `--config` / `--transformer` /
|
|
35
|
+
// `--resolver-option` configure Metro's global cache, worker pool and
|
|
36
|
+
// resolver. A rolldown production build has none of them: it transforms
|
|
37
|
+
// from source in-process. React Native's own bundle command ignores
|
|
38
|
+
// `--read-global-cache` too.
|
|
39
|
+
// `--unstable-transform-profile` picks a JS engine to target. This pipeline
|
|
40
|
+
// always downlevels for Hermes, which is what every profile it accepts
|
|
41
|
+
// ('default', 'hermes', 'hermes-canary') can run.
|
|
42
|
+
// `--sourcemap-use-absolute-path`, `--sourcemap-sources-root`,
|
|
43
|
+
// `--asset-catalog-dest` and `--indexed-ram-bundle` are not implemented.
|
|
30
44
|
} = args
|
|
31
45
|
|
|
32
46
|
const { root } = ctx
|
|
@@ -53,6 +67,10 @@ export async function buildBundle(
|
|
|
53
67
|
process.env.NODE_ENV = 'production'
|
|
54
68
|
}
|
|
55
69
|
|
|
70
|
+
if (resetCache) {
|
|
71
|
+
clearTransformCache()
|
|
72
|
+
}
|
|
73
|
+
|
|
56
74
|
console.info(`[vxrn] building native bundle for ${platform}...`)
|
|
57
75
|
const nativeEntryFile = (globalThis as { __vxrnNativeEntryFile?: unknown })
|
|
58
76
|
.__vxrnNativeEntryFile
|
|
@@ -67,6 +85,7 @@ export async function buildBundle(
|
|
|
67
85
|
serverUrl: process.env.ONE_SERVER_URL,
|
|
68
86
|
assetsDest,
|
|
69
87
|
sourcemap: !!args.sourcemapOutput,
|
|
88
|
+
minify,
|
|
70
89
|
})
|
|
71
90
|
const builtBundle = result.code
|
|
72
91
|
|
|
@@ -76,7 +95,7 @@ export async function buildBundle(
|
|
|
76
95
|
}
|
|
77
96
|
|
|
78
97
|
console.info(`Writing bundle to ${bundleOutput}...`)
|
|
79
|
-
FSExtra.writeFileSync(bundleOutput, builtBundle, { encoding:
|
|
98
|
+
FSExtra.writeFileSync(bundleOutput, builtBundle, { encoding: bundleEncoding })
|
|
80
99
|
console.info('Done.')
|
|
81
100
|
|
|
82
101
|
// Prevent the process not getting exited for some unknown reason.
|