UNPKG

metro-babel-transformer

Version:

🚇 Base Babel transformer for Metro.

199 lines (175 loc) • 5.74 kB
/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow * @format * @oncall react_native */ import type {BabelFileMetadata} from '@babel/core'; import type {File as BabelNodeFile} from '@babel/types'; import { loadPartialConfigSync, parseSync, transformFromAstSync, } from '@babel/core'; import {getCacheKey as getFileCacheKey} from 'metro-cache-key'; import nullthrows from 'nullthrows'; type BabelTransformOptions = NonNullable< Parameters<typeof transformFromAstSync>[2], >; export type CustomTransformOptions = { [string]: unknown, __proto__: null, ... }; export type TransformProfile = 'default' | 'hermes-stable' | 'hermes-canary'; type BabelTransformerOptions = Readonly<{ customTransformOptions?: CustomTransformOptions, dev: boolean, enableBabelRCLookup?: boolean, enableBabelRuntime: boolean | string, extendsBabelConfigPath?: string, experimentalImportSupport?: boolean, hermesParser?: boolean, minify: boolean, platform: ?string, projectRoot: string, publicPath: string, unstable_transformProfile?: TransformProfile, globalPrefix: string, inlineRequires?: void, ... }>; export type BabelTransformerArgs = Readonly<{ filename: string, options: BabelTransformerOptions, plugins?: BabelTransformOptions['plugins'], src: string, }>; export type BabelFileFunctionMapMetadata = Readonly<{ names: ReadonlyArray<string>, mappings: string, }>; export type BabelFileImportLocsMetadata = ReadonlySet<string>; export type MetroBabelFileMetadata = { ...BabelFileMetadata, metro?: ?{ functionMap?: ?BabelFileFunctionMapMetadata, unstable_importDeclarationLocs?: ?BabelFileImportLocsMetadata, ... }, ... }; export type BabelTransformerCacheKeyOptions = Readonly<{ projectRoot?: string, enableBabelRCLookup?: boolean, }>; export type BabelTransformer = Readonly<{ transform: BabelTransformerArgs => Readonly<{ ast: BabelNodeFile, // Deprecated, will be removed in a future breaking release. Function maps // will be generated by an input Babel plugin instead and written into // `metadata` - transformers don't need to return them explicitly. functionMap?: BabelFileFunctionMapMetadata, metadata?: MetroBabelFileMetadata, ... }>, getCacheKey?: (options?: BabelTransformerCacheKeyOptions) => string, }>; function transform({ filename, options, plugins, src, }: BabelTransformerArgs): ReturnType<BabelTransformer['transform']> { const OLD_BABEL_ENV = process.env.BABEL_ENV; process.env.BABEL_ENV = options.dev ? 'development' : process.env.BABEL_ENV || 'production'; try { const babelConfig: BabelTransformOptions = { ast: true, babelrc: options.enableBabelRCLookup, caller: {bundler: 'metro', name: 'metro', platform: options.platform}, // 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, code: false, cwd: options.projectRoot, filename, highlightCode: true, plugins, sourceType: 'module', }; const sourceAst = options.hermesParser ? // eslint-disable-next-line import/no-commonjs require('hermes-parser').parse(src, { babel: true, sourceType: babelConfig.sourceType, }) : parseSync(src, babelConfig); const transformResult = transformFromAstSync<MetroBabelFileMetadata>( // $FlowFixMe[incompatible-type] BabelFile vs BabelNodeFile sourceAst, src, babelConfig, ); return { ast: nullthrows(transformResult.ast), metadata: transformResult.metadata, }; } finally { // Restore the old process.env.BABEL_ENV if (OLD_BABEL_ENV == null) { // We have to treat this as a special case because writing undefined to // an environment variable coerces it to the string 'undefined'. To // unset it, we must delete it. // See https://github.com/facebook/metro/pull/446 delete process.env.BABEL_ENV; } else { process.env.BABEL_ENV = OLD_BABEL_ENV; } } } /** * Generates a cache key component based on the user's Babel configuration files. * This uses Babel's loadPartialConfigSync to resolve which config files apply * to a given file, and includes their contents in the cache key so that changes * to babel.config.js or .babelrc will invalidate the transform cache. * * This is called once by the main thread (not on worker instances). */ function getCacheKey(options?: BabelTransformerCacheKeyOptions): string { if (options == null) { return ''; } // Load the partial babel config to get the resolved config file paths const partialConfig = loadPartialConfigSync({ cwd: options.projectRoot, root: options.projectRoot, babelrc: options.enableBabelRCLookup ?? true, }); const files = partialConfig?.files; if (files == null || files.size === 0) { return ''; } // Hash the contents of all config files return getFileCacheKey([...files].sort()); } // Type check exports /*:: ({transform, getCacheKey}) as BabelTransformer; */ export {transform, getCacheKey}; /** * Backwards-compatibility with CommonJS consumers using interopRequireDefault. * Do not add to this list. * * @deprecated Default import from 'metro-babel-transformer' is deprecated, use named exports. */ export default {transform, getCacheKey};