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
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { basename, dirname, extname, join, relative, resolve } from "node:path";
|
|
3
|
-
import { copyFileSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
3
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync, watch, writeFileSync } from "node:fs";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
import micromatch from "micromatch";
|
|
6
6
|
import { loadEnv, normalizePath } from "vite";
|
|
7
7
|
import { shouldStripFlow, transformHermesAsync } from "@vxrn/compiler";
|
|
8
|
+
import { resolvePath } from "@vxrn/resolve";
|
|
8
9
|
import { DEFAULT_ASSET_EXTS } from "../constants/defaults.mjs";
|
|
9
10
|
import { getNativePrelude } from "../runtime/native-prelude.mjs";
|
|
10
11
|
import { rnCodegenPlugin } from "../plugins/rnCodegenPlugin.mjs";
|
|
@@ -116,7 +117,7 @@ function getNativePlugins(root, platform, viteImportGlobPlugin, dev, assetsDest,
|
|
|
116
117
|
platform,
|
|
117
118
|
dev
|
|
118
119
|
};
|
|
119
|
-
return [...(globalThis.__vxrnAddNativePlugins || []), serverFileExclusionPlugin(), environmentGuardPlugin(), hmrClientNoopPlugin(), cssStubPlugin(), viteImportGlobPlugin({
|
|
120
|
+
return [...(globalThis.__vxrnAddNativePlugins || []), serverFileExclusionPlugin(), environmentGuardPlugin(), hmrClientNoopPlugin(), ...(dev ? [reactNativeDedupePlugin(root)] : []), cssStubPlugin(), viteImportGlobPlugin({
|
|
120
121
|
root
|
|
121
122
|
}), vxrnCompilerPlugin(platform, dev, root, sourceMaps), rnCodegenPlugin({
|
|
122
123
|
projectRoot: root
|
|
@@ -252,7 +253,9 @@ async function createNativeDevEngine(options) {
|
|
|
252
253
|
let bundlePromise = null;
|
|
253
254
|
const resolvedHost = host === "0.0.0.0" ? "localhost" : host;
|
|
254
255
|
const virtualEntry = nativeVirtualEntryPlugin(root, {
|
|
255
|
-
dev: true
|
|
256
|
+
dev: true,
|
|
257
|
+
platform,
|
|
258
|
+
dynamicRoutes: true
|
|
256
259
|
});
|
|
257
260
|
let engineWork = Promise.resolve();
|
|
258
261
|
const queueEngineWork = work => {
|
|
@@ -298,30 +301,18 @@ try {
|
|
|
298
301
|
};
|
|
299
302
|
let outputProcessed = Promise.resolve();
|
|
300
303
|
let engine;
|
|
301
|
-
|
|
304
|
+
let routeWatcher;
|
|
305
|
+
const rebuildIfRouteGraphChanged = () => queueEngineWork(async () => {
|
|
302
306
|
const {
|
|
303
|
-
routeRoot,
|
|
304
307
|
files: knownRoutes,
|
|
305
|
-
|
|
308
|
+
scanFiles,
|
|
309
|
+
updateFiles
|
|
306
310
|
} = virtualEntry.routes;
|
|
307
|
-
const
|
|
308
|
-
if (!
|
|
309
|
-
|
|
310
|
-
if (isRouteFile(normalized)) return !knownRoutes.has(normalized);
|
|
311
|
-
return normalized.startsWith(routeRootPrefix) && statSync(file, {
|
|
312
|
-
throwIfNoEntry: false
|
|
313
|
-
})?.isDirectory() === true;
|
|
314
|
-
})) return false;
|
|
315
|
-
await queueEngineWork(async () => {
|
|
316
|
-
engine.triggerFullBuild();
|
|
317
|
-
await engine.ensureLatestBuildOutput();
|
|
318
|
-
await outputProcessed;
|
|
319
|
-
});
|
|
320
|
-
onHmrUpdate?.({
|
|
321
|
-
type: "hmr:reload"
|
|
322
|
-
});
|
|
311
|
+
const currentRoutes = scanFiles();
|
|
312
|
+
if (!(currentRoutes.size !== knownRoutes.size || [...currentRoutes].some(route => !knownRoutes.has(route)))) return false;
|
|
313
|
+
updateFiles(currentRoutes);
|
|
323
314
|
return true;
|
|
324
|
-
};
|
|
315
|
+
});
|
|
325
316
|
engine = await dev(inputOptions, outputOptions, {
|
|
326
317
|
onOutput: async result => {
|
|
327
318
|
let finishOutput = () => {};
|
|
@@ -342,7 +333,7 @@ try {
|
|
|
342
333
|
});
|
|
343
334
|
return;
|
|
344
335
|
}
|
|
345
|
-
if (await
|
|
336
|
+
if (await rebuildIfRouteGraphChanged()) return;
|
|
346
337
|
for (const {
|
|
347
338
|
clientId,
|
|
348
339
|
update
|
|
@@ -398,6 +389,29 @@ try {
|
|
|
398
389
|
}
|
|
399
390
|
}
|
|
400
391
|
await engine.run();
|
|
392
|
+
let routeWatchTarget = virtualEntry.routes.routeRoot;
|
|
393
|
+
while (!statSync(routeWatchTarget, {
|
|
394
|
+
throwIfNoEntry: false
|
|
395
|
+
})?.isDirectory()) {
|
|
396
|
+
const parent = dirname(routeWatchTarget);
|
|
397
|
+
if (parent === routeWatchTarget) break;
|
|
398
|
+
routeWatchTarget = parent;
|
|
399
|
+
}
|
|
400
|
+
const normalizedRouteRoot = normalizePath(virtualEntry.routes.routeRoot);
|
|
401
|
+
routeWatcher = watch(routeWatchTarget, {
|
|
402
|
+
recursive: true
|
|
403
|
+
}, (_eventType, file) => {
|
|
404
|
+
if (file === null) return;
|
|
405
|
+
const changed = normalizePath(resolve(routeWatchTarget, file.toString()));
|
|
406
|
+
if (changed !== normalizedRouteRoot && !changed.startsWith(`${normalizedRouteRoot}/`)) return;
|
|
407
|
+
rebuildIfRouteGraphChanged().catch(error => {
|
|
408
|
+
console.error(`[vxrn] rebuilding the native route map failed`, error);
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
routeWatcher.on("error", error => {
|
|
412
|
+
console.error(`[vxrn] watching the native route map failed`, error);
|
|
413
|
+
});
|
|
414
|
+
await rebuildIfRouteGraphChanged();
|
|
401
415
|
return {
|
|
402
416
|
engine,
|
|
403
417
|
async getBundle() {
|
|
@@ -432,10 +446,8 @@ try {
|
|
|
432
446
|
return assetRegistry.resolve(pathname, hash);
|
|
433
447
|
},
|
|
434
448
|
async close() {
|
|
449
|
+
routeWatcher?.close();
|
|
435
450
|
await engine.close();
|
|
436
|
-
},
|
|
437
|
-
async handleAddedFile(file) {
|
|
438
|
-
await rebuildIfNewRoute([file]);
|
|
439
451
|
}
|
|
440
452
|
};
|
|
441
453
|
}
|
|
@@ -477,7 +489,8 @@ async function buildNativeBundle(options) {
|
|
|
477
489
|
".js": "jsx"
|
|
478
490
|
},
|
|
479
491
|
plugins: [...(entryFile ? [] : [nativeVirtualEntryPlugin(root, {
|
|
480
|
-
dev
|
|
492
|
+
dev,
|
|
493
|
+
platform
|
|
481
494
|
}).plugin]), ...getNativePlugins(root, platform, viteImportGlobPlugin, dev, assetsDest, void 0, sourcemap, userPlugins), nativeBundlePostProcessPlugin()],
|
|
482
495
|
output: getNativeOutputOptions(prelude, sourcemap, minify)
|
|
483
496
|
})).output.find(o => o.type === "chunk" && o.isEntry);
|
|
@@ -491,6 +504,7 @@ async function buildNativeBundle(options) {
|
|
|
491
504
|
const VIRTUAL_NATIVE_ENTRY = "virtual:native-entry";
|
|
492
505
|
function nativeVirtualEntryPlugin(root, opts) {
|
|
493
506
|
const isDev = opts?.dev !== false;
|
|
507
|
+
const useDynamicRouteRegistry = opts.dynamicRoutes === true;
|
|
494
508
|
const resolvedId = normalizePath(resolve(root, "__virtual-native-entry.tsx"));
|
|
495
509
|
const entryConfig = globalThis.__vxrnNativeEntryConfig || {};
|
|
496
510
|
const routerRoot = entryConfig.routerRoot || "app";
|
|
@@ -505,8 +519,7 @@ function nativeVirtualEntryPlugin(root, opts) {
|
|
|
505
519
|
return `import ${JSON.stringify(resolved)};`;
|
|
506
520
|
})();
|
|
507
521
|
const routeGlobs = [`./${routerRoot}/**/*.tsx`, `./${routerRoot}/**/*.ts`, `!./${routerRoot}/**/*+api.*`, `!./${routerRoot}/**/*.test.*`, `!./${routerRoot}/**/*.d.ts`, `!./${routerRoot}/**/*.server.*`, `!./${routerRoot}/**/_middleware.*`, `!./${routerRoot}/**/*.web.*`, ...(entryConfig.ignoredRouteFiles || []).map(pattern => `!./${routerRoot}/${pattern}`)];
|
|
508
|
-
const
|
|
509
|
-
${isDev ? `
|
|
522
|
+
const refreshSetup = isDev ? `
|
|
510
523
|
// react-refresh/runtime MUST initialize before React loads
|
|
511
524
|
import RefreshRuntime from 'react-refresh/runtime';
|
|
512
525
|
RefreshRuntime.injectIntoGlobalHook(globalThis);
|
|
@@ -515,22 +528,25 @@ globalThis.$RefreshReg$ = function(type, id) {
|
|
|
515
528
|
RefreshRuntime.register(type, id);
|
|
516
529
|
};
|
|
517
530
|
globalThis.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;
|
|
518
|
-
` : ""
|
|
531
|
+
` : "";
|
|
532
|
+
const entryCode = () => `
|
|
533
|
+
${refreshSetup}
|
|
519
534
|
import * as ReactNativeInitializeCore from 'react-native/Libraries/Core/InitializeCore';
|
|
520
535
|
import NativeWebSocket from 'react-native/Libraries/WebSocket/WebSocket';
|
|
536
|
+
import DevSettings from 'react-native/Libraries/Utilities/DevSettings';
|
|
521
537
|
${setupFileImport}
|
|
538
|
+
${routeImports}
|
|
522
539
|
import { createApp } from 'one';
|
|
523
540
|
|
|
524
541
|
void ReactNativeInitializeCore;
|
|
525
542
|
globalThis.WebSocket = NativeWebSocket;
|
|
543
|
+
globalThis.__VXRN_RELOAD_NATIVE_DEV_BUNDLE__ = function(reason) {
|
|
544
|
+
DevSettings.reload(reason);
|
|
545
|
+
};
|
|
526
546
|
|
|
527
|
-
|
|
528
|
-
//
|
|
529
|
-
|
|
530
|
-
Object.keys(_routes).forEach(function(key) {
|
|
531
|
-
var normalizedKey = key.replace(/^\\.\\//, '');
|
|
532
|
-
routes['/' + normalizedKey] = _routes[key];
|
|
533
|
-
});
|
|
547
|
+
// React Native reloads a dev bundle in the existing JS runtime. Advance One's
|
|
548
|
+
// route-cache version so a full reload cannot reuse the previous context.
|
|
549
|
+
globalThis.__vxrnVersion = (globalThis.__vxrnVersion || 0) + 1;
|
|
534
550
|
|
|
535
551
|
createApp({
|
|
536
552
|
routes: routes,
|
|
@@ -538,6 +554,8 @@ createApp({
|
|
|
538
554
|
flags: ${JSON.stringify(flags)},
|
|
539
555
|
linking: ${JSON.stringify(linking)},
|
|
540
556
|
});
|
|
557
|
+
|
|
558
|
+
${routeHmr}
|
|
541
559
|
`;
|
|
542
560
|
const routeRoot = resolve(root, routerRoot);
|
|
543
561
|
const stripDot = pattern => pattern.replace(/^\.\//, "");
|
|
@@ -547,11 +565,61 @@ createApp({
|
|
|
547
565
|
const relativeToRoot = normalizePath(relative(root, file));
|
|
548
566
|
return micromatch.isMatch(relativeToRoot, includeGlobs) && !micromatch.isMatch(relativeToRoot, excludeGlobs);
|
|
549
567
|
};
|
|
568
|
+
const scanFiles = () => {
|
|
569
|
+
const files = /* @__PURE__ */new Set();
|
|
570
|
+
const walkRouteRoot = dir => {
|
|
571
|
+
for (const entry of readdirSync(dir, {
|
|
572
|
+
withFileTypes: true
|
|
573
|
+
})) {
|
|
574
|
+
const child = resolve(dir, entry.name);
|
|
575
|
+
if (entry.isDirectory()) walkRouteRoot(child);else if (isRouteFile(child)) files.add(normalizePath(child));
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
if (statSync(routeRoot, {
|
|
579
|
+
throwIfNoEntry: false
|
|
580
|
+
})?.isDirectory()) walkRouteRoot(routeRoot);
|
|
581
|
+
return files;
|
|
582
|
+
};
|
|
583
|
+
const registrySpecifier = `./node_modules/.vxrn/native-routes-${opts.platform}.mjs`;
|
|
584
|
+
const registryPath = resolve(root, registrySpecifier);
|
|
550
585
|
const routes = {
|
|
551
586
|
routeRoot,
|
|
552
587
|
files: /* @__PURE__ */new Set(),
|
|
553
|
-
isRouteFile
|
|
588
|
+
isRouteFile,
|
|
589
|
+
scanFiles,
|
|
590
|
+
updateFiles(files) {
|
|
591
|
+
routes.files = files;
|
|
592
|
+
if (!useDynamicRouteRegistry) return;
|
|
593
|
+
const source = `export const routes = {
|
|
594
|
+
${[...files].sort().map(file => {
|
|
595
|
+
const routeKey = `/${normalizePath(relative(root, file))}`;
|
|
596
|
+
let importPath = normalizePath(relative(dirname(registryPath), file));
|
|
597
|
+
if (!importPath.startsWith(".")) importPath = `./${importPath}`;
|
|
598
|
+
return ` ${JSON.stringify(routeKey)}: () => import(${JSON.stringify(importPath)})`;
|
|
599
|
+
}).join(",\n")}
|
|
600
|
+
}
|
|
601
|
+
`;
|
|
602
|
+
mkdirSync(dirname(registryPath), {
|
|
603
|
+
recursive: true
|
|
604
|
+
});
|
|
605
|
+
if (!existsSync(registryPath) || readFileSync(registryPath, "utf8") !== source) writeFileSync(registryPath, source);
|
|
606
|
+
}
|
|
554
607
|
};
|
|
608
|
+
routes.updateFiles(routes.scanFiles());
|
|
609
|
+
const routeImports = useDynamicRouteRegistry ? `import { routes } from ${JSON.stringify(registrySpecifier)};` : `var _routes = import.meta.glob(${JSON.stringify(routeGlobs)}, { exhaustive: true });
|
|
610
|
+
// fix route keys: One expects '/${routerRoot}/...' prefix but import.meta.glob returns './${routerRoot}/...'
|
|
611
|
+
var routes = {};
|
|
612
|
+
Object.keys(_routes).forEach(function(key) {
|
|
613
|
+
var normalizedKey = key.replace(/^\\.\\//, '');
|
|
614
|
+
routes['/' + normalizedKey] = _routes[key];
|
|
615
|
+
});`;
|
|
616
|
+
const routeHmr = useDynamicRouteRegistry ? `if (import.meta.hot) {
|
|
617
|
+
import.meta.hot.accept(${JSON.stringify(registrySpecifier)}, function(next) {
|
|
618
|
+
if (globalThis.__VXRN_UPDATE_NATIVE_ROUTES__) {
|
|
619
|
+
globalThis.__VXRN_UPDATE_NATIVE_ROUTES__(next.routes);
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
}` : "";
|
|
555
623
|
return {
|
|
556
624
|
routes,
|
|
557
625
|
plugin: {
|
|
@@ -561,20 +629,7 @@ createApp({
|
|
|
561
629
|
},
|
|
562
630
|
load(id) {
|
|
563
631
|
if (id !== resolvedId) return;
|
|
564
|
-
|
|
565
|
-
const walkRouteRoot = dir => {
|
|
566
|
-
this.addWatchFile(dir);
|
|
567
|
-
for (const entry of readdirSync(dir, {
|
|
568
|
-
withFileTypes: true
|
|
569
|
-
})) {
|
|
570
|
-
const child = resolve(dir, entry.name);
|
|
571
|
-
if (entry.isDirectory()) walkRouteRoot(child);else if (isRouteFile(child)) routes.files.add(normalizePath(child));
|
|
572
|
-
}
|
|
573
|
-
};
|
|
574
|
-
if (statSync(routeRoot, {
|
|
575
|
-
throwIfNoEntry: false
|
|
576
|
-
})?.isDirectory()) walkRouteRoot(routeRoot);else this.addWatchFile(dirname(routeRoot));
|
|
577
|
-
return entryCode;
|
|
632
|
+
return entryCode();
|
|
578
633
|
}
|
|
579
634
|
}
|
|
580
635
|
};
|
|
@@ -660,6 +715,20 @@ export default HMRClient`,
|
|
|
660
715
|
}
|
|
661
716
|
};
|
|
662
717
|
}
|
|
718
|
+
function reactNativeDedupePlugin(root) {
|
|
719
|
+
let reactNativeRoot;
|
|
720
|
+
return {
|
|
721
|
+
name: "vxrn:react-native-dedupe",
|
|
722
|
+
async resolveId(source, importer) {
|
|
723
|
+
if (source !== "react-native" && !source.startsWith("react-native/")) return;
|
|
724
|
+
reactNativeRoot ||= realpathSync(dirname(resolvePath("react-native/package.json", root)));
|
|
725
|
+
const subpath = source === "react-native" ? "index.js" : source.slice(13);
|
|
726
|
+
return this.resolve(normalizePath(resolve(reactNativeRoot, subpath)), importer, {
|
|
727
|
+
skipSelf: true
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
}
|
|
663
732
|
function cssStubPlugin() {
|
|
664
733
|
return {
|
|
665
734
|
name: "vxrn:css-stub",
|
|
@@ -714,14 +783,16 @@ function vxrnCompilerPlugin(platform, dev, projectRoot = process.cwd(), sourceMa
|
|
|
714
783
|
if (prepared.map) intermediateMaps.push(prepared.map);
|
|
715
784
|
}
|
|
716
785
|
}
|
|
717
|
-
const
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
786
|
+
const compilerPluginOptions = babelOptions.plugins[compilerPluginIndex][1] || {};
|
|
787
|
+
if (!(compilerPluginOptions.compiler === "babel" || process.env.ONE_REACT_COMPILER === "babel" || process.env.VXRN_REACT_COMPILER === "babel")) {
|
|
788
|
+
const compilerOut = await compiler.transformOxcReactCompiler(id, curCode, compilerPluginOptions, sourceMaps);
|
|
789
|
+
if (compilerOut?.code) {
|
|
790
|
+
curCode = compilerOut.code;
|
|
791
|
+
if (sourceMaps && compilerOut.map) intermediateMaps.push(compilerOut.map);
|
|
792
|
+
}
|
|
793
|
+
babelOptions.plugins.splice(compilerPluginIndex, 1);
|
|
794
|
+
if (babelOptions.plugins.length === 0 && !babelOptions.configFile && !babelOptions.babelrc) babelOptions = null;
|
|
722
795
|
}
|
|
723
|
-
babelOptions.plugins.splice(compilerPluginIndex, 1);
|
|
724
|
-
if (babelOptions.plugins.length === 0) babelOptions = null;
|
|
725
796
|
}
|
|
726
797
|
if (useWorklets) {
|
|
727
798
|
const workletOut = await compiler.transformWorklets(id, curCode, sourceMaps, {
|
|
@@ -734,7 +805,7 @@ function vxrnCompilerPlugin(platform, dev, projectRoot = process.cwd(), sourceMa
|
|
|
734
805
|
}
|
|
735
806
|
if (babelOptions?.plugins) {
|
|
736
807
|
babelOptions.plugins = babelOptions.plugins.filter(entry => !isWorkletPlugin(entry));
|
|
737
|
-
if (babelOptions.plugins.length === 0) babelOptions = null;
|
|
808
|
+
if (babelOptions.plugins.length === 0 && !babelOptions.configFile && !babelOptions.babelrc) babelOptions = null;
|
|
738
809
|
}
|
|
739
810
|
}
|
|
740
811
|
if (!babelOptions && !needsRefresh) {
|
|
@@ -1256,11 +1327,13 @@ class ReactNativeDevRuntime extends BaseDevRuntime {
|
|
|
1256
1327
|
}
|
|
1257
1328
|
}
|
|
1258
1329
|
|
|
1259
|
-
reload() {
|
|
1260
|
-
var
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1330
|
+
reload(reason) {
|
|
1331
|
+
var reloadNativeBundle = globalThis.__VXRN_RELOAD_NATIVE_DEV_BUNDLE__;
|
|
1332
|
+
if (typeof reloadNativeBundle === 'function') {
|
|
1333
|
+
reloadNativeBundle(reason);
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
console.error('[vxrn HMR]: DevSettings.reload is unavailable');
|
|
1264
1337
|
}
|
|
1265
1338
|
|
|
1266
1339
|
setup(socket) {
|
|
@@ -1282,10 +1355,10 @@ class ReactNativeDevRuntime extends BaseDevRuntime {
|
|
|
1282
1355
|
if (message.type === 'hmr:update') {
|
|
1283
1356
|
// a patch that cannot be applied in place is the reload signal
|
|
1284
1357
|
if (!runtime.applyHmrUpdate(message.code, message.changedIds, message.seq)) {
|
|
1285
|
-
runtime.reload();
|
|
1358
|
+
runtime.reload('native HMR update could not be applied');
|
|
1286
1359
|
}
|
|
1287
1360
|
} else if (message.type === 'hmr:reload') {
|
|
1288
|
-
runtime.reload();
|
|
1361
|
+
runtime.reload('native route map changed');
|
|
1289
1362
|
}
|
|
1290
1363
|
} catch (error) {
|
|
1291
1364
|
console.error('[vxrn HMR]: failed to apply update', error);
|