"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const client_module_proxy_plugin_1 = require("./client-module-proxy-plugin"); const common_1 = require("./common"); const environment_restricted_imports_1 = require("./environment-restricted-imports"); const expo_inline_manifest_plugin_1 = require("./expo-inline-manifest-plugin"); const expo_router_plugin_1 = require("./expo-router-plugin"); const import_meta_transform_plugin_1 = require("./import-meta-transform-plugin"); const inline_env_vars_1 = require("./inline-env-vars"); const lazyImports_1 = require("./lazyImports"); const restricted_react_api_plugin_1 = require("./restricted-react-api-plugin"); const server_actions_plugin_1 = require("./server-actions-plugin"); const server_data_loaders_plugin_1 = require("./server-data-loaders-plugin"); const use_dom_directive_plugin_1 = require("./use-dom-directive-plugin"); const widgets_plugin_1 = require("./widgets-plugin"); function getOptions(options, platform) { const tag = platform === 'web' ? 'web' : 'native'; return { ...options, ...options[tag], }; } function babelPresetExpo(api, options = {}) { const bundler = api.caller(common_1.getBundler); const isWebpack = bundler === 'webpack'; let platform = api.caller((caller) => caller?.platform); const engine = api.caller((caller) => caller?.engine) ?? 'default'; const isDev = api.caller(common_1.getIsDev); const isNodeModule = api.caller(common_1.getIsNodeModule); const isServer = api.caller(common_1.getIsServer); const isReactServer = api.caller(common_1.getIsReactServer); const isFastRefreshEnabled = api.caller(common_1.getIsFastRefreshEnabled); const isReactCompilerEnabled = api.caller(common_1.getReactCompiler); const metroSourceType = api.caller(common_1.getMetroSourceType); const baseUrl = api.caller(common_1.getBaseUrl); const supportsStaticESM = api.caller((caller) => caller?.supportsStaticESM); const isServerEnv = isServer || isReactServer; // Unlike `isDev`, this will be `true` when the bundler is explicitly set to `production`, // i.e. `false` when testing, development, or used with a bundler that doesn't specify the correct inputs. const isProduction = api.caller(common_1.getIsProd); const inlineEnvironmentVariables = api.caller(common_1.getInlineEnvVarsEnabled); // If the `platform` prop is not defined then this must be a custom config that isn't // defining a platform in the babel-loader. Currently this may happen with Next.js + Expo web. if (!platform && isWebpack) { platform = 'web'; } // Use the simpler babel preset for web and server environments (both web and native SSR). const isModernEngine = platform === 'web' || isServerEnv; const platformOptions = getOptions(options, platform); // If the input is a script, we're unable to add any dependencies. Since the @babel/runtime transformer // adds extra dependencies (requires/imports) we need to disable it if (metroSourceType === 'script') { platformOptions.enableBabelRuntime = false; } if (platformOptions.useTransformReactJSXExperimental != null) { throw new Error(`babel-preset-expo: The option 'useTransformReactJSXExperimental' has been removed in favor of { jsxRuntime: 'classic' }.`); } if (platformOptions.disableImportExportTransform == null) { if (platform === 'web') { // Only disable import/export transform when Webpack is used because // Metro does not support tree-shaking. platformOptions.disableImportExportTransform = supportsStaticESM ?? isWebpack; } else { platformOptions.disableImportExportTransform = supportsStaticESM ?? false; } } if (platformOptions.unstable_transformProfile == null) { platformOptions.unstable_transformProfile = engine === 'hermes' ? 'hermes-stable' : 'default'; } // Note that if `options.lazyImports` is not set (i.e., `null` or `undefined`), // `@react-native/babel-preset` will handle it. const lazyImportsOption = platformOptions?.lazyImports; const extraPlugins = []; // Add compiler as soon as possible to prevent other plugins from modifying the code. if (isReactCompilerEnabled && // Don't run compiler on node modules, it can only safely be run on the user's code. !isNodeModule && // Only run for client code. It's unclear if compiler has any benefits for React Server Components. // NOTE: We might want to allow running it to prevent hydration errors. !isServerEnv && // Give users the ability to opt-out of the feature, per-platform. platformOptions['react-compiler'] !== false) { const reactCompilerOptions = platformOptions['react-compiler']; const reactCompilerOptOutDirectives = new Set([ // We need to opt-out for our widgets, since they're stringified functions that output Swift UI JSX 'widget', // We need to manually include the default opt-out directives, since they get overridden // See: // - https://github.com/facebook/react/blob/e0cc720/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts#L48C1-L48C77 // - https://github.com/facebook/react/blob/e0cc720/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts#L69-L86 'use no memo', 'use no forget', // Add the user's override but preserve defaults above to avoid the pitfall of them being removed ...(reactCompilerOptions?.customOptOutDirectives ?? []), ]); extraPlugins.push([ require('babel-plugin-react-compiler'), { target: '19', environment: { enableResetCacheOnSourceFileChanges: !isProduction, ...(platformOptions['react-compiler']?.environment ?? {}), }, panicThreshold: isDev ? undefined : 'NONE', ...reactCompilerOptions, // See: https://github.com/facebook/react/blob/074d96b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts#L160-L163 customOptOutDirectives: [...reactCompilerOptOutDirectives], }, ]); } if (engine !== 'hermes') { // `@react-native/babel-preset` configures this plugin with `{ loose: true }`, which breaks all // getters and setters in spread objects. We need to add this plugin ourself without that option. // @see https://github.com/expo/expo/pull/11960#issuecomment-887796455 extraPlugins.push([ require('@babel/plugin-transform-object-rest-spread'), // Assume no dependence on getters or evaluation order. See https://github.com/babel/babel/pull/11520 { loose: true, useBuiltIns: true }, ]); } else if (!isModernEngine) { // This is added back on hermes to ensure the react-jsx-dev plugin (`@babel/preset-react`) works as expected when // JSX is used in a function body. This is technically not required in production, but we // should retain the same behavior since it's hard to debug the differences. extraPlugins.push(require('@babel/plugin-transform-parameters'), // Add support for class static blocks. [require('@babel/plugin-transform-class-static-block'), { loose: true }]); } const inlines = { 'process.env.EXPO_OS': platform, // 'typeof document': isServerEnv ? 'undefined' : 'object', 'process.env.EXPO_SERVER': !!isServerEnv, }; // `typeof window` is left in place for native + client environments. // NOTE(@kitten): We're temporarily disabling this default optimization for Web targets due to Web Workers // We're currently not passing metadata to indicate we're transforming for a Web Worker to disable this automatically const minifyTypeofWindow = platformOptions.minifyTypeofWindow ?? isServerEnv; if (minifyTypeofWindow !== false) { // This nets out slightly faster in development when considering the cost of bundling server dependencies. inlines['typeof window'] = isServerEnv ? 'undefined' : 'object'; } if (isProduction) { inlines['process.env.NODE_ENV'] = 'production'; inlines['__DEV__'] = false; inlines['Platform.OS'] = platform; } if (process.env.NODE_ENV !== 'test') { inlines['process.env.EXPO_BASE_URL'] = baseUrl; } extraPlugins.push([require('./define-plugin'), inlines]); if (isProduction) { // Metro applies a version of this plugin too but it does it after the Platform modules have been transformed to CJS, this breaks the transform. // Here, we'll apply it before the commonjs transform, in production only, to ensure `Platform.OS` is replaced with a string literal. extraPlugins.push([ require('./minify-platform-select-plugin'), { platform, }, ]); } if (platformOptions.useTransformReactJSXExperimental != null) { throw new Error(`babel-preset-expo: The option 'useTransformReactJSXExperimental' has been removed in favor of { jsxRuntime: 'classic' }.`); } // Only apply in non-server, for metro-only, in production environments, when the user hasn't disabled the feature. // Webpack uses DefinePlugin for environment variables. // Development uses an uncached serializer. // Servers read from the environment. // Users who disable the feature may be using a different babel plugin. if (inlineEnvironmentVariables) { extraPlugins.push(inline_env_vars_1.expoInlineEnvVars); } if (platform === 'web') { extraPlugins.push(require('babel-plugin-react-native-web')); } // Webpack uses the DefinePlugin to provide the manifest to `expo-constants`. if (bundler !== 'webpack') { extraPlugins.push(expo_inline_manifest_plugin_1.expoInlineManifestPlugin); } if ((0, common_1.hasModule)('expo-router')) { extraPlugins.push(expo_router_plugin_1.expoRouterBabelPlugin); // Process `loader()` functions for client, loader and server bundles (excluding RSC) // - Client bundles: Remove loader exports, they run on server only // - Server bundles: Keep loader exports (needed for SSG) // - Loader-only bundles: Keep only loader exports, remove everything else if (!isReactServer) { extraPlugins.push(server_data_loaders_plugin_1.serverDataLoadersPlugin); } } extraPlugins.push(client_module_proxy_plugin_1.reactClientReferencesPlugin); // Ensure these only run when the user opts-in to bundling for a react server to prevent unexpected behavior for // users who are bundling using the client-only system. if (isReactServer) { extraPlugins.push(server_actions_plugin_1.reactServerActionsPlugin); extraPlugins.push(restricted_react_api_plugin_1.environmentRestrictedReactAPIsPlugin); } else { // DOM components must run after "use client" and only in client environments. extraPlugins.push(use_dom_directive_plugin_1.expoUseDomDirectivePlugin); } // This plugin is fine to run whenever as the server-only imports were introduced as part of RSC and shouldn't be used in any client code. extraPlugins.push(environment_restricted_imports_1.environmentRestrictedImportsPlugin); // Transform widget component JSX expressions to capture widget components for native-side evaluation. // This enables the native side to re-evaluate widget components with updated props without re-sending the entire layout. if ((0, common_1.hasModule)('expo-widgets')) { extraPlugins.push(widgets_plugin_1.widgetsPlugin); } if (platformOptions.enableReactFastRefresh || (isFastRefreshEnabled && platformOptions.enableReactFastRefresh !== false)) { extraPlugins.push([ require('react-refresh/babel'), { // We perform the env check to enable `isFastRefreshEnabled`, unless the plugin is force-enabled skipEnvCheck: platformOptions.enableReactFastRefresh !== true, }, ]); } if (platformOptions.disableImportExportTransform) { extraPlugins.push([require('./detect-dynamic-exports').detectDynamicExports]); } const polyfillImportMeta = platformOptions.unstable_transformImportMeta ?? isServerEnv; extraPlugins.push((0, import_meta_transform_plugin_1.expoImportMetaTransformPluginFactory)(polyfillImportMeta === true)); return { presets: [ (() => { const presetOpts = { // Defaults to undefined, set to `true` to disable `@babel/plugin-transform-flow-strip-types` disableFlowStripTypesTransform: platformOptions.disableFlowStripTypesTransform, // Defaults to Babel caller's `babelRuntimeVersion` or the version of `@babel/runtime` for this package's peer // Set to `false` to disable `@babel/plugin-transform-runtime` enableBabelRuntime: platformOptions.enableBabelRuntime == null || platformOptions.enableBabelRuntime === true ? (0, common_1.getBabelRuntimeVersion)() : platformOptions.enableBabelRuntime, // This reduces the amount of transforms required, as Hermes supports many modern language features. unstable_transformProfile: platformOptions.unstable_transformProfile, // Set true to disable `@babel/plugin-transform-react-jsx` and // the deprecated packages `@babel/plugin-transform-react-jsx-self`, and `@babel/plugin-transform-react-jsx-source`. // // Otherwise, you'll sometime get errors like the following (starting in Expo SDK 43, React Native 64, React 17): // // TransformError App.js: /path/to/App.js: Duplicate __self prop found. You are most likely using the deprecated transform-react-jsx-self Babel plugin. // Both __source and __self are automatically set when using the automatic jsxRuntime. Remove transform-react-jsx-source and transform-react-jsx-self from your Babel config. useTransformReactJSXExperimental: true, // This will never be used regardless because `useTransformReactJSXExperimental` is set to `true`. // https://github.com/facebook/react-native/blob/a4a8695cec640e5cf12be36a0c871115fbce9c87/packages/react-native-babel-preset/src/configs/main.js#L151 withDevTools: false, disableImportExportTransform: platformOptions.disableImportExportTransform, disableDeepImportWarnings: platformOptions.disableDeepImportWarnings, lazyImportExportTransform: lazyImportsOption === true ? (importModuleSpecifier) => { // Do not lazy-initialize packages that are local imports (similar to `lazy: true` // behavior) or are in the blacklist. return !(importModuleSpecifier.includes('./') || lazyImports_1.lazyImports.has(importModuleSpecifier)); } : // Pass the option directly to `@react-native/babel-preset`, which in turn // passes it to `babel-plugin-transform-modules-commonjs` lazyImportsOption, dev: isDev, }; if (isModernEngine) { return [require('./web-preset'), presetOpts]; } // We use `require` here instead of directly using the package name because we want to // specifically use the `@react-native/babel-preset` installed by this package (ex: // `babel-preset-expo/node_modules/`). This way the preset will not change unintentionally. // Reference: https://github.com/expo/expo/pull/4685#discussion_r307143920 const { getPreset } = require('@react-native/babel-preset'); // We need to customize the `@react-native/babel-preset` to ensure that the `@babel/plugin-transform-export-namespace-from` // plugin is run after the TypeScript plugins. This is normally handled by the combination of standard `@babel/preset-env` and `@babel/preset-typescript` but React Native // doesn't do that and we can't rely on Hermes spec compliance enough to use standard presets. const babelPresetReactNativeEnv = getPreset(null, presetOpts); // Add the `@babel/plugin-transform-export-namespace-from` plugin to the preset but ensure it runs after // the TypeScript plugins to ensure namespace type exports (TypeScript 5.0+) `export type * as Types from './module';` // are stripped before the transform. Otherwise the transform will extraneously include the types as syntax. babelPresetReactNativeEnv.overrides.push({ plugins: [require('./babel-plugin-transform-export-namespace-from')], }); return babelPresetReactNativeEnv; })(), // React support with similar options to Metro. // We override this logic outside of the metro preset so we can add support for // React 17 automatic JSX transformations. // The only known issue is the plugin `@babel/plugin-transform-react-display-name` will be run twice, // once in the Metro plugin, and another time here. [ require('@babel/preset-react'), { development: isDev, // Defaults to `automatic`, pass in `classic` to disable auto JSX transformations. runtime: platformOptions?.jsxRuntime || 'automatic', ...(platformOptions && platformOptions.jsxRuntime !== 'classic' && { importSource: (platformOptions && platformOptions.jsxImportSource) || 'react', }), // NOTE: Unexposed props: // pragma?: string; // pragmaFrag?: string; // pure?: string; // throwIfNamespace?: boolean; // useBuiltIns?: boolean; // useSpread?: boolean; }, ], ], plugins: [ ...extraPlugins, // TODO: Remove platformOptions.decorators !== false && [ require('@babel/plugin-proposal-decorators'), platformOptions.decorators ?? { legacy: true }, ], // Automatically add `react-native-reanimated/plugin` when the package is installed. // TODO: Move to be a customTransformOption. (0, common_1.hasModule)('react-native-worklets') && platformOptions.worklets !== false && platformOptions.reanimated !== false ? [require('react-native-worklets/plugin')] : (0, common_1.hasModule)('react-native-reanimated') && platformOptions.reanimated !== false && [require('react-native-reanimated/plugin')], ].filter(Boolean), }; } exports.default = babelPresetExpo; module.exports = babelPresetExpo;