@expo/metro-config
Version:
A Metro config for running React Native projects with the Metro bundler
136 lines • 6.9 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const node_assert_1 = __importDefault(require("node:assert"));
const loadBabelConfig_1 = require("./loadBabelConfig");
const transformSync_1 = require("./transformSync");
const debug = require('debug')('expo:metro-config:babel-transformer');
function isCustomTruthy(value) {
return String(value) === 'true';
}
function memoize(fn) {
const cache = new Map();
return ((...args) => {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn(...args);
cache.set(key, result);
return result;
});
}
const memoizeWarning = memoize((message) => {
debug(message);
});
function getBabelCaller({ filename, options, }) {
const isNodeModule = filename.includes('node_modules');
const isReactServer = options.customTransformOptions?.environment === 'react-server';
const isGenericServer = options.customTransformOptions?.environment === 'node';
const isServer = isReactServer || isGenericServer;
const routerRoot = typeof options.customTransformOptions?.routerRoot === 'string'
? decodeURI(options.customTransformOptions.routerRoot)
: undefined;
if (routerRoot == null) {
memoizeWarning('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.');
}
return {
name: 'metro',
bundler: 'metro',
platform: options.platform,
// Empower the babel preset to know the env it's bundling for.
// Metro automatically updates the cache to account for the custom transform options.
isServer,
// Enable React Server Component rules for AST. The naming maps to the resolver property `--conditions=react-server`.
isReactServer,
// The base url to make requests from, used for hosting from non-standard locations.
baseUrl: typeof options.customTransformOptions?.baseUrl === 'string'
? decodeURI(options.customTransformOptions.baseUrl)
: '',
// Ensure we always use a mostly-valid router root.
routerRoot: routerRoot ?? 'app',
isDev: options.dev,
// This value indicates if the user has disabled the feature or not.
// Other criteria may still cause the feature to be disabled, but all inputs used are
// already considered in the cache key.
preserveEnvVars: isCustomTruthy(options.customTransformOptions?.preserveEnvVars)
? true
: undefined,
asyncRoutes: isCustomTruthy(options.customTransformOptions?.asyncRoutes) ? true : undefined,
// Pass the engine to babel so we can automatically transpile for the correct
// target environment.
engine: stringOrUndefined(options.customTransformOptions?.engine),
// Provide the project root for accurately reading the Expo config.
projectRoot: options.projectRoot,
isNodeModule,
isHMREnabled: options.hot,
// Set the standard Babel flag to disable ESM transformations.
supportsStaticESM: isCustomTruthy(options.customTransformOptions?.optimize) || options.experimentalImportSupport,
// Enable React compiler support in Babel.
// TODO: Remove this in the future when compiler is on by default.
supportsReactCompiler: isCustomTruthy(options.customTransformOptions?.reactCompiler)
? true
: undefined,
};
}
function stringOrUndefined(value) {
return typeof value === 'string' ? value : undefined;
}
const transform = ({ filename, src, options,
// `plugins` is used for `functionMapBabelPlugin` from `metro-source-map`. Could make sense to move this to `babel-preset-expo` too.
plugins, }) => {
const OLD_BABEL_ENV = process.env.BABEL_ENV;
process.env.BABEL_ENV = options.dev ? 'development' : process.env.BABEL_ENV || 'production';
try {
const babelConfig = {
// ES modules require sourceType='module' but OSS may not always want that
sourceType: 'unambiguous',
// The output we want from Babel methods
ast: true,
code: false,
// NOTE(EvanBacon): We split the parse/transform steps up to accommodate
// Hermes parsing, but this defaults to cloning the AST which increases
// the transformation time by a fair amount.
// You get this behavior by default when using Babel's `transform` method directly.
cloneInputAst: false,
// Options for debugging
cwd: options.projectRoot,
filename,
highlightCode: true,
// Load the project babel config file.
...(0, loadBabelConfig_1.loadBabelConfig)(options),
babelrc: typeof options.enableBabelRCLookup === 'boolean' ? options.enableBabelRCLookup : true,
plugins,
// NOTE(EvanBacon): We heavily leverage the caller functionality to mutate the babel config.
// This compensates for the lack of a format plugin system in Metro. Users can modify the
// all (most) of the transforms in their local Babel config.
// This also helps us keep the transform layers small and focused on a single task. We can also use this to
// ensure the Babel config caching is more accurate.
// Additionally, by moving everything Babel-related to the Babel preset, it makes it easier for users to reason
// about the requirements of an Expo project, making it easier to migrate to other transpilers in the future.
caller: getBabelCaller({ filename, options }),
};
const result = (0, transformSync_1.transformSync)(src, babelConfig, options);
// The result from `transformFromAstSync` can be null (if the file is ignored)
if (!result) {
// BabelTransformer specifies that the `ast` can never be null but
// the function returns here. Discovered when typing `BabelNode`.
// @ts-expect-error: see https://github.com/facebook/react-native/blob/401991c3f073bf734ee04f9220751c227d2abd31/packages/react-native-babel-transformer/src/index.js#L220-L224
return { ast: null };
}
(0, node_assert_1.default)(result.ast);
return { ast: result.ast, metadata: result.metadata };
}
finally {
if (OLD_BABEL_ENV) {
process.env.BABEL_ENV = OLD_BABEL_ENV;
}
}
};
const babelTransformer = {
transform,
};
module.exports = babelTransformer;
//# sourceMappingURL=babel-transformer.js.map
;