"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.EXPO_DEBUG = exports.INTERNAL_CALLSITES_REGEX = exports.internal_supervisingTransformerPath = exports.unstable_transformerPath = void 0; exports.createStableModuleIdFactory = createStableModuleIdFactory; exports.getDefaultConfig = getDefaultConfig; // Copyright 2023-present 650 Industries (Expo). All rights reserved. const config_1 = require("@expo/config"); const paths_1 = require("@expo/config/paths"); const json_file_1 = __importDefault(require("@expo/json-file")); const metro_cache_1 = require("@expo/metro/metro-cache"); const chalk_1 = __importDefault(require("chalk")); const os_1 = __importDefault(require("os")); const path_1 = __importDefault(require("path")); const resolve_from_1 = __importDefault(require("resolve-from")); const customizeFrame_1 = require("./customizeFrame"); Object.defineProperty(exports, "INTERNAL_CALLSITES_REGEX", { enumerable: true, get: function () { return customizeFrame_1.INTERNAL_CALLSITES_REGEX; } }); const env_1 = require("./env"); const file_store_1 = require("./file-store"); const getModulesPaths_1 = require("./getModulesPaths"); const getWatchFolders_1 = require("./getWatchFolders"); const rewriteRequestUrl_1 = require("./rewriteRequestUrl"); const sideEffects_1 = require("./serializer/sideEffects"); const withExpoSerializers_1 = require("./serializer/withExpoSerializers"); const postcss_1 = require("./transform-worker/postcss"); const filePath_1 = require("./utils/filePath"); const setOnReadonly_1 = require("./utils/setOnReadonly"); const debug = require('debug')('expo:metro:config'); let hasWarnedAboutExotic = false; let hasWarnedAboutReactNative = false; // Patch Metro's graph to support always parsing certain modules. This enables // things like Tailwind CSS which update based on their own heuristics. function patchMetroGraphToSupportUncachedModules() { const { Graph, } = require('@expo/metro/metro/DeltaBundler/Graph'); const original_traverseDependencies = Graph.prototype .traverseDependencies; if (!original_traverseDependencies.__patched) { original_traverseDependencies.__patched = true; // eslint-disable-next-line no-inner-declarations function traverseDependencies(paths, options) { this.dependencies.forEach((dependency) => { // Find any dependencies that have been marked as `skipCache` and ensure they are invalidated. // `skipCache` is set when a CSS module is found by PostCSS. if (dependency.output.find((file) => file.data.css?.skipCache) && !paths.includes(dependency.path)) { // Ensure we invalidate the `unstable_transformResultKey` (input hash) so the module isn't removed in // the Graph._processModule method. (0, setOnReadonly_1.setOnReadonly)(dependency, 'unstable_transformResultKey', dependency.unstable_transformResultKey + '.'); // Add the path to the list of modified paths so it gets run through the transformer again, // this will ensure it is passed to PostCSS -> Tailwind. paths.push(dependency.path); } }); // Invoke the original method with the new paths to ensure the standard behavior is preserved. return original_traverseDependencies.call(this, paths, options); } // Ensure we don't patch the method twice. Graph.prototype.traverseDependencies = traverseDependencies; traverseDependencies.__patched = true; } } function createNumericModuleIdFactory() { const fileToIdMap = new Map(); let nextId = 0; return (modulePath) => { let id = fileToIdMap.get(modulePath); if (typeof id !== 'number') { id = nextId++; fileToIdMap.set(modulePath, id); } return id; }; } 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; }); } function asMetroConfigInput(config) { return config; } function createStableModuleIdFactory(root) { const getModulePath = (modulePath, scope) => { // NOTE: Metro allows this but it can lead to confusing errors when dynamic requires cannot be resolved, e.g. `module 456 cannot be found`. if (modulePath == null) { return 'MODULE_NOT_FOUND'; } else if ((0, sideEffects_1.isVirtualModule)(modulePath)) { // Virtual modules should be stable. return modulePath; } else if (path_1.default.isAbsolute(modulePath)) { return (0, filePath_1.toPosixPath)(path_1.default.relative(root, modulePath)) + scope; } else { return (0, filePath_1.toPosixPath)(modulePath) + scope; } }; const memoizedGetModulePath = memoize(getModulePath); // This is an absolute file path. // TODO: We may want a hashed version for production builds in the future. return (modulePath, context) => { const env = context?.environment ?? 'client'; if (env === 'client') { // Only need scope for server bundles where multiple dimensions could run simultaneously. // @ts-expect-error: we patch this to support being a string. return memoizedGetModulePath(modulePath, ''); } // Helps find missing parts to the patch. if (!context?.platform) { // context = { platform: 'web' }; throw new Error('createStableModuleIdFactory: `context.platform` is required'); } // Only need scope for server bundles where multiple dimensions could run simultaneously. const scope = env !== 'client' ? `?platform=${context?.platform}&env=${env}` : ''; // @ts-expect-error: we patch this to support being a string. return memoizedGetModulePath(modulePath, scope); }; } function getDefaultConfig(projectRoot, { mode, isCSSEnabled = true, unstable_beforeAssetSerializationPlugins } = {}) { const { getDefaultConfig: getDefaultMetroConfig, mergeConfig, } = require('@expo/metro/metro-config'); if (isCSSEnabled) { patchMetroGraphToSupportUncachedModules(); } const isExotic = mode === 'exotic' || env_1.env.EXPO_USE_EXOTIC; if (isExotic && !hasWarnedAboutExotic) { hasWarnedAboutExotic = true; console.log(chalk_1.default.gray(`\u203A Feature ${chalk_1.default.bold `EXPO_USE_EXOTIC`} has been removed in favor of the default transformer.`)); } const reactNativePath = path_1.default.dirname(resolve_from_1.default.silent(projectRoot, 'react-native/package.json') ?? 'react-native/package.json'); if (reactNativePath === 'react-native' && !hasWarnedAboutReactNative) { hasWarnedAboutReactNative = true; console.log(chalk_1.default.yellow(`\u203A Could not resolve react-native! Is it installed and a project dependency?`)); } const sourceExtsConfig = { isTS: true, isReact: true, isModern: true }; const sourceExts = (0, paths_1.getBareExtensions)([], sourceExtsConfig); // Add support for cjs (without platform extensions). sourceExts.push('cjs'); const reanimatedVersion = getPkgVersion(projectRoot, 'react-native-reanimated'); const workletsVersion = getPkgVersion(projectRoot, 'react-native-worklets'); const babelRuntimeVersion = getPkgVersion(projectRoot, '@babel/runtime'); let sassVersion = null; if (isCSSEnabled) { sassVersion = getPkgVersion(projectRoot, 'sass'); // Enable SCSS by default so we can provide a better error message // when sass isn't installed. sourceExts.push('scss', 'sass', 'css'); } let pkg; try { pkg = (0, config_1.getPackageJson)(projectRoot); } catch (error) { if (error && error.name === 'ConfigError') { console.log(chalk_1.default.yellow(`\u203A Could not find a package.json at the project root! ("${projectRoot}")`)); } else { throw error; } } const watchFolders = (0, getWatchFolders_1.getWatchFolders)(projectRoot); const nodeModulesPaths = (0, getModulesPaths_1.getModulesPaths)(projectRoot); if (env_1.env.EXPO_DEBUG) { console.log(); console.log(`Expo Metro config:`); try { console.log(`- Version: ${require('../package.json').version}`); } catch { } console.log(`- Extensions: ${sourceExts.join(', ')}`); console.log(`- React Native: ${reactNativePath}`); console.log(`- Watch Folders: ${watchFolders.join(', ')}`); console.log(`- Node Module Paths: ${nodeModulesPaths.join(', ')}`); console.log(`- Sass: ${sassVersion}`); console.log(`- Reanimated: ${reanimatedVersion}`); console.log(`- Worklets: ${workletsVersion}`); console.log(`- Babel Runtime: ${babelRuntimeVersion}`); console.log(); } const metroDefaultValues = getDefaultMetroConfig.getDefaultValues(projectRoot); const cacheStore = new file_store_1.FileStore({ root: path_1.default.join(os_1.default.tmpdir(), 'metro-cache'), }); const serverRoot = (0, paths_1.getMetroServerRoot)(projectRoot); const routerPackageRoot = resolve_from_1.default.silent(projectRoot, 'expo-router'); const expoMetroConfig = asMetroConfigInput({ reporter: { // Remove the default reporter which metro always resolves to be the react-native-community/cli reporter. // This prints a giant React logo which is less accessible to users on smaller terminals. update() { /*noop*/ }, }, watchFolders, resolver: { unstable_conditionsByPlatform: { ios: ['react-native'], android: ['react-native'], // This is removed for server platforms. web: ['browser'], }, resolverMainFields: ['react-native', 'browser', 'main'], platforms: ['ios', 'android'], assetExts: metroDefaultValues.resolver.assetExts .concat( // Add default support for `expo-image` file types. ['heic', 'avif'], // Add default support for `expo-sqlite` file types. ['db']) .filter((assetExt) => !sourceExts.includes(assetExt)), sourceExts, nodeModulesPaths, blockList: [ // .expo/types contains generated declaration files which are not and should not be processed by Metro. // This prevents unwanted fast refresh on the declaration files changes. /\.expo[\\/]types/, ].concat(metroDefaultValues.resolver.blockList ?? []), }, cacheStores: [cacheStore], watcher: { unstable_workerThreads: false, // strip starting dot from env files. We only support watching development variants of env files as production is inlined using a different system. additionalExts: ['env', 'local', 'development'], }, serializer: { isThirdPartyModule(module) { // Block virtual modules from appearing in the source maps. if ((0, sideEffects_1.isVirtualModule)(module.path)) return true; // Generally block node modules if (/(?:^|[/\\])node_modules[/\\]/.test(module.path)) { // Allow the expo-router/entry and expo/AppEntry modules to be considered first party so the root of the app appears in the trace. return !module.path.match(/[/\\](expo-router[/\\]entry|expo[/\\]AppEntry)/); } return false; }, createModuleIdFactory: env_1.env.EXPO_USE_METRO_REQUIRE ? createStableModuleIdFactory.bind(null, serverRoot) : createNumericModuleIdFactory, getModulesRunBeforeMainModule: () => { const preModules = [ // MUST be first require.resolve(path_1.default.join(reactNativePath, 'Libraries/Core/InitializeCore')), ]; const stdRuntime = resolve_from_1.default.silent(projectRoot, 'expo/src/winter/index.ts'); if (stdRuntime) { preModules.push(stdRuntime); } else { debug('"expo/src/winter" not found, this may cause issues'); } // We need to shift this to be the first module so web Fast Refresh works as expected. // This will only be applied if the module is installed and imported somewhere in the bundle already. const metroRuntime = getExpoMetroRuntimeOptional(projectRoot); if (metroRuntime) { preModules.push(metroRuntime); } else { debug('"@expo/metro-runtime" not found, this may cause issues'); } return preModules; }, getPolyfills: ({ platform }) => { // Do nothing for nullish platforms. if (!platform) { return []; } // Native behavior. return require(path_1.default.join(reactNativePath, 'rn-get-polyfills'))(); }, }, server: { rewriteRequestUrl: (0, rewriteRequestUrl_1.getRewriteRequestUrl)(projectRoot), port: Number(env_1.env.RCT_METRO_PORT) || 8081, // NOTE(EvanBacon): Moves the server root down to the monorepo root. // This enables proper monorepo support for web. unstable_serverRoot: serverRoot, }, symbolicator: { customizeFrame: (0, customizeFrame_1.getDefaultCustomizeFrame)(), }, transformerPath: require.resolve('./transform-worker/transform-worker'), // NOTE: All of these values are used in the cache key. They should not contain any absolute paths. transformer: { unstable_workerThreads: true, // Custom: These are passed to `getCacheKey` and ensure invalidation when the version changes. unstable_renameRequire: false, _expoRouterPath: routerPackageRoot ? path_1.default.relative(serverRoot, routerPackageRoot) : undefined, postcssHash: (0, postcss_1.getPostcssConfigHash)(projectRoot), browserslistHash: pkg?.browserslist ? (0, metro_cache_1.stableHash)(JSON.stringify(pkg?.browserslist)).toString('hex') : null, sassVersion, // Ensure invalidation when the version changes due to the Reanimated and Worklets Babel plugins. reanimatedVersion, workletsVersion, // Ensure invalidation when using identical projects in monorepos _expoRelativeProjectRoot: path_1.default.relative(serverRoot, projectRoot), // `require.context` support unstable_allowRequireContext: true, allowOptionalDependencies: true, babelTransformerPath: require.resolve('./babel-transformer'), // Only apply expo internal asyncRequireModulePath when `expo` is installed // This must be a module request, rather than an absolute path to keep the cache clean asyncRequireModulePath: getExpoOptional(projectRoot, 'internal/async-require-module') ? 'expo/internal/async-require-module' : metroDefaultValues.transformer.asyncRequireModulePath, assetRegistryPath: '@react-native/assets-registry/registry', // Determines the minimum version of `@babel/runtime`, so we default it to the project's installed version of `@babel/runtime` enableBabelRuntime: babelRuntimeVersion ?? undefined, // hermesParser: true, getTransformOptions: async () => ({ transform: { experimentalImportSupport: true, inlineRequires: false, }, }), }, }); // Merge in the default config from Metro here, even though loadConfig uses it as defaults. // This is a convenience for getDefaultConfig use in metro.config.js, e.g. to modify assetExts. const metroConfig = mergeConfig( // NOTE(@kitten): We neither want ConfigT/MetroConfig here, which is mostly marked as readonly, // nor InputConfigT which is inexact and partial. Instead, we want an exact type combination of // the default config and Expo's config metroDefaultValues, expoMetroConfig); return (0, withExpoSerializers_1.withExpoSerializers)(metroConfig, { unstable_beforeAssetSerializationPlugins }); } /** Use to access the Expo Metro transformer path */ exports.unstable_transformerPath = require.resolve('./transform-worker/transform-worker'); exports.internal_supervisingTransformerPath = require.resolve('./transform-worker/supervising-transform-worker'); // re-export for legacy cases. exports.EXPO_DEBUG = env_1.env.EXPO_DEBUG; function getPkgVersion(projectRoot, pkgName) { const targetPkg = resolve_from_1.default.silent(projectRoot, pkgName); if (!targetPkg) return null; const targetPkgJson = findUpPackageJson(targetPkg); if (!targetPkgJson) return null; const pkg = json_file_1.default.read(targetPkgJson); debug(`${pkgName} package.json:`, targetPkgJson); const pkgVersion = pkg.version; if (typeof pkgVersion === 'string') { return pkgVersion; } return null; } function findUpPackageJson(cwd) { if (['.', path_1.default.sep].includes(cwd)) return null; const found = resolve_from_1.default.silent(cwd, './package.json'); if (found) { return found; } return findUpPackageJson(path_1.default.dirname(cwd)); } function getExpoOptional(projectRoot, subModule = 'package.json') { return resolve_from_1.default.silent(projectRoot, `expo/${subModule}`); } function getExpoMetroRuntimeOptional(projectRoot) { const EXPO_METRO_RUNTIME = '@expo/metro-runtime'; const metroRuntime = resolve_from_1.default.silent(projectRoot, EXPO_METRO_RUNTIME); if (metroRuntime) { return metroRuntime; } // NOTE(@kitten): While `@expo/metro-runtime` is a peer, auto-installing this peer is valid and expected // When it's auto-installed it may not be hoisted or not accessible from the project root, so we need to // try to also resolve it via `expo-router`, where it's a required peer const baseExpoRouter = resolve_from_1.default.silent(projectRoot, 'expo-router/package.json'); if (baseExpoRouter) { return resolve_from_1.default.silent(baseExpoRouter, EXPO_METRO_RUNTIME); } // When expo-router isn't installed, however, we instead try to resolve it from `expo`, where it's an // optional peer dependency const baseExpo = getExpoOptional(projectRoot); return baseExpo ? resolve_from_1.default.silent(baseExpo, EXPO_METRO_RUNTIME) : undefined; } //# sourceMappingURL=ExpoMetroConfig.js.map