@expo/metro-config
Version:
A Metro config for running React Native projects with the Metro bundler
696 lines • 32.6 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.minifyCode = exports.InvalidRequireCallError = void 0;
exports.applyImportSupport = applyImportSupport;
exports.transform = transform;
exports.getCacheKey = getCacheKey;
exports.collectDependenciesForShaking = collectDependenciesForShaking;
/**
* Copyright 2023-present 650 Industries (Expo). All rights reserved.
* 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.
*
* Fork of the Metro transformer worker, but with additional transforms moved to `babel-preset-expo` and modifications made for web support.
* https://github.com/facebook/metro/blob/412771475c540b6f85d75d9dcd5a39a6e0753582/packages/metro-transform-worker/src/index.js#L1
*/
const core_1 = require("@babel/core");
const generator_1 = __importDefault(require("@babel/generator"));
const JsFileWrapping = __importStar(require("@expo/metro/metro/ModuleGraph/worker/JsFileWrapping"));
const generateImportNames_1 = __importDefault(require("@expo/metro/metro/ModuleGraph/worker/generateImportNames"));
const importLocationsPlugin_1 = require("@expo/metro/metro/ModuleGraph/worker/importLocationsPlugin");
const metro_cache_1 = require("@expo/metro/metro-cache");
const metro_cache_key_1 = require("@expo/metro/metro-cache-key");
const metro_source_map_1 = require("@expo/metro/metro-source-map");
const metroTransformPlugins = __importStar(require("@expo/metro/metro-transform-plugins"));
const node_assert_1 = __importDefault(require("node:assert"));
const assetTransformer = __importStar(require("./asset-transformer"));
const collect_dependencies_1 = __importStar(require("./collect-dependencies"));
const resolveOptions_1 = require("./resolveOptions");
const packedMap_1 = require("../serializer/packedMap");
const sourceMap_1 = require("../serializer/sourceMap");
const transform_plugins_1 = require("../transform-plugins");
const getMinifier_1 = require("./utils/getMinifier");
class InvalidRequireCallError extends Error {
innerError;
filename;
constructor(innerError, filename) {
super(`${filename}:${innerError.message}`);
this.innerError = innerError;
this.filename = filename;
}
}
exports.InvalidRequireCallError = InvalidRequireCallError;
function asWritable(input) {
return input;
}
function nullthrows(x, message) {
(0, node_assert_1.default)(x != null, message);
return x;
}
function getDynamicDepsBehavior(inPackages, filename) {
switch (inPackages) {
case 'reject':
return 'reject';
case 'throwAtRuntime':
return /(?:^|[/\\])node_modules[/\\]/.test(filename) ? inPackages : 'reject';
default:
throw new Error(`invalid value for dynamic deps behavior: \`${inPackages}\``);
}
}
const minifyCode = async (config, filename, code, source, rawMappings, reserved = []) => {
const sourceMap = (0, sourceMap_1.rawMappingsToEncodedMap)({ filename, source, rawMappings });
const minify = (0, getMinifier_1.getMinifier)(config.minifierPath);
try {
const minified = await minify({
code,
map: sourceMap,
filename,
reserved,
config: config.minifierConfig,
});
return {
code: minified.code,
sourceMap: minified.map
? (0, packedMap_1.packDecodedMappings)({ mappings: minified.map.mappings, names: minified.map.names })
: (0, packedMap_1.emptySourceMap)(),
};
}
catch (error) {
if (error.constructor.name === 'JS_Parse_Error') {
throw new Error(`${error.message} in file ${filename} at ${error.line}:${error.col}`);
}
throw error;
}
};
exports.minifyCode = minifyCode;
function applyUseStrictDirective(ast) {
// Add "use strict" if the file was parsed as a module, and the directive did
// not exist yet.
const { directives } = ast.program;
if (ast.program.sourceType === 'module' &&
directives != null &&
directives.findIndex((d) => d.value.value === 'use strict') === -1) {
directives.push(core_1.types.directive(core_1.types.directiveLiteral('use strict')));
}
}
function getImportNames(options, ast) {
if (options.experimentalImportSupport === true &&
options.customTransformOptions?.liveBindings !== 'false') {
// NOTE(@kitten): The live bindings import/export plugin doesn't use these helpers
// If it's used, we can assume that there's no conflicts (since we reserve this name, and assume users won't use it)
// and skip the expensive `generateImportNames` call
return {
importAll: '_$$_IMPORT_ALL',
importDefault: '_$$_IMPORT_DEFAULT',
};
}
// NOTE(EvanBacon): This can be really expensive on larger files. We should replace it with a cheaper alternative that just iterates and matches.
return (0, generateImportNames_1.default)(ast);
}
function applyImportSupport(ast, { filename, options, importDefault, importAll, collectLocations, performConstantFolding, }) {
// Perform the import-export transform (in case it's still needed), then
// fold requires and perform constant folding (if in dev).
const plugins = [];
const babelPluginOpts = {
...options,
inlineableCalls: [importDefault, importAll],
importDefault,
importAll,
};
if (collectLocations) {
plugins.push(
// TODO: Only enable this during reconciling.
importLocationsPlugin_1.importLocationsPlugin);
}
// NOTE(EvanBacon): This is effectively a replacement for the `@babel/plugin-transform-modules-commonjs`
// plugin that's running in `@react-native/babel-preset`, but with shared names for inlining requires.
if (options.experimentalImportSupport === true) {
const liveBindings = options.customTransformOptions?.liveBindings !== 'false';
plugins.push([
liveBindings ? transform_plugins_1.importExportLiveBindingsPlugin : transform_plugins_1.importExportPlugin,
{ ...babelPluginOpts, performConstantFolding },
]);
}
// NOTE(EvanBacon): This can basically never be safely enabled because it doesn't respect side-effects and
// has no ability to respect side-effects because the transformer hasn't collected all dependencies yet.
if (options.inlineRequires) {
plugins.push([
metroTransformPlugins.inlineRequiresPlugin,
{
...babelPluginOpts,
ignoredRequires: options.nonInlinedRequires,
},
]);
}
// NOTE(EvanBacon): We apply this conditionally in `babel-preset-expo` with other AST transforms.
// plugins.push([metroTransformPlugins.inlinePlugin, babelPluginOpts]);
// TODO: This MUST be run even though no plugins are added, otherwise the babel runtime generators are broken.
if (plugins.length) {
const result = nullthrows((0, core_1.transformFromAstSync)(ast, '', {
ast: true,
babelrc: false,
code: false,
configFile: false,
comments: true,
filename,
plugins,
sourceMaps: false,
// NOTE(kitten): This was done to wipe the paths/scope caches, which the `constantFoldingPlugin` needs to work,
// but has been replaced with `programPath.scope.crawl()`.
// Old Note from Metro:
// > Not-Cloning the input AST here should be safe because other code paths above this call
// > are mutating the AST as well and no code is depending on the original AST.
// > However, switching the flag to false caused issues with ES Modules if `experimentalImportSupport` isn't used https://github.com/facebook/metro/issues/641
// > either because one of the plugins is doing something funky or Babel messes up some caches.
// > Make sure to test the above mentioned case before flipping the flag back to false.
cloneInputAst: false,
}));
return {
ast: result.ast,
metadata: result.metadata,
};
}
return { ast };
}
function performConstantFolding(ast, { filename }) {
// NOTE(kitten): Any Babel helpers that have been added (`path.hub.addHelper(...)`) will usually not have any
// references, and hence the `constantFoldingPlugin` below will remove them.
// To fix the references we add an explicit `programPath.scope.crawl()`. Alternatively, we could also wipe the
// Babel traversal cache (`traverse.cache.clear()`)
const clearProgramScopePlugin = {
visitor: {
Program: {
enter(path) {
path.scope.crawl();
},
},
},
};
// Run the constant folding plugin in its own pass, avoiding race conditions
// with other plugins that have exit() visitors on Program (e.g. the ESM
// transform).
const result = (0, core_1.transformFromAstSync)(ast, '', {
ast: true,
babelrc: false,
code: false,
configFile: false,
comments: true,
filename,
plugins: [clearProgramScopePlugin, metroTransformPlugins.constantFoldingPlugin],
sourceMaps: false,
// NOTE(kitten): In Metro, this is also false, but only works because the prior run of `transformFromAstSync` was always
// running with `cloneInputAst: true`.
// This isn't needed anymore since `clearProgramScopePlugin` re-crawls the AST’s scope instead.
cloneInputAst: false,
})?.ast;
return nullthrows(result);
}
async function transformJS(file, { config, options }) {
const targetEnv = options.customTransformOptions?.environment;
const isServerEnv = targetEnv === 'node' || targetEnv === 'react-server';
const optimize =
// Ensure we don't enable tree shaking for scripts or assets.
file.type === 'js/module' &&
String(options.customTransformOptions?.optimize) === 'true' &&
// Disable tree shaking on JSON files.
!file.filename.match(/\.(json|s?css|sass)$/);
const unstable_disableModuleWrapping = optimize || config.unstable_disableModuleWrapping;
if (optimize && !options.experimentalImportSupport) {
// Add a warning so devs can incrementally migrate since experimentalImportSupport may cause other issues in their app.
throw new Error('Experimental graph optimizations only work with experimentalImportSupport enabled.');
}
// Transformers can output null ASTs (if they ignore the file). In that case
// we need to parse the module source code to get their AST.
let ast = file.ast ?? nullthrows((0, core_1.parse)(file.code, { sourceType: 'unambiguous' }));
const { importDefault, importAll } = getImportNames(options, ast);
// Add "use strict" if the file was parsed as a module, and the directive did
// not exist yet.
applyUseStrictDirective(ast);
const unstable_renameRequire = config.unstable_renameRequire;
// NOTE(@hassankhan): Constant folding can be an expensive/slow operation, so we limit it to
// production builds, or files that have specifically seen a change in their exports
if (!options.dev || file.performConstantFolding) {
ast = performConstantFolding(ast, { filename: file.filename });
}
// Disable all Metro single-file optimizations when full-graph optimization will be used.
if (!optimize) {
ast = applyImportSupport(ast, {
filename: file.filename,
performConstantFolding: Boolean(file.performConstantFolding),
options,
importDefault,
importAll,
}).ast;
}
let dependencyMapName = '';
let dependencies;
let wrappedAst;
// If the module to transform is a script (meaning that is not part of the
// dependency graph and it code will just be prepended to the bundle modules),
// we need to wrap it differently than a commonJS module (also, scripts do
// not have dependencies).
let collectDependenciesOptions;
if (file.type === 'js/script') {
dependencies = [];
wrappedAst = JsFileWrapping.wrapPolyfill(ast);
}
else {
try {
const importDeclarationLocs = file.unstable_importDeclarationLocs ?? null;
// These values must be serializable to JSON as they're stored in the transform cache when tree shaking is enabled.
collectDependenciesOptions = {
asyncRequireModulePath: config.asyncRequireModulePath,
dependencyTransformer: config.unstable_disableModuleWrapping === true
? disabledDependencyTransformer
: undefined,
dynamicRequires: isServerEnv
? // NOTE(EvanBacon): Allow arbitrary imports in server environments.
// This requires a patch to Metro collectDeps.
'warn'
: getDynamicDepsBehavior(config.dynamicDepsInPackages, file.filename),
inlineableCalls: [importDefault, importAll],
keepRequireNames: options.dev,
allowOptionalDependencies: config.allowOptionalDependencies,
dependencyMapName: config.unstable_dependencyMapReservedName,
unstable_allowRequireContext: config.unstable_allowRequireContext,
unstable_isESMImportAtSource: null,
// If tree shaking is enabled, then preserve the original require calls.
// This ensures require.context calls are not broken.
collectOnly: optimize === true,
};
({ ast, dependencies, dependencyMapName } = (0, collect_dependencies_1.default)(ast, {
...collectDependenciesOptions,
// This setting shouldn't be shared with the tree shaking transformer.
dependencyTransformer: unstable_disableModuleWrapping === true ? disabledDependencyTransformer : undefined,
unstable_isESMImportAtSource: importDeclarationLocs != null
? (loc) => importDeclarationLocs.has((0, importLocationsPlugin_1.locToKey)(loc))
: null,
}));
// Ensure we use the same name for the second pass of the dependency collection in the serializer.
collectDependenciesOptions = {
...collectDependenciesOptions,
dependencyMapName,
};
}
catch (error) {
if (error instanceof collect_dependencies_1.InvalidRequireCallError) {
throw new InvalidRequireCallError(error, file.filename);
}
throw error;
}
if (unstable_disableModuleWrapping === true) {
wrappedAst = ast;
}
else {
// TODO: Replace this with a cheaper transform that doesn't require AST.
({ ast: wrappedAst } = JsFileWrapping.wrapModule(ast, importDefault, importAll, dependencyMapName, config.globalPrefix,
// TODO: This config is optional to allow its introduction in a minor
// release. It should be made non-optional in ConfigT or removed in
// future.
unstable_renameRequire === false));
}
}
const minify = (0, resolveOptions_1.shouldMinify)(options);
const shouldNormalizePseudoGlobals = minify &&
file.inputFileSize <= config.optimizationSizeLimit &&
!config.unstable_disableNormalizePseudoGlobals;
const reserved = [];
if (config.unstable_dependencyMapReservedName != null) {
reserved.push(config.unstable_dependencyMapReservedName);
}
if (shouldNormalizePseudoGlobals &&
// TODO: If the module wrapping is disabled then the normalize function needs to change to account for not being in a body.
!unstable_disableModuleWrapping) {
// NOTE(EvanBacon): Simply pushing this function will mutate the AST, so it must run before the `generate` step!!
reserved.push(...metroTransformPlugins.normalizePseudoGlobals(wrappedAst, {
reservedNames: reserved,
}));
}
const result = (0, generator_1.default)(wrappedAst, {
comments: true,
compact: config.unstable_compactOutput,
filename: file.filename,
retainLines: false,
sourceFileName: file.filename,
sourceMaps: true,
}, file.code);
// `rawMappings` is omitted from `@types/babel__generator`'s
// `GeneratorResult`, but Babel emits it whenever `sourceMaps: true`.
const rawMappings = result?.rawMappings ?? [];
let code = result.code;
let sourceMap;
// NOTE: We might want to enable this on native + hermes when tree shaking is enabled.
if (minify) {
({ sourceMap, code } = await (0, exports.minifyCode)(config, file.filename, result.code, file.code, rawMappings, reserved));
}
else {
sourceMap = (0, packedMap_1.packRawMappings)(rawMappings);
}
const possibleReconcile = optimize && collectDependenciesOptions
? {
inlineRequires: options.inlineRequires,
importDefault,
importAll,
normalizePseudoGlobals: shouldNormalizePseudoGlobals,
globalPrefix: config.globalPrefix,
unstable_compactOutput: config.unstable_compactOutput,
collectDependenciesOptions,
minify: minify
? {
minifierPath: config.minifierPath,
minifierConfig: config.minifierConfig,
}
: undefined,
unstable_dependencyMapReservedName: config.unstable_dependencyMapReservedName,
optimizationSizeLimit: config.optimizationSizeLimit,
unstable_disableNormalizePseudoGlobals: config.unstable_disableNormalizePseudoGlobals,
unstable_renameRequire,
}
: undefined;
let lineCount;
({ lineCount, sourceMap } = (0, packedMap_1.countLinesAndTerminateSourceMap)(code, sourceMap));
// Clean the AST for tree shaking by stripping non-serializable values (Symbols, functions, etc.)
// that React Compiler and other Babel plugins may add.
const output = [
{
data: {
code,
lineCount,
// Reconcile re-runs Babel codegen and replaces `data.map` via
// `installPackedMap` before any reader sees it, so the sourceMap emitted
// here would be discarded — short-circuit to an empty Array to skip
// the work and avoid GC pressure on optimize builds.
map: possibleReconcile ? [] : sourceMap,
functionMap: file.functionMap,
hasCjsExports: file.hasCjsExports,
reactServerReference: file.reactServerReference,
reactClientReference: file.reactClientReference,
expoDomComponentReference: file.expoDomComponentReference,
loaderReference: file.loaderReference,
...(possibleReconcile
? {
ast: wrappedAst,
// Store settings for the module that will be used to finish transformation after graph-based optimizations
// have finished.
reconcile: possibleReconcile,
}
: {}),
},
type: file.type,
},
];
if (possibleReconcile) {
// TODO(@kitten): Check why `reactCompilerFlag === true` is checked below
const reactCompilerFlag = options.customTransformOptions?.reactCompiler;
if (reactCompilerFlag === true || reactCompilerFlag === 'true') {
try {
return {
dependencies,
// React compiler adds symbols to the AST which break threading. This will ensure the
// AST and other properties are fully serialized before being sent to the worker.
output: JSON.parse(JSON.stringify(output)),
};
}
catch (error) {
throw new Error(`Failed to serialize output for file ${file.filename}: ${error}`);
}
}
}
return {
dependencies,
output,
};
}
/** Transforms an asset file. */
async function transformAsset(file, context) {
const { assetRegistryPath, assetPlugins } = context.config;
// TODO: Add web asset hashing in production.
const result = await assetTransformer.transform(getBabelTransformArgs(file, context), assetRegistryPath, assetPlugins);
const jsFile = {
...file,
type: 'js/module/asset',
ast: result.ast,
functionMap: null,
hasCjsExports: true,
reactClientReference: result.reactClientReference,
};
return transformJS(jsFile, context);
}
/**
* Transforms a JavaScript file with Babel before processing the file with
* the generic JavaScript transformation.
*/
async function transformJSWithBabel(file, context) {
const { babelTransformerPath } = context.config;
const transformer = require(babelTransformerPath);
// HACK: React Compiler injects import statements and exits the Babel process which leaves the code in
// a malformed state. For now, we'll enable the experimental import support which compiles import statements
// outside of the standard Babel process.
if (!context.options.experimentalImportSupport) {
// TODO(@kitten): Check why `reactCompilerFlag === true` is checked below
const reactCompilerFlag = context.options.customTransformOptions?.reactCompiler;
if (reactCompilerFlag === true || reactCompilerFlag === 'true') {
asWritable(context.options).experimentalImportSupport = true;
}
}
// TODO: Add a babel plugin which returns if the module has commonjs, and if so, disable all tree shaking optimizations early.
const transformResult = await transformer.transform(getBabelTransformArgs(file, context, [
// functionMapBabelPlugin populates metadata.metro.functionMap
metro_source_map_1.functionMapBabelPlugin,
// importLocationsPlugin populates metadata.metro.unstable_importDeclarationLocs
importLocationsPlugin_1.importLocationsPlugin,
]));
const jsFile = {
...file,
ast: transformResult.ast,
functionMap: transformResult.metadata?.metro?.functionMap ??
// Fallback to deprecated explicitly-generated `functionMap`
transformResult.functionMap ??
null,
unstable_importDeclarationLocs: transformResult?.metadata?.metro?.unstable_importDeclarationLocs,
hasCjsExports: transformResult.metadata?.hasCjsExports,
reactServerReference: transformResult.metadata?.reactServerReference,
reactClientReference: transformResult.metadata?.reactClientReference,
expoDomComponentReference: transformResult.metadata?.expoDomComponentReference,
loaderReference: transformResult.metadata?.loaderReference,
performConstantFolding: transformResult.metadata?.performConstantFolding,
};
return await transformJS(jsFile, context);
}
async function transformJSON(file, { options, config }) {
let code = config.unstable_disableModuleWrapping === true
? JsFileWrapping.jsonToCommonJS(file.code)
: JsFileWrapping.wrapJson(file.code, config.globalPrefix);
let sourceMap = (0, packedMap_1.emptySourceMap)();
const minify = (0, resolveOptions_1.shouldMinify)(options);
if (minify) {
({ sourceMap, code } = await (0, exports.minifyCode)(config, file.filename, code, file.code, []));
}
let jsType;
if (file.type === 'asset') {
jsType = 'js/module/asset';
}
else if (file.type === 'script') {
jsType = 'js/script';
}
else {
jsType = 'js/module';
}
let lineCount;
({ lineCount, sourceMap } = (0, packedMap_1.countLinesAndTerminateSourceMap)(code, sourceMap));
const output = [
{
data: { code, lineCount, map: sourceMap, functionMap: null },
type: jsType,
},
];
return {
dependencies: [],
output,
};
}
function getBabelTransformArgs(file, { options, config, projectRoot }, plugins = []) {
const { inlineRequires: _, ...babelTransformerOptions } = options;
return {
filename: file.filename,
options: {
...babelTransformerOptions,
enableBabelRCLookup: config.enableBabelRCLookup,
// NOTE(@kitten): Hint for babel transformer on where to look up Babel config from
extendsBabelConfigPath: config.enableBabelRCLookup !== false ? config.extendsBabelConfigPath : undefined,
// NOTE(@kitten): This shouldn't be relevant via this code path. However, in case it does,
// this prevents us from adding imports/requires to @babel/runtime when we're transforming a script
enableBabelRuntime: options.type === 'script' ? false : config.enableBabelRuntime,
hermesParser: config.hermesParser,
projectRoot,
publicPath: config.publicPath,
globalPrefix: config.globalPrefix,
platform: babelTransformerOptions.platform ?? null,
},
plugins,
src: file.code,
};
}
async function transform(config, projectRoot, filename, data, options) {
const context = {
config,
projectRoot,
options,
};
const sourceCode = data.toString('utf8');
const { unstable_dependencyMapReservedName } = config;
if (unstable_dependencyMapReservedName != null) {
const position = sourceCode.indexOf(unstable_dependencyMapReservedName);
if (position > -1) {
throw new SyntaxError('Source code contains the reserved string `' +
unstable_dependencyMapReservedName +
'` at character offset ' +
position);
}
}
if (filename.endsWith('.json')) {
const jsonFile = {
filename,
inputFileSize: data.length,
code: sourceCode,
type: options.type,
};
return transformJSON(jsonFile, context);
}
if (options.type === 'asset') {
const file = {
filename,
inputFileSize: data.length,
code: sourceCode,
type: options.type,
};
return transformAsset(file, context);
}
const file = {
filename,
inputFileSize: data.length,
code: sourceCode,
type: options.type === 'script' ? 'js/script' : 'js/module',
functionMap: null,
};
return transformJSWithBabel(file, context);
}
// NOTE: Increment if cache becomes incompatible (original value would be '')
// 1. Added new packed source map format
const CACHE_VERSION = '1';
function getCacheKey(config, opts) {
const {
// The `expo_customTransformerPath` from `./supervising-transform-worker` should not participate be part of the cache key
expo_customTransformerPath: _customTransformerPath, babelTransformerPath, minifierPath, ...remainingConfig } = config;
// TODO(@kitten): We can now tie this into `@expo/metro`, which could also simply export a static version export
const filesKey = (0, metro_cache_key_1.getCacheKey)([
require.resolve(babelTransformerPath),
(0, getMinifier_1.resolveMinifier)(minifierPath),
require.resolve('@expo/metro/metro-transform-worker/utils/getMinifier'),
require.resolve('./collect-dependencies'),
require.resolve('./asset-transformer'),
require.resolve('./resolveOptions'),
require.resolve('@expo/metro/metro/ModuleGraph/worker/generateImportNames'),
require.resolve('@expo/metro/metro/ModuleGraph/worker/JsFileWrapping'),
...metroTransformPlugins.getTransformPluginCacheKeyFiles(),
]);
let babelTransformer = require(babelTransformerPath);
// NOTE(@kitten): Many custom Babel transformers won't have `getCacheKey` yet and won't
// pass ours through. We should still try to derive a cache key though, since the default
// looks at a user's Babel config, if they have one
if (config.extendsBabelConfigPath && !babelTransformer.getCacheKey) {
babelTransformer = require('../babel-transformer');
}
const babelTransformerCacheKey = babelTransformer.getCacheKey
? babelTransformer.getCacheKey({
projectRoot: opts?.projectRoot,
enableBabelRCLookup: config.enableBabelRCLookup,
// NOTE(@kitten): Custom modification to pass this custom Babel resolution option to `getCacheKey` for consistency
extendsBabelConfigPath: config.extendsBabelConfigPath,
})
: '';
const keyParts = [];
if (CACHE_VERSION) {
keyParts.push(CACHE_VERSION);
}
keyParts.push(filesKey, (0, metro_cache_1.stableHash)(remainingConfig).toString('hex'), babelTransformerCacheKey);
return keyParts.join('$');
}
/**
* Produces a Babel template that transforms an "import(...)" call into a
* "require(...)" call to the asyncRequire specified.
*/
const makeShimAsyncRequireTemplate = core_1.template.expression(`require(ASYNC_REQUIRE_MODULE_PATH)`);
const disabledDependencyTransformer = {
transformSyncRequire: (path) => { },
transformImportMaybeSyncCall: () => { },
transformImportCall: (path, dependency, state) => {
// TODO: Prevent extraneous includes of the async require for normal imports.
// HACK: Ensure the async import code is included in the bundle when an import() call is found.
let topParent = path;
while (topParent.parentPath) {
topParent = topParent.parentPath;
}
if (topParent._handled) {
return;
}
path.insertAfter(makeShimAsyncRequireTemplate({
ASYNC_REQUIRE_MODULE_PATH: nullthrows(state.asyncRequireModulePathStringLiteral),
}));
topParent._handled = true;
},
transformPrefetch: () => { },
transformIllegalDynamicRequire: () => { },
};
function collectDependenciesForShaking(ast, options) {
const collectDependenciesOptions = {
...options,
// If tree shaking is enabled, then preserve the original require calls.
// This ensures require.context calls are not broken.
collectOnly: true,
};
return (0, collect_dependencies_1.default)(ast, {
...collectDependenciesOptions,
// This setting shouldn't be shared with the tree shaking transformer.
dependencyTransformer: disabledDependencyTransformer,
});
}
//# sourceMappingURL=metro-transform-worker.js.map