vxrn 1.26.1 → 1.27.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/plugins/reactNativeDevServer.mjs +15 -50
- package/dist/plugins/reactNativeDevServer.mjs.map +1 -1
- package/dist/plugins/reactNativeDevServer.native.js +17 -113
- package/dist/plugins/reactNativeDevServer.native.js.map +1 -1
- package/dist/utils/createNativeDevEngine.mjs +141 -68
- package/dist/utils/createNativeDevEngine.mjs.map +1 -1
- package/dist/utils/createNativeDevEngine.native.js +166 -90
- package/dist/utils/createNativeDevEngine.native.js.map +1 -1
- package/package.json +12 -12
- package/src/plugins/reactNativeDevServer.ts +27 -67
- package/src/utils/createNativeDevEngine.ts +204 -96
- package/types/plugins/reactNativeDevServer.d.ts.map +1 -1
- package/types/utils/createNativeDevEngine.d.ts +0 -1
- package/types/utils/createNativeDevEngine.d.ts.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vxrn",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.27.0",
|
|
4
4
|
"sideEffects": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -65,16 +65,16 @@
|
|
|
65
65
|
"@jridgewell/trace-mapping": "^0.3.25",
|
|
66
66
|
"@react-native/codegen": "^0.86.2",
|
|
67
67
|
"@react-native/dev-middleware": "^0.86.2",
|
|
68
|
-
"@vxrn/compiler": "1.
|
|
69
|
-
"@vxrn/debug": "1.
|
|
70
|
-
"@vxrn/query-string": "1.
|
|
71
|
-
"@vxrn/resolve": "1.
|
|
72
|
-
"@vxrn/safe-area": "1.
|
|
73
|
-
"@vxrn/url-parse": "1.
|
|
74
|
-
"@vxrn/utils": "1.
|
|
75
|
-
"@vxrn/vendor": "1.
|
|
76
|
-
"@vxrn/vite-flow": "1.
|
|
77
|
-
"@vxrn/vite-plugin-metro": "1.
|
|
68
|
+
"@vxrn/compiler": "1.27.0",
|
|
69
|
+
"@vxrn/debug": "1.27.0",
|
|
70
|
+
"@vxrn/query-string": "1.27.0",
|
|
71
|
+
"@vxrn/resolve": "1.27.0",
|
|
72
|
+
"@vxrn/safe-area": "1.27.0",
|
|
73
|
+
"@vxrn/url-parse": "1.27.0",
|
|
74
|
+
"@vxrn/utils": "1.27.0",
|
|
75
|
+
"@vxrn/vendor": "1.27.0",
|
|
76
|
+
"@vxrn/vite-flow": "1.27.0",
|
|
77
|
+
"@vxrn/vite-plugin-metro": "1.27.0",
|
|
78
78
|
"citty": "^0.1.6",
|
|
79
79
|
"dotenv": "^17.2.1",
|
|
80
80
|
"dotenv-expand": "^12.0.2",
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
"perfect-debounce": "^1.0.0",
|
|
96
96
|
"picocolors": "^1.0.0",
|
|
97
97
|
"pkg-types": "^1.0.3",
|
|
98
|
-
"rolldown": "^1.2.
|
|
98
|
+
"rolldown": "^1.2.8",
|
|
99
99
|
"semver": "^7.6.3",
|
|
100
100
|
"vite": "^8.2.2",
|
|
101
101
|
"ws": "^8.21.0"
|
|
@@ -83,6 +83,7 @@ export function createReactNativeDevServerPlugin(
|
|
|
83
83
|
> = {}
|
|
84
84
|
const devEngineCreating: Record<string, Promise<unknown> | null> = {}
|
|
85
85
|
const warnedProdBundleRequest = new Set<string>()
|
|
86
|
+
const pendingReloadPlatforms = new Set<'ios' | 'android'>()
|
|
86
87
|
|
|
87
88
|
const devToolsSocketEndpoints = ['/inspector/device', '/inspector/debug']
|
|
88
89
|
const reactNativeDevToolsUrl = `http://${host}:${getBoundPort(server)}`
|
|
@@ -210,6 +211,9 @@ export function createReactNativeDevServerPlugin(
|
|
|
210
211
|
return
|
|
211
212
|
}
|
|
212
213
|
addConnectedNativeClient()
|
|
214
|
+
if (pendingReloadPlatforms.has(socket.vxrnPlatform)) {
|
|
215
|
+
socket.send(JSON.stringify({ type: 'hmr:reload' }))
|
|
216
|
+
}
|
|
213
217
|
|
|
214
218
|
socket.on('message', (message) => {
|
|
215
219
|
const value = message.toString()
|
|
@@ -244,66 +248,6 @@ export function createReactNativeDevServerPlugin(
|
|
|
244
248
|
})
|
|
245
249
|
})
|
|
246
250
|
|
|
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
|
-
|
|
307
251
|
clientWSS.on('connection', (socket) => {
|
|
308
252
|
socket.on('message', (messageRaw) => {
|
|
309
253
|
const message = JSON.parse(messageRaw.toString()) as any as ClientMessage
|
|
@@ -335,10 +279,10 @@ export function createReactNativeDevServerPlugin(
|
|
|
335
279
|
})
|
|
336
280
|
|
|
337
281
|
const getDevEngine = async (platform: 'ios' | 'android') => {
|
|
338
|
-
|
|
282
|
+
while (!devEngines[platform]) {
|
|
339
283
|
// prevent duplicate creation from concurrent requests
|
|
340
284
|
if (!devEngineCreating[platform]) {
|
|
341
|
-
|
|
285
|
+
const creating = (async () => {
|
|
342
286
|
try {
|
|
343
287
|
console.info(`[vxrn] creating rolldown DevEngine for ${platform}...`)
|
|
344
288
|
devEngines[platform] = await createNativeDevEngine({
|
|
@@ -352,6 +296,12 @@ export function createReactNativeDevServerPlugin(
|
|
|
352
296
|
// an update with no clientId is for every client on the
|
|
353
297
|
// platform: an error, or a reload after a full rebuild
|
|
354
298
|
const target = 'clientId' in update ? update.clientId : undefined
|
|
299
|
+
if (update.type === 'hmr:reload' && !target) {
|
|
300
|
+
// keep this pending until the rebuilt bundle is served.
|
|
301
|
+
// a previous app instance can leave a live socket while
|
|
302
|
+
// its replacement is still mounting and connecting.
|
|
303
|
+
pendingReloadPlatforms.add(platform)
|
|
304
|
+
}
|
|
355
305
|
hmrWSS.clients.forEach((client) => {
|
|
356
306
|
const nativeClient = client as NativeHmrSocket
|
|
357
307
|
if (
|
|
@@ -365,12 +315,12 @@ export function createReactNativeDevServerPlugin(
|
|
|
365
315
|
},
|
|
366
316
|
})
|
|
367
317
|
console.info(`[vxrn] rolldown DevEngine ready for ${platform}`)
|
|
368
|
-
}
|
|
369
|
-
// clear so
|
|
318
|
+
} finally {
|
|
319
|
+
// clear so a failed creation can be retried.
|
|
370
320
|
devEngineCreating[platform] = null
|
|
371
|
-
throw err
|
|
372
321
|
}
|
|
373
322
|
})()
|
|
323
|
+
devEngineCreating[platform] = creating
|
|
374
324
|
}
|
|
375
325
|
await devEngineCreating[platform]
|
|
376
326
|
}
|
|
@@ -403,8 +353,18 @@ export function createReactNativeDevServerPlugin(
|
|
|
403
353
|
|
|
404
354
|
try {
|
|
405
355
|
const bundle = await (await getDevEngine(platform)).getBundle()
|
|
406
|
-
|
|
407
|
-
|
|
356
|
+
// a client that connects after this response starts from the current
|
|
357
|
+
// route map and does not need the pending reload intended for the
|
|
358
|
+
// previous runtime.
|
|
359
|
+
res.once('finish', () => pendingReloadPlatforms.delete(platform))
|
|
360
|
+
|
|
361
|
+
// a DevSettings reload requests this exact URL again. native URL
|
|
362
|
+
// loading may otherwise reuse the first response and restart the app
|
|
363
|
+
// on a route map from before a file was added or removed.
|
|
364
|
+
res.writeHead(200, {
|
|
365
|
+
'Cache-Control': 'no-store',
|
|
366
|
+
'Content-Type': 'text/javascript',
|
|
367
|
+
})
|
|
408
368
|
res.end(bundle.code)
|
|
409
369
|
} catch (err) {
|
|
410
370
|
console.error(` Error building React Native bundle`)
|
|
@@ -16,6 +16,9 @@ import {
|
|
|
16
16
|
readdirSync,
|
|
17
17
|
statSync,
|
|
18
18
|
readFileSync,
|
|
19
|
+
writeFileSync,
|
|
20
|
+
watch,
|
|
21
|
+
type FSWatcher,
|
|
19
22
|
} from 'node:fs'
|
|
20
23
|
import { pathToFileURL } from 'node:url'
|
|
21
24
|
import micromatch from 'micromatch'
|
|
@@ -23,6 +26,7 @@ import type { InputOptions, OutputOptions, Plugin, RolldownOutput } from 'rolldo
|
|
|
23
26
|
import type { DevEngine } from 'rolldown/experimental'
|
|
24
27
|
import { loadEnv as loadViteEnv, normalizePath } from 'vite'
|
|
25
28
|
import { shouldStripFlow, transformHermesAsync } from '@vxrn/compiler'
|
|
29
|
+
import { resolvePath } from '@vxrn/resolve'
|
|
26
30
|
import { DEFAULT_ASSET_EXTS } from '../constants/defaults'
|
|
27
31
|
import { getNativePrelude } from '../runtime/native-prelude'
|
|
28
32
|
import { rnCodegenPlugin } from '../plugins/rnCodegenPlugin'
|
|
@@ -98,7 +102,6 @@ interface NativeDevEngineResult {
|
|
|
98
102
|
getBundle: () => Promise<NativeDevBundle>
|
|
99
103
|
getAsset: (pathname: string, hash?: string) => NativeDevAsset | undefined
|
|
100
104
|
close: () => Promise<void>
|
|
101
|
-
handleAddedFile: (file: string) => Promise<void>
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
// shared resolve extensions for native builds
|
|
@@ -262,6 +265,7 @@ function getNativePlugins(
|
|
|
262
265
|
// rolldown-runtime WebSocket); RN's client otherwise opens a /hot socket and
|
|
263
266
|
// red-boxes "unknown-message [object Object]" on every edit (new arch)
|
|
264
267
|
hmrClientNoopPlugin(),
|
|
268
|
+
...(dev ? [reactNativeDedupePlugin(root)] : []),
|
|
265
269
|
// stub CSS imports — native doesn't support CSS and rolldown removed CSS bundling
|
|
266
270
|
cssStubPlugin(),
|
|
267
271
|
// handle import.meta.glob (used by One's route system)
|
|
@@ -556,7 +560,11 @@ export async function createNativeDevEngine(
|
|
|
556
560
|
let bundlePromise: Promise<NativeDevBundle> | null = null
|
|
557
561
|
|
|
558
562
|
const resolvedHost = host === '0.0.0.0' ? 'localhost' : host
|
|
559
|
-
const virtualEntry = nativeVirtualEntryPlugin(root, {
|
|
563
|
+
const virtualEntry = nativeVirtualEntryPlugin(root, {
|
|
564
|
+
dev: true,
|
|
565
|
+
platform,
|
|
566
|
+
dynamicRoutes: true,
|
|
567
|
+
})
|
|
560
568
|
|
|
561
569
|
// one build at a time: a bundle request and a full rebuild can arrive
|
|
562
570
|
// together (a reload lands on the bundle route while a new route file is
|
|
@@ -645,27 +653,26 @@ try {
|
|
|
645
653
|
let outputProcessed: Promise<void> = Promise.resolve()
|
|
646
654
|
|
|
647
655
|
let engine: Awaited<ReturnType<typeof dev>>
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
656
|
+
let routeWatcher: FSWatcher | undefined
|
|
657
|
+
|
|
658
|
+
const rebuildIfRouteGraphChanged = (): Promise<boolean> =>
|
|
659
|
+
queueEngineWork(async () => {
|
|
660
|
+
// decide from the complete route set after all earlier engine work. a
|
|
661
|
+
// watcher can report a directory, a rename, or several events for one
|
|
662
|
+
// write, and none of those event shapes are the source of truth.
|
|
663
|
+
const { files: knownRoutes, scanFiles, updateFiles } = virtualEntry.routes
|
|
664
|
+
const currentRoutes = scanFiles()
|
|
665
|
+
const routeSetChanged =
|
|
666
|
+
currentRoutes.size !== knownRoutes.size ||
|
|
667
|
+
[...currentRoutes].some((route) => !knownRoutes.has(route))
|
|
668
|
+
if (!routeSetChanged) return false
|
|
669
|
+
|
|
670
|
+
// changing this small imported module lets Rolldown compile only the new
|
|
671
|
+
// route and the registry delta. rebuilding a multi-megabyte native bundle
|
|
672
|
+
// makes route creation scale with the entire app.
|
|
673
|
+
updateFiles(currentRoutes)
|
|
674
|
+
return true
|
|
659
675
|
})
|
|
660
|
-
if (!routeAdded) return false
|
|
661
|
-
await queueEngineWork(async () => {
|
|
662
|
-
engine.triggerFullBuild()
|
|
663
|
-
await engine.ensureLatestBuildOutput()
|
|
664
|
-
await outputProcessed
|
|
665
|
-
})
|
|
666
|
-
onHmrUpdate?.({ type: 'hmr:reload' })
|
|
667
|
-
return true
|
|
668
|
-
}
|
|
669
676
|
|
|
670
677
|
engine = await dev(inputOptions, outputOptions, {
|
|
671
678
|
onOutput: async (result) => {
|
|
@@ -687,12 +694,10 @@ try {
|
|
|
687
694
|
return
|
|
688
695
|
}
|
|
689
696
|
|
|
690
|
-
//
|
|
691
|
-
// the
|
|
692
|
-
//
|
|
693
|
-
|
|
694
|
-
// happened, so the reload is sent from here.
|
|
695
|
-
if (await rebuildIfNewRoute(result.changedFiles)) return
|
|
697
|
+
// adding or deleting a route changes a route map that was expanded when
|
|
698
|
+
// the entry transformed. only a full build re-expands it, and rolldown
|
|
699
|
+
// tells no client that happened, so the reload is sent from here.
|
|
700
|
+
if (await rebuildIfRouteGraphChanged()) return
|
|
696
701
|
|
|
697
702
|
for (const { clientId, update } of result.updates) {
|
|
698
703
|
if (update.type === 'Patch' && update.code) {
|
|
@@ -778,6 +783,35 @@ try {
|
|
|
778
783
|
|
|
779
784
|
await engine.run()
|
|
780
785
|
|
|
786
|
+
// import.meta.glob records the files it matched, not the directory whose
|
|
787
|
+
// membership defines the route map. own that directory watch next to the
|
|
788
|
+
// engine and compare complete route sets on every event. this also covers a
|
|
789
|
+
// router root created after the dev server starts.
|
|
790
|
+
let routeWatchTarget = virtualEntry.routes.routeRoot
|
|
791
|
+
while (!statSync(routeWatchTarget, { throwIfNoEntry: false })?.isDirectory()) {
|
|
792
|
+
const parent = dirname(routeWatchTarget)
|
|
793
|
+
if (parent === routeWatchTarget) break
|
|
794
|
+
routeWatchTarget = parent
|
|
795
|
+
}
|
|
796
|
+
const normalizedRouteRoot = normalizePath(virtualEntry.routes.routeRoot)
|
|
797
|
+
routeWatcher = watch(routeWatchTarget, { recursive: true }, (_eventType, file) => {
|
|
798
|
+
if (file === null) return
|
|
799
|
+
const changed = normalizePath(resolve(routeWatchTarget, file.toString()))
|
|
800
|
+
if (
|
|
801
|
+
changed !== normalizedRouteRoot &&
|
|
802
|
+
!changed.startsWith(`${normalizedRouteRoot}/`)
|
|
803
|
+
) {
|
|
804
|
+
return
|
|
805
|
+
}
|
|
806
|
+
rebuildIfRouteGraphChanged().catch((error) => {
|
|
807
|
+
console.error(`[vxrn] rebuilding the native route map failed`, error)
|
|
808
|
+
})
|
|
809
|
+
})
|
|
810
|
+
routeWatcher.on('error', (error) => {
|
|
811
|
+
console.error(`[vxrn] watching the native route map failed`, error)
|
|
812
|
+
})
|
|
813
|
+
await rebuildIfRouteGraphChanged()
|
|
814
|
+
|
|
781
815
|
return {
|
|
782
816
|
engine,
|
|
783
817
|
|
|
@@ -817,16 +851,9 @@ try {
|
|
|
817
851
|
},
|
|
818
852
|
|
|
819
853
|
async close() {
|
|
854
|
+
routeWatcher?.close()
|
|
820
855
|
await engine.close()
|
|
821
856
|
},
|
|
822
|
-
|
|
823
|
-
// vite's watcher sees a created file even when rolldown's addWatchFile
|
|
824
|
-
// directory watches do not, which is what happens on github's macos
|
|
825
|
-
// runners. scoped to route files so a random write in the project is not
|
|
826
|
-
// a full rebuild.
|
|
827
|
-
async handleAddedFile(file: string) {
|
|
828
|
-
await rebuildIfNewRoute([file])
|
|
829
|
-
},
|
|
830
857
|
}
|
|
831
858
|
}
|
|
832
859
|
|
|
@@ -894,7 +921,7 @@ export async function buildNativeBundle(
|
|
|
894
921
|
shimMissingExports: true,
|
|
895
922
|
moduleTypes: { '.js': 'jsx' },
|
|
896
923
|
plugins: [
|
|
897
|
-
...(entryFile ? [] : [nativeVirtualEntryPlugin(root, { dev }).plugin]),
|
|
924
|
+
...(entryFile ? [] : [nativeVirtualEntryPlugin(root, { dev, platform }).plugin]),
|
|
898
925
|
...getNativePlugins(
|
|
899
926
|
root,
|
|
900
927
|
platform,
|
|
@@ -942,13 +969,20 @@ interface NativeRouteRegistry {
|
|
|
942
969
|
routeRoot: string
|
|
943
970
|
files: Set<string>
|
|
944
971
|
isRouteFile: (file: string) => boolean
|
|
972
|
+
scanFiles: () => Set<string>
|
|
973
|
+
updateFiles: (files: Set<string>) => void
|
|
945
974
|
}
|
|
946
975
|
|
|
947
976
|
function nativeVirtualEntryPlugin(
|
|
948
977
|
root: string,
|
|
949
|
-
opts
|
|
978
|
+
opts: {
|
|
979
|
+
dev?: boolean
|
|
980
|
+
platform: 'ios' | 'android'
|
|
981
|
+
dynamicRoutes?: boolean
|
|
982
|
+
}
|
|
950
983
|
): { plugin: Plugin; routes: NativeRouteRegistry } {
|
|
951
984
|
const isDev = opts?.dev !== false
|
|
985
|
+
const useDynamicRouteRegistry = opts.dynamicRoutes === true
|
|
952
986
|
// absolute for import.meta.glob resolution; forward-slash for module-graph convention
|
|
953
987
|
const resolvedId = normalizePath(resolve(root, '__virtual-native-entry.tsx'))
|
|
954
988
|
|
|
@@ -1001,23 +1035,24 @@ globalThis.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;
|
|
|
1001
1035
|
`
|
|
1002
1036
|
: ''
|
|
1003
1037
|
|
|
1004
|
-
const entryCode = `
|
|
1038
|
+
const entryCode = () => `
|
|
1005
1039
|
${refreshSetup}
|
|
1006
1040
|
import * as ReactNativeInitializeCore from 'react-native/Libraries/Core/InitializeCore';
|
|
1007
1041
|
import NativeWebSocket from 'react-native/Libraries/WebSocket/WebSocket';
|
|
1042
|
+
import DevSettings from 'react-native/Libraries/Utilities/DevSettings';
|
|
1008
1043
|
${setupFileImport}
|
|
1044
|
+
${routeImports}
|
|
1009
1045
|
import { createApp } from 'one';
|
|
1010
1046
|
|
|
1011
1047
|
void ReactNativeInitializeCore;
|
|
1012
1048
|
globalThis.WebSocket = NativeWebSocket;
|
|
1049
|
+
globalThis.__VXRN_RELOAD_NATIVE_DEV_BUNDLE__ = function(reason) {
|
|
1050
|
+
DevSettings.reload(reason);
|
|
1051
|
+
};
|
|
1013
1052
|
|
|
1014
|
-
|
|
1015
|
-
//
|
|
1016
|
-
|
|
1017
|
-
Object.keys(_routes).forEach(function(key) {
|
|
1018
|
-
var normalizedKey = key.replace(/^\\.\\//, '');
|
|
1019
|
-
routes['/' + normalizedKey] = _routes[key];
|
|
1020
|
-
});
|
|
1053
|
+
// React Native reloads a dev bundle in the existing JS runtime. Advance One's
|
|
1054
|
+
// route-cache version so a full reload cannot reuse the previous context.
|
|
1055
|
+
globalThis.__vxrnVersion = (globalThis.__vxrnVersion || 0) + 1;
|
|
1021
1056
|
|
|
1022
1057
|
createApp({
|
|
1023
1058
|
routes: routes,
|
|
@@ -1025,6 +1060,8 @@ createApp({
|
|
|
1025
1060
|
flags: ${JSON.stringify(flags)},
|
|
1026
1061
|
linking: ${JSON.stringify(linking)},
|
|
1027
1062
|
});
|
|
1063
|
+
|
|
1064
|
+
${routeHmr}
|
|
1028
1065
|
`
|
|
1029
1066
|
|
|
1030
1067
|
const routeRoot = resolve(root, routerRoot)
|
|
@@ -1045,11 +1082,66 @@ createApp({
|
|
|
1045
1082
|
!micromatch.isMatch(relativeToRoot, excludeGlobs)
|
|
1046
1083
|
)
|
|
1047
1084
|
}
|
|
1085
|
+
const scanFiles = () => {
|
|
1086
|
+
const files = new Set<string>()
|
|
1087
|
+
const walkRouteRoot = (dir: string) => {
|
|
1088
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
1089
|
+
const child = resolve(dir, entry.name)
|
|
1090
|
+
if (entry.isDirectory()) walkRouteRoot(child)
|
|
1091
|
+
else if (isRouteFile(child)) files.add(normalizePath(child))
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
if (statSync(routeRoot, { throwIfNoEntry: false })?.isDirectory()) {
|
|
1095
|
+
walkRouteRoot(routeRoot)
|
|
1096
|
+
}
|
|
1097
|
+
return files
|
|
1098
|
+
}
|
|
1099
|
+
const registrySpecifier = `./node_modules/.vxrn/native-routes-${opts.platform}.mjs`
|
|
1100
|
+
const registryPath = resolve(root, registrySpecifier)
|
|
1048
1101
|
const routes: NativeRouteRegistry = {
|
|
1049
1102
|
routeRoot,
|
|
1050
1103
|
files: new Set(),
|
|
1051
1104
|
isRouteFile,
|
|
1105
|
+
scanFiles,
|
|
1106
|
+
updateFiles(files) {
|
|
1107
|
+
routes.files = files
|
|
1108
|
+
if (!useDynamicRouteRegistry) return
|
|
1109
|
+
|
|
1110
|
+
const source = `export const routes = {\n${[...files]
|
|
1111
|
+
.sort()
|
|
1112
|
+
.map((file) => {
|
|
1113
|
+
const routeKey = `/${normalizePath(relative(root, file))}`
|
|
1114
|
+
let importPath = normalizePath(relative(dirname(registryPath), file))
|
|
1115
|
+
if (!importPath.startsWith('.')) importPath = `./${importPath}`
|
|
1116
|
+
return ` ${JSON.stringify(routeKey)}: () => import(${JSON.stringify(importPath)})`
|
|
1117
|
+
})
|
|
1118
|
+
.join(',\n')}\n}\n`
|
|
1119
|
+
mkdirSync(dirname(registryPath), { recursive: true })
|
|
1120
|
+
if (!existsSync(registryPath) || readFileSync(registryPath, 'utf8') !== source) {
|
|
1121
|
+
writeFileSync(registryPath, source)
|
|
1122
|
+
}
|
|
1123
|
+
},
|
|
1052
1124
|
}
|
|
1125
|
+
routes.updateFiles(routes.scanFiles())
|
|
1126
|
+
|
|
1127
|
+
const routeImports = useDynamicRouteRegistry
|
|
1128
|
+
? `import { routes } from ${JSON.stringify(registrySpecifier)};`
|
|
1129
|
+
: `var _routes = import.meta.glob(${JSON.stringify(routeGlobs)}, { exhaustive: true });
|
|
1130
|
+
// fix route keys: One expects '/${routerRoot}/...' prefix but import.meta.glob returns './${routerRoot}/...'
|
|
1131
|
+
var routes = {};
|
|
1132
|
+
Object.keys(_routes).forEach(function(key) {
|
|
1133
|
+
var normalizedKey = key.replace(/^\\.\\//, '');
|
|
1134
|
+
routes['/' + normalizedKey] = _routes[key];
|
|
1135
|
+
});`
|
|
1136
|
+
const routeHmr = useDynamicRouteRegistry
|
|
1137
|
+
? `if (import.meta.hot) {
|
|
1138
|
+
import.meta.hot.accept(${JSON.stringify(registrySpecifier)}, function(next) {
|
|
1139
|
+
if (globalThis.__VXRN_UPDATE_NATIVE_ROUTES__) {
|
|
1140
|
+
globalThis.__VXRN_UPDATE_NATIVE_ROUTES__(next.routes);
|
|
1141
|
+
}
|
|
1142
|
+
});
|
|
1143
|
+
}`
|
|
1144
|
+
: ''
|
|
1053
1145
|
|
|
1054
1146
|
return {
|
|
1055
1147
|
routes,
|
|
@@ -1062,35 +1154,7 @@ createApp({
|
|
|
1062
1154
|
},
|
|
1063
1155
|
load(id) {
|
|
1064
1156
|
if (id !== resolvedId) return
|
|
1065
|
-
|
|
1066
|
-
// dependency on the files it matched, never on the directories it
|
|
1067
|
-
// searched. registering those directories is what makes its own watcher
|
|
1068
|
-
// report a route file appearing, and walking them is what lets the
|
|
1069
|
-
// engine tell that report apart from an edit.
|
|
1070
|
-
//
|
|
1071
|
-
// watching a directory is not recursive, so every directory under the
|
|
1072
|
-
// route root is registered. a new subdirectory is itself a change to
|
|
1073
|
-
// its watched parent, and the full build that follows re-runs this
|
|
1074
|
-
// hook, so the build that picks up `app/foo/index.tsx` is also what
|
|
1075
|
-
// starts watching `app/foo`.
|
|
1076
|
-
routes.files.clear()
|
|
1077
|
-
const walkRouteRoot = (dir: string) => {
|
|
1078
|
-
this.addWatchFile(dir)
|
|
1079
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
1080
|
-
const child = resolve(dir, entry.name)
|
|
1081
|
-
if (entry.isDirectory()) walkRouteRoot(child)
|
|
1082
|
-
else if (isRouteFile(child)) routes.files.add(normalizePath(child))
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
|
-
// a project can have no route root at all: `import.meta.glob` then
|
|
1086
|
-
// expands to nothing and the app builds fine. watch the directory that
|
|
1087
|
-
// would hold it, so creating it is the change that starts the walk.
|
|
1088
|
-
if (statSync(routeRoot, { throwIfNoEntry: false })?.isDirectory()) {
|
|
1089
|
-
walkRouteRoot(routeRoot)
|
|
1090
|
-
} else {
|
|
1091
|
-
this.addWatchFile(dirname(routeRoot))
|
|
1092
|
-
}
|
|
1093
|
-
return entryCode
|
|
1157
|
+
return entryCode()
|
|
1094
1158
|
},
|
|
1095
1159
|
},
|
|
1096
1160
|
}
|
|
@@ -1226,6 +1290,29 @@ export function hmrClientNoopPlugin(): Plugin {
|
|
|
1226
1290
|
}
|
|
1227
1291
|
}
|
|
1228
1292
|
|
|
1293
|
+
function reactNativeDedupePlugin(root: string): Plugin {
|
|
1294
|
+
// package managers can install the same react native version at multiple
|
|
1295
|
+
// physical paths. rolldown assigns each path a module identity, so resolving
|
|
1296
|
+
// every entry through the app's instance prevents initializeCore from running twice.
|
|
1297
|
+
let reactNativeRoot: string | undefined
|
|
1298
|
+
|
|
1299
|
+
return {
|
|
1300
|
+
name: 'vxrn:react-native-dedupe',
|
|
1301
|
+
async resolveId(source, importer) {
|
|
1302
|
+
if (source !== 'react-native' && !source.startsWith('react-native/')) return
|
|
1303
|
+
|
|
1304
|
+
reactNativeRoot ||= realpathSync(
|
|
1305
|
+
dirname(resolvePath('react-native/package.json', root))
|
|
1306
|
+
)
|
|
1307
|
+
const subpath =
|
|
1308
|
+
source === 'react-native' ? 'index.js' : source.slice('react-native/'.length)
|
|
1309
|
+
return this.resolve(normalizePath(resolve(reactNativeRoot, subpath)), importer, {
|
|
1310
|
+
skipSelf: true,
|
|
1311
|
+
})
|
|
1312
|
+
},
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1229
1316
|
/**
|
|
1230
1317
|
* Stub CSS imports for native builds.
|
|
1231
1318
|
* Native doesn't support CSS and rolldown removed CSS bundling support.
|
|
@@ -1324,20 +1411,33 @@ export function vxrnCompilerPlugin(
|
|
|
1324
1411
|
if (prepared.map) intermediateMaps.push(prepared.map)
|
|
1325
1412
|
}
|
|
1326
1413
|
}
|
|
1327
|
-
const
|
|
1328
|
-
|
|
1329
|
-
const
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
)
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1414
|
+
const compilerPluginConfig = babelOptions!.plugins![compilerPluginIndex] as any[]
|
|
1415
|
+
const compilerPluginOptions = compilerPluginConfig[1] || {}
|
|
1416
|
+
const useBabelCompiler =
|
|
1417
|
+
compilerPluginOptions.compiler === 'babel' ||
|
|
1418
|
+
process.env.ONE_REACT_COMPILER === 'babel' ||
|
|
1419
|
+
process.env.VXRN_REACT_COMPILER === 'babel'
|
|
1420
|
+
|
|
1421
|
+
if (!useBabelCompiler) {
|
|
1422
|
+
const compilerOut = await compiler.transformOxcReactCompiler(
|
|
1423
|
+
id,
|
|
1424
|
+
curCode,
|
|
1425
|
+
compilerPluginOptions,
|
|
1426
|
+
sourceMaps
|
|
1427
|
+
)
|
|
1428
|
+
if (compilerOut?.code) {
|
|
1429
|
+
curCode = compilerOut.code
|
|
1430
|
+
if (sourceMaps && compilerOut.map) intermediateMaps.push(compilerOut.map)
|
|
1431
|
+
}
|
|
1432
|
+
babelOptions!.plugins!.splice(compilerPluginIndex, 1)
|
|
1433
|
+
if (
|
|
1434
|
+
babelOptions!.plugins!.length === 0 &&
|
|
1435
|
+
!babelOptions!.configFile &&
|
|
1436
|
+
!babelOptions!.babelrc
|
|
1437
|
+
) {
|
|
1438
|
+
babelOptions = null
|
|
1439
|
+
}
|
|
1338
1440
|
}
|
|
1339
|
-
babelOptions!.plugins!.splice(compilerPluginIndex, 1)
|
|
1340
|
-
if (babelOptions!.plugins!.length === 0) babelOptions = null
|
|
1341
1441
|
}
|
|
1342
1442
|
|
|
1343
1443
|
if (useWorklets) {
|
|
@@ -1353,7 +1453,13 @@ export function vxrnCompilerPlugin(
|
|
|
1353
1453
|
babelOptions.plugins = babelOptions.plugins.filter(
|
|
1354
1454
|
(entry) => !isWorkletPlugin(entry)
|
|
1355
1455
|
)
|
|
1356
|
-
if (
|
|
1456
|
+
if (
|
|
1457
|
+
babelOptions.plugins.length === 0 &&
|
|
1458
|
+
!babelOptions.configFile &&
|
|
1459
|
+
!babelOptions.babelrc
|
|
1460
|
+
) {
|
|
1461
|
+
babelOptions = null
|
|
1462
|
+
}
|
|
1357
1463
|
}
|
|
1358
1464
|
}
|
|
1359
1465
|
|
|
@@ -2069,11 +2175,13 @@ class ReactNativeDevRuntime extends BaseDevRuntime {
|
|
|
2069
2175
|
}
|
|
2070
2176
|
}
|
|
2071
2177
|
|
|
2072
|
-
reload() {
|
|
2073
|
-
var
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2178
|
+
reload(reason) {
|
|
2179
|
+
var reloadNativeBundle = globalThis.__VXRN_RELOAD_NATIVE_DEV_BUNDLE__;
|
|
2180
|
+
if (typeof reloadNativeBundle === 'function') {
|
|
2181
|
+
reloadNativeBundle(reason);
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2184
|
+
console.error('[vxrn HMR]: DevSettings.reload is unavailable');
|
|
2077
2185
|
}
|
|
2078
2186
|
|
|
2079
2187
|
setup(socket) {
|
|
@@ -2095,10 +2203,10 @@ class ReactNativeDevRuntime extends BaseDevRuntime {
|
|
|
2095
2203
|
if (message.type === 'hmr:update') {
|
|
2096
2204
|
// a patch that cannot be applied in place is the reload signal
|
|
2097
2205
|
if (!runtime.applyHmrUpdate(message.code, message.changedIds, message.seq)) {
|
|
2098
|
-
runtime.reload();
|
|
2206
|
+
runtime.reload('native HMR update could not be applied');
|
|
2099
2207
|
}
|
|
2100
2208
|
} else if (message.type === 'hmr:reload') {
|
|
2101
|
-
runtime.reload();
|
|
2209
|
+
runtime.reload('native route map changed');
|
|
2102
2210
|
}
|
|
2103
2211
|
} catch (error) {
|
|
2104
2212
|
console.error('[vxrn HMR]: failed to apply update', error);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reactNativeDevServer.d.ts","sourceRoot":"","sources":["../../src/plugins/reactNativeDevServer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,MAAM,EAAiB,MAAM,MAAM,CAAA;AAO1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;AAwBnE,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA4B9D;AAED,wBAAgB,gCAAgC,CAC9C,OAAO,CAAC,EAAE,OAAO,CACf,IAAI,CAAC,iBAAiB,EAAE,UAAU,GAAG,aAAa,GAAG,kBAAkB,GAAG,SAAS,CAAC,CACrF,GACA,MAAM,
|
|
1
|
+
{"version":3,"file":"reactNativeDevServer.d.ts","sourceRoot":"","sources":["../../src/plugins/reactNativeDevServer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,MAAM,EAAiB,MAAM,MAAM,CAAA;AAO1D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;AAwBnE,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA4B9D;AAED,wBAAgB,gCAAgC,CAC9C,OAAO,CAAC,EAAE,OAAO,CACf,IAAI,CAAC,iBAAiB,EAAE,UAAU,GAAG,aAAa,GAAG,kBAAkB,GAAG,SAAS,CAAC,CACrF,GACA,MAAM,CAiXR"}
|
|
@@ -49,7 +49,6 @@ interface NativeDevEngineResult {
|
|
|
49
49
|
getBundle: () => Promise<NativeDevBundle>;
|
|
50
50
|
getAsset: (pathname: string, hash?: string) => NativeDevAsset | undefined;
|
|
51
51
|
close: () => Promise<void>;
|
|
52
|
-
handleAddedFile: (file: string) => Promise<void>;
|
|
53
52
|
}
|
|
54
53
|
export declare function getNativeTransformConfig(platform: 'ios' | 'android', dev: boolean, root: string): {
|
|
55
54
|
jsx: {
|