Reference version

This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.

metro.config.js

编辑页面

Metro 可用配置参考。


原文内容已经是简体中文,无需翻译。

// 手动确保 `./my-module.js` 被包含在相对于该模块的正确位置。 const myModule = await import(/* @metro-ignore */ './my-module.js');

Expo CLI 会跳过 ./my-module.js 依赖,并假定开发者已将其手动添加到输出 bundle 中。在内部,这用于导出自定义服务器代码,该代码会根据请求在文件之间动态切换。请避免在原生 bundle 中使用这种语法,因为在启用 Hermes 的 React Native 中,import() 通常不可用。

许多 React 库都附带了 Webpack 的 /* webpackIgnore: true */ 注释来实现类似的行为。为了弥合这一差距,我们也增加了对 Webpack 注释的支持,但建议你在应用中使用 Metro 对应的写法。

ES 模块解析

Metro 使用不同的解析策略分别解析 ES Module import 和 CommonJS require。

在之前,Metro 采用的是经典的 Node.js 模块解析策略(与 v12 之前的 Node.js 版本一致),并做了一些扩展以支持 ES Modules。在这种解析策略中,Metro 从 node_modules、JS 文件中解析模块,并可选择省略扩展名,例如 .js,同时使用诸如 main、module 和 react-native 等 package.json 字段。

现在,随着现代 ES Modules 解析策略的引入,Metro 会先从 node_modules 中解析模块,然后匹配不同的 package.json 字段,例如 exports、包公开的子路径嵌套映射 和 main。

根据包的导入方式,这两种解析策略中的一种会被使用。通常,从 Node 模块中使用 import(而不是 require)导入的文件,会使用 ES Modules 解析策略,并回退到普通的经典 Node.js 解析。未通过 ES Modules 解析策略解析的文件,或者通过 CommonJS require 导入的文件,将使用经典解析策略。

package.json:exports

在执行 ES Modules 解析时,Metro 会查看 package.json:exports 条件映射。这是一个将导入子路径和条件映射到 Node 模块包中文件的对应关系。

例如,一个始终公开 index.js 文件,并且与 Metro 经典 CommonJS 模块解析匹配的包,可以通过 default 条件指定一个映射。

{ "exports": { "default": "./index.js" } }

不过,一个同时提供 CommonJS 和 ES Modules 入口点的包,可以通过 import 和 require 条件提供映射。

{ "exports": { "import": "./index.mjs", "require": "./index.cjs" } }

默认情况下,Metro 会根据平台以及解析是从 CommonJS require 调用开始,还是从 ES Modules import 语句开始,来匹配不同的条件,并相应地改变条件。

对于原生平台,会添加 react-native 条件;对于 web 导出,会添加 browser 条件;对于 server 导出(例如 API 路由或 React Server 函数),会添加 node、react-server 和 workerd 条件。这些条件的匹配顺序并不是按它们定义的顺序,而是按 package.json:exports 映射中属性的顺序进行匹配。

TypeScript 会独立于 Metro 执行 ES Module 解析,并且在其 compilerOptions.moduleResolution 配置选项设置为 "bundler"(这与 Metro 的行为更接近)或 "node16" / "nodenext" 时,也会遵循 package.json:exports 映射。不过,TypeScript 还会匹配 types 条件。因此,如果包没有把 types 条件放在 exports 映射的最前面,类型可能无法正确解析。

由于 exports 映射可能包含子路径,包导入不再一定需要匹配包的 modules 目录中的某个文件,而可能是一个“重定向”导入。导入 'package/submodule' 时,如果在 package.json:exports 中有指定,可能匹配到与 node_modules/package/submodule.js 不同的文件。

{ "exports": { ".": "./index.js", "./submodule": "./submodule/submodule.js" } }

如果你遇到与新的 ES Modules 解析策略不兼容,或者尚未为其做好准备的包,你可以通过修补其 package.json 文件并添加或修正其 package.json:exports 条件映射来解决问题。不过,也可以通过禁用 unstable_enablePackageExports 选项,阻止 Metro 在解析中使用 package.json:exports 映射。

metro.config.js
const { getDefaultConfig } = require('expo/metro-config'); /** @type {import('expo/metro-config').MetroConfig} */ const config = getDefaultConfig(__dirname); config.resolver.unstable_enablePackageExports = false; module.exports = config;

资源导入

当资源被导入时,会创建一个虚拟模块来表示导入该资源所需的数据。

在原生平台上,资源将是一个数字 ID:1、2、3 等,可以通过 require("@react-native/assets-registry/registry").getAssetByID(<NUMBER>) 进行查找。在 Web 和服务端平台上,资源会根据文件类型而变化。如果文件是图片,那么资源将是 { uri: string, width?: number, height?: number };否则,资源将是一个表示该资源远程 URL 的 string。从 SDK 55 开始,你可以使用 String(asset) 获取 Web 上任意资源的公开 URL,这不包括无法包含 toString 函数的 React Server Component 环境。

资源可以按如下方式使用:

import { Image } from 'react-native'; import asset from './img.png'; function Demo() { return <Image source={asset} />; }

在 API 路由中,你始终可以假定资源的类型不会是数字:

import asset from './img.png'; export async function GET(req: Request) { const ImageData = await fetch( new URL( // 访问资源 URI。 asset.uri, // 追加到当前请求 URL 的 origin。 req.url ) ).then(res => res.arrayBuffer()); return new Response(ImageData, { headers: { 'Content-Type': 'image/png', }, }); }

Web workers

new Worker(new URL('./worker', window.location.href));

Expo Metro 提供了实验性的 web worker 支持。此功能目前仅支持 web,无法在原生端使用,在原生端使用会触发错误 "Property 'Worker' doesn't exist"。

Web workers 可用于将工作卸载到 web 上的单独线程,使主线程保持响应。这对计算开销较大的任务很有用,例如图像处理、密码学或其他原本会阻塞主线程的任务。

Worker 可以使用 Blob 内联生成,但有时你可能希望利用 TypeScript 或导入其他模块等现代特性。

Web workers 依赖于 Expo 的 bundle 拆分支持,这意味着你需要使用 Expo Router,或者安装并导入 @expo/metro-runtime。你也不能在使用 web workers 时设置环境变量 EXPO_NO_METRO_LAZY=1。

请看下面这个将数字乘以 2 的 worker 示例:

worker.ts
self.onmessage = ({ data }) => { const result = data * 2; // 示例:将数字乘以 2 self.postMessage(result); };

这个 worker 文件可以在主应用中作为 Worker 导入:

// worker 的类型为 `Worker` const worker = new Worker(new URL('./worker', window.location.href)); worker.onmessage = ({ data }) => { console.log(`Worker responded: ${data}`); }; worker.postMessage(5);

在幕后,Expo CLI 会生成如下代码:

const worker = new Worker( new URL('/worker.bundle?platform=web&dev=true&etc', window.location.href) );

生成的 bundle URL 会根据开发/生产环境而变化,以确保 worker 被正确加载和打包。不同于传统的 bundle 拆分,worker 文件需要包含其自身对所有模块的拷贝,且不能依赖主 bundle 中的公共模块。

原生 API Worker 传统上在 React Native 中不可用,Expo SDK 也没有提供,因此即使这个打包特性在技术上适用于所有平台,也只对 web 有用。如果你想同时支持原生平台,理论上可以编写一个原生 Expo 模块来为 Worker API 提供 polyfill。或者,你也可以在 React Native Reanimated 中使用 "worklet" API,将工作卸载到原生端的单独线程。

另外,你可以通过 public 路径导入 Workers:先将一个经过转换的 JS 文件放入 public 目录,然后在 worker 导入中使用变量引用它:

// 将避免转换,并直接使用 public 路径。 const worker = new Worker('/worker.js'); // 变量会破坏转换,使其使用字面路径而不是转换后的路径。 const path = '/worker.js'; const anotherWorker = new Worker(new URL(path, window.location.href));

在 Worker 构造函数中使用变量不支持打包。要检查内部 URL,你可以使用内部语法 require.unstable_resolveWorker('./path/to/worker.js') 来获取 URL 片段。

现有的 React Native 应用

不使用 Expo Prebuild 的项目必须配置原生文件,以确保始终使用 Expo Metro 配置来打包项目。

这些修改旨在分别将 npx react-native bundle 和 npx react-native start 替换为 npx expo export:embed 和 npx expo start。

metro.config.js

确保 metro.config.js 继承自 expo/metro-config:

metro.config.js
const { getDefaultConfig } = require('expo/metro-config'); const config = getDefaultConfig(__dirname); module.exports = config;

android/app/build.gradle

Android 的 app/build.gradle 必须配置为在生产打包时使用 Expo CLI。修改 react 配置对象:

ios/<Project>.xcodeproj/project.pbxproj

在你的 ios/<Project>.xcodeproj/project.pbxproj 文件中,替换以下脚本:

“Start Packager” 脚本

移除 “Start Packager” 脚本。开发服务器必须在运行应用前后使用 npx expo 启动。

“Bundle React Native code and images” 脚本

或者,在 Xcode 项目中,选择 “Bundle React Native code and images” 构建阶段并添加以下修改:

自定义入口文件

默认情况下,React Native 只支持使用根目录下的 index.js 文件作为入口文件(或平台特定变体,如 index.ios.js)。Expo 项目允许使用任意入口文件,但这需要额外的裸工作流设置。

开发

可以通过使用 expo-dev-client 包来启用开发模式入口文件。或者,你也可以添加以下配置:

生产

在你的 ios/<Project>.xcodeproj/project.pbxproj 文件中,替换 “Bundle React Native code and images” 脚本,以便根据 Metro 设置 $ENTRY_FILE:

Android 的 app/build.gradle 必须配置为使用 Metro 模块解析来查找根入口文件。修改 react 配置对象: