vxrn 1.25.16 → 1.26.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/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -1
- package/dist/index.native.js +2 -0
- package/dist/index.native.js.map +1 -1
- package/dist/patches/builtInDepPatches.mjs +21 -0
- package/dist/patches/builtInDepPatches.mjs.map +1 -1
- package/dist/patches/builtInDepPatches.native.js +21 -0
- package/dist/patches/builtInDepPatches.native.js.map +1 -1
- package/dist/plugins/reactNativeDevServer.mjs +5 -0
- package/dist/plugins/reactNativeDevServer.mjs.map +1 -1
- package/dist/plugins/reactNativeDevServer.native.js +5 -0
- package/dist/plugins/reactNativeDevServer.native.js.map +1 -1
- package/dist/plugins/rnCodegenPlugin.mjs +16 -0
- package/dist/plugins/rnCodegenPlugin.mjs.map +1 -0
- package/dist/plugins/rnCodegenPlugin.native.js +16 -0
- package/dist/plugins/rnCodegenPlugin.native.js.map +1 -0
- package/dist/plugins/rnCodegenPlugin.test.mjs +239 -0
- package/dist/plugins/rnCodegenPlugin.test.mjs.map +1 -0
- package/dist/plugins/rnCodegenPlugin.test.native.js +242 -0
- package/dist/plugins/rnCodegenPlugin.test.native.js.map +1 -0
- package/dist/plugins/workletImportsPlugin.mjs +160 -0
- package/dist/plugins/workletImportsPlugin.mjs.map +1 -0
- package/dist/plugins/workletImportsPlugin.native.js +230 -0
- package/dist/plugins/workletImportsPlugin.native.js.map +1 -0
- package/dist/utils/createNativeDevEngine.mjs +198 -92
- package/dist/utils/createNativeDevEngine.mjs.map +1 -1
- package/dist/utils/createNativeDevEngine.native.js +252 -99
- package/dist/utils/createNativeDevEngine.native.js.map +1 -1
- package/dist/utils/createNativeDevEngine.test.mjs +775 -4
- package/dist/utils/createNativeDevEngine.test.mjs.map +1 -1
- package/dist/utils/createNativeDevEngine.test.native.js +836 -4
- package/dist/utils/createNativeDevEngine.test.native.js.map +1 -1
- package/dist/utils/patches.mjs +12 -5
- package/dist/utils/patches.mjs.map +1 -1
- package/dist/utils/patches.native.js +13 -5
- package/dist/utils/patches.native.js.map +1 -1
- package/dist/utils/patches.test.mjs +28 -0
- package/dist/utils/patches.test.mjs.map +1 -1
- package/dist/utils/patches.test.native.js +47 -0
- package/dist/utils/patches.test.native.js.map +1 -1
- package/dist/utils/scanDepsToOptimize.mjs +5 -1
- package/dist/utils/scanDepsToOptimize.mjs.map +1 -1
- package/dist/utils/scanDepsToOptimize.native.js +5 -1
- package/dist/utils/scanDepsToOptimize.native.js.map +1 -1
- package/package.json +19 -12
- package/src/index.ts +2 -0
- package/src/patches/builtInDepPatches.ts +25 -0
- package/src/plugins/reactNativeDevServer.ts +12 -0
- package/src/plugins/rnCodegenPlugin.test.ts +313 -0
- package/src/plugins/rnCodegenPlugin.ts +20 -0
- package/src/plugins/workletImportsPlugin.ts +226 -0
- package/src/utils/createNativeDevEngine.test.ts +877 -3
- package/src/utils/createNativeDevEngine.ts +259 -107
- package/src/utils/patches.test.ts +33 -0
- package/src/utils/patches.ts +17 -6
- package/src/utils/scanDepsToOptimize.ts +9 -0
- package/types/index.d.ts +2 -0
- package/types/index.d.ts.map +1 -1
- package/types/patches/builtInDepPatches.d.ts.map +1 -1
- package/types/plugins/reactNativeDevServer.d.ts.map +1 -1
- package/types/plugins/rnCodegenPlugin.d.ts +6 -0
- package/types/plugins/rnCodegenPlugin.d.ts.map +1 -0
- package/types/plugins/rnCodegenPlugin.test.d.ts +2 -0
- package/types/plugins/rnCodegenPlugin.test.d.ts.map +1 -0
- package/types/plugins/workletImportsPlugin.d.ts +8 -0
- package/types/plugins/workletImportsPlugin.d.ts.map +1 -0
- package/types/utils/createNativeDevEngine.d.ts +13 -0
- package/types/utils/createNativeDevEngine.d.ts.map +1 -1
- package/types/utils/patches.d.ts.map +1 -1
- package/types/utils/scanDepsToOptimize.d.ts.map +1 -1
|
@@ -20,11 +20,10 @@ import { pathToFileURL } from 'node:url'
|
|
|
20
20
|
import type { InputOptions, OutputOptions, Plugin, RolldownOutput } from 'rolldown'
|
|
21
21
|
import type { DevEngine } from 'rolldown/experimental'
|
|
22
22
|
import { loadEnv as loadViteEnv, normalizePath } from 'vite'
|
|
23
|
+
import { shouldStripFlow, transformHermesAsync } from '@vxrn/compiler'
|
|
23
24
|
import { DEFAULT_ASSET_EXTS } from '../constants/defaults'
|
|
24
25
|
import { getNativePrelude } from '../runtime/native-prelude'
|
|
25
|
-
|
|
26
|
-
// files that contain Flow syntax and need stripping
|
|
27
|
-
const FLOW_FILE_PATTERN = /node_modules[\\/](?:react-native|@react-native)[\\/].*\.js$/
|
|
26
|
+
import { rnCodegenPlugin } from '../plugins/rnCodegenPlugin'
|
|
28
27
|
|
|
29
28
|
// Hermes needs the whole class shape lowered *together*. downleveling only the
|
|
30
29
|
// class fields while leaving `class ... extends` as modern ES6 produces a
|
|
@@ -136,6 +135,16 @@ export function getNativeTransformConfig(
|
|
|
136
135
|
}
|
|
137
136
|
})()
|
|
138
137
|
|
|
138
|
+
// app-supplied defines (One's native.bundlerOptions.define). vite's rule:
|
|
139
|
+
// a string is a raw expression, anything else is JSON stringified. rolldown's
|
|
140
|
+
// binding only accepts strings, so normalize rather than crashing the build.
|
|
141
|
+
const userDefines: Record<string, string> = {}
|
|
142
|
+
for (const [key, value] of Object.entries(
|
|
143
|
+
((globalThis as any).__vxrnNativeUserDefine || {}) as Record<string, unknown>
|
|
144
|
+
)) {
|
|
145
|
+
userDefines[key] = typeof value === 'string' ? value : JSON.stringify(value)
|
|
146
|
+
}
|
|
147
|
+
|
|
139
148
|
const mode = dev ? 'development' : 'production'
|
|
140
149
|
|
|
141
150
|
// Match One's Vite client contract: load public values from process.env and
|
|
@@ -174,6 +183,9 @@ export function getNativeTransformConfig(
|
|
|
174
183
|
runtime: 'classic' as const,
|
|
175
184
|
},
|
|
176
185
|
define: {
|
|
186
|
+
// first in the map, so nothing the user set can shadow a platform-owned
|
|
187
|
+
// key below.
|
|
188
|
+
...userDefines,
|
|
177
189
|
// Public values are applied first so platform-owned keys cannot inherit
|
|
178
190
|
// the SSR values used while loading One's Vite config.
|
|
179
191
|
...envDefines,
|
|
@@ -237,6 +249,8 @@ function getNativePlugins(
|
|
|
237
249
|
// type argument intact. stripping Flow first would erase it (which is why the
|
|
238
250
|
// codegen "didn't run for <Component>" warning fired).
|
|
239
251
|
vxrnCompilerPlugin(platform, dev, root, sourceMaps),
|
|
252
|
+
// react-native codegen: transforms TurboModule / Fabric specs before Flow stripping
|
|
253
|
+
rnCodegenPlugin({ projectRoot: root }),
|
|
240
254
|
// strip Flow from any react-native / @react-native `.js` the compiler didn't
|
|
241
255
|
// handle, the guaranteed safety net before rolldown's oxc core parse (which
|
|
242
256
|
// can't parse Flow). now downstream of the compiler, so codegen sees the types.
|
|
@@ -247,6 +261,9 @@ function getNativePlugins(
|
|
|
247
261
|
assetPlugin({ root, platform, assetsDest, onAsset }),
|
|
248
262
|
// hermes compat: transform class properties and private fields
|
|
249
263
|
hermesCompatSWCPlugin(dev, sourceMaps),
|
|
264
|
+
// hermes compat: per-iteration loop bindings. runs last so it also covers
|
|
265
|
+
// loops the earlier lowering steps emit.
|
|
266
|
+
hermesLoopsPlugin(sourceMaps),
|
|
250
267
|
]
|
|
251
268
|
}
|
|
252
269
|
|
|
@@ -268,12 +285,10 @@ function getNativeOutputOptions(prelude: string, sourcemap: boolean): OutputOpti
|
|
|
268
285
|
* - DevSettings stripping → stripDevSettingsPlugin
|
|
269
286
|
*/
|
|
270
287
|
export function normalizeNativeCommonJSInterop(code: string): string {
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
// expose their actual default behind `exports.default`; Node mode instead
|
|
274
|
-
// returns the whole exports object and React receives `{ default: Component }`.
|
|
288
|
+
// native packages expose Babel defaults through exports.default. apply the
|
|
289
|
+
// same interop to bundled require helpers and HMR's runtime export lookups.
|
|
275
290
|
return code.replace(
|
|
276
|
-
/(\b__toESM(?:\$\d+)?\(\s*require[\w$]*\(\)\s*),\s*1(\s*\))/g,
|
|
291
|
+
/(\b__toESM(?:\$\d+)?\(\s*(?:require[\w$]*\(\)|__rolldown_runtime__\.loadExports\("(?:\\.|[^"\\])*"\))\s*),\s*1(\s*\))/g,
|
|
277
292
|
'$1$2'
|
|
278
293
|
)
|
|
279
294
|
}
|
|
@@ -373,34 +388,16 @@ async function downlevelClassFieldsInBundle(code: string): Promise<string> {
|
|
|
373
388
|
const runtimeSection = code.slice(startIdx, runtimeEnd)
|
|
374
389
|
const originalNewlines = runtimeSection.match(/\n/g)?.length ?? 0
|
|
375
390
|
|
|
376
|
-
const
|
|
377
|
-
const result =
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
sourceMaps: false,
|
|
382
|
-
inputSourceMap: false,
|
|
383
|
-
isModule: false,
|
|
384
|
-
// Compact only Rolldown's generated runtime, then restore its original
|
|
385
|
-
// newline count below. This keeps every application module at the exact
|
|
386
|
-
// generated line recorded by Rolldown's source map while still lowering
|
|
387
|
-
// runtime class fields for Hermes.
|
|
388
|
-
minify: true,
|
|
389
|
-
env: {
|
|
390
|
-
targets: { node: 9999 },
|
|
391
|
-
// dev-only runtime prelude: the class set only, no prod bytecode transforms
|
|
392
|
-
include: [...HERMES_CLASS_TRANSFORMS],
|
|
393
|
-
},
|
|
394
|
-
jsc: {
|
|
395
|
-
parser: { syntax: 'ecmascript' },
|
|
396
|
-
transform: { react: { runtime: 'preserve' } },
|
|
397
|
-
externalHelpers: false,
|
|
398
|
-
assumptions: {
|
|
399
|
-
setPublicClassFields: true,
|
|
400
|
-
privateFieldsAsProperties: true,
|
|
401
|
-
},
|
|
391
|
+
const { transformSync } = await import('oxc-transform')
|
|
392
|
+
const result = transformSync('rolldown-runtime.js', runtimeSection, {
|
|
393
|
+
target: 'es2020',
|
|
394
|
+
assumptions: {
|
|
395
|
+
setPublicClassFields: true,
|
|
402
396
|
},
|
|
403
397
|
})
|
|
398
|
+
if (result.errors?.length) {
|
|
399
|
+
throw new Error(result.errors.map((e) => e.message).join('\n'))
|
|
400
|
+
}
|
|
404
401
|
const transformedCode = result.code.trimEnd()
|
|
405
402
|
// SWC minification strips comments, but the dev bundle scope wrapper uses
|
|
406
403
|
// this generated-runtime marker as its structural boundary.
|
|
@@ -585,7 +582,7 @@ try {
|
|
|
585
582
|
onHmrUpdate?.({
|
|
586
583
|
type: 'hmr:update',
|
|
587
584
|
clientId,
|
|
588
|
-
code: update.code,
|
|
585
|
+
code: normalizeNativeCommonJSInterop(update.code),
|
|
589
586
|
changedIds: update.changedIds,
|
|
590
587
|
seq: update.seq,
|
|
591
588
|
})
|
|
@@ -1018,50 +1015,150 @@ export function vxrnCompilerPlugin(
|
|
|
1018
1015
|
|
|
1019
1016
|
let babelOptions = compiler.getBabelOptions(props)
|
|
1020
1017
|
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1018
|
+
let curCode = code
|
|
1019
|
+
const intermediateMaps: any[] = []
|
|
1020
|
+
|
|
1021
|
+
const isWorkletPlugin = (entry: any) => {
|
|
1022
|
+
if (!entry) return false
|
|
1023
|
+
const name =
|
|
1024
|
+
typeof entry === 'string' ? entry : Array.isArray(entry) ? entry[0] : null
|
|
1025
|
+
return (
|
|
1026
|
+
typeof name === 'string' &&
|
|
1027
|
+
(name.includes('react-native-worklets') ||
|
|
1028
|
+
name.includes('react-native-reanimated'))
|
|
1029
|
+
)
|
|
1030
|
+
}
|
|
1031
|
+
const workletEntry = babelOptions?.plugins?.find(isWorkletPlugin)
|
|
1032
|
+
const workletPluginOptions = Array.isArray(workletEntry) ? workletEntry[1] : undefined
|
|
1033
|
+
const useWorklets =
|
|
1034
|
+
compiler.isNativeWorkletsEnabled() &&
|
|
1035
|
+
(Boolean(workletEntry) || compiler.shouldTransformWorklets({ id, code }))
|
|
1036
|
+
const compilerPluginIndex =
|
|
1037
|
+
babelOptions?.plugins?.findIndex(
|
|
1038
|
+
(x) => Array.isArray(x) && x[0] === 'babel-plugin-react-compiler'
|
|
1039
|
+
) ?? -1
|
|
1040
|
+
|
|
1041
|
+
if (compilerPluginIndex !== -1) {
|
|
1042
|
+
// preserve automatic worklet candidates before react compiler hoists them.
|
|
1043
|
+
if (useWorklets) {
|
|
1044
|
+
const prepared = compiler.prepareWorkletsForReactCompiler(id, curCode, sourceMaps)
|
|
1045
|
+
if (prepared) {
|
|
1046
|
+
curCode = prepared.code
|
|
1047
|
+
if (prepared.map) intermediateMaps.push(prepared.map)
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
const compilerTarget =
|
|
1051
|
+
(babelOptions!.plugins![compilerPluginIndex] as any[])[1]?.target ?? '19'
|
|
1052
|
+
const compilerOut = await compiler.transformOxcReactCompiler(
|
|
1053
|
+
id, curCode, compilerTarget, sourceMaps
|
|
1054
|
+
)
|
|
1055
|
+
if (compilerOut?.code) {
|
|
1056
|
+
curCode = compilerOut.code
|
|
1057
|
+
if (sourceMaps && compilerOut.map) intermediateMaps.push(compilerOut.map)
|
|
1058
|
+
}
|
|
1059
|
+
babelOptions!.plugins!.splice(compilerPluginIndex, 1)
|
|
1060
|
+
if (babelOptions!.plugins!.length === 0) babelOptions = null
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
if (useWorklets) {
|
|
1064
|
+
const workletOut = await compiler.transformWorklets(id, curCode, sourceMaps, {
|
|
1065
|
+
projectRoot,
|
|
1066
|
+
...workletPluginOptions,
|
|
1067
|
+
})
|
|
1068
|
+
if (workletOut?.code) {
|
|
1069
|
+
curCode = workletOut.code
|
|
1070
|
+
if (sourceMaps && workletOut.map) intermediateMaps.push(workletOut.map)
|
|
1071
|
+
}
|
|
1072
|
+
if (babelOptions?.plugins) {
|
|
1073
|
+
babelOptions.plugins = babelOptions.plugins.filter(
|
|
1074
|
+
(entry) => !isWorkletPlugin(entry)
|
|
1075
|
+
)
|
|
1076
|
+
if (babelOptions.plugins.length === 0) babelOptions = null
|
|
1037
1077
|
}
|
|
1038
1078
|
}
|
|
1039
1079
|
|
|
1040
|
-
if (!babelOptions)
|
|
1080
|
+
if (!babelOptions && !needsRefresh) {
|
|
1081
|
+
if (curCode !== code) {
|
|
1082
|
+
let finalMap: any = undefined
|
|
1083
|
+
if (sourceMaps && intermediateMaps.length > 0) {
|
|
1084
|
+
if (intermediateMaps.length === 1) {
|
|
1085
|
+
finalMap = intermediateMaps[0]
|
|
1086
|
+
} else {
|
|
1087
|
+
const remapping = (await import('@jridgewell/remapping')).default
|
|
1088
|
+
finalMap = remapping(intermediateMaps.slice().reverse(), () => null)
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
return { code: curCode, map: finalMap }
|
|
1092
|
+
}
|
|
1093
|
+
return
|
|
1094
|
+
}
|
|
1041
1095
|
|
|
1042
|
-
if (sourceMaps) {
|
|
1043
|
-
//
|
|
1044
|
-
//
|
|
1045
|
-
//
|
|
1046
|
-
//
|
|
1096
|
+
if (sourceMaps && babelOptions) {
|
|
1097
|
+
// Individual stage maps are collected in intermediateMaps and composed
|
|
1098
|
+
// once via remapping([wrapMap, refreshMap, ...intermediateMaps.reverse()]).
|
|
1099
|
+
// Do not pass intermediateMaps as inputSourceMap to Babel, which would
|
|
1100
|
+
// cause Babel to pre-compose and remapping to double-compose the compiler map.
|
|
1047
1101
|
babelOptions = {
|
|
1048
1102
|
...babelOptions,
|
|
1049
1103
|
sourceMaps: true,
|
|
1050
1104
|
sourceFileName: id,
|
|
1051
1105
|
}
|
|
1106
|
+
delete (babelOptions as any).inputSourceMap
|
|
1052
1107
|
}
|
|
1053
1108
|
|
|
1054
|
-
|
|
1109
|
+
if (babelOptions) {
|
|
1110
|
+
const result = await compiler.transformBabel(id, curCode, babelOptions)
|
|
1111
|
+
if (result?.code) {
|
|
1112
|
+
curCode = result.code
|
|
1113
|
+
if (sourceMaps && result.map) {
|
|
1114
|
+
intermediateMaps.push(result.map)
|
|
1115
|
+
}
|
|
1116
|
+
if (!needsRefresh) {
|
|
1117
|
+
let finalMap: any = undefined
|
|
1118
|
+
if (sourceMaps && intermediateMaps.length > 0) {
|
|
1119
|
+
if (intermediateMaps.length === 1) {
|
|
1120
|
+
finalMap = intermediateMaps[0]
|
|
1121
|
+
} else {
|
|
1122
|
+
const remapping = (await import('@jridgewell/remapping')).default
|
|
1123
|
+
finalMap = remapping(intermediateMaps.slice().reverse(), () => null)
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
return { code: curCode, map: finalMap }
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1055
1130
|
|
|
1056
|
-
if (
|
|
1057
|
-
|
|
1131
|
+
if (needsRefresh) {
|
|
1132
|
+
const { transformSync } = await import('oxc-transform-react')
|
|
1133
|
+
const jsxImportSource =
|
|
1134
|
+
compiler.configuration?.enableNativewind && !id.includes('node_modules')
|
|
1135
|
+
? 'nativewind'
|
|
1136
|
+
: 'react'
|
|
1137
|
+
|
|
1138
|
+
const res = transformSync(id, curCode, {
|
|
1139
|
+
jsx: {
|
|
1140
|
+
development: dev,
|
|
1141
|
+
runtime: 'automatic',
|
|
1142
|
+
importSource: jsxImportSource,
|
|
1143
|
+
refresh: {
|
|
1144
|
+
refreshReg: '__vxrnRefreshReg',
|
|
1145
|
+
refreshSig: '__vxrnRefreshSig',
|
|
1146
|
+
},
|
|
1147
|
+
},
|
|
1148
|
+
reactCompiler: false,
|
|
1149
|
+
sourcemap: sourceMaps,
|
|
1150
|
+
})
|
|
1058
1151
|
|
|
1059
|
-
if (
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1152
|
+
if (res.fatal) {
|
|
1153
|
+
throw new Error(
|
|
1154
|
+
`[vxrn:compiler] oxc react transform failed on ${id}: ${(res.errors ?? [])
|
|
1155
|
+
.map((e: any) => e.message ?? String(e))
|
|
1156
|
+
.join(', ')}`
|
|
1157
|
+
)
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
const escapedId = id.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
|
|
1161
|
+
const header = `var __prevRefreshReg = globalThis.$RefreshReg$;
|
|
1065
1162
|
var __prevRefreshSig = globalThis.$RefreshSig$;
|
|
1066
1163
|
if (globalThis.__ReactRefresh) {
|
|
1067
1164
|
globalThis.$RefreshReg$ = function(type, id) {
|
|
@@ -1073,7 +1170,8 @@ if (globalThis.__ReactRefresh) {
|
|
|
1073
1170
|
var __vxrnRefreshReg = globalThis.$RefreshReg$;
|
|
1074
1171
|
var __vxrnRefreshSig = globalThis.$RefreshSig$;
|
|
1075
1172
|
|
|
1076
|
-
|
|
1173
|
+
`
|
|
1174
|
+
const footer = `
|
|
1077
1175
|
|
|
1078
1176
|
globalThis.$RefreshReg$ = __prevRefreshReg;
|
|
1079
1177
|
globalThis.$RefreshSig$ = __prevRefreshSig;
|
|
@@ -1085,24 +1183,48 @@ if (import.meta.hot) {
|
|
|
1085
1183
|
});
|
|
1086
1184
|
}
|
|
1087
1185
|
`
|
|
1186
|
+
let out: string
|
|
1187
|
+
let finalMap: any = undefined
|
|
1188
|
+
|
|
1189
|
+
if (sourceMaps) {
|
|
1190
|
+
const MagicString = (await import('magic-string')).default
|
|
1191
|
+
const ms = new MagicString(res.code)
|
|
1192
|
+
ms.prepend(header)
|
|
1193
|
+
ms.append(footer)
|
|
1194
|
+
out = ms.toString()
|
|
1195
|
+
const wrapMap = ms.generateMap({ source: id, hires: true })
|
|
1196
|
+
|
|
1197
|
+
const allMaps = [
|
|
1198
|
+
wrapMap,
|
|
1199
|
+
res.map,
|
|
1200
|
+
...intermediateMaps.slice().reverse(),
|
|
1201
|
+
].filter(Boolean)
|
|
1202
|
+
const remapping = (await import('@jridgewell/remapping')).default
|
|
1203
|
+
finalMap = remapping(allMaps, () => null)
|
|
1204
|
+
} else {
|
|
1205
|
+
out = header + res.code + footer
|
|
1088
1206
|
}
|
|
1089
1207
|
|
|
1090
|
-
return { code: out, map:
|
|
1208
|
+
return { code: out, map: finalMap }
|
|
1091
1209
|
}
|
|
1092
1210
|
},
|
|
1093
1211
|
}
|
|
1094
1212
|
}
|
|
1095
1213
|
|
|
1096
1214
|
/**
|
|
1097
|
-
* Strip Flow types from react-native source files.
|
|
1098
|
-
*
|
|
1215
|
+
* Strip Flow types from react-native and other Flow-authored source files.
|
|
1216
|
+
*
|
|
1217
|
+
* The `.vxrn.original` patch step rewrites some of these packages in
|
|
1218
|
+
* node_modules ahead of time, but that step is best-effort and skips a module
|
|
1219
|
+
* whose transform throws. Deciding here on the file's own contents means the
|
|
1220
|
+
* bundle no longer depends on that mutation having succeeded.
|
|
1099
1221
|
*/
|
|
1100
1222
|
function flowStripPlugin(): Plugin {
|
|
1101
1223
|
return {
|
|
1102
1224
|
name: 'vxrn:flow-strip',
|
|
1103
1225
|
transform: {
|
|
1104
1226
|
async handler(code, id) {
|
|
1105
|
-
if (!
|
|
1227
|
+
if (!shouldStripFlow(id, code)) return
|
|
1106
1228
|
|
|
1107
1229
|
const fft = await import('fast-flow-transform')
|
|
1108
1230
|
const result = await fft.default({
|
|
@@ -1389,7 +1511,7 @@ function copyNativeAssetFiles(
|
|
|
1389
1511
|
* Inspired by rollipop's swc-plugin.ts.
|
|
1390
1512
|
*/
|
|
1391
1513
|
export function hermesCompatSWCPlugin(dev: boolean, sourceMaps = false): Plugin {
|
|
1392
|
-
let
|
|
1514
|
+
let oxc: typeof import('oxc-transform') | null = null
|
|
1393
1515
|
|
|
1394
1516
|
return {
|
|
1395
1517
|
name: 'vxrn:hermes-compat',
|
|
@@ -1398,43 +1520,73 @@ export function hermesCompatSWCPlugin(dev: boolean, sourceMaps = false): Plugin
|
|
|
1398
1520
|
if (id.includes('\0') || id.includes('virtual:')) return
|
|
1399
1521
|
// skip files that don't need transformation
|
|
1400
1522
|
const hasClass = code.includes('class ') || code.includes('class{')
|
|
1401
|
-
const hasAsync = code.includes('async
|
|
1523
|
+
const hasAsync = code.includes('async')
|
|
1402
1524
|
const hasBlockScopedLoop = /\bfor\s*\(\s*(?:const|let)\b/.test(code)
|
|
1403
1525
|
if (!hasClass && !hasAsync && !hasBlockScopedLoop) return
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
if (
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1526
|
+
let output: { code: string; map?: any } | undefined
|
|
1527
|
+
// keep the existing oxc limit for large prebuilt files. async lowering
|
|
1528
|
+
// below has no size exemption and runs after the worklet transform.
|
|
1529
|
+
if (code.length <= 500_000) {
|
|
1530
|
+
if (!oxc) oxc = await import('oxc-transform')
|
|
1531
|
+
const lang = /\.[cm]?ts$/.test(id) ? 'ts' : id.endsWith('.tsx') ? 'tsx' : 'jsx'
|
|
1532
|
+
const result = oxc.transformSync(id, code, {
|
|
1533
|
+
lang,
|
|
1534
|
+
target: 'es2020',
|
|
1535
|
+
assumptions: { setPublicClassFields: true },
|
|
1536
|
+
jsx: 'preserve',
|
|
1537
|
+
sourcemap: sourceMaps,
|
|
1538
|
+
sourceType: id.endsWith('.cjs') ? 'script' : 'module',
|
|
1539
|
+
})
|
|
1540
|
+
if (result.errors?.length) {
|
|
1541
|
+
const err = result.errors[0]
|
|
1542
|
+
throw new Error(err.message + (err.codeframe ? `\n${err.codeframe}` : ''))
|
|
1543
|
+
}
|
|
1544
|
+
output = { code: result.code, map: sourceMaps ? result.map : undefined }
|
|
1545
|
+
}
|
|
1546
|
+
const lowered = await transformHermesAsync(output?.code ?? code, id, sourceMaps)
|
|
1547
|
+
if (!lowered) return output
|
|
1548
|
+
if (lowered.map && output?.map) {
|
|
1549
|
+
const remapping = (await import('@jridgewell/remapping')).default
|
|
1550
|
+
lowered.map = remapping([lowered.map, output.map], () => null)
|
|
1551
|
+
}
|
|
1552
|
+
return lowered
|
|
1553
|
+
},
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
export const hermesCompatPlugin = hermesCompatSWCPlugin
|
|
1558
|
+
|
|
1559
|
+
/**
|
|
1560
|
+
* Hermes gives a loop one environment, not one per iteration, so every closure
|
|
1561
|
+
* created in a loop body sees the binding's final value. zod installs its schema
|
|
1562
|
+
* methods with `for (const key in methods) defineProperty(proto, key, {get(){...}})`,
|
|
1563
|
+
* and without this every method on every zod schema resolved to the last one:
|
|
1564
|
+
* `string().nullish()` called `apply` and the app red-screened at startup.
|
|
1565
|
+
* See @vxrn/compiler's transformHermesLoops for the bytecode evidence.
|
|
1566
|
+
*
|
|
1567
|
+
* Rolldown's own interop helpers are emitted after this runs, but they are
|
|
1568
|
+
* already `var`-based with `.bind(null, key)`, so they need no rewriting.
|
|
1569
|
+
*/
|
|
1570
|
+
export function hermesLoopsPlugin(sourceMaps = false): Plugin {
|
|
1571
|
+
let compiler: typeof import('@vxrn/compiler') | null = null
|
|
1572
|
+
|
|
1573
|
+
return {
|
|
1574
|
+
name: 'vxrn:hermes-loops',
|
|
1575
|
+
async transform(code, id) {
|
|
1576
|
+
if (!/\.[cm]?[jt]sx?$/.test(id)) return
|
|
1577
|
+
if (id.includes('\0') || id.includes('virtual:')) return
|
|
1578
|
+
|
|
1579
|
+
if (!compiler) compiler = await import('@vxrn/compiler')
|
|
1580
|
+
const result = compiler.transformHermesLoops(code, id)
|
|
1581
|
+
if (!result) return
|
|
1582
|
+
|
|
1583
|
+
if (!sourceMaps) return { code: result.code, map: null }
|
|
1584
|
+
if (result.maps.length === 1) return { code: result.code, map: result.maps[0] as any }
|
|
1585
|
+
const remapping = (await import('@jridgewell/remapping')).default
|
|
1586
|
+
return {
|
|
1587
|
+
code: result.code,
|
|
1588
|
+
map: remapping(result.maps.slice().reverse(), () => null) as any,
|
|
1589
|
+
}
|
|
1438
1590
|
},
|
|
1439
1591
|
}
|
|
1440
1592
|
}
|
|
@@ -10,6 +10,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
10
10
|
vi.mock('@vxrn/compiler', () => ({ transformSWC: vi.fn() }))
|
|
11
11
|
vi.mock('@vxrn/vite-flow', () => ({ transformFlowBabel: vi.fn() }))
|
|
12
12
|
|
|
13
|
+
import { transformSWC } from '@vxrn/compiler'
|
|
13
14
|
import {
|
|
14
15
|
type DepPatch,
|
|
15
16
|
applyDependencyPatches,
|
|
@@ -336,4 +337,36 @@ describe('applyDependencyPatches', () => {
|
|
|
336
337
|
expect(warn.mock.calls.flat().join(' ')).not.toContain('stale')
|
|
337
338
|
warn.mockRestore()
|
|
338
339
|
})
|
|
340
|
+
|
|
341
|
+
// a patch is written to node_modules once and then read by every later build,
|
|
342
|
+
// so it must be transformed as production. the dev JSX runtime emits `jsxDEV`,
|
|
343
|
+
// which React's production build exports as `void 0`, so a patched dependency
|
|
344
|
+
// rendering JSX threw "undefined is not a function" from render in every
|
|
345
|
+
// production bundle while dev builds stayed green.
|
|
346
|
+
it('transforms strategy-array patches as production, never the dev JSX runtime', async () => {
|
|
347
|
+
const mockedTransform = vi.mocked(transformSWC)
|
|
348
|
+
mockedTransform.mockClear()
|
|
349
|
+
mockedTransform.mockResolvedValue({ code: 'transformed' } as never)
|
|
350
|
+
|
|
351
|
+
await setupAndPatch({
|
|
352
|
+
patches: [{ module: 'test-pkg', patchFiles: { 'index.js': ['swc'] } }],
|
|
353
|
+
setupFs: async (nm) => {
|
|
354
|
+
const pkgDir = join(nm, 'test-pkg')
|
|
355
|
+
await FSExtra.ensureDir(pkgDir)
|
|
356
|
+
await FSExtra.writeFile(
|
|
357
|
+
join(pkgDir, 'package.json'),
|
|
358
|
+
makePkg('test-pkg', '1.0.0')
|
|
359
|
+
)
|
|
360
|
+
await FSExtra.writeFile(
|
|
361
|
+
join(pkgDir, 'index.js'),
|
|
362
|
+
'export default () => <div />'
|
|
363
|
+
)
|
|
364
|
+
},
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
expect(mockedTransform).toHaveBeenCalled()
|
|
368
|
+
for (const call of mockedTransform.mock.calls) {
|
|
369
|
+
expect(call[2]).toMatchObject({ production: true })
|
|
370
|
+
}
|
|
371
|
+
})
|
|
339
372
|
})
|
package/src/utils/patches.ts
CHANGED
|
@@ -260,8 +260,10 @@ export async function applyDependencyPatches(
|
|
|
260
260
|
patchedContent = patchDef
|
|
261
261
|
} else if (Array.isArray(patchDef)) {
|
|
262
262
|
// strategy-array patches (flow/jsx/swc transforms) are non-critical
|
|
263
|
-
// if they fail
|
|
264
|
-
//
|
|
263
|
+
// if they fail, warn and skip rather than crashing the whole patch
|
|
264
|
+
// process. the message carries the real error: a skipped Flow strip
|
|
265
|
+
// here leaves raw Flow in node_modules, and the only sign of it used
|
|
266
|
+
// to be a bundler parse failure several steps later.
|
|
265
267
|
try {
|
|
266
268
|
let contents = sourceContent
|
|
267
269
|
for (const strategy of patchDef) {
|
|
@@ -277,6 +279,16 @@ export async function applyDependencyPatches(
|
|
|
277
279
|
forceJSX: strategy === 'jsx',
|
|
278
280
|
noHMR: true,
|
|
279
281
|
fixNonTypeSpecificImports: true,
|
|
282
|
+
// a patch is written to node_modules ONCE and then read by
|
|
283
|
+
// every later build, dev and production alike, so its output
|
|
284
|
+
// has to be valid in both. the automatic JSX runtime is not:
|
|
285
|
+
// its dev form emits `jsxDEV` from react/jsx-dev-runtime, and
|
|
286
|
+
// React's production build exports `jsxDEV = void 0`, so a
|
|
287
|
+
// patched dependency that renders JSX throws "undefined is not
|
|
288
|
+
// a function" from render in any production bundle. `jsx`/`jsxs`
|
|
289
|
+
// work in both builds, and refresh is already off for
|
|
290
|
+
// node_modules, so the stable runtime is the only correct output.
|
|
291
|
+
production: true,
|
|
280
292
|
})
|
|
281
293
|
)?.code || contents
|
|
282
294
|
}
|
|
@@ -288,11 +300,10 @@ export async function applyDependencyPatches(
|
|
|
288
300
|
if (!transformWarnedModules.has(patch.module)) {
|
|
289
301
|
transformWarnedModules.add(patch.module)
|
|
290
302
|
console.warn(
|
|
291
|
-
` ⚠ Patch transform failed for ${patch.module}
|
|
303
|
+
` ⚠ Patch transform failed for ${patch.module} in ${relativePath}, skipping non-critical patch: ${
|
|
304
|
+
(transformErr as Error)?.stack ?? transformErr
|
|
305
|
+
}`
|
|
292
306
|
)
|
|
293
|
-
if (process.env.DEBUG) {
|
|
294
|
-
console.warn(transformErr)
|
|
295
|
-
}
|
|
296
307
|
}
|
|
297
308
|
return
|
|
298
309
|
}
|
|
@@ -55,6 +55,15 @@ export const EXCLUDE_LIST = [
|
|
|
55
55
|
'@vxrn/vite-native-hmr',
|
|
56
56
|
'@vxrn/compiler',
|
|
57
57
|
'@vxrn/vite-native-client',
|
|
58
|
+
|
|
59
|
+
// build-time only, and they load native .node addons the ssr dep optimizer
|
|
60
|
+
// cannot parse. apps list vxrn as a dependency because it is their dev
|
|
61
|
+
// server, which is exactly why it must never be pre-bundled for ssr.
|
|
62
|
+
'vxrn',
|
|
63
|
+
'@vxrn/vite-plugin-metro',
|
|
64
|
+
'oxc-transform',
|
|
65
|
+
'oxc-parser',
|
|
66
|
+
|
|
58
67
|
'react-native-ios-utilities',
|
|
59
68
|
'react-native-ios-modal',
|
|
60
69
|
'react-native-ios-context-menu',
|
package/types/index.d.ts
CHANGED
|
@@ -17,5 +17,7 @@ export * from './utils/environmentUtils';
|
|
|
17
17
|
export * from './utils/getServerEntry';
|
|
18
18
|
export * from './plugins/rollupRemoveUnusedImports';
|
|
19
19
|
export * from './plugins/autoDepOptimizePlugin';
|
|
20
|
+
export * from './plugins/rnCodegenPlugin';
|
|
21
|
+
export * from './plugins/workletImportsPlugin';
|
|
20
22
|
export * from './types';
|
|
21
23
|
//# sourceMappingURL=index.d.ts.map
|
package/types/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AACvC,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAA;AACnC,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AACjD,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AACvC,OAAO,EAAE,KAAK,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAA;AAE/D,OAAO,EACL,KAAK,iBAAiB,EACtB,gBAAgB,EAChB,WAAW,GACZ,MAAM,2BAA2B,CAAA;AAClC,cAAc,0BAA0B,CAAA;AACxC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,iBAAiB,CAAA;AAC/B,cAAc,0BAA0B,CAAA;AACxC,cAAc,wBAAwB,CAAA;AAEtC,cAAc,qCAAqC,CAAA;AACnD,cAAc,iCAAiC,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AACvC,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAA;AACnC,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AACjD,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AACvC,OAAO,EAAE,KAAK,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAA;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAA;AAE/D,OAAO,EACL,KAAK,iBAAiB,EACtB,gBAAgB,EAChB,WAAW,GACZ,MAAM,2BAA2B,CAAA;AAClC,cAAc,0BAA0B,CAAA;AACxC,cAAc,gCAAgC,CAAA;AAC9C,cAAc,6BAA6B,CAAA;AAC3C,cAAc,iBAAiB,CAAA;AAC/B,cAAc,0BAA0B,CAAA;AACxC,cAAc,wBAAwB,CAAA;AAEtC,cAAc,qCAAqC,CAAA;AACnD,cAAc,iCAAiC,CAAA;AAC/C,cAAc,2BAA2B,CAAA;AACzC,cAAc,gCAAgC,CAAA;AAE9C,cAAc,SAAS,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builtInDepPatches.d.ts","sourceRoot":"","sources":["../../src/patches/builtInDepPatches.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,QAAQ,EAAiC,MAAM,kBAAkB,CAAA;AAE/E,eAAO,MAAM,iBAAiB,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"builtInDepPatches.d.ts","sourceRoot":"","sources":["../../src/patches/builtInDepPatches.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,QAAQ,EAAiC,MAAM,kBAAkB,CAAA;AAE/E,eAAO,MAAM,iBAAiB,EAAE,QAAQ,EA8pBvC,CAAA"}
|
|
@@ -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;AAkBnE,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;AAkBnE,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,CA+SR"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rnCodegenPlugin.d.ts","sourceRoot":"","sources":["../../src/plugins/rnCodegenPlugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAMlC,OAAO,EAAE,2BAA2B,EAAE,MAAM,gBAAgB,CAAA;AAE5D,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAW1E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rnCodegenPlugin.test.d.ts","sourceRoot":"","sources":["../../src/plugins/rnCodegenPlugin.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Plugin } from 'rolldown';
|
|
2
|
+
/**
|
|
3
|
+
* bundles selected, synchronous pure-function exports for normal worklets mode.
|
|
4
|
+
* install in native.bundlerOptions.plugins. the replacement module exposes only
|
|
5
|
+
* the configured exports; each entry owns its complete dependency graph.
|
|
6
|
+
*/
|
|
7
|
+
export declare function workletImportsPlugin(imports: Record<string, readonly string[]>): Plugin;
|
|
8
|
+
//# sourceMappingURL=workletImportsPlugin.d.ts.map
|