babel-preset-expo
Version:
The Babel preset for Expo projects
218 lines (217 loc) • 12.7 kB
JavaScript
;
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 inline_env_vars_1 = require("./inline-env-vars");
const lazyImports_1 = require("./lazyImports");
const restricted_react_api_plugin_1 = require("./restricted-react-api-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 isServer = api.caller(common_1.getIsServer);
const isReactServer = api.caller(common_1.getIsReactServer);
const isFastRefreshEnabled = api.caller(common_1.getIsFastRefreshEnabled);
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';
}
const platformOptions = getOptions(options, platform);
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 = [];
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'), { loose: false }]);
}
else if (!isServerEnv) {
// 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'));
}
const inlines = {
'process.env.EXPO_OS': platform,
// 'typeof document': isServerEnv ? 'undefined' : 'object',
};
// `typeof window` is left in place for native + client environments.
const minifyTypeofWindow = (platformOptions.minifyTypeofWindow ?? isServerEnv) || platform === 'web';
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);
}
// 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(client_module_proxy_plugin_1.reactClientReferencesPlugin);
extraPlugins.push(restricted_react_api_plugin_1.environmentRestrictedReactAPIsPlugin);
}
// 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);
if (isFastRefreshEnabled) {
extraPlugins.push([
require('react-refresh/babel'),
{
// We perform the env check to enable `isFastRefreshEnabled`.
skipEnvCheck: true,
},
]);
}
// Use the simpler babel preset for web and server environments (both web and native SSR).
const isModernEngine = platform === 'web' || isServerEnv;
return {
presets: [
[
// 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
isModernEngine ? require('./web-preset') : require('@react-native/babel-preset'),
{
// Defaults to undefined, set to `true` to disable `@babel/plugin-transform-flow-strip-types`
disableFlowStripTypesTransform: platformOptions.disableFlowStripTypesTransform,
// Defaults to undefined, set to `false` to disable `@babel/plugin-transform-runtime`
enableBabelRuntime: 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. Please 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,
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,
},
],
// 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
[require('@babel/plugin-proposal-decorators'), { legacy: true }],
require('@babel/plugin-transform-export-namespace-from'),
// Automatically add `react-native-reanimated/plugin` when the package is installed.
// TODO: Move to be a customTransformOption.
(0, common_1.hasModule)('react-native-reanimated') &&
platformOptions.reanimated !== false && [require('react-native-reanimated/plugin')],
].filter(Boolean),
};
}
exports.default = babelPresetExpo;
module.exports = babelPresetExpo;