@vxrn/vite-plugin-metro 1.1.501-1751155612296

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.
Files changed (142) hide show
  1. package/dist/cjs/index.cjs +28 -0
  2. package/dist/cjs/index.js +22 -0
  3. package/dist/cjs/index.js.map +6 -0
  4. package/dist/cjs/index.native.js +28 -0
  5. package/dist/cjs/index.native.js.map +6 -0
  6. package/dist/cjs/plugins/expoManifestRequestHandlerPlugin.cjs +56 -0
  7. package/dist/cjs/plugins/expoManifestRequestHandlerPlugin.js +49 -0
  8. package/dist/cjs/plugins/expoManifestRequestHandlerPlugin.js.map +6 -0
  9. package/dist/cjs/plugins/expoManifestRequestHandlerPlugin.native.js +48 -0
  10. package/dist/cjs/plugins/expoManifestRequestHandlerPlugin.native.js.map +6 -0
  11. package/dist/cjs/plugins/metroPlugin.cjs +214 -0
  12. package/dist/cjs/plugins/metroPlugin.js +186 -0
  13. package/dist/cjs/plugins/metroPlugin.js.map +6 -0
  14. package/dist/cjs/plugins/metroPlugin.native.js +179 -0
  15. package/dist/cjs/plugins/metroPlugin.native.js.map +6 -0
  16. package/dist/cjs/transformer/babel-core.cjs +27 -0
  17. package/dist/cjs/transformer/babel-core.js +22 -0
  18. package/dist/cjs/transformer/babel-core.js.map +6 -0
  19. package/dist/cjs/transformer/babel-core.native.js +28 -0
  20. package/dist/cjs/transformer/babel-core.native.js.map +6 -0
  21. package/dist/cjs/transformer/babel-transformer.cjs +148 -0
  22. package/dist/cjs/transformer/babel-transformer.js +129 -0
  23. package/dist/cjs/transformer/babel-transformer.js.map +6 -0
  24. package/dist/cjs/transformer/babel-transformer.native.js +135 -0
  25. package/dist/cjs/transformer/babel-transformer.native.js.map +6 -0
  26. package/dist/cjs/transformer/loadBabelConfig.cjs +53 -0
  27. package/dist/cjs/transformer/loadBabelConfig.js +46 -0
  28. package/dist/cjs/transformer/loadBabelConfig.js.map +6 -0
  29. package/dist/cjs/transformer/loadBabelConfig.native.js +55 -0
  30. package/dist/cjs/transformer/loadBabelConfig.native.js.map +6 -0
  31. package/dist/cjs/transformer/transformSync.cjs +64 -0
  32. package/dist/cjs/transformer/transformSync.js +53 -0
  33. package/dist/cjs/transformer/transformSync.js.map +6 -0
  34. package/dist/cjs/transformer/transformSync.native.js +58 -0
  35. package/dist/cjs/transformer/transformSync.native.js.map +6 -0
  36. package/dist/cjs/utils/getTerminalReporter.cjs +59 -0
  37. package/dist/cjs/utils/getTerminalReporter.js +48 -0
  38. package/dist/cjs/utils/getTerminalReporter.js.map +6 -0
  39. package/dist/cjs/utils/getTerminalReporter.native.js +53 -0
  40. package/dist/cjs/utils/getTerminalReporter.native.js.map +6 -0
  41. package/dist/cjs/utils/patchExpoGoManifestHandlerMiddlewareWithCustomMainModuleName.cjs +35 -0
  42. package/dist/cjs/utils/patchExpoGoManifestHandlerMiddlewareWithCustomMainModuleName.js +30 -0
  43. package/dist/cjs/utils/patchExpoGoManifestHandlerMiddlewareWithCustomMainModuleName.js.map +6 -0
  44. package/dist/cjs/utils/patchExpoGoManifestHandlerMiddlewareWithCustomMainModuleName.native.js +37 -0
  45. package/dist/cjs/utils/patchExpoGoManifestHandlerMiddlewareWithCustomMainModuleName.native.js.map +6 -0
  46. package/dist/cjs/utils/projectImport.cjs +56 -0
  47. package/dist/cjs/utils/projectImport.js +43 -0
  48. package/dist/cjs/utils/projectImport.js.map +6 -0
  49. package/dist/cjs/utils/projectImport.native.js +54 -0
  50. package/dist/cjs/utils/projectImport.native.js.map +6 -0
  51. package/dist/esm/index.js +7 -0
  52. package/dist/esm/index.js.map +6 -0
  53. package/dist/esm/index.mjs +4 -0
  54. package/dist/esm/index.mjs.map +1 -0
  55. package/dist/esm/index.native.js +4 -0
  56. package/dist/esm/index.native.js.map +1 -0
  57. package/dist/esm/plugins/expoManifestRequestHandlerPlugin.js +35 -0
  58. package/dist/esm/plugins/expoManifestRequestHandlerPlugin.js.map +6 -0
  59. package/dist/esm/plugins/expoManifestRequestHandlerPlugin.mjs +33 -0
  60. package/dist/esm/plugins/expoManifestRequestHandlerPlugin.mjs.map +1 -0
  61. package/dist/esm/plugins/expoManifestRequestHandlerPlugin.native.js +34 -0
  62. package/dist/esm/plugins/expoManifestRequestHandlerPlugin.native.js.map +1 -0
  63. package/dist/esm/plugins/metroPlugin.js +168 -0
  64. package/dist/esm/plugins/metroPlugin.js.map +6 -0
  65. package/dist/esm/plugins/metroPlugin.mjs +180 -0
  66. package/dist/esm/plugins/metroPlugin.mjs.map +1 -0
  67. package/dist/esm/plugins/metroPlugin.native.js +189 -0
  68. package/dist/esm/plugins/metroPlugin.native.js.map +1 -0
  69. package/dist/esm/transformer/babel-core.js +9 -0
  70. package/dist/esm/transformer/babel-core.js.map +6 -0
  71. package/dist/esm/transformer/babel-core.mjs +3 -0
  72. package/dist/esm/transformer/babel-core.mjs.map +1 -0
  73. package/dist/esm/transformer/babel-core.native.js +3 -0
  74. package/dist/esm/transformer/babel-core.native.js.map +1 -0
  75. package/dist/esm/transformer/babel-transformer.js +110 -0
  76. package/dist/esm/transformer/babel-transformer.js.map +6 -0
  77. package/dist/esm/transformer/babel-transformer.mjs +122 -0
  78. package/dist/esm/transformer/babel-transformer.mjs.map +1 -0
  79. package/dist/esm/transformer/babel-transformer.native.js +136 -0
  80. package/dist/esm/transformer/babel-transformer.native.js.map +1 -0
  81. package/dist/esm/transformer/loadBabelConfig.js +23 -0
  82. package/dist/esm/transformer/loadBabelConfig.js.map +6 -0
  83. package/dist/esm/transformer/loadBabelConfig.mjs +19 -0
  84. package/dist/esm/transformer/loadBabelConfig.mjs.map +1 -0
  85. package/dist/esm/transformer/loadBabelConfig.native.js +23 -0
  86. package/dist/esm/transformer/loadBabelConfig.native.js.map +1 -0
  87. package/dist/esm/transformer/transformSync.js +29 -0
  88. package/dist/esm/transformer/transformSync.js.map +6 -0
  89. package/dist/esm/transformer/transformSync.mjs +30 -0
  90. package/dist/esm/transformer/transformSync.mjs.map +1 -0
  91. package/dist/esm/transformer/transformSync.native.js +31 -0
  92. package/dist/esm/transformer/transformSync.native.js.map +1 -0
  93. package/dist/esm/utils/getTerminalReporter.js +25 -0
  94. package/dist/esm/utils/getTerminalReporter.js.map +6 -0
  95. package/dist/esm/utils/getTerminalReporter.mjs +25 -0
  96. package/dist/esm/utils/getTerminalReporter.mjs.map +1 -0
  97. package/dist/esm/utils/getTerminalReporter.native.js +26 -0
  98. package/dist/esm/utils/getTerminalReporter.native.js.map +1 -0
  99. package/dist/esm/utils/patchExpoGoManifestHandlerMiddlewareWithCustomMainModuleName.js +14 -0
  100. package/dist/esm/utils/patchExpoGoManifestHandlerMiddlewareWithCustomMainModuleName.js.map +6 -0
  101. package/dist/esm/utils/patchExpoGoManifestHandlerMiddlewareWithCustomMainModuleName.mjs +12 -0
  102. package/dist/esm/utils/patchExpoGoManifestHandlerMiddlewareWithCustomMainModuleName.mjs.map +1 -0
  103. package/dist/esm/utils/patchExpoGoManifestHandlerMiddlewareWithCustomMainModuleName.native.js +14 -0
  104. package/dist/esm/utils/patchExpoGoManifestHandlerMiddlewareWithCustomMainModuleName.native.js.map +1 -0
  105. package/dist/esm/utils/projectImport.js +20 -0
  106. package/dist/esm/utils/projectImport.js.map +6 -0
  107. package/dist/esm/utils/projectImport.mjs +20 -0
  108. package/dist/esm/utils/projectImport.mjs.map +1 -0
  109. package/dist/esm/utils/projectImport.native.js +22 -0
  110. package/dist/esm/utils/projectImport.native.js.map +1 -0
  111. package/empty.js +0 -0
  112. package/package.json +68 -0
  113. package/src/index.ts +4 -0
  114. package/src/plugins/expoManifestRequestHandlerPlugin.ts +75 -0
  115. package/src/plugins/metroPlugin.ts +314 -0
  116. package/src/transformer/babel-core.ts +7 -0
  117. package/src/transformer/babel-transformer.ts +211 -0
  118. package/src/transformer/loadBabelConfig.ts +53 -0
  119. package/src/transformer/transformSync.ts +54 -0
  120. package/src/utils/getTerminalReporter.ts +42 -0
  121. package/src/utils/patchExpoGoManifestHandlerMiddlewareWithCustomMainModuleName.ts +27 -0
  122. package/src/utils/projectImport.ts +32 -0
  123. package/types/index.d.ts +5 -0
  124. package/types/index.d.ts.map +1 -0
  125. package/types/plugins/expoManifestRequestHandlerPlugin.d.ts +14 -0
  126. package/types/plugins/expoManifestRequestHandlerPlugin.d.ts.map +1 -0
  127. package/types/plugins/metroPlugin.d.ts +22 -0
  128. package/types/plugins/metroPlugin.d.ts.map +1 -0
  129. package/types/transformer/babel-core.d.ts +2 -0
  130. package/types/transformer/babel-core.d.ts.map +1 -0
  131. package/types/transformer/babel-transformer.d.ts +18 -0
  132. package/types/transformer/babel-transformer.d.ts.map +1 -0
  133. package/types/transformer/loadBabelConfig.d.ts +10 -0
  134. package/types/transformer/loadBabelConfig.d.ts.map +1 -0
  135. package/types/transformer/transformSync.d.ts +11 -0
  136. package/types/transformer/transformSync.d.ts.map +1 -0
  137. package/types/utils/getTerminalReporter.d.ts +3 -0
  138. package/types/utils/getTerminalReporter.d.ts.map +1 -0
  139. package/types/utils/patchExpoGoManifestHandlerMiddlewareWithCustomMainModuleName.d.ts +5 -0
  140. package/types/utils/patchExpoGoManifestHandlerMiddlewareWithCustomMainModuleName.d.ts.map +1 -0
  141. package/types/utils/projectImport.d.ts +12 -0
  142. package/types/utils/projectImport.d.ts.map +1 -0
@@ -0,0 +1,314 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { readFile } from 'node:fs/promises'
3
+ import { parse } from 'node:url'
4
+ import type { PluginOption } from 'vite'
5
+ import launchEditor from 'launch-editor'
6
+
7
+ // For Metro and Expo, we only import types here.
8
+ // We use `projectImport` to dynamically import the actual modules
9
+ // at runtime to ensure they are loaded from the user's project root.
10
+ import type MetroT from 'metro'
11
+ import type { loadConfig as loadConfigT } from 'metro'
12
+ import type MetroHmrServerT from 'metro/src/HmrServer'
13
+ import type createWebsocketServerT from 'metro/src/lib/createWebsocketServer'
14
+ import type { getDefaultConfig as getDefaultConfigT } from '@expo/metro-config'
15
+ import type { createDevMiddleware as createDevMiddlewareT } from '@react-native/dev-middleware'
16
+
17
+ import { projectImport, projectResolve } from '../utils/projectImport'
18
+ import { getTerminalReporter } from '../utils/getTerminalReporter'
19
+ import { patchExpoGoManifestHandlerMiddlewareWithCustomMainModuleName } from '../utils/patchExpoGoManifestHandlerMiddlewareWithCustomMainModuleName'
20
+ import type { TransformOptions } from '../transformer/babel-core'
21
+
22
+ type MetroYargArguments = Parameters<typeof loadConfigT>[0]
23
+ type MetroInputConfig = Parameters<typeof loadConfigT>[1]
24
+
25
+ export type MetroPluginOptions = {
26
+ argv?: MetroYargArguments
27
+ defaultConfigOverrides?:
28
+ | MetroInputConfig
29
+ | ((defaultConfig: MetroInputConfig) => MetroInputConfig)
30
+ babelConfig?: TransformOptions
31
+ /**
32
+ * Overrides the main module name which is normally defined as the `main` field in `package.json`.
33
+ *
34
+ * This will affect how `/.expo/.virtual-metro-entry.bundle` behaves.
35
+ *
36
+ * It can be used to change the entry point of the React Native app without the need of using
37
+ * the `main` field in `package.json`.
38
+ */
39
+ mainModuleName?: string
40
+ }
41
+
42
+ export function metroPlugin({
43
+ argv,
44
+ defaultConfigOverrides,
45
+ babelConfig,
46
+ mainModuleName,
47
+ }: MetroPluginOptions = {}): PluginOption {
48
+ // let projectRoot = ''
49
+
50
+ return {
51
+ name: 'metro',
52
+ // configResolved(config) {
53
+ // projectRoot = config.root
54
+ // },
55
+
56
+ async configureServer(server) {
57
+ const { logger, root: projectRoot } = server.config
58
+
59
+ const { default: Metro, loadConfig } = await projectImport<{
60
+ default: typeof MetroT
61
+ loadConfig: typeof loadConfigT
62
+ }>(projectRoot, 'metro')
63
+ const { default: MetroHmrServer } = await projectImport<{
64
+ default: typeof MetroHmrServerT
65
+ }>(projectRoot, 'metro/src/HmrServer')
66
+ const { default: createWebsocketServer } = await projectImport<{
67
+ default: typeof createWebsocketServerT
68
+ }>(projectRoot, 'metro/src/lib/createWebsocketServer')
69
+ const { getDefaultConfig } = await projectImport<{
70
+ getDefaultConfig: typeof getDefaultConfigT
71
+ }>(projectRoot, '@expo/metro-config')
72
+ const { createDevMiddleware } = await projectImport<{
73
+ createDevMiddleware: typeof createDevMiddlewareT
74
+ }>(projectRoot, '@react-native/dev-middleware')
75
+
76
+ const _defaultConfig: MetroInputConfig = getDefaultConfig(projectRoot) as any
77
+
78
+ if (mainModuleName) {
79
+ const origRewriteRequestUrl = _defaultConfig!.server!.rewriteRequestUrl!
80
+
81
+ // We need to patch Expo's default `config.server.rewriteRequestUrl`
82
+ // to change how URLs like '/.expo/.virtual-metro-entry.bundle?' are
83
+ // rewritten.
84
+ // But since that function is difficult to override, here we borrow
85
+ // the ExpoGoManifestHandlerMiddleware and use it to resolve the
86
+ // URL to the main module name.
87
+ const resolveMainModuleName: (p: { platform: 'ios' | 'android' }) => string =
88
+ await (async () => {
89
+ const ExpoGoManifestHandlerMiddleware = (
90
+ await projectImport(
91
+ projectRoot,
92
+ '@expo/cli/build/src/start/server/middleware/ExpoGoManifestHandlerMiddleware.js'
93
+ )
94
+ ).default.ExpoGoManifestHandlerMiddleware
95
+
96
+ const manifestHandlerMiddleware = new ExpoGoManifestHandlerMiddleware(projectRoot, {})
97
+
98
+ patchExpoGoManifestHandlerMiddlewareWithCustomMainModuleName(
99
+ manifestHandlerMiddleware,
100
+ mainModuleName
101
+ )
102
+
103
+ return (p) => {
104
+ return manifestHandlerMiddleware.resolveMainModuleName({
105
+ pkg: { main: mainModuleName },
106
+ platform: p.platform,
107
+ })
108
+ }
109
+ })()
110
+
111
+ _defaultConfig!.server!.rewriteRequestUrl = (url) => {
112
+ if (url.includes('/.expo/.virtual-metro-entry.bundle?')) {
113
+ const resolvedMainModulePath = resolveMainModuleName({
114
+ platform: 'ios', // we probably need to handle android here, but currently in our use case this won't affect the result
115
+ })
116
+
117
+ return url.replace('.expo/.virtual-metro-entry', resolvedMainModulePath)
118
+ }
119
+
120
+ return origRewriteRequestUrl(url)
121
+ }
122
+ }
123
+
124
+ const defaultConfig: MetroInputConfig = {
125
+ ..._defaultConfig,
126
+ resolver: {
127
+ ..._defaultConfig?.resolver,
128
+ sourceExts: ['js', 'jsx', 'json', 'ts', 'tsx', 'mjs', 'cjs'], // `one` related packages are using `.mjs` extensions. This somehow fixes `.native` files not being resolved correctly when `.mjs` files are present.
129
+ resolveRequest: (context, moduleName, platform) => {
130
+ const origResolveRequestFn =
131
+ _defaultConfig?.resolver?.resolveRequest || context.resolveRequest
132
+
133
+ // HACK: Do not assert the "import" condition for `@babel/runtime`. This
134
+ // is a workaround for ESM <-> CJS interop, as we need the CJS versions of
135
+ // `@babel/runtime` helpers.
136
+ //
137
+ // This hack is originally made in Metro and was removed in `v0.81.3`, but
138
+ // we somehow still need it.
139
+ // See: https://github.com/facebook/metro/commit/9552a64a0487af64cd86d8591e203a55c59c9686#diff-b03f1b511a2be7abd755b9c2561e47f513f84931466f2cc20a17a4238d70f12bL370-L378
140
+ //
141
+ // Resolves the "TypeError: _interopRequireDefault is not a function (it is Object)" error.
142
+ if (moduleName.startsWith('@babel/runtime')) {
143
+ const contextOverride = {
144
+ ...context,
145
+ unstable_conditionNames: context.unstable_conditionNames.filter(
146
+ (c) => c !== 'import'
147
+ ),
148
+ }
149
+ return origResolveRequestFn(contextOverride, moduleName, platform)
150
+ }
151
+
152
+ return origResolveRequestFn(context, moduleName, platform)
153
+ },
154
+ },
155
+ transformer: {
156
+ ..._defaultConfig?.transformer,
157
+ babelTransformerPath: projectResolve(
158
+ projectRoot,
159
+ '@vxrn/vite-plugin-metro/babel-transformer'
160
+ ),
161
+ // TODO: This is what Expo is doing, but do we really need this?
162
+ publicPath: '/assets/?unstable_path=.',
163
+ },
164
+ reporter: await getTerminalReporter(projectRoot),
165
+ }
166
+
167
+ const config = await loadConfig(
168
+ {
169
+ cwd: projectRoot,
170
+ projectRoot,
171
+ 'reset-cache': !!process.env.METRO_RESET_CACHE,
172
+ ...argv,
173
+ },
174
+ {
175
+ ...defaultConfig,
176
+ ...(typeof defaultConfigOverrides === 'function'
177
+ ? defaultConfigOverrides(defaultConfig)
178
+ : defaultConfigOverrides),
179
+ }
180
+ )
181
+
182
+ const { middleware, end, metroServer } = await Metro.createConnectMiddleware(config, {
183
+ // Force enable file watching, even on CI.
184
+ // This is needed for HMR tests to work on CI.
185
+ watch: true,
186
+ })
187
+
188
+ // Patch transformFile to inject custom transform options.
189
+ const originalTransformFile = metroServer
190
+ .getBundler()
191
+ .getBundler()
192
+ .transformFile.bind(metroServer.getBundler().getBundler())
193
+ metroServer.getBundler().getBundler().transformFile = async (
194
+ filePath: string,
195
+ transformOptions: Parameters<typeof originalTransformFile>[1],
196
+ fileBuffer?: Parameters<typeof originalTransformFile>[2]
197
+ ) => {
198
+ return originalTransformFile(
199
+ filePath,
200
+ {
201
+ ...transformOptions,
202
+ customTransformOptions: {
203
+ ...transformOptions.customTransformOptions,
204
+ vite: {
205
+ babelConfig,
206
+ },
207
+ },
208
+ },
209
+ fileBuffer
210
+ )
211
+ }
212
+
213
+ const hmrServer = new MetroHmrServer(
214
+ metroServer.getBundler(),
215
+ metroServer.getCreateModuleId(),
216
+ config
217
+ )
218
+
219
+ const reactNativeDevToolsUrl = `http://${typeof server.config.server.host === 'boolean' ? 'localhost' : server.config.server.host}:${server.config.server.port}`
220
+ const { middleware: rnDevtoolsMiddleware, websocketEndpoints: rnDevtoolsWebsocketEndpoints } =
221
+ createDevMiddleware({
222
+ projectRoot,
223
+ serverBaseUrl: reactNativeDevToolsUrl,
224
+ logger: console,
225
+ })
226
+
227
+ server.middlewares.use(async (req, res, next) => {
228
+ try {
229
+ // Just for debugging purposes.
230
+ if (req.url?.includes('.bundle')) {
231
+ const VITE_METRO_DEBUG_BUNDLE = process.env.VITE_METRO_DEBUG_BUNDLE
232
+ if (VITE_METRO_DEBUG_BUNDLE) {
233
+ if (existsSync(VITE_METRO_DEBUG_BUNDLE)) {
234
+ console.info(` !!! - serving debug bundle from`, VITE_METRO_DEBUG_BUNDLE)
235
+ const content = await readFile(VITE_METRO_DEBUG_BUNDLE, 'utf-8')
236
+ res.setHeader('Content-Type', 'application/javascript')
237
+ res.end(content)
238
+ return
239
+ }
240
+ }
241
+ }
242
+
243
+ // Handle `isPackagerRunning` request from native app.
244
+ // Without this, people may see a `No script URL provided. Make sure the packager is running or you have embedded a JS bundle in your application bundle.`, `unsanitizedScriptURLString = (null)` error in their native app running with the "Debug" configuration.
245
+ // See: https://github.com/facebook/react-native/blob/v0.80.0-rc.4/packages/react-native/React/Base/RCTBundleURLProvider.mm#L87-L113
246
+ if (
247
+ req.url === '/status' &&
248
+ req.headers['user-agent']?.includes(
249
+ 'CFNetwork/'
250
+ ) /* The path (`/status`) is too general and may conflict with the user's web app, so we also check the User-Agent header to ensure it's a request from a native app. */ /* TODO: Android */
251
+ ) {
252
+ res.statusCode = 200
253
+ res.end('packager-status:running')
254
+ return
255
+ }
256
+
257
+ if (req.url === '/open-stack-frame' && req.method === 'POST') {
258
+ let body = ''
259
+
260
+ req.on('data', (chunk) => {
261
+ body += chunk.toString()
262
+ })
263
+
264
+ req.on('end', () => {
265
+ try {
266
+ const frame = JSON.parse(body)
267
+
268
+ // https://github.com/yyx990803/launch-editor/blob/master/packages/launch-editor-middleware/index.js
269
+ launchEditor(frame.file)
270
+ res.statusCode = 200
271
+ res.end('Stack frame opened in editor')
272
+ } catch (e) {
273
+ console.error('Failed to parse stack frame:', e)
274
+ res.statusCode = 400
275
+ return res.end('Invalid stack frame JSON')
276
+ }
277
+ })
278
+
279
+ return
280
+ }
281
+
282
+ // this is the actual Metro middleware that handles bundle requests
283
+ await (middleware as any)(req, res, next)
284
+ } catch (error) {
285
+ // TODO: handle errors from Metro middleware?
286
+ console.error('Metro middleware error:', error)
287
+ next()
288
+ }
289
+ })
290
+
291
+ server.middlewares.use(rnDevtoolsMiddleware)
292
+
293
+ const websocketEndpoints = {
294
+ '/hot': createWebsocketServer({
295
+ websocketServer: hmrServer,
296
+ }),
297
+ ...rnDevtoolsWebsocketEndpoints,
298
+ }
299
+
300
+ server.httpServer?.on('upgrade', (request, socket, head) => {
301
+ const { pathname } = parse(request.url!)
302
+
303
+ if (pathname != null && websocketEndpoints[pathname]) {
304
+ websocketEndpoints[pathname].handleUpgrade(request, socket, head, (ws) => {
305
+ websocketEndpoints[pathname].emit('connection', ws, request)
306
+ })
307
+ } else {
308
+ // TODO: Do we need this?
309
+ // socket.destroy()
310
+ }
311
+ })
312
+ },
313
+ }
314
+ }
@@ -0,0 +1,7 @@
1
+ // re-exported because babel/core is hard to mock.
2
+ export {
3
+ type PluginItem,
4
+ type TransformOptions,
5
+ transformFromAstSync,
6
+ transformSync,
7
+ } from '@babel/core';
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Copyright (c) 650 Industries (Expo). All rights reserved.
3
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ // A fork of the upstream babel-transformer that uses Expo-specific babel defaults
9
+ // and adds support for web and Node.js environments via `isServer` on the Babel caller.
10
+ // See:
11
+ // * https://github.com/facebook/metro/blob/main/packages/metro-babel-transformer/src/index.js
12
+ // * https://github.com/expo/expo/blob/main/packages/%40expo/metro-config/src/babel-transformer.ts
13
+ import type { BabelTransformer, BabelTransformerArgs } from 'metro-babel-transformer'
14
+ import assert from 'node:assert'
15
+
16
+ import type { TransformOptions } from './babel-core'
17
+ import { loadBabelConfig } from './loadBabelConfig'
18
+ import { transformSync } from './transformSync'
19
+
20
+ export type ExpoBabelCaller = TransformOptions['caller'] & {
21
+ supportsReactCompiler?: boolean
22
+ isReactServer?: boolean
23
+ isHMREnabled?: boolean
24
+ isServer?: boolean
25
+ isNodeModule?: boolean
26
+ preserveEnvVars?: boolean
27
+ isDev?: boolean
28
+ asyncRoutes?: boolean
29
+ baseUrl?: string
30
+ engine?: string
31
+ bundler?: 'metro' | (string & object)
32
+ platform?: string | null
33
+ routerRoot?: string
34
+ projectRoot: string
35
+ }
36
+
37
+ const debug = require('debug')('expo:metro-config:babel-transformer') as typeof console.log
38
+
39
+ function isCustomTruthy(value: any): boolean {
40
+ return String(value) === 'true'
41
+ }
42
+
43
+ function memoize<T extends (...args: any[]) => any>(fn: T): T {
44
+ const cache = new Map<string, ReturnType<T>>()
45
+ return ((...args: any[]) => {
46
+ const key = JSON.stringify(args)
47
+ if (cache.has(key)) {
48
+ return cache.get(key)
49
+ }
50
+ const result = fn(...args)
51
+ cache.set(key, result)
52
+ return result
53
+ }) as T
54
+ }
55
+
56
+ const memoizeWarning = memoize((message: string) => {
57
+ debug(message)
58
+ })
59
+
60
+ function getBabelCaller({
61
+ filename,
62
+ options,
63
+ }: Pick<BabelTransformerArgs, 'filename' | 'options'>): ExpoBabelCaller {
64
+ const isNodeModule = filename.includes('node_modules')
65
+ const isReactServer = options.customTransformOptions?.environment === 'react-server'
66
+ const isGenericServer = options.customTransformOptions?.environment === 'node'
67
+ const isServer = isReactServer || isGenericServer
68
+
69
+ const routerRoot =
70
+ typeof options.customTransformOptions?.routerRoot === 'string'
71
+ ? decodeURI(options.customTransformOptions.routerRoot)
72
+ : undefined
73
+
74
+ if (routerRoot == null) {
75
+ memoizeWarning(
76
+ 'Warning: Missing transform.routerRoot option in Metro bundling request, falling back to `app` as routes directory. This can occur if you bundle without Expo CLI or expo/metro-config.'
77
+ )
78
+ }
79
+
80
+ return {
81
+ name: 'metro',
82
+ bundler: 'metro',
83
+ platform: options.platform,
84
+ // Empower the babel preset to know the env it's bundling for.
85
+ // Metro automatically updates the cache to account for the custom transform options.
86
+ isServer,
87
+
88
+ // Enable React Server Component rules for AST. The naming maps to the resolver property `--conditions=react-server`.
89
+ isReactServer,
90
+
91
+ // The base url to make requests from, used for hosting from non-standard locations.
92
+ baseUrl:
93
+ typeof options.customTransformOptions?.baseUrl === 'string'
94
+ ? decodeURI(options.customTransformOptions.baseUrl)
95
+ : '',
96
+
97
+ // Ensure we always use a mostly-valid router root.
98
+ routerRoot: routerRoot ?? 'app',
99
+
100
+ isDev: options.dev,
101
+
102
+ // This value indicates if the user has disabled the feature or not.
103
+ // Other criteria may still cause the feature to be disabled, but all inputs used are
104
+ // already considered in the cache key.
105
+ preserveEnvVars: isCustomTruthy(options.customTransformOptions?.preserveEnvVars)
106
+ ? true
107
+ : undefined,
108
+ asyncRoutes: isCustomTruthy(options.customTransformOptions?.asyncRoutes) ? true : undefined,
109
+ // Pass the engine to babel so we can automatically transpile for the correct
110
+ // target environment.
111
+ engine: stringOrUndefined(options.customTransformOptions?.engine),
112
+
113
+ // Provide the project root for accurately reading the Expo config.
114
+ projectRoot: options.projectRoot,
115
+
116
+ isNodeModule,
117
+
118
+ isHMREnabled: options.hot,
119
+
120
+ // Set the standard Babel flag to disable ESM transformations.
121
+ supportsStaticESM:
122
+ isCustomTruthy(options.customTransformOptions?.optimize) || options.experimentalImportSupport,
123
+
124
+ // Enable React compiler support in Babel.
125
+ // TODO: Remove this in the future when compiler is on by default.
126
+ supportsReactCompiler: isCustomTruthy(options.customTransformOptions?.reactCompiler)
127
+ ? true
128
+ : undefined,
129
+ }
130
+ }
131
+
132
+ function stringOrUndefined(value: unknown): string | undefined {
133
+ return typeof value === 'string' ? value : undefined
134
+ }
135
+
136
+ const transform: BabelTransformer['transform'] = ({
137
+ filename,
138
+ src,
139
+ options,
140
+ // `plugins` is used for `functionMapBabelPlugin` from `metro-source-map`. Could make sense to move this to `babel-preset-expo` too.
141
+ plugins,
142
+ }: BabelTransformerArgs): ReturnType<BabelTransformer['transform']> => {
143
+ const customOptionsFromVite: {
144
+ [key: string]: unknown
145
+ } = options.customTransformOptions?.vite || ({} as any)
146
+ const babelConfigFromVitePlugin: TransformOptions = customOptionsFromVite.babelConfig || {}
147
+
148
+ const OLD_BABEL_ENV = process.env.BABEL_ENV
149
+ process.env.BABEL_ENV = options.dev ? 'development' : process.env.BABEL_ENV || 'production'
150
+ try {
151
+ const babelConfig: TransformOptions = {
152
+ // ES modules require sourceType='module' but OSS may not always want that
153
+ sourceType: 'unambiguous',
154
+
155
+ // The output we want from Babel methods
156
+ ast: true,
157
+ code: false,
158
+ // NOTE(EvanBacon): We split the parse/transform steps up to accommodate
159
+ // Hermes parsing, but this defaults to cloning the AST which increases
160
+ // the transformation time by a fair amount.
161
+ // You get this behavior by default when using Babel's `transform` method directly.
162
+ cloneInputAst: false,
163
+
164
+ // Options for debugging
165
+ cwd: options.projectRoot,
166
+ filename,
167
+ highlightCode: true,
168
+
169
+ ...babelConfigFromVitePlugin,
170
+
171
+ ...loadBabelConfig(options),
172
+
173
+ babelrc:
174
+ typeof options.enableBabelRCLookup === 'boolean' ? options.enableBabelRCLookup : true,
175
+
176
+ plugins: [...(babelConfigFromVitePlugin.plugins || []), ...(plugins as any)],
177
+
178
+ // NOTE(EvanBacon): We heavily leverage the caller functionality to mutate the babel config.
179
+ // This compensates for the lack of a format plugin system in Metro. Users can modify the
180
+ // all (most) of the transforms in their local Babel config.
181
+ // This also helps us keep the transform layers small and focused on a single task. We can also use this to
182
+ // ensure the Babel config caching is more accurate.
183
+ // Additionally, by moving everything Babel-related to the Babel preset, it makes it easier for users to reason
184
+ // about the requirements of an Expo project, making it easier to migrate to other transpilers in the future.
185
+ caller: getBabelCaller({ filename, options }),
186
+ }
187
+
188
+ const result = transformSync(src, babelConfig, options)
189
+
190
+ // The result from `transformFromAstSync` can be null (if the file is ignored)
191
+ if (!result) {
192
+ // BabelTransformer specifies that the `ast` can never be null but
193
+ // the function returns here. Discovered when typing `BabelNode`.
194
+ // @ts-expect-error: see https://github.com/facebook/react-native/blob/401991c3f073bf734ee04f9220751c227d2abd31/packages/react-native-babel-transformer/src/index.js#L220-L224
195
+ return { ast: null }
196
+ }
197
+
198
+ assert(result.ast)
199
+ return { ast: result.ast, metadata: result.metadata }
200
+ } finally {
201
+ if (OLD_BABEL_ENV) {
202
+ process.env.BABEL_ENV = OLD_BABEL_ENV
203
+ }
204
+ }
205
+ }
206
+
207
+ const babelTransformer: BabelTransformer = {
208
+ transform,
209
+ }
210
+
211
+ module.exports = babelTransformer
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Copyright (c) 650 Industries (Expo). All rights reserved.
3
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ import fs from 'node:fs'
9
+ import path from 'node:path'
10
+
11
+ import type { TransformOptions } from './babel-core'
12
+
13
+ /**
14
+ * Returns a memoized function that checks for the existence of a
15
+ * project-level .babelrc file. If it doesn't exist, it reads the
16
+ * default React Native babelrc file and uses that.
17
+ */
18
+ export const loadBabelConfig = (() => {
19
+ let babelRC: Pick<TransformOptions, 'extends' | 'presets'> | null = null
20
+
21
+ return function _getBabelRC({ projectRoot }: { projectRoot: string }) {
22
+ if (babelRC !== null) {
23
+ return babelRC
24
+ }
25
+
26
+ babelRC = {}
27
+
28
+ if (projectRoot) {
29
+ // Check for various babel config files in the project root
30
+ // TODO(EvanBacon): We might want to disable babelrc lookup when the user specifies `enableBabelRCLookup: false`.
31
+ const possibleBabelRCPaths = ['.babelrc', '.babelrc.js', 'babel.config.js']
32
+
33
+ const foundBabelRCPath = possibleBabelRCPaths.find((configFileName) =>
34
+ fs.existsSync(path.resolve(projectRoot, configFileName))
35
+ )
36
+
37
+ // Extend the config if a babel config file is found
38
+ if (foundBabelRCPath) {
39
+ babelRC.extends = path.resolve(projectRoot, foundBabelRCPath)
40
+ }
41
+ }
42
+
43
+ // Use the default preset for react-native if no babel config file is found
44
+ if (!babelRC.extends) {
45
+ babelRC.presets = [
46
+ // { plugins: [transformImportMetaGlobPlugin] }, // Added to support Vite's `import.meta.glob`
47
+ require('babel-preset-expo'),
48
+ ]
49
+ }
50
+
51
+ return babelRC
52
+ }
53
+ })()
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Copyright (c) 650 Industries (Expo). All rights reserved.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import * as babel from './babel-core';
8
+
9
+ // TS detection conditions copied from @react-native/babel-preset
10
+ function isTypeScriptSource(fileName: string): boolean {
11
+ return fileName?.endsWith('.ts');
12
+ }
13
+
14
+ function isTSXSource(fileName: string): boolean {
15
+ return fileName?.endsWith('.tsx');
16
+ }
17
+
18
+ export function transformSync(
19
+ src: string,
20
+ babelConfig: babel.TransformOptions,
21
+ { hermesParser }: { hermesParser?: boolean }
22
+ ) {
23
+ const isTypeScript =
24
+ isTypeScriptSource(babelConfig.filename!) || isTSXSource(babelConfig.filename!);
25
+
26
+ if (isTypeScript) {
27
+ return parseWithBabel(src, babelConfig);
28
+ }
29
+
30
+ if (
31
+ !isTypeScript &&
32
+ // The Hermes parser doesn't support comments or babel proposals such as export default from, meaning it has lower compatibility with larger parts
33
+ // of the ecosystem.
34
+ // However, React Native ships with flow syntax that isn't supported in Babel so we need to use Hermes for those files.
35
+ // We can try to quickly detect if the file uses flow syntax by checking for the @flow pragma which is present in every React Native file.
36
+ (hermesParser || src.includes(' @flow'))
37
+ ) {
38
+ return parseWithHermes(src, babelConfig);
39
+ }
40
+
41
+ return parseWithBabel(src, babelConfig);
42
+ }
43
+
44
+ function parseWithHermes(src: string, babelConfig: babel.TransformOptions) {
45
+ const sourceAst = require('hermes-parser').parse(src, {
46
+ babel: true,
47
+ sourceType: babelConfig.sourceType,
48
+ });
49
+ return babel.transformFromAstSync(sourceAst, src, babelConfig);
50
+ }
51
+
52
+ function parseWithBabel(src: string, babelConfig: babel.TransformOptions) {
53
+ return babel.transformSync(src, babelConfig);
54
+ }
@@ -0,0 +1,42 @@
1
+ // For Metro and Expo, we only import types here.
2
+ // We use `projectImport` to dynamically import the actual modules
3
+ // at runtime to ensure they are loaded from the user's project root.
4
+ import type { TerminalReporter as TerminalReporterT, Terminal as TerminalT } from 'metro'
5
+ import colors from 'picocolors'
6
+
7
+ import { projectImport } from './projectImport'
8
+
9
+ export async function getTerminalReporter(projectRoot: string) {
10
+ const { Terminal, TerminalReporter } = await projectImport<{
11
+ Terminal: typeof TerminalT
12
+ TerminalReporter: typeof TerminalReporterT
13
+ }>(projectRoot, 'metro')
14
+
15
+ let metroWelcomeMessagePrinted = false
16
+
17
+ const terminal = new Terminal(process.stdout)
18
+ ;(terminal as any)._log = terminal.log
19
+ terminal.log = (message, ...rest) => {
20
+ // Minimal Metro welcome message.
21
+ // See: https://github.com/facebook/metro/blob/v0.82.4/packages/metro/src/lib/TerminalReporter.js#L278-L283
22
+ if (!metroWelcomeMessagePrinted) {
23
+ const match = message.match(/Welcome to Metro.+v(\d+\.\d+\.\d+)/)
24
+ if (match) {
25
+ const version = match[1]
26
+ metroWelcomeMessagePrinted = true
27
+ return (terminal as any)._log(colors.dim(`\n Using Metro Bundler v${version}`), ...rest)
28
+ }
29
+ }
30
+
31
+ return (terminal as any)._log(message, ...rest)
32
+ }
33
+
34
+ // See: https://github.com/facebook/metro/blob/v0.82.4/packages/metro/src/lib/TerminalReporter.js
35
+ const terminalReporter = new TerminalReporter(terminal)
36
+
37
+ // Do not print a giant Metro logo on start.
38
+ // See: https://github.com/facebook/metro/blob/v0.82.4/packages/metro/src/lib/TerminalReporter.js#L230-L232
39
+ ;(terminalReporter as any)._logInitializing = () => {}
40
+
41
+ return terminalReporter
42
+ }