@expo/cli
Version:
738 lines (737 loc) • 39.8 kB
JavaScript
/**
* Copyright © 2022 650 Industries.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/ "use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: all[name]
});
}
_export(exports, {
getNodejsExtensions: function() {
return getNodejsExtensions;
},
shouldAliasModule: function() {
return shouldAliasModule;
},
withExtendedResolver: function() {
return withExtendedResolver;
},
withMetroMultiPlatformAsync: function() {
return withMetroMultiPlatformAsync;
}
});
function _chalk() {
const data = /*#__PURE__*/ _interop_require_default(require("chalk"));
_chalk = function() {
return data;
};
return data;
}
function _fs() {
const data = /*#__PURE__*/ _interop_require_default(require("fs"));
_fs = function() {
return data;
};
return data;
}
function _metroresolver() {
const data = /*#__PURE__*/ _interop_require_wildcard(require("metro-resolver"));
_metroresolver = function() {
return data;
};
return data;
}
function _path() {
const data = /*#__PURE__*/ _interop_require_default(require("path"));
_path = function() {
return data;
};
return data;
}
function _resolvefrom() {
const data = /*#__PURE__*/ _interop_require_default(require("resolve-from"));
_resolvefrom = function() {
return data;
};
return data;
}
const _createExpoFallbackResolver = require("./createExpoFallbackResolver");
const _createExpoMetroResolver = require("./createExpoMetroResolver");
const _externals = require("./externals");
const _metroErrors = require("./metroErrors");
const _metroVirtualModules = require("./metroVirtualModules");
const _withMetroResolvers = require("./withMetroResolvers");
const _log = require("../../../log");
const _FileNotifier = require("../../../utils/FileNotifier");
const _env = require("../../../utils/env");
const _errors = require("../../../utils/errors");
const _exit = require("../../../utils/exit");
const _interactive = require("../../../utils/interactive");
const _loadTsConfigPaths = require("../../../utils/tsconfig/loadTsConfigPaths");
const _resolveWithTsConfigPaths = require("../../../utils/tsconfig/resolveWithTsConfigPaths");
const _metroOptions = require("../middleware/metroOptions");
function _interop_require_default(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
function _getRequireWildcardCache(nodeInterop) {
if (typeof WeakMap !== "function") return null;
var cacheBabelInterop = new WeakMap();
var cacheNodeInterop = new WeakMap();
return (_getRequireWildcardCache = function(nodeInterop) {
return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
})(nodeInterop);
}
function _interop_require_wildcard(obj, nodeInterop) {
if (!nodeInterop && obj && obj.__esModule) {
return obj;
}
if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
return {
default: obj
};
}
var cache = _getRequireWildcardCache(nodeInterop);
if (cache && cache.has(obj)) {
return cache.get(obj);
}
var newObj = {
__proto__: null
};
var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
for(var key in obj){
if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
if (desc && (desc.get || desc.set)) {
Object.defineProperty(newObj, key, desc);
} else {
newObj[key] = obj[key];
}
}
}
newObj.default = obj;
if (cache) {
cache.set(obj, newObj);
}
return newObj;
}
const ASSET_REGISTRY_SRC = `const assets=[];module.exports={registerAsset:s=>assets.push(s),getAssetByID:s=>assets[s-1]};`;
const debug = require('debug')('expo:start:server:metro:multi-platform');
function withWebPolyfills(config, { getMetroBundler }) {
const originalGetPolyfills = config.serializer.getPolyfills ? config.serializer.getPolyfills.bind(config.serializer) : ()=>[];
const getPolyfills = (ctx)=>{
const virtualEnvVarId = `\0polyfill:environment-variables`;
(0, _metroVirtualModules.getMetroBundlerWithVirtualModules)(getMetroBundler()).setVirtualModule(virtualEnvVarId, (()=>{
return `//`;
})());
const virtualModuleId = `\0polyfill:external-require`;
(0, _metroVirtualModules.getMetroBundlerWithVirtualModules)(getMetroBundler()).setVirtualModule(virtualModuleId, (()=>{
if (ctx.platform === 'web') {
return `global.$$require_external = typeof require !== "undefined" ? require : () => null;`;
} else {
// Wrap in try/catch to support Android.
return 'try { global.$$require_external = typeof expo === "undefined" ? require : (moduleId) => { throw new Error(`Node.js standard library module ${moduleId} is not available in this JavaScript environment`);} } catch { global.$$require_external = (moduleId) => { throw new Error(`Node.js standard library module ${moduleId} is not available in this JavaScript environment`);} }';
}
})());
if (ctx.platform === 'web') {
return [
virtualModuleId,
virtualEnvVarId,
// Ensure that the error-guard polyfill is included in the web polyfills to
// make metro-runtime work correctly.
// TODO: This module is pretty big for a function that simply re-throws an error that doesn't need to be caught.
require.resolve('@react-native/js-polyfills/error-guard')
];
}
// Generally uses `rn-get-polyfills`
const polyfills = originalGetPolyfills(ctx);
return [
...polyfills,
virtualModuleId,
virtualEnvVarId,
// Removed on server platforms during the transform.
require.resolve('expo/virtual/streams.js')
];
};
return {
...config,
serializer: {
...config.serializer,
getPolyfills
}
};
}
function normalizeSlashes(p) {
return p.replace(/\\/g, '/');
}
function getNodejsExtensions(srcExts) {
const mjsExts = srcExts.filter((ext)=>/mjs$/.test(ext));
const nodejsSourceExtensions = srcExts.filter((ext)=>!/mjs$/.test(ext));
// find index of last `*.js` extension
const jsIndex = nodejsSourceExtensions.reduce((index, ext, i)=>{
return /jsx?$/.test(ext) ? i : index;
}, -1);
// insert `*.mjs` extensions after `*.js` extensions
nodejsSourceExtensions.splice(jsIndex + 1, 0, ...mjsExts);
return nodejsSourceExtensions;
}
function withExtendedResolver(config, { tsconfig, isTsconfigPathsEnabled, isFastResolverEnabled, isExporting, isReactCanaryEnabled, isReactServerComponentsEnabled, getMetroBundler }) {
var _config_resolver, _config_resolver1, _config_resolver2, _config_resolver3, _config_serializer_createModuleIdFactory, _config_serializer;
if (isReactServerComponentsEnabled) {
_log.Log.warn(`React Server Components (beta) is enabled.`);
}
if (isReactCanaryEnabled) {
_log.Log.warn(`Experimental React 19 canary is enabled.`);
}
if (isFastResolverEnabled) {
_log.Log.log(_chalk().default.dim`Fast resolver is enabled.`);
}
const defaultResolver = _metroresolver().resolve;
const resolver = isFastResolverEnabled ? (0, _createExpoMetroResolver.createFastResolver)({
preserveSymlinks: true,
blockList: !((_config_resolver = config.resolver) == null ? void 0 : _config_resolver.blockList) ? [] : Array.isArray((_config_resolver1 = config.resolver) == null ? void 0 : _config_resolver1.blockList) ? (_config_resolver2 = config.resolver) == null ? void 0 : _config_resolver2.blockList : [
(_config_resolver3 = config.resolver) == null ? void 0 : _config_resolver3.blockList
]
}) : defaultResolver;
const aliases = {
web: {
'react-native': 'react-native-web',
'react-native/index': 'react-native-web',
'react-native/Libraries/Image/resolveAssetSource': 'expo-asset/build/resolveAssetSource'
}
};
// The vendored canary modules live inside /static/canary-full/node_modules
// Adding the `index.js` allows us to add this path as `originModulePath` to
// resolve the nested `node_modules` folder properly.
const canaryModulesPath = _path().default.join(require.resolve('@expo/cli/package.json'), '../static/canary-full/index.js');
let _universalAliases;
function getUniversalAliases() {
if (_universalAliases) {
return _universalAliases;
}
_universalAliases = [];
// This package is currently always installed as it is included in the `expo` package.
if (_resolvefrom().default.silent(config.projectRoot, '@expo/vector-icons')) {
debug('Enabling alias: react-native-vector-icons -> @expo/vector-icons');
_universalAliases.push([
/^react-native-vector-icons(\/.*)?/,
'@expo/vector-icons$1'
]);
}
if (isReactServerComponentsEnabled) {
if (_resolvefrom().default.silent(config.projectRoot, 'expo-router/rsc')) {
debug('Enabling bridge alias: expo-router -> expo-router/rsc');
_universalAliases.push([
/^expo-router$/,
'expo-router/rsc'
]);
// Bridge the internal entry point which is a standalone import to ensure package.json resolution works as expected.
_universalAliases.push([
/^expo-router\/entry-classic$/,
'expo-router/rsc/entry'
]);
}
}
return _universalAliases;
}
const preferredMainFields = {
// Defaults from Expo Webpack. Most packages using `react-native` don't support web
// in the `react-native` field, so we should prefer the `browser` field.
// https://github.com/expo/router/issues/37
web: [
'browser',
'module',
'main'
]
};
let tsConfigResolve = isTsconfigPathsEnabled && ((tsconfig == null ? void 0 : tsconfig.paths) || (tsconfig == null ? void 0 : tsconfig.baseUrl) != null) ? _resolveWithTsConfigPaths.resolveWithTsConfigPaths.bind(_resolveWithTsConfigPaths.resolveWithTsConfigPaths, {
paths: tsconfig.paths ?? {},
baseUrl: tsconfig.baseUrl ?? config.projectRoot,
hasBaseUrl: !!tsconfig.baseUrl
}) : null;
// TODO: Move this to be a transform key for invalidation.
if (!isExporting && (0, _interactive.isInteractive)()) {
if (isTsconfigPathsEnabled) {
// TODO: We should track all the files that used imports and invalidate them
// currently the user will need to save all the files that use imports to
// use the new aliases.
const configWatcher = new _FileNotifier.FileNotifier(config.projectRoot, [
'./tsconfig.json',
'./jsconfig.json'
]);
configWatcher.startObserving(()=>{
debug('Reloading tsconfig.json');
(0, _loadTsConfigPaths.loadTsConfigPathsAsync)(config.projectRoot).then((tsConfigPaths)=>{
if ((tsConfigPaths == null ? void 0 : tsConfigPaths.paths) && !!Object.keys(tsConfigPaths.paths).length) {
debug('Enabling tsconfig.json paths support');
tsConfigResolve = _resolveWithTsConfigPaths.resolveWithTsConfigPaths.bind(_resolveWithTsConfigPaths.resolveWithTsConfigPaths, {
paths: tsConfigPaths.paths ?? {},
baseUrl: tsConfigPaths.baseUrl ?? config.projectRoot,
hasBaseUrl: !!tsConfigPaths.baseUrl
});
} else {
debug('Disabling tsconfig.json paths support');
tsConfigResolve = null;
}
});
});
// TODO: This probably prevents the process from exiting.
(0, _exit.installExitHooks)(()=>{
configWatcher.stopObserving();
});
} else {
debug('Skipping tsconfig.json paths support');
}
}
let nodejsSourceExtensions = null;
const getStrictResolver = ({ resolveRequest, ...context }, platform)=>{
return function doResolve(moduleName) {
return resolver(context, moduleName, platform);
};
};
function getOptionalResolver(context, platform) {
const doResolve = getStrictResolver(context, platform);
return function optionalResolve(moduleName) {
try {
return doResolve(moduleName);
} catch (error) {
// If the error is directly related to a resolver not being able to resolve a module, then
// we can ignore the error and try the next resolver. Otherwise, we should throw the error.
const isResolutionError = (0, _metroErrors.isFailedToResolveNameError)(error) || (0, _metroErrors.isFailedToResolvePathError)(error);
if (!isResolutionError) {
throw error;
}
}
return null;
};
}
// TODO: This is a hack to get resolveWeak working.
const idFactory = ((_config_serializer = config.serializer) == null ? void 0 : (_config_serializer_createModuleIdFactory = _config_serializer.createModuleIdFactory) == null ? void 0 : _config_serializer_createModuleIdFactory.call(_config_serializer)) ?? ((id, context)=>id);
const getAssetRegistryModule = ()=>{
const virtualModuleId = `\0polyfill:assets-registry`;
(0, _metroVirtualModules.getMetroBundlerWithVirtualModules)(getMetroBundler()).setVirtualModule(virtualModuleId, ASSET_REGISTRY_SRC);
return {
type: 'sourceFile',
filePath: virtualModuleId
};
};
// If Node.js pass-through, then remap to a module like `module.exports = $$require_external(<module>)`.
// If module should be shimmed, remap to an empty module.
const externals = [
{
match: (context, moduleName)=>{
var _context_customResolverOptions, _context_customResolverOptions1;
if (// Disable internal externals when exporting for production.
context.customResolverOptions.exporting || // These externals are only for Node.js environments.
!(0, _metroOptions.isServerEnvironment)((_context_customResolverOptions = context.customResolverOptions) == null ? void 0 : _context_customResolverOptions.environment)) {
return false;
}
if (((_context_customResolverOptions1 = context.customResolverOptions) == null ? void 0 : _context_customResolverOptions1.environment) === 'react-server') {
// Ensure these non-react-server modules are excluded when bundling for React Server Components in development.
return /^(source-map-support(\/.*)?|@babel\/runtime\/.+|debug|metro-runtime\/src\/modules\/HMRClient|metro|acorn-loose|acorn|chalk|ws|ansi-styles|supports-color|color-convert|has-flag|utf-8-validate|color-name|react-refresh\/runtime|@remix-run\/node\/.+)$/.test(moduleName);
}
// TODO: Windows doesn't support externals somehow.
if (process.platform === 'win32') {
return /^(source-map-support(\/.*)?)$/.test(moduleName);
}
// Extern these modules in standard Node.js environments in development to prevent API routes side-effects
// from leaking into the dev server process.
return /^(source-map-support(\/.*)?|react|@radix-ui\/.+|@babel\/runtime\/.+|react-dom(\/.+)?|debug|acorn-loose|acorn|css-in-js-utils\/lib\/.+|hyphenate-style-name|color|color-string|color-convert|color-name|fontfaceobserver|fast-deep-equal|query-string|escape-string-regexp|invariant|postcss-value-parser|memoize-one|nullthrows|strict-uri-encode|decode-uri-component|split-on-first|filter-obj|warn-once|simple-swizzle|is-arrayish|inline-style-prefixer\/.+)$/.test(moduleName);
},
replace: 'node'
},
// Externals to speed up async split chunks by extern-ing common packages that appear in the root client chunk.
{
match: (context, moduleName, platform)=>{
var _context_customResolverOptions;
if (// Disable internal externals when exporting for production.
context.customResolverOptions.exporting || // These externals are only for client environments.
(0, _metroOptions.isServerEnvironment)((_context_customResolverOptions = context.customResolverOptions) == null ? void 0 : _context_customResolverOptions.environment) || // Only enable for client boundaries
!context.customResolverOptions.clientboundary) {
return false;
}
// We don't support this in the resolver at the moment.
if (moduleName.endsWith('/package.json')) {
return false;
}
const isExternal = /^(deprecated-react-native-prop-types|react|react\/jsx-dev-runtime|scheduler|react-native|react-dom(\/.+)?|metro-runtime(\/.+)?)$/.test(moduleName) || // TODO: Add more
/^@babel\/runtime\/helpers\/(wrapNativeSuper)$/.test(moduleName);
return isExternal;
},
replace: 'weak'
}
];
const metroConfigWithCustomResolver = (0, _withMetroResolvers.withMetroResolvers)(config, [
// Mock out production react imports in development.
function requestDevMockProdReact(context, moduleName, platform) {
// This resolution is dev-only to prevent bundling the production React packages in development.
if (!context.dev) return null;
if (// Match react-native renderers.
platform !== 'web' && context.originModulePath.match(/[\\/]node_modules[\\/]react-native[\\/]/) && moduleName.match(/([\\/]ReactFabric|ReactNativeRenderer)-prod/) || // Match react production imports.
moduleName.match(/\.production(\.min)?\.js$/) && // Match if the import originated from a react package.
context.originModulePath.match(/[\\/]node_modules[\\/](react[-\\/]|scheduler[\\/])/)) {
debug(`Skipping production module: ${moduleName}`);
// /Users/path/to/expo/node_modules/react/index.js ./cjs/react.production.min.js
// /Users/path/to/expo/node_modules/react/jsx-dev-runtime.js ./cjs/react-jsx-dev-runtime.production.min.js
// /Users/path/to/expo/node_modules/react-is/index.js ./cjs/react-is.production.min.js
// /Users/path/to/expo/node_modules/react-refresh/runtime.js ./cjs/react-refresh-runtime.production.min.js
// /Users/path/to/expo/node_modules/react-native/node_modules/scheduler/index.native.js ./cjs/scheduler.native.production.min.js
// /Users/path/to/expo/node_modules/react-native/node_modules/react-is/index.js ./cjs/react-is.production.min.js
return {
type: 'empty'
};
}
return null;
},
// tsconfig paths
function requestTsconfigPaths(context, moduleName, platform) {
return (tsConfigResolve == null ? void 0 : tsConfigResolve({
originModulePath: context.originModulePath,
moduleName
}, getOptionalResolver(context, platform))) ?? null;
},
// Node.js externals support
function requestNodeExternals(context, moduleName, platform) {
var _context_customResolverOptions, _context_customResolverOptions1;
const isServer = ((_context_customResolverOptions = context.customResolverOptions) == null ? void 0 : _context_customResolverOptions.environment) === 'node' || ((_context_customResolverOptions1 = context.customResolverOptions) == null ? void 0 : _context_customResolverOptions1.environment) === 'react-server';
const moduleId = (0, _externals.isNodeExternal)(moduleName);
if (!moduleId) {
return null;
}
if (// In browser runtimes, we want to either resolve a local node module by the same name, or shim the module to
// prevent crashing when Node.js built-ins are imported.
!isServer) {
// Perform optional resolve first. If the module doesn't exist (no module in the node_modules)
// then we can mock the file to use an empty module.
const result = getOptionalResolver(context, platform)(moduleName);
if (!result && platform !== 'web') {
// Preserve previous behavior where native throws an error on node.js internals.
return null;
}
return result ?? {
// In this case, mock the file to use an empty module.
type: 'empty'
};
}
const contents = `module.exports=$$require_external('node:${moduleId}');`;
debug(`Virtualizing Node.js "${moduleId}"`);
const virtualModuleId = `\0node:${moduleId}`;
(0, _metroVirtualModules.getMetroBundlerWithVirtualModules)(getMetroBundler()).setVirtualModule(virtualModuleId, contents);
return {
type: 'sourceFile',
filePath: virtualModuleId
};
},
// Custom externals support
function requestCustomExternals(context, moduleName, platform) {
var _context_customResolverOptions;
// We don't support this in the resolver at the moment.
if (moduleName.endsWith('/package.json')) {
return null;
}
// Skip applying JS externals for CSS files.
if (/\.(s?css|sass)$/.test(context.originModulePath)) {
return null;
}
const environment = (_context_customResolverOptions = context.customResolverOptions) == null ? void 0 : _context_customResolverOptions.environment;
const strictResolve = getStrictResolver(context, platform);
for (const external of externals){
if (external.match(context, moduleName, platform)) {
if (external.replace === 'empty') {
debug(`Redirecting external "${moduleName}" to "${external.replace}"`);
return {
type: external.replace
};
} else if (external.replace === 'weak') {
// TODO: Make this use require.resolveWeak again. Previously this was just resolving to the same path.
const realModule = strictResolve(moduleName);
const realPath = realModule.type === 'sourceFile' ? realModule.filePath : moduleName;
const opaqueId = idFactory(realPath, {
platform: platform,
environment
});
const contents = typeof opaqueId === 'number' ? `module.exports=/*${moduleName}*/__r(${opaqueId})` : `module.exports=/*${moduleName}*/__r(${JSON.stringify(opaqueId)})`;
// const contents = `module.exports=/*${moduleName}*/__r(require.resolveWeak('${moduleName}'))`;
// const generatedModuleId = fastHashMemoized(contents);
const virtualModuleId = `\0weak:${opaqueId}`;
debug('Virtualizing module:', moduleName, '->', virtualModuleId);
(0, _metroVirtualModules.getMetroBundlerWithVirtualModules)(getMetroBundler()).setVirtualModule(virtualModuleId, contents);
return {
type: 'sourceFile',
filePath: virtualModuleId
};
} else if (external.replace === 'node') {
const contents = `module.exports=$$require_external('${moduleName}')`;
const virtualModuleId = `\0node:${moduleName}`;
debug('Virtualizing Node.js (custom):', moduleName, '->', virtualModuleId);
(0, _metroVirtualModules.getMetroBundlerWithVirtualModules)(getMetroBundler()).setVirtualModule(virtualModuleId, contents);
return {
type: 'sourceFile',
filePath: virtualModuleId
};
} else {
throw new _errors.CommandError(`Invalid external alias type: "${external.replace}" for module "${moduleName}" (platform: ${platform}, originModulePath: ${context.originModulePath})`);
}
}
}
return null;
},
// Basic moduleId aliases
function requestAlias(context, moduleName, platform) {
// Conditionally remap `react-native` to `react-native-web` on web in
// a way that doesn't require Babel to resolve the alias.
if (platform && platform in aliases && aliases[platform][moduleName]) {
const redirectedModuleName = aliases[platform][moduleName];
return getStrictResolver(context, platform)(redirectedModuleName);
}
for (const [matcher, alias] of getUniversalAliases()){
const match = moduleName.match(matcher);
if (match) {
const aliasedModule = alias.replace(/\$(\d+)/g, (_, index)=>match[parseInt(index, 10)] ?? '');
const doResolve = getStrictResolver(context, platform);
debug(`Alias "${moduleName}" to "${aliasedModule}"`);
return doResolve(aliasedModule);
}
}
return null;
},
// Polyfill for asset registry
function requestStableAssetRegistry(context, moduleName, platform) {
if (/^@react-native\/assets-registry\/registry(\.js)?$/.test(moduleName)) {
return getAssetRegistryModule();
}
if (platform === 'web' && context.originModulePath.match(/node_modules[\\/]react-native-web[\\/]/) && moduleName.includes('/modules/AssetRegistry')) {
return getAssetRegistryModule();
}
return null;
},
// TODO: Reduce these as much as possible in the future.
// Complex post-resolution rewrites.
function requestPostRewrites(context, moduleName, platform) {
const doResolve = getStrictResolver(context, platform);
const result = doResolve(moduleName);
if (result.type !== 'sourceFile') {
return result;
}
if (platform === 'web') {
if (result.filePath.includes('node_modules')) {
// // Disallow importing confusing native modules on web
if (moduleName.includes('react-native/Libraries/Utilities/codegenNativeCommands')) {
throw new _createExpoMetroResolver.FailedToResolvePathError(`Importing native-only module "${moduleName}" on web from: ${context.originModulePath}`);
}
// Replace with static shims
const normalName = normalizeSlashes(result.filePath)// Drop everything up until the `node_modules` folder.
.replace(/.*node_modules\//, '');
const shimFile = (0, _externals.shouldCreateVirtualShim)(normalName);
if (shimFile) {
const virtualId = `\0shim:${normalName}`;
const bundler = (0, _metroVirtualModules.getMetroBundlerWithVirtualModules)(getMetroBundler());
if (!bundler.hasVirtualModule(virtualId)) {
bundler.setVirtualModule(virtualId, _fs().default.readFileSync(shimFile, 'utf8'));
}
debug(`Redirecting module "${result.filePath}" to shim`);
return {
...result,
filePath: virtualId
};
}
}
} else {
var _context_customResolverOptions, _context_customResolverOptions1;
const isServer = ((_context_customResolverOptions = context.customResolverOptions) == null ? void 0 : _context_customResolverOptions.environment) === 'node' || ((_context_customResolverOptions1 = context.customResolverOptions) == null ? void 0 : _context_customResolverOptions1.environment) === 'react-server';
// react-native/Libraries/Core/InitializeCore
const normal = normalizeSlashes(result.filePath);
// Shim out React Native native runtime globals in server mode for native.
if (isServer) {
if (normal.endsWith('react-native/Libraries/Core/InitializeCore.js')) {
debug('Shimming out InitializeCore for React Native in native SSR bundle');
return {
type: 'empty'
};
}
}
// When server components are enabled, redirect React Native's renderer to the canary build
// this will enable the use hook and other requisite features from React 19.
if (isReactCanaryEnabled && result.filePath.includes('node_modules')) {
const normalName = normalizeSlashes(result.filePath)// Drop everything up until the `node_modules` folder.
.replace(/.*node_modules\//, '');
const canaryFile = (0, _externals.shouldCreateVirtualCanary)(normalName);
if (canaryFile) {
debug(`Redirecting React Native module "${result.filePath}" to canary build`);
return {
...result,
filePath: canaryFile
};
}
}
}
return result;
},
// If at this point, we haven't resolved a module yet, if it's a module specifier for a known dependency
// of either `expo` or `expo-router`, attempt to resolve it from these origin modules instead
(0, _createExpoFallbackResolver.createFallbackModuleResolver)({
projectRoot: config.projectRoot,
originModuleNames: [
'expo',
'expo-router'
],
getStrictResolver
})
]);
// Ensure we mutate the resolution context to include the custom resolver options for server and web.
const metroConfigWithCustomContext = (0, _withMetroResolvers.withMetroMutatedResolverContext)(metroConfigWithCustomResolver, (immutableContext, moduleName, platform)=>{
var _context_customResolverOptions;
const context = {
...immutableContext,
preferNativePlatform: platform !== 'web'
};
// TODO: Remove this when we have React 19 in the expo/expo monorepo.
if (isReactCanaryEnabled && // Change the node modules path for react and react-dom to use the vendor in Expo CLI.
/^(react|react\/.*|react-dom|react-dom\/.*)$/.test(moduleName)) {
// Modifying the origin module path changes the starting Node module resolution path to this folder
context.originModulePath = canaryModulesPath;
// Hierarchical lookup has to be enabled for this to work
context.disableHierarchicalLookup = false;
}
if ((0, _metroOptions.isServerEnvironment)((_context_customResolverOptions = context.customResolverOptions) == null ? void 0 : _context_customResolverOptions.environment)) {
var _context_customResolverOptions1, _context_customResolverOptions2;
// Adjust nodejs source extensions to sort mjs after js, including platform variants.
if (nodejsSourceExtensions === null) {
nodejsSourceExtensions = getNodejsExtensions(context.sourceExts);
}
context.sourceExts = nodejsSourceExtensions;
context.unstable_enablePackageExports = true;
context.unstable_conditionsByPlatform = {};
const isReactServerComponents = ((_context_customResolverOptions1 = context.customResolverOptions) == null ? void 0 : _context_customResolverOptions1.environment) === 'react-server';
if (isReactServerComponents) {
// NOTE: Align the behavior across server and client. This is a breaking change so we'll just roll it out with React Server Components.
// This ensures that react-server and client code both resolve `module` and `main` in the same order.
if (platform === 'web') {
// Node.js runtimes should only be importing main at the moment.
// This is a temporary fix until we can support the package.json exports.
context.mainFields = [
'module',
'main'
];
} else {
// In Node.js + native, use the standard main fields.
context.mainFields = [
'react-native',
'module',
'main'
];
}
} else {
if (platform === 'web') {
// Node.js runtimes should only be importing main at the moment.
// This is a temporary fix until we can support the package.json exports.
context.mainFields = [
'main',
'module'
];
} else {
// In Node.js + native, use the standard main fields.
context.mainFields = [
'react-native',
'main',
'module'
];
}
}
// Enable react-server import conditions.
if (((_context_customResolverOptions2 = context.customResolverOptions) == null ? void 0 : _context_customResolverOptions2.environment) === 'react-server') {
context.unstable_conditionNames = [
'node',
'react-server',
'workerd'
];
} else {
context.unstable_conditionNames = [
'node'
];
}
} else {
// Non-server changes
if (!_env.env.EXPO_METRO_NO_MAIN_FIELD_OVERRIDE && platform && platform in preferredMainFields) {
context.mainFields = preferredMainFields[platform];
}
}
return context;
});
return (0, _withMetroResolvers.withMetroErrorReportingResolver)(metroConfigWithCustomContext);
}
function shouldAliasModule(input, alias) {
var _input_result, _input_result1;
return input.platform === alias.platform && ((_input_result = input.result) == null ? void 0 : _input_result.type) === 'sourceFile' && typeof ((_input_result1 = input.result) == null ? void 0 : _input_result1.filePath) === 'string' && normalizeSlashes(input.result.filePath).endsWith(alias.output);
}
async function withMetroMultiPlatformAsync(projectRoot, { config, exp, platformBundlers, isTsconfigPathsEnabled, isFastResolverEnabled, isExporting, isReactCanaryEnabled, isNamedRequiresEnabled, isReactServerComponentsEnabled, getMetroBundler }) {
if (isNamedRequiresEnabled) {
debug('Using Expo metro require runtime.');
// Change the default metro-runtime to a custom one that supports bundle splitting.
require('metro-config/src/defaults/defaults').moduleSystem = require.resolve('@expo/cli/build/metro-require/require');
}
if (!config.projectRoot) {
// @ts-expect-error: read-only types
config.projectRoot = projectRoot;
}
// Required for @expo/metro-runtime to format paths in the web LogBox.
process.env.EXPO_PUBLIC_PROJECT_ROOT = process.env.EXPO_PUBLIC_PROJECT_ROOT ?? projectRoot;
// This is used for running Expo CLI in development against projects outside the monorepo.
if (!isDirectoryIn(__dirname, projectRoot)) {
if (!config.watchFolders) {
// @ts-expect-error: watchFolders is readonly
config.watchFolders = [];
}
// @ts-expect-error: watchFolders is readonly
config.watchFolders.push(_path().default.join(require.resolve('metro-runtime/package.json'), '../..'));
// @ts-expect-error: watchFolders is readonly
config.watchFolders.push(_path().default.join(require.resolve('@expo/metro-config/package.json'), '../..'), // For virtual modules
_path().default.join(require.resolve('expo/package.json'), '..'));
if (isReactCanaryEnabled) {
// @ts-expect-error: watchFolders is readonly
config.watchFolders.push(_path().default.join(require.resolve('@expo/cli/package.json'), '..'));
}
}
// TODO: Remove this
// @ts-expect-error: Invalidate the cache when the location of expo-router changes on-disk.
config.transformer._expoRouterPath = _resolvefrom().default.silent(projectRoot, 'expo-router');
let tsconfig = null;
if (isTsconfigPathsEnabled) {
tsconfig = await (0, _loadTsConfigPaths.loadTsConfigPathsAsync)(projectRoot);
}
let expoConfigPlatforms = Object.entries(platformBundlers).filter(([platform, bundler])=>{
var _exp_platforms;
return bundler === 'metro' && ((_exp_platforms = exp.platforms) == null ? void 0 : _exp_platforms.includes(platform));
}).map(([platform])=>platform);
if (Array.isArray(config.resolver.platforms)) {
expoConfigPlatforms = [
...new Set(expoConfigPlatforms.concat(config.resolver.platforms))
];
}
// @ts-expect-error: typed as `readonly`.
config.resolver.platforms = expoConfigPlatforms;
config = withWebPolyfills(config, {
getMetroBundler
});
return withExtendedResolver(config, {
tsconfig,
isExporting,
isTsconfigPathsEnabled,
isFastResolverEnabled,
isReactCanaryEnabled,
isReactServerComponentsEnabled,
getMetroBundler
});
}
function isDirectoryIn(targetPath, rootPath) {
return targetPath.startsWith(rootPath) && targetPath.length >= rootPath.length;
}
//# sourceMappingURL=withMetroMultiPlatform.js.map