UNPKG

one

Version:

One is a new React Framework that makes Vite serve both native and web.

113 lines (102 loc) 4 kB
import path from 'node:path' import { buildMetroConfigInputFromViteConfig } from '@vxrn/vite-plugin-metro' import { getViteMetroPluginOptions } from './getViteMetroPluginOptions' import { loadUserOneOptions } from '../vite/loadConfig' export type WithOneOptions = { /** Absolute path to the project root. Defaults to `process.cwd()`. */ projectRoot?: string /** Router root folder relative to the project root. Defaults to `'app'`. */ routerRoot?: string /** Patterns to exclude from router file resolution. */ ignoredRouteFiles?: Array<`**/*${string}`> /** Routing linking config — mirrors `one({ router: { linking } })`. */ linking?: unknown /** Native setup file path relative to the project root. */ setupFile?: string | { native?: string; ios?: string; android?: string } /** * Load the app's vite.config and use the real One native Metro options. * Defaults to true so generated Expo/EAS configs match One's own native path. */ loadViteConfig?: boolean } async function loadUserViteMetroOptions(projectRoot: string) { const previousCwd = process.cwd() const previousIsVxrnCli = process.env.IS_VXRN_CLI try { process.chdir(projectRoot) process.env.IS_VXRN_CLI = 'true' return await loadUserOneOptions('build', true) } finally { process.chdir(previousCwd) if (previousIsVxrnCli === undefined) { delete process.env.IS_VXRN_CLI } else { process.env.IS_VXRN_CLI = previousIsVxrnCli } } } /** * Produce a Metro config that invokes the EXACT same `getMetroConfigFromViteConfig` * pipeline that One's native production builds use. This way `expo export`, * `eas update`, and any other Metro-direct workflow produce a bundle that's * byte-equivalent to what `react-native bundle` (the iOS build phase) produces. * * The first argument is ignored — kept only for ergonomic compatibility with * the typical `withOne(getDefaultConfig(__dirname))` call shape that Expo * users are used to. We discard it because @expo/metro-config's defaults * differ from what One needs, and the production pipeline applies its own * defaults internally. * * @example * ```js * // metro.config.cjs * const { withOne } = require('one/metro-config') * * module.exports = withOne(__dirname) * ``` */ export async function withOne( baseConfigOrProjectRoot: string | object | undefined, options: WithOneOptions = {} ): Promise<unknown> { const projectRoot = path.resolve( typeof baseConfigOrProjectRoot === 'string' ? baseConfigOrProjectRoot : (options.projectRoot ?? process.cwd()) ) const loaded = options.loadViteConfig === false ? undefined : await loadUserViteMetroOptions(projectRoot) // Reuse the proven pipeline rather than re-implementing it: same resolver, // same babel transformer path, same sourceExts ordering, same blockList, // same @babel/runtime workaround. When possible, load the app's actual // vite.config so native.bundlerOptions/defaultConfigOverrides match the // One dev/build path. const metroPluginOptions = loaded?.metroOptions ?? getViteMetroPluginOptions({ projectRoot, relativeRouterRoot: options.routerRoot ?? 'app', ignoredRouteFiles: options.ignoredRouteFiles, linking: options.linking, setupFile: options.setupFile, }) // Call `buildMetroConfigInputFromViteConfig`, NOT `getMetroConfigFromViteConfig` — // the latter calls Metro's `loadConfig`, which loads the user's metro.config.cjs, // which calls `withOne`, infinite recursion. The outer `loadConfig` (driven by // Expo CLI / Metro CLI) does the final config merge. const viteConfig = { ...loaded?.config?.config, root: path.resolve(loaded?.config?.config?.root ?? projectRoot), } as any const { defaultConfig } = await buildMetroConfigInputFromViteConfig( viteConfig, { ...metroPluginOptions, mainModuleName: 'one/metro-entry', } ) return defaultConfig } export default withOne