vxrn 1.13.5 → 1.13.7
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/exports/prebuild.mjs +12 -1
- package/dist/exports/prebuild.mjs.map +1 -1
- package/dist/exports/prebuild.native.js +18 -0
- package/dist/exports/prebuild.native.js.map +1 -1
- package/dist/exports/serveStaticAssets.mjs +3 -2
- package/dist/exports/serveStaticAssets.mjs.map +1 -1
- package/dist/exports/serveStaticAssets.native.js +3 -2
- package/dist/exports/serveStaticAssets.native.js.map +1 -1
- package/dist/utils/createNativeDevEngine.mjs +211 -103
- package/dist/utils/createNativeDevEngine.mjs.map +1 -1
- package/dist/utils/createNativeDevEngine.native.js +258 -113
- package/dist/utils/createNativeDevEngine.native.js.map +1 -1
- package/package.json +11 -11
- package/src/exports/prebuild.ts +51 -0
- package/src/exports/serveStaticAssets.ts +4 -1
- package/src/utils/createNativeDevEngine.ts +258 -181
- package/types/exports/prebuild.d.ts.map +1 -1
- package/types/exports/serveStaticAssets.d.ts.map +1 -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.13.
|
|
3
|
+
"version": "1.13.7",
|
|
4
4
|
"sideEffects": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -59,16 +59,16 @@
|
|
|
59
59
|
"@expo/config-plugins": "~55.0.6",
|
|
60
60
|
"@hono/node-server": "^1.19.10",
|
|
61
61
|
"@react-native/dev-middleware": "^0.83.4",
|
|
62
|
-
"@vxrn/compiler": "1.13.
|
|
63
|
-
"@vxrn/debug": "1.13.
|
|
64
|
-
"@vxrn/query-string": "1.13.
|
|
65
|
-
"@vxrn/resolve": "1.13.
|
|
66
|
-
"@vxrn/safe-area": "1.13.
|
|
67
|
-
"@vxrn/url-parse": "1.13.
|
|
68
|
-
"@vxrn/utils": "1.13.
|
|
69
|
-
"@vxrn/vendor": "1.13.
|
|
70
|
-
"@vxrn/vite-flow": "1.13.
|
|
71
|
-
"@vxrn/vite-plugin-metro": "1.13.
|
|
62
|
+
"@vxrn/compiler": "1.13.7",
|
|
63
|
+
"@vxrn/debug": "1.13.7",
|
|
64
|
+
"@vxrn/query-string": "1.13.7",
|
|
65
|
+
"@vxrn/resolve": "1.13.7",
|
|
66
|
+
"@vxrn/safe-area": "1.13.7",
|
|
67
|
+
"@vxrn/url-parse": "1.13.7",
|
|
68
|
+
"@vxrn/utils": "1.13.7",
|
|
69
|
+
"@vxrn/vendor": "1.13.7",
|
|
70
|
+
"@vxrn/vite-flow": "1.13.7",
|
|
71
|
+
"@vxrn/vite-plugin-metro": "1.13.7",
|
|
72
72
|
"citty": "^0.1.6",
|
|
73
73
|
"dotenv": "^17.2.1",
|
|
74
74
|
"dotenv-expand": "^12.0.2",
|
package/src/exports/prebuild.ts
CHANGED
|
@@ -62,6 +62,19 @@ export const prebuild = async ({
|
|
|
62
62
|
)
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
// in rolldown mode (not metro), ensure Podfile.properties.json has the
|
|
66
|
+
// settings needed for hermes v1 with source-built react native
|
|
67
|
+
if (!platform || platform === 'ios') {
|
|
68
|
+
const isMetro =
|
|
69
|
+
process.env.ONE_METRO_MODE ||
|
|
70
|
+
globalThis['__vxrnMetroOptions__'] ||
|
|
71
|
+
globalThis['__vxrnPluginConfig__']?.native?.bundler === 'metro'
|
|
72
|
+
|
|
73
|
+
if (!isMetro) {
|
|
74
|
+
ensureRolldownPodfileProperties(root)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
65
78
|
// validate swift 6 workaround was injected into Podfile (outside prebuild try/catch
|
|
66
79
|
// so a validation error doesn't produce a misleading "Is expo listed?" message)
|
|
67
80
|
if (!platform || platform === 'ios') {
|
|
@@ -291,3 +304,41 @@ function findXcworkspaceName(directory: string): string | null {
|
|
|
291
304
|
}
|
|
292
305
|
return null
|
|
293
306
|
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* In rolldown mode the bundle contains class syntax that only hermes v1
|
|
310
|
+
* can parse, and hermes v1 requires react native to be built from source
|
|
311
|
+
* so the frameworks link against the correct hermes symbols.
|
|
312
|
+
*/
|
|
313
|
+
function ensureRolldownPodfileProperties(root: string) {
|
|
314
|
+
const propsPath = path.join(root, 'ios', 'Podfile.properties.json')
|
|
315
|
+
if (!FSExtra.existsSync(path.join(root, 'ios'))) return
|
|
316
|
+
|
|
317
|
+
let props: Record<string, string> = {}
|
|
318
|
+
if (FSExtra.existsSync(propsPath)) {
|
|
319
|
+
try {
|
|
320
|
+
props = JSON.parse(FSExtra.readFileSync(propsPath, 'utf8'))
|
|
321
|
+
} catch {}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
let changed = false
|
|
325
|
+
|
|
326
|
+
if (props['expo.useHermesV1'] !== 'true') {
|
|
327
|
+
props['expo.useHermesV1'] = 'true'
|
|
328
|
+
changed = true
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (props['ios.buildReactNativeFromSource'] !== 'true') {
|
|
332
|
+
props['ios.buildReactNativeFromSource'] = 'true'
|
|
333
|
+
changed = true
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (changed) {
|
|
337
|
+
FSExtra.writeFileSync(propsPath, JSON.stringify(props, null, 2) + '\n', 'utf8')
|
|
338
|
+
console.info(
|
|
339
|
+
colors.cyan(
|
|
340
|
+
`[vxrn] Updated ios/Podfile.properties.json for rolldown mode (hermes v1 + build from source)`
|
|
341
|
+
)
|
|
342
|
+
)
|
|
343
|
+
}
|
|
344
|
+
}
|
|
@@ -3,6 +3,9 @@ import type { Context } from 'hono'
|
|
|
3
3
|
import micromatch from 'micromatch'
|
|
4
4
|
|
|
5
5
|
// hashed assets can be cached forever, html must revalidate
|
|
6
|
+
// vite/rolldown always puts content-hashed files in the assets/ directory
|
|
7
|
+
const assetsPathRe = /[\\/]assets[\\/]/
|
|
8
|
+
// fallback regex for hashed filenames outside assets/
|
|
6
9
|
const hashedAssetRe = /[.-](?=[a-zA-Z0-9_-]*\d)[a-zA-Z0-9_-]{8,}\.\w+$/
|
|
7
10
|
|
|
8
11
|
export type CompiledCacheRules = { re: RegExp; values: string[] }
|
|
@@ -81,7 +84,7 @@ export async function serveStaticAssets({
|
|
|
81
84
|
}
|
|
82
85
|
}
|
|
83
86
|
|
|
84
|
-
if (hashedAssetRe.test(fsPath)) {
|
|
87
|
+
if (assetsPathRe.test(fsPath) || hashedAssetRe.test(fsPath)) {
|
|
85
88
|
c.header('Cache-Control', 'public, immutable, max-age=31536000')
|
|
86
89
|
} else {
|
|
87
90
|
c.header('Cache-Control', 'public, max-age=0, must-revalidate')
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { basename, dirname, extname, join, relative, resolve } from 'node:path'
|
|
10
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
10
11
|
import type { InputOptions, OutputOptions, Plugin, RolldownOutput } from 'rolldown'
|
|
11
12
|
import { DEFAULT_ASSET_EXTS } from '../constants/defaults'
|
|
12
13
|
import { getNativePrelude } from '../runtime/native-prelude'
|
|
@@ -51,7 +52,89 @@ function getNativeResolveConfig(platform: 'ios' | 'android') {
|
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
// shared rolldown transform config for native builds
|
|
54
|
-
function getNativeTransformConfig(
|
|
55
|
+
function getNativeTransformConfig(
|
|
56
|
+
platform: 'ios' | 'android',
|
|
57
|
+
dev: boolean,
|
|
58
|
+
root: string
|
|
59
|
+
) {
|
|
60
|
+
// read setupFile defines from One's config (mirrors one:init-config define block)
|
|
61
|
+
const entryConfig = (globalThis as any).__vxrnNativeEntryConfig || {}
|
|
62
|
+
const setupFileDefines = (() => {
|
|
63
|
+
const sf = entryConfig.setupFile
|
|
64
|
+
if (!sf) return {}
|
|
65
|
+
const files =
|
|
66
|
+
typeof sf === 'string'
|
|
67
|
+
? { client: sf, server: sf, ios: sf, android: sf }
|
|
68
|
+
: 'native' in sf
|
|
69
|
+
? { client: sf.client, server: sf.server, ios: sf.native, android: sf.native }
|
|
70
|
+
: sf
|
|
71
|
+
return {
|
|
72
|
+
...(files.client && {
|
|
73
|
+
'process.env.ONE_SETUP_FILE_CLIENT': JSON.stringify(files.client),
|
|
74
|
+
}),
|
|
75
|
+
...(files.server && {
|
|
76
|
+
'process.env.ONE_SETUP_FILE_SERVER': JSON.stringify(files.server),
|
|
77
|
+
}),
|
|
78
|
+
...(files.ios && { 'process.env.ONE_SETUP_FILE_IOS': JSON.stringify(files.ios) }),
|
|
79
|
+
...(files.android && {
|
|
80
|
+
'process.env.ONE_SETUP_FILE_ANDROID': JSON.stringify(files.android),
|
|
81
|
+
}),
|
|
82
|
+
}
|
|
83
|
+
})()
|
|
84
|
+
|
|
85
|
+
// load .env files for VITE_* variables (mirrors what Vite does)
|
|
86
|
+
const envDefines = (() => {
|
|
87
|
+
const defines: Record<string, string> = {}
|
|
88
|
+
try {
|
|
89
|
+
const mode = dev ? 'development' : 'production'
|
|
90
|
+
// load .env, .env.local, .env.[mode], .env.[mode].local (same order as Vite)
|
|
91
|
+
for (const envFile of [
|
|
92
|
+
'.env',
|
|
93
|
+
'.env.local',
|
|
94
|
+
`.env.${mode}`,
|
|
95
|
+
`.env.${mode}.local`,
|
|
96
|
+
]) {
|
|
97
|
+
const envPath = join(root, envFile)
|
|
98
|
+
if (!existsSync(envPath)) continue
|
|
99
|
+
const content = readFileSync(envPath, 'utf8')
|
|
100
|
+
for (const line of content.split('\n')) {
|
|
101
|
+
const match = line.match(/^\s*(VITE_\w+)\s*=\s*(.*)$/)
|
|
102
|
+
if (match) {
|
|
103
|
+
const [, key, rawVal] = match
|
|
104
|
+
const val = rawVal.replace(/^['"]|['"]$/g, '').trim()
|
|
105
|
+
defines[`import.meta.env.${key}`] = JSON.stringify(val)
|
|
106
|
+
defines[`process.env.${key}`] = JSON.stringify(val)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
} catch {}
|
|
111
|
+
return defines
|
|
112
|
+
})()
|
|
113
|
+
|
|
114
|
+
const mode = dev ? 'development' : 'production'
|
|
115
|
+
|
|
116
|
+
// build the full import.meta.env object for when it's used as a whole (e.g. JSON.stringify(import.meta.env))
|
|
117
|
+
const envObject: Record<string, any> = {
|
|
118
|
+
MODE: mode,
|
|
119
|
+
DEV: dev,
|
|
120
|
+
PROD: !dev,
|
|
121
|
+
SSR: false,
|
|
122
|
+
VITE_ENVIRONMENT: platform,
|
|
123
|
+
VITE_NATIVE: '1',
|
|
124
|
+
EXPO_OS: platform,
|
|
125
|
+
}
|
|
126
|
+
// add VITE_* from .env files
|
|
127
|
+
for (const [key, val] of Object.entries(envDefines)) {
|
|
128
|
+
const match = key.match(/^import\.meta\.env\.(.+)$/)
|
|
129
|
+
if (match) {
|
|
130
|
+
try {
|
|
131
|
+
envObject[match[1]] = JSON.parse(val as string)
|
|
132
|
+
} catch {
|
|
133
|
+
envObject[match[1]] = val
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
55
138
|
return {
|
|
56
139
|
jsx: {
|
|
57
140
|
// use 'classic' mode (babel plugin-transform-react-jsx)
|
|
@@ -59,9 +142,25 @@ function getNativeTransformConfig(dev: boolean) {
|
|
|
59
142
|
runtime: 'classic' as const,
|
|
60
143
|
},
|
|
61
144
|
define: {
|
|
62
|
-
'process.env.NODE_ENV':
|
|
145
|
+
'process.env.NODE_ENV': JSON.stringify(mode),
|
|
63
146
|
'process.env.VXRN_REACT_19': 'false',
|
|
147
|
+
'process.env.VITE_ENVIRONMENT': JSON.stringify(platform),
|
|
148
|
+
'process.env.VITE_NATIVE': '"1"',
|
|
149
|
+
'process.env.EXPO_OS': JSON.stringify(platform),
|
|
150
|
+
'process.env.TAMAGUI_ENVIRONMENT': JSON.stringify(platform),
|
|
64
151
|
__DEV__: dev ? 'true' : 'false',
|
|
152
|
+
// import.meta.env as a whole object (for JSON.stringify(import.meta.env) etc.)
|
|
153
|
+
'import.meta.env': JSON.stringify(envObject),
|
|
154
|
+
// import.meta.env.* individual properties (for direct access)
|
|
155
|
+
'import.meta.env.MODE': JSON.stringify(mode),
|
|
156
|
+
'import.meta.env.DEV': dev ? 'true' : 'false',
|
|
157
|
+
'import.meta.env.PROD': dev ? 'false' : 'true',
|
|
158
|
+
'import.meta.env.SSR': 'false',
|
|
159
|
+
'import.meta.env.VITE_ENVIRONMENT': JSON.stringify(platform),
|
|
160
|
+
'import.meta.env.VITE_NATIVE': '"1"',
|
|
161
|
+
'import.meta.env.EXPO_OS': JSON.stringify(platform),
|
|
162
|
+
...envDefines,
|
|
163
|
+
...setupFileDefines,
|
|
65
164
|
},
|
|
66
165
|
// auto-inject React import for classic JSX (React.createElement)
|
|
67
166
|
inject: {
|
|
@@ -78,10 +177,16 @@ function getNativePlugins(
|
|
|
78
177
|
dev: boolean
|
|
79
178
|
): Plugin[] {
|
|
80
179
|
return [
|
|
180
|
+
// plugins provided by One (clientTreeShakePlugin for loader removal, etc.)
|
|
181
|
+
...(globalThis.__vxrnAddNativePlugins || []),
|
|
182
|
+
// block .server.* and _middleware.* files from entering the native bundle
|
|
183
|
+
serverFileExclusionPlugin(),
|
|
184
|
+
// guard server-only / client-only / web-only / native-only imports
|
|
185
|
+
environmentGuardPlugin(),
|
|
81
186
|
// stub CSS imports — native doesn't support CSS and rolldown removed CSS bundling
|
|
82
187
|
cssStubPlugin(),
|
|
83
188
|
// handle import.meta.glob (used by One's route system)
|
|
84
|
-
viteImportGlobPlugin({ root })
|
|
189
|
+
viteImportGlobPlugin({ root }),
|
|
85
190
|
// strip Flow types from react-native and @react-native packages
|
|
86
191
|
flowStripPlugin(),
|
|
87
192
|
// handle asset imports (.png, .jpg, .ttf, etc.)
|
|
@@ -91,10 +196,6 @@ function getNativePlugins(
|
|
|
91
196
|
vxrnCompilerPlugin(platform, dev),
|
|
92
197
|
// hermes compat: transform class properties and private fields
|
|
93
198
|
hermesCompatSWCPlugin(dev),
|
|
94
|
-
// downgrade polyfill "not configurable" errors to warnings (hermes v1)
|
|
95
|
-
polyfillErrorDowngradePlugin(),
|
|
96
|
-
// strip DevSettings in prod (dev-only native module)
|
|
97
|
-
stripDevSettingsPlugin(dev),
|
|
98
199
|
]
|
|
99
200
|
}
|
|
100
201
|
|
|
@@ -113,7 +214,6 @@ function getNativeOutputOptions(prelude: string): OutputOptions {
|
|
|
113
214
|
* Post-process a native bundle to fix rolldown devMode output quirks.
|
|
114
215
|
* Most concerns have been moved to plugins/config:
|
|
115
216
|
* - VXRN_REACT_19 → handled by define in getNativeTransformConfig
|
|
116
|
-
* - polyfill error downgrade → polyfillErrorDowngradePlugin
|
|
117
217
|
* - DevSettings stripping → stripDevSettingsPlugin
|
|
118
218
|
*/
|
|
119
219
|
function postProcessNativeBundle(code: string): string {
|
|
@@ -214,7 +314,7 @@ export async function createNativeDevEngine(
|
|
|
214
314
|
cwd: root,
|
|
215
315
|
platform: 'neutral',
|
|
216
316
|
resolve: getNativeResolveConfig(platform),
|
|
217
|
-
transform: getNativeTransformConfig(true),
|
|
317
|
+
transform: getNativeTransformConfig(platform, true, root),
|
|
218
318
|
|
|
219
319
|
experimental: {
|
|
220
320
|
devMode: { implement: hmrRuntimeSource, host, port },
|
|
@@ -235,11 +335,6 @@ export async function createNativeDevEngine(
|
|
|
235
335
|
plugins: [
|
|
236
336
|
nativeVirtualEntryPlugin(root, { dev: true }),
|
|
237
337
|
...getNativePlugins(root, platform, viteImportGlobPlugin, true),
|
|
238
|
-
|
|
239
|
-
// add import.meta.hot.accept() to user files for HMR boundaries
|
|
240
|
-
// rolldown compiles import.meta.hot -> createModuleHotContext at build time
|
|
241
|
-
nativeReactRefreshPlugin(),
|
|
242
|
-
|
|
243
338
|
...userPlugins,
|
|
244
339
|
],
|
|
245
340
|
}
|
|
@@ -406,7 +501,7 @@ export async function buildNativeBundle(
|
|
|
406
501
|
cwd: root,
|
|
407
502
|
platform: 'neutral',
|
|
408
503
|
resolve: getNativeResolveConfig(platform),
|
|
409
|
-
transform: getNativeTransformConfig(dev),
|
|
504
|
+
transform: getNativeTransformConfig(platform, dev, root),
|
|
410
505
|
treeshake: !dev,
|
|
411
506
|
shimMissingExports: true,
|
|
412
507
|
moduleTypes: { '.js': 'jsx' },
|
|
@@ -435,6 +530,43 @@ function nativeVirtualEntryPlugin(root: string, opts?: { dev?: boolean }): Plugi
|
|
|
435
530
|
// resolve to an absolute path rooted in the project so import.meta.glob('./app/...') resolves correctly
|
|
436
531
|
const resolvedId = resolve(root, '__virtual-native-entry.tsx')
|
|
437
532
|
|
|
533
|
+
// read config passed from One's vite plugin via globalThis
|
|
534
|
+
const entryConfig = (globalThis as any).__vxrnNativeEntryConfig || {}
|
|
535
|
+
const routerRoot = entryConfig.routerRoot || 'app'
|
|
536
|
+
const flags = entryConfig.flags || {}
|
|
537
|
+
|
|
538
|
+
// build setupFile import (static import for native)
|
|
539
|
+
const setupFileImport = (() => {
|
|
540
|
+
const sf = entryConfig.setupFile
|
|
541
|
+
if (!sf) return ''
|
|
542
|
+
// resolve which file to use for ios (covers both formats)
|
|
543
|
+
const file = typeof sf === 'string' ? sf : 'native' in sf ? sf.native : sf.ios
|
|
544
|
+
if (!file) return ''
|
|
545
|
+
const resolved = resolve(root, file)
|
|
546
|
+
return `import ${JSON.stringify(resolved)};`
|
|
547
|
+
})()
|
|
548
|
+
|
|
549
|
+
// build glob patterns matching One's virtualEntryPlugin
|
|
550
|
+
// platform-specific files (.native/.ios/.android) are resolved by rolldown's
|
|
551
|
+
// resolve.extensions at import time — they must NOT appear as separate route entries
|
|
552
|
+
const routeGlobs = [
|
|
553
|
+
`./${routerRoot}/**/*.tsx`,
|
|
554
|
+
`./${routerRoot}/**/*.ts`,
|
|
555
|
+
`!./${routerRoot}/**/*+api.*`,
|
|
556
|
+
`!./${routerRoot}/**/*.test.*`,
|
|
557
|
+
`!./${routerRoot}/**/*.d.ts`,
|
|
558
|
+
`!./${routerRoot}/**/*.server.*`,
|
|
559
|
+
`!./${routerRoot}/**/_middleware.*`,
|
|
560
|
+
`!./${routerRoot}/**/*.web.*`,
|
|
561
|
+
`!./${routerRoot}/**/*.native.*`,
|
|
562
|
+
`!./${routerRoot}/**/*.ios.*`,
|
|
563
|
+
`!./${routerRoot}/**/*.android.*`,
|
|
564
|
+
// ignoredRouteFiles from One's router config
|
|
565
|
+
...(entryConfig.ignoredRouteFiles || []).map(
|
|
566
|
+
(pattern: string) => `!./${routerRoot}/${pattern}`
|
|
567
|
+
),
|
|
568
|
+
]
|
|
569
|
+
|
|
438
570
|
const refreshSetup = isDev
|
|
439
571
|
? `
|
|
440
572
|
// react-refresh/runtime MUST initialize before React loads
|
|
@@ -450,10 +582,11 @@ globalThis.$RefreshSig$ = RefreshRuntime.createSignatureFunctionForTransform;
|
|
|
450
582
|
|
|
451
583
|
const entryCode = `
|
|
452
584
|
${refreshSetup}
|
|
585
|
+
${setupFileImport}
|
|
453
586
|
import { createApp } from 'one';
|
|
454
587
|
|
|
455
|
-
var _routes = import.meta.glob(
|
|
456
|
-
// fix route keys: One expects '
|
|
588
|
+
var _routes = import.meta.glob(${JSON.stringify(routeGlobs)}, { exhaustive: true });
|
|
589
|
+
// fix route keys: One expects '/${routerRoot}/...' prefix but import.meta.glob returns './${routerRoot}/...'
|
|
457
590
|
var routes = {};
|
|
458
591
|
Object.keys(_routes).forEach(function(key) {
|
|
459
592
|
routes[key.replace(/^\\./, '')] = _routes[key];
|
|
@@ -461,8 +594,8 @@ Object.keys(_routes).forEach(function(key) {
|
|
|
461
594
|
|
|
462
595
|
createApp({
|
|
463
596
|
routes: routes,
|
|
464
|
-
routerRoot:
|
|
465
|
-
flags: {},
|
|
597
|
+
routerRoot: ${JSON.stringify(routerRoot)},
|
|
598
|
+
flags: ${JSON.stringify(flags)},
|
|
466
599
|
});
|
|
467
600
|
`
|
|
468
601
|
|
|
@@ -483,6 +616,53 @@ createApp({
|
|
|
483
616
|
|
|
484
617
|
// --- plugins ---
|
|
485
618
|
|
|
619
|
+
/**
|
|
620
|
+
* Block .server.* and _middleware.* files from entering the native bundle.
|
|
621
|
+
* These are server-only code paths that should never ship to the client.
|
|
622
|
+
*/
|
|
623
|
+
function serverFileExclusionPlugin(): Plugin {
|
|
624
|
+
return {
|
|
625
|
+
name: 'vxrn:server-file-exclusion',
|
|
626
|
+
load(id) {
|
|
627
|
+
if (/\.server\.\w+$/.test(id)) {
|
|
628
|
+
return { code: 'export default undefined;', moduleType: 'js' as any }
|
|
629
|
+
}
|
|
630
|
+
if (/[\\/]_middleware\.\w+$/.test(id)) {
|
|
631
|
+
return { code: 'export default undefined;', moduleType: 'js' as any }
|
|
632
|
+
}
|
|
633
|
+
},
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Guard environment-specific bare imports in native bundles.
|
|
639
|
+
* - server-only, client-only, web-only → throw at runtime
|
|
640
|
+
* - native-only → no-op (we ARE native)
|
|
641
|
+
*/
|
|
642
|
+
function environmentGuardPlugin(): Plugin {
|
|
643
|
+
const THROWING = ['server-only', 'client-only', 'web-only']
|
|
644
|
+
const NOOP = ['native-only']
|
|
645
|
+
return {
|
|
646
|
+
name: 'vxrn:environment-guard',
|
|
647
|
+
resolveId(source) {
|
|
648
|
+
if (THROWING.includes(source))
|
|
649
|
+
return { id: `\0env-guard-throw:${source}`, external: false }
|
|
650
|
+
if (NOOP.includes(source))
|
|
651
|
+
return { id: `\0env-guard-noop:${source}`, external: false }
|
|
652
|
+
},
|
|
653
|
+
load(id) {
|
|
654
|
+
if (id.startsWith('\0env-guard-throw:')) {
|
|
655
|
+
const pkg = id.slice('\0env-guard-throw:'.length)
|
|
656
|
+
return {
|
|
657
|
+
code: `throw new Error("Cannot import '${pkg}' in a native bundle.");`,
|
|
658
|
+
moduleType: 'js' as any,
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
if (id.startsWith('\0env-guard-noop:')) return { code: '', moduleType: 'js' as any }
|
|
662
|
+
},
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
486
666
|
/**
|
|
487
667
|
* Stub CSS imports for native builds.
|
|
488
668
|
* Native doesn't support CSS and rolldown removed CSS bundling support.
|
|
@@ -504,18 +684,27 @@ function cssStubPlugin(): Plugin {
|
|
|
504
684
|
/**
|
|
505
685
|
* Pipe files through @vxrn/compiler's babel transforms.
|
|
506
686
|
* Handles reanimated worklet compilation, async generator downleveling,
|
|
507
|
-
* react-native codegen,
|
|
508
|
-
*
|
|
687
|
+
* react-native codegen, react compiler, and react-refresh (dev only) —
|
|
688
|
+
* same pipeline as metro, single babel pass per file.
|
|
509
689
|
*/
|
|
510
690
|
function vxrnCompilerPlugin(platform: string, dev: boolean): Plugin {
|
|
511
691
|
let compiler: typeof import('@vxrn/compiler') | null = null
|
|
512
692
|
|
|
693
|
+
// whether a file is a user file that should get react-refresh wiring
|
|
694
|
+
const isRefreshCandidate = (id: string) =>
|
|
695
|
+
dev &&
|
|
696
|
+
!id.includes('node_modules') &&
|
|
697
|
+
!id.includes('__virtual-native-entry') &&
|
|
698
|
+
/\.[tj]sx?$/.test(id)
|
|
699
|
+
|
|
513
700
|
return {
|
|
514
701
|
name: 'vxrn:compiler',
|
|
515
702
|
async transform(code, id) {
|
|
516
703
|
if (!/\.[cm]?[jt]sx?$/.test(id)) return
|
|
517
704
|
if (id.includes('\0') || id.includes('virtual:')) return
|
|
518
705
|
|
|
706
|
+
const needsRefresh = isRefreshCandidate(id)
|
|
707
|
+
|
|
519
708
|
try {
|
|
520
709
|
if (!compiler) compiler = await import('@vxrn/compiler')
|
|
521
710
|
|
|
@@ -527,19 +716,65 @@ function vxrnCompilerPlugin(platform: string, dev: boolean): Plugin {
|
|
|
527
716
|
reactForRNVersion: '19' as const,
|
|
528
717
|
}
|
|
529
718
|
|
|
530
|
-
|
|
719
|
+
let babelOptions = compiler.getBabelOptions(props)
|
|
720
|
+
|
|
721
|
+
if (needsRefresh) {
|
|
722
|
+
// merge react-refresh/babel into the existing plugins (or create new options)
|
|
723
|
+
const existingPlugins = babelOptions?.plugins || []
|
|
724
|
+
babelOptions = {
|
|
725
|
+
...babelOptions,
|
|
726
|
+
plugins: [...existingPlugins, 'react-refresh/babel'],
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
531
730
|
if (!babelOptions) return
|
|
532
731
|
|
|
533
732
|
const result = await compiler.transformBabel(id, code, babelOptions)
|
|
534
733
|
|
|
535
734
|
if (result?.code) {
|
|
536
|
-
|
|
735
|
+
let out = result.code
|
|
736
|
+
|
|
737
|
+
if (needsRefresh) {
|
|
738
|
+
// wrap with per-file $RefreshReg$ that includes the file path as unique ID
|
|
739
|
+
// and schedule performReactRefresh() after HMR patch re-execution
|
|
740
|
+
const escapedId = id.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
|
|
741
|
+
out = `
|
|
742
|
+
var __prevRefreshReg = globalThis.$RefreshReg$;
|
|
743
|
+
var __prevRefreshSig = globalThis.$RefreshSig$;
|
|
744
|
+
if (globalThis.__ReactRefresh) {
|
|
745
|
+
globalThis.$RefreshReg$ = function(type, id) {
|
|
746
|
+
globalThis.__ReactRefresh.register(type, "${escapedId}" + " " + id);
|
|
747
|
+
};
|
|
748
|
+
globalThis.$RefreshSig$ = globalThis.__ReactRefresh.createSignatureFunctionForTransform;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
${out}
|
|
752
|
+
|
|
753
|
+
globalThis.$RefreshReg$ = __prevRefreshReg;
|
|
754
|
+
globalThis.$RefreshSig$ = __prevRefreshSig;
|
|
755
|
+
if (import.meta.hot) {
|
|
756
|
+
import.meta.hot.accept(function() {
|
|
757
|
+
if (globalThis.__ReactRefresh) {
|
|
758
|
+
setTimeout(function() { globalThis.__ReactRefresh.performReactRefresh(); }, 30);
|
|
759
|
+
}
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
`
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
return { code: out }
|
|
537
766
|
}
|
|
538
767
|
} catch (err: any) {
|
|
539
768
|
// log but don't crash — fallback to rolldown's own transform
|
|
540
769
|
if (dev) {
|
|
541
770
|
console.warn(`[vxrn:compiler] ${id}: ${err.message || err}`)
|
|
542
771
|
}
|
|
772
|
+
// if babel transform fails for a refresh candidate, still add accept boundary
|
|
773
|
+
if (needsRefresh) {
|
|
774
|
+
return {
|
|
775
|
+
code: code + `\nif (import.meta.hot) { import.meta.hot.accept(); }\n`,
|
|
776
|
+
}
|
|
777
|
+
}
|
|
543
778
|
}
|
|
544
779
|
},
|
|
545
780
|
}
|
|
@@ -687,164 +922,6 @@ function hermesCompatSWCPlugin(dev: boolean): Plugin {
|
|
|
687
922
|
}
|
|
688
923
|
}
|
|
689
924
|
|
|
690
|
-
/**
|
|
691
|
-
* Run @react-native/babel-plugin-codegen on native component spec files.
|
|
692
|
-
* Without this, native components (RNSScreen, SafeAreaView, etc.) get
|
|
693
|
-
* "Codegen didn't run" warnings and may not render correctly.
|
|
694
|
-
*/
|
|
695
|
-
function codegenPlugin(): Plugin {
|
|
696
|
-
const NATIVE_COMPONENT_RE = /NativeComponent\.[jt]sx?$/
|
|
697
|
-
const SPEC_FILE_RE = /[/\\]specs?[/\\]/
|
|
698
|
-
|
|
699
|
-
return {
|
|
700
|
-
name: 'vxrn:codegen',
|
|
701
|
-
async transform(code, id) {
|
|
702
|
-
if (!NATIVE_COMPONENT_RE.test(id) && !SPEC_FILE_RE.test(id)) return
|
|
703
|
-
|
|
704
|
-
try {
|
|
705
|
-
const babel = await import('@babel/core')
|
|
706
|
-
const result = await babel.transformAsync(code, {
|
|
707
|
-
filename: id,
|
|
708
|
-
babelrc: false,
|
|
709
|
-
configFile: false,
|
|
710
|
-
compact: false,
|
|
711
|
-
plugins: ['@react-native/babel-plugin-codegen'],
|
|
712
|
-
sourceType: 'unambiguous',
|
|
713
|
-
})
|
|
714
|
-
if (result?.code) return { code: result.code }
|
|
715
|
-
} catch {}
|
|
716
|
-
},
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
/**
|
|
721
|
-
* Downgrade polyfill "not configurable" errors to warnings.
|
|
722
|
-
* hermes v1 has native fetch/Headers/etc that are non-configurable,
|
|
723
|
-
* so polyfill attempts throw errors that are noisy but harmless.
|
|
724
|
-
*/
|
|
725
|
-
function polyfillErrorDowngradePlugin(): Plugin {
|
|
726
|
-
return {
|
|
727
|
-
name: 'vxrn:polyfill-error-downgrade',
|
|
728
|
-
transform(code, id) {
|
|
729
|
-
if (!id.includes('node_modules')) return
|
|
730
|
-
if (!code.includes('console.error("Failed to set polyfill.')) return
|
|
731
|
-
|
|
732
|
-
return {
|
|
733
|
-
code: code.replace(
|
|
734
|
-
/console\.error\(\s*"Failed to set polyfill\.\s*"\s*\+/g,
|
|
735
|
-
'console.warn("Failed to set polyfill. " +'
|
|
736
|
-
),
|
|
737
|
-
}
|
|
738
|
-
},
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
/**
|
|
743
|
-
* Strip DevSettings reference in production builds.
|
|
744
|
-
* In production, NativeModules.getEnforcing('DevSettings') will fail
|
|
745
|
-
* since DevSettings is a dev-only module.
|
|
746
|
-
*/
|
|
747
|
-
function stripDevSettingsPlugin(dev: boolean): Plugin {
|
|
748
|
-
return {
|
|
749
|
-
name: 'vxrn:strip-dev-settings',
|
|
750
|
-
transform(code, id) {
|
|
751
|
-
if (dev) return
|
|
752
|
-
if (!code.includes('DevSettings')) return
|
|
753
|
-
|
|
754
|
-
return {
|
|
755
|
-
code: code.replace(
|
|
756
|
-
/getEnforcing\s*\(\s*["']DevSettings["']\s*\)/g,
|
|
757
|
-
'patched_getEnforcing_DevSettings_will_not_work_in_production()'
|
|
758
|
-
),
|
|
759
|
-
}
|
|
760
|
-
},
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
/**
|
|
765
|
-
* React Refresh wrapper for native HMR.
|
|
766
|
-
* 1. Runs react-refresh/babel to add $RefreshReg$/$RefreshSig$ calls
|
|
767
|
-
* 2. Wraps each file with per-module $RefreshReg$ that includes the file path
|
|
768
|
-
* (react-refresh needs unique IDs like "filepath ComponentName")
|
|
769
|
-
* 3. Appends import.meta.hot.accept() for HMR boundaries
|
|
770
|
-
* 4. Schedules performReactRefresh() after module re-execution
|
|
771
|
-
*/
|
|
772
|
-
function nativeReactRefreshPlugin(): Plugin {
|
|
773
|
-
return {
|
|
774
|
-
name: 'vxrn:react-refresh',
|
|
775
|
-
async transform(code, id) {
|
|
776
|
-
// only wrap user app files (not node_modules, not generated entry, not virtual)
|
|
777
|
-
if (id.includes('node_modules')) return
|
|
778
|
-
if (id.includes('__virtual-native-entry')) return
|
|
779
|
-
if (id.startsWith('\0')) return
|
|
780
|
-
if (!/\.[tj]sx?$/.test(id)) return
|
|
781
|
-
// skip files that clearly have no components (raw source before JSX transform)
|
|
782
|
-
// check for JSX syntax (<), function keyword, or arrow functions (=>)
|
|
783
|
-
if (!/[<]|function\s|=>\s*[{(]/.test(code)) return
|
|
784
|
-
|
|
785
|
-
try {
|
|
786
|
-
// run react-refresh/babel to add $RefreshReg$ and $RefreshSig$
|
|
787
|
-
const babel = await import('@babel/core')
|
|
788
|
-
|
|
789
|
-
// babel needs parser plugins for TypeScript/JSX
|
|
790
|
-
const parserPlugins: any[] = []
|
|
791
|
-
if (id.endsWith('.tsx')) {
|
|
792
|
-
parserPlugins.push(['@babel/plugin-syntax-typescript', { isTSX: true }])
|
|
793
|
-
} else if (id.endsWith('.ts')) {
|
|
794
|
-
parserPlugins.push('@babel/plugin-syntax-typescript')
|
|
795
|
-
} else if (id.endsWith('.jsx')) {
|
|
796
|
-
parserPlugins.push('@babel/plugin-syntax-jsx')
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
const result = await babel.transformAsync(code, {
|
|
800
|
-
filename: id,
|
|
801
|
-
babelrc: false,
|
|
802
|
-
configFile: false,
|
|
803
|
-
compact: false,
|
|
804
|
-
plugins: [...parserPlugins, 'react-refresh/babel'],
|
|
805
|
-
sourceType: 'unambiguous',
|
|
806
|
-
})
|
|
807
|
-
|
|
808
|
-
if (result?.code) {
|
|
809
|
-
// escape the id for use in string literal
|
|
810
|
-
const escapedId = id.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
|
|
811
|
-
|
|
812
|
-
// wrap with per-file $RefreshReg$ that includes the file path as unique ID
|
|
813
|
-
// and schedule performReactRefresh() after HMR patch re-execution
|
|
814
|
-
const wrappedCode = `
|
|
815
|
-
var __prevRefreshReg = globalThis.$RefreshReg$;
|
|
816
|
-
var __prevRefreshSig = globalThis.$RefreshSig$;
|
|
817
|
-
if (globalThis.__ReactRefresh) {
|
|
818
|
-
globalThis.$RefreshReg$ = function(type, id) {
|
|
819
|
-
globalThis.__ReactRefresh.register(type, "${escapedId}" + " " + id);
|
|
820
|
-
};
|
|
821
|
-
globalThis.$RefreshSig$ = globalThis.__ReactRefresh.createSignatureFunctionForTransform;
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
${result.code}
|
|
825
|
-
|
|
826
|
-
globalThis.$RefreshReg$ = __prevRefreshReg;
|
|
827
|
-
globalThis.$RefreshSig$ = __prevRefreshSig;
|
|
828
|
-
if (import.meta.hot) {
|
|
829
|
-
import.meta.hot.accept(function() {
|
|
830
|
-
if (globalThis.__ReactRefresh) {
|
|
831
|
-
setTimeout(function() { globalThis.__ReactRefresh.performReactRefresh(); }, 30);
|
|
832
|
-
}
|
|
833
|
-
});
|
|
834
|
-
}
|
|
835
|
-
`
|
|
836
|
-
return { code: wrappedCode }
|
|
837
|
-
}
|
|
838
|
-
} catch {
|
|
839
|
-
// if babel transform fails, just add the accept boundary
|
|
840
|
-
return {
|
|
841
|
-
code: code + `\nif (import.meta.hot) { import.meta.hot.accept(); }\n`,
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
},
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
|
|
848
925
|
// --- HMR runtime ---
|
|
849
926
|
|
|
850
927
|
function getHmrRuntimeSource(): string {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prebuild.d.ts","sourceRoot":"","sources":["../../src/exports/prebuild.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,QAAQ,GAAU,oDAK5B;IACD,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,KAAK,GAAG,SAAS,GAAG,MAAM,CAAA;IACrC,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,IAAI,EAAE,OAAO,CAAA;CACd,
|
|
1
|
+
{"version":3,"file":"prebuild.d.ts","sourceRoot":"","sources":["../../src/exports/prebuild.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,QAAQ,GAAU,oDAK5B;IACD,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,KAAK,GAAG,SAAS,GAAG,MAAM,CAAA;IACrC,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,IAAI,EAAE,OAAO,CAAA;CACd,kBA8MA,CAAA;AA2DD,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,iBAOpB"}
|