Metro 中可用配置的参考。
请参阅 定制 Metro 指南 中有关 Metro.config.js 的更多信息。
¥See more information about metro.config.js in the customizing Metro guide.
¥Environment variables
在 SDK 49 及更高版本中,Expo CLI 可以从 .env 文件加载环境变量。了解有关如何在 环境变量指南 中的 Expo CLI 中使用环境变量的更多信息。
¥In SDK 49 and higher, the Expo CLI can load environment variables from .env files. Learn more about how to use environment variables in Expo CLI in the environment variables guide.
EAS CLI 对环境变量使用不同的机制,除非它调用 Expo CLI 进行编译和打包。了解有关 EAS 中的环境变量 的更多信息。
¥EAS CLI uses a different mechanism for environment variables, except when it invokes Expo CLI for compiling and bundling. Learn more about environment variables in EAS.
如果你要将旧项目迁移到 SDK 49 或更高版本,那么你应该通过将以下内容添加到 .gitignore 来忽略本地环境文件:
¥If you are migrating an older project to SDK 49 or above, then you should ignore local env files by adding the following to your .gitignore:
# local env files
.env*.local
¥Disabling dotenv files
在调用任何 Expo CLI 命令之前,通过启用 EXPO_NO_DOTENV
环境变量,可以在 Expo CLI 中完全禁用 Dotenv 文件加载。
¥Dotenv file loading can be fully disabled in Expo CLI by enabling the EXPO_NO_DOTENV
environment variable, before invoking any Expo CLI command.
# All users can run cross-env, followed by the Expo CLI command
-
npx cross-env EXPO_NO_DOTENV=1 expo start
# Alternatively, macOS and Linux users can define the environment variable, then run npx, followed by the Expo CLI command
-
EXPO_NO_DOTENV=1 npx expo start
EXPO_PUBLIC_
前缀的客户端环境变量¥Disabling EXPO_PUBLIC_
-prefixed client environment variables
以 EXPO_PUBLIC_
为前缀的环境变量将在构建时公开给应用。例如,EXPO_PUBLIC_API_KEY
将作为 process.env.EXPO_PUBLIC_API_KEY
提供。
¥Environment variables prefixed with EXPO_PUBLIC_
will be exposed to the app at build-time. For example, EXPO_PUBLIC_API_KEY
will be available as process.env.EXPO_PUBLIC_API_KEY
.
可以使用环境变量 EXPO_NO_CLIENT_ENV_VARS=1
禁用客户端环境变量内联,必须在执行任何打包之前定义它。
¥Client environment variable inlining can be disabled with the environment variable EXPO_NO_CLIENT_ENV_VARS=1
, this must be defined before any bundling is performed.
# All users can run cross-env, followed by the Expo CLI command
-
npx cross-env EXPO_NO_CLIENT_ENV_VARS=1 expo start
# Alternatively, macOS and Linux users can define the environment variable, then run npx, followed by the Expo CLI command
-
EXPO_NO_CLIENT_ENV_VARS=1 npx expo start
CSS 支持正在开发中,目前仅适用于 Web。
Expo 在你的项目中支持 CSS。你可以从任何组件导入 CSS 文件。还支持 CSS 模块。
¥Expo supports CSS in your project. You can import CSS files from any component. CSS Modules are also supported.
CSS 支持默认启用。你可以通过在 Metro 配置中设置 isCSSEnabled
来禁用该功能。
¥CSS support is enabled by default. You can disable the feature by setting isCSSEnabled
in the Metro config.
/** @type {import('expo/metro-config').MetroConfig} */
const config = getDefaultConfig(__dirname, {
// Disable CSS support.
isCSSEnabled: false,
});
要启用 CSS 支持,请在 Metro 配置中将 isCSSEnabled
设置为 true
。
¥To enable CSS support, set isCSSEnabled
to true
in the Metro config.
/** @type {import('expo/metro-config').MetroConfig} */
const config = getDefaultConfig(__dirname, {
isCSSEnabled: true,
});
¥Global CSS
全局样式仅适用于 Web,使用会导致你的应用在原生上在视觉上有所不同。
你可以从任何组件导入 CSS 文件。CSS 将应用于整个页面。
¥You can import a CSS file from any component. The CSS will be applied to the entire page.
在这里,我们将为类名 .container
定义一个全局样式:
¥Here, we'll define a global style for the class name .container
:
.container {
background-color: red;
}
然后,我们可以通过导入样式表并使用 .container
在组件中使用类名:
¥We can then use the class name in our component by importing the stylesheet and using .container
:
import './styles.css';
import { View } from 'react-native';
export default function App() {
return (
<>
{/* Use `className` to assign the style with React DOM components. */}
<div className="container">Hello World</div>
{/* Use `style` with the following syntax to append class names in React Native for web. */}
<View
style={{
$$css: true,
_: 'container',
}}>
Hello World
</View>
</>
);
}
你还可以导入库中提供的样式表,就像任何节点模块一样:
¥You can also import stylesheets that are vendored in libraries, just like you would any node module:
// Applies the styles app-wide.
import 'emoji-mart/css/emoji-mart.css';
在原生上,所有全局样式表都会自动忽略。
¥On native, all global stylesheets are automatically ignored.
全局样式表支持热重载,只需保存文件即可应用更改。
¥Hot reloading is supported for global stylesheets, simply save the file and the changes will be applied.
¥CSS Modules
原生 CSS 模块正在开发中,目前仅适用于 Web。
CSS 模块是一种将 CSS 范围限定到特定组件的方法。这对于避免命名冲突和确保样式仅应用于预期的组件很有用。
¥CSS Modules are a way to scope CSS to a specific component. This is useful for avoiding naming collisions and for ensuring that styles are only applied to the intended component.
在 Expo 中,CSS 模块是通过创建扩展名为 .module.css
的文件来定义的。该文件可以从任何组件导入。导出的值是一个对象,其中类名称作为键,仅限 Web 范围的名称作为值。导入 unstable_styles
可用于访问 react-native-web
安全样式。
¥In Expo, CSS Modules are defined by creating a file with the .module.css
extension. The file can be imported from any component. The exported value is an object with the class names as keys and the web-only scoped names as the values. The import unstable_styles
can be used to access react-native-web
-safe styles.
CSS 模块支持平台扩展,允许你为不同平台定义不同的样式。例如,你可以定义 module.ios.css
和 module.android.css
文件来分别定义 Android 和 iOS 的样式。你需要在不带扩展名的情况下导入,例如:
¥CSS Modules support platform extensions to allow you to define different styles for different platforms. For example, you can define a module.ios.css
and module.android.css
file to define styles for Android and iOS respectively. You'll need to import without the extension, for example:
// Importing `./App.module.ios.css`:
- import styles from './App.module.css';
+ import styles from './App.module';
例如,翻转扩展名 App.ios.module.css
将不起作用,并会生成名为 App.ios.module
的通用模块。
¥Flipping the extension, for example, App.ios.module.css
will not work and result in a universal module named App.ios.module
.
你无法将样式传递给 React Native 或 React Native for Web 组件的
className
属性。相反,你必须使用style
属性。¥You cannot pass styles to the
className
prop of a React Native or React Native for web component. Instead, you must use thestyle
prop.
import styles, { unstable_styles } from './App.module.css';
export default function Page() {
return (
<>
<Text
style={{
// This is how react-native-web class names are applied
$$css: true,
_: styles.text,
}}>
Hello World
</Text>
<Text style={unstable_styles.text}>Hello World</Text>
{/* Web-only usage: */}
<p className={styles.text}>Hello World</p>
</>
);
}
.text {
color: red;
}
在网络上,所有 CSS 值都可用。CSS 不会像 React Native Web StyleSheet
API 那样进行处理或自动添加前缀。你可以使用 postcss.config.js
来自动添加 CSS 前缀。
¥On web, all CSS values are available. CSS is not processed or auto-prefixed like it is with the React Native Web StyleSheet
API. You can use postcss.config.js
to autoprefix your CSS.
CSS 模块在底层使用 lightningcss,检查 问题 是否有不支持的功能。
¥CSS Modules use lightningcss under the hood, check the issues for unsupported features.
更改 Post CSS 或
browserslist
配置将要求你清除 Metro 缓存:npx expo start --clear
|npx expo export --clear
.¥Changing the Post CSS or
browserslist
config will require you to clear the Metro cache:npx expo start --clear
|npx expo export --clear
.
可以通过将 postcss.config.json
文件添加到项目的根目录来自定义 PostCSS。该文件应该导出一个返回 PostCSS 配置对象的函数。例如:
¥PostCSS can be customized by adding a postcss.config.json
file to the root of your project. This file should export a function that returns a PostCSS configuration object. For example:
{
"plugins": {
"autoprefixer": {}
}
}
postcss.config.json
和 postcss.config.js
均受支持,但 postcss.config.json
可以实现更好的缓存。
¥Both postcss.config.json
and postcss.config.js
are supported, but postcss.config.json
enables better caching.
Expo Metro 部分支持 SCSS/SASS。
¥Expo Metro has partial support for SCSS/SASS.
要进行设置,请在项目中安装 sass
包:
¥To setup, install the sass
package in your project:
-
yarn add -D sass
然后,确保 Metro.config.js 文件中的 CSS 已设置。
¥Then, ensure CSS is setup in the metro.config.js file.
当安装 sass
时,没有扩展的模块将按以下顺序解析:scss
、sass
、css
。
¥When sass
is installed, then modules without extensions will be resolved in the following order: scss
, sass
, css
.
仅对 sass
文件使用预期语法。
¥Only use the intended syntax with sass
files.
目前不支持从 scss/sass 文件中导入其他文件。
¥Importing other files from inside a scss/sass file is not currently supported.
标准 Tailwind 不支持原生平台。这是仅限网络的。为了获得通用支持,请使用像 本土风 这样的兼容库。
Tailwind 可与 Metro 网络版一起使用。需要修改以下文件才能进行设置:
¥Tailwind can be used with Metro for web. The following files are required to be modified to set it up:
app.json
package.json
global.css
tailwind.config.js
index.js
首先,确保你在 app.json 中使用 Metro 网页版:
¥First, ensure you are using Metro for web in your app.json:
{
"expo": {
"web": {
"bundler": "metro"
}
}
}
然后,根据 Tailwind PostCSS 文档.1 配置 Tailwind。
¥Then, configure Tailwind according to the Tailwind PostCSS docs.
1
安装 tailwindcss
及其对等依赖,并创建 tailwind.config.js 文件:
¥Install tailwindcss
and its peer dependencies, and create a tailwind.config.js file:
-
yarn add --dev tailwindcss postcss autoprefixer
-
npx tailwindcss init
2
将 tailwindcss
和 autoprefixer
添加到 postcss.config.js 文件中:
¥Add tailwindcss
and autoprefixer
to your postcss.config.js file:
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
3
在 tailwind.config.js 文件中添加所有模板文件的路径:
¥Add the paths to all of your template files in your tailwind.config.js file:
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
// Ensure this points to your source code...
'./app/**/*.{js,tsx,ts,jsx}',
// If you use a `src` folder, add: './src/**/*.{js,tsx,ts,jsx}'
// Do the same with `components`, `hooks`, `styles`, or any other top-level folders...
],
theme: {
extend: {},
},
plugins: [],
};
考虑使用根
/src
目录来简化此步骤。在src
目录 中了解更多信息。¥Consider using a root
/src
directory to simplify this step. Learn more insrc
directory.
4
在项目中创建 global.css 并为 Tailwind 的每个层添加指令:
¥Create global.css in your project and add directives for each of Tailwind's layers:
/* This file adds the requisite utility classes for Tailwind to work. */
@tailwind base;
@tailwind components;
@tailwind utilities;
确保将此文件导入到最根 JavaScript 文件中:
¥Ensure you import this file in your root-most JavaScript file:
import './global.css';
使用 Expo Router 时,可以使用./index.js 或./app/_layout.js。它们的工作原理相同。
¥When using Expo Router, you can use ./index.js or ./app/_layout.js. They both work the same.
5
通过清除转换缓存来启动你的项目以达到良好的效果:
¥Start your project by clearing the transform cache for good measure:
-
npx expo --clear
¥Tailwind usage
你可以按原样将 Tailwind 与 React DOM 元素一起使用:
¥You can use Tailwind with React DOM elements as-is:
export default function Page() {
return (
<div className="bg-slate-100 rounded-xl">
<p className="text-lg font-medium">Welcome to Tailwind</p>
</div>
);
}
你可以使用 { $$css: true }
语法将 Tailwind 与 React Native Web 元素一起使用:
¥You can use the { $$css: true }
syntax to use Tailwind with React Native web elements:
import { View, Text } from 'react-native';
export default function Page() {
return (
<View style={{ $$css: true, _: 'bg-slate-100 rounded-xl' }}>
<Text style={{ $$css: true, _: 'text-lg font-medium' }}>Welcome to Tailwind</Text>
</View>
);
}
¥Tailwind troubleshooting
如果你的 Metro.config.js 中有自定义 config.cacheStores
,则需要扩展 FileStore
的 Expo 超类:
¥If you have a custom config.cacheStores
in your metro.config.js, you need to extend the Expo superclass of FileStore
:
// Import the Expo superclass which has support for PostCSS.
const { FileStore } = require('@expo/metro-config/file-store');
config.cacheStores = [
new FileStore({
root: '/path/to/custom/cache',
}),
];
module.exports = config;
确保你的 Metro.config.js 中没有禁用 CSS:
¥Ensure you don't have CSS disabled in your metro.config.js:
/** @type {import('expo/metro-config').MetroConfig} */
const config = getDefaultConfig(__dirname, {
// Do not disable CSS support when using Tailwind.
isCSSEnabled: true,
});
¥Extending the Babel transformer
Expo 的 Metro 配置使用自定义 transformer.babelTransformerPath
值来确保始终使用 expo-babel-preset
并支持 web/Node.js 环境。
¥Expo's Metro config uses a custom transformer.babelTransformerPath
value to ensure expo-babel-preset
is always used and web/Node.js environments are supported.
如果要扩展 Babel 转换器,请从 @expo/metro-config/babel-transformer
而不是 metro-react-native-babel-transformer
导入上游转换器。例如:
¥If you want to extend the Babel transformer, import the upstream transformer from @expo/metro-config/babel-transformer
instead of metro-react-native-babel-transformer
. For example:
const upstreamTransformer = require('@expo/metro-config/babel-transformer');
module.exports.transform = async ({ src, filename, options }) => {
// Do something custom for SVG files...
if (filename.endsWith('.svg')) {
src = '...';
}
// Pass the source through the upstream Expo transformer.
return upstreamTransformer.transform({ src, filename, options });
};
¥Custom resolving
Expo CLI 扩展了默认的 Metro 解析器,添加了 Web、服务器和 tsconfig 别名支持等功能。你可以类似地通过链接 config.resolver.resolveRequest
函数来自定义 Metro 的默认解析行为。
¥Expo CLI extends the default Metro resolver to add features like Web, Server, and tsconfig aliases support. You can similarly customize the default resolution behavior of Metro by chaining the config.resolver.resolveRequest
function.
const { getDefaultConfig } = require('expo/metro-config');
/** @type {import('expo/metro-config').MetroConfig} */
const config = getDefaultConfig(__dirname);
config.resolver.resolveRequest = (context, moduleName, platform) => {
if (moduleName.startsWith('my-custom-resolver:')) {
// Logic to resolve the module name to a file path...
// NOTE: Throw an error if there is no resolution.
return {
filePath: 'path/to/file',
type: 'sourceFile',
};
}
// Ensure you call the default resolver.
return context.resolveRequest(context, moduleName, platform);
};
module.exports = config;
与传统的打包器不同,Metro 在所有平台上共享相同的解析器功能。因此,你可以使用 context
对象在每个请求上动态改变解析设置。
¥Unlike traditional bundlers, Metro shared the same resolver function across all platforms. As a result, you can mutate the resolution settings dynamically on each request with the context
object.
¥Mocking modules
如果你希望给定平台的模块为空,则可以从解析器返回 type: 'empty'
对象。以下示例将导致 lodash
在 Web 上为空:
¥If you want a module to be empty for a given platform, you can return a type: 'empty'
object from the resolver. The following example will cause lodash
to be empty on web:
const { getDefaultConfig } = require('expo/metro-config');
/** @type {import('expo/metro-config').MetroConfig} */
const config = getDefaultConfig(__dirname);
config.resolver.resolveRequest = (context, moduleName, platform) => {
if (platform === 'web' && moduleName === 'lodash') {
return {
type: 'empty',
};
}
// Ensure you call the default resolver.
return context.resolveRequest(context, moduleName, platform);
};
module.exports = config;
这种技术相当于在 Webpack 或 Vite 中使用空外部,但具有能够针对特定平台的额外好处。
¥This technique is equivalent to using empty externals in Webpack or Vite, but with the added benefit of being able to target specific platforms.
¥Virtual modules
Metro 目前不支持虚拟模块。你可以用来获得类似行为的一种技术是在 node_modules/.cache/...
目录中创建一个模块并将解析重定向到该文件。
¥Metro doesn't support virtual modules at the moment. One technique you can use to obtain similar behavior is to create a module in the node_modules/.cache/...
directory and redirect the resolution to that file.
以下示例将在 node_modules/.cache/virtual/virtual-module.js
创建一个模块并将 virtual:my-module
的解析重定向到该文件:
¥The following example will create a module at node_modules/.cache/virtual/virtual-module.js
and redirect the resolution of virtual:my-module
to that file:
const path = require('path');
const fs = require('fs');
const { getDefaultConfig } = require('expo/metro-config');
/** @type {import('expo/metro-config').MetroConfig} */
const config = getDefaultConfig(__dirname);
const virtualPath = path.resolve(__dirname, 'node_modules/.cache/virtual/virtual-module.js');
// Create the virtual module in a generated directory...
fs.mkdirSync(path.dirname(virtualPath), { recursive: true });
fs.writeFileSync(virtualPath, 'export default "Hello World";');
config.resolver.resolveRequest = (context, moduleName, platform) => {
if (moduleName === 'virtual:my-module') {
return {
filePath: virtualPath,
type: 'sourceFile',
};
}
// Ensure you call the default resolver.
return context.resolveRequest(context, moduleName, platform);
};
module.exports = config;
这可用于通过自定义导入来模拟 externals
。例如,如果你想将 require('expo')
重定向到 SystemJS.require('expo')
这样的自定义文件,你可以创建一个导出 SystemJS.require('expo')
的虚拟模块,并将 expo
的解析重定向到该文件。
¥This can be used to emulate externals
with custom imports. For example, if you want to redirect require('expo')
to something custom like SystemJS.require('expo')
, you can create a virtual module that exports SystemJS.require('expo')
and redirect the resolution of expo
to that file.
¥Custom transforming
转换在 Metro 中被大量缓存。如果你更新了某些内容,请使用
--clear
标志来查看更新。例如,npx expo start --clear
。¥Transformations are heavily cached in Metro. If you update something, use the
--clear
flag to see updates. For example,npx expo start --clear
.
Metro 没有一个非常有表现力的插件系统来转换文件,而是选择使用 babel.config.js
和调用者对象来自定义转换。
¥Metro doesn't have a very expressive plugin system for transforming files, instead opt to use the babel.config.js
and caller object to customize the transformation.
module.exports = function (api) {
// Get the platform that Expo CLI is transforming for.
const platform = api.caller(caller => (caller ? caller.platform : 'ios'));
// Detect if the bundling operation is for Hermes engine or not, e.g. `'hermes'` | `undefined`.
const engine = api.caller(caller => (caller ? caller.engine : null));
// Is bundling for a server environment, e.g. API Routes.
const isServer = api.caller(caller => (caller ? caller.isServer : false));
// Is bundling for development or production.
const isDev = api.caller(caller =>
caller
? caller.isDev
: process.env.BABEL_ENV === 'development' || process.env.NODE_ENV === 'development'
);
// Ensure the config is not cached otherwise the platform will not be updated.
api.cache(false);
// You can alternatively provide a more robust CONFIG cache invalidation:
// api.cache.invalidate(() => platform);
return {
presets: ['babel-preset-expo'],
plugins: [
// Add a plugin based on the platform...
platform === 'web' && 'my-plugin',
// Ensure you filter out falsy values.
].filter(Boolean),
};
};
如果调用者没有 engine
、platform
、bundler
等,请确保你使用 @expo/metro-config/babel-transformer
作为转换器。如果你使用自定义转换器,那么可能需要扩展 Expo 转换器。
¥If the caller doesn't have engine
, platform
, bundler
, and so on, then ensure you are using @expo/metro-config/babel-transformer
for the transformer. If you're using a custom transformer then it may need to extend the Expo transformer.
如果可能的话,始终尝试在解析器中实现自定义逻辑,缓存更简单且更容易推断。例如,如果你需要重新映射导入,则使用解析器解析为静态文件比解析所有可能的导入方法并使用转换器重新映射它们更简单、更快。
¥Always try to implement custom logic in the resolver if possible, caching is much simpler and easier to reason about. For example, if you need to remap an import, it's simpler and faster to resolve to a static file with the resolver than to parse all possible import methods and remap them with the transformer.
始终使用 babel-preset-expo
作为默认 Babel 预设,这可确保转换始终与 Expo 运行时兼容。babel-preset-expo
在内部使用所有调用者输入来针对给定平台、引擎和环境进行优化。
¥Always use babel-preset-expo
as the default Babel preset, this ensures the transformation is always compatible with Expo runtimes. babel-preset-expo
uses all of the caller inputs internally to optimize for a given platform, engine, and environment.
¥Node.js built-ins
打包服务器环境时,Expo 的 Metro 配置会自动支持基于当前 Node.js 版本的外部化 Node.js 内置模块(fs
、path
、node:crypto
等)。如果 CLI 是针对浏览器环境进行打包的,则内置程序将首先检查该模块是否在本地安装,然后回退到空垫片上。比如你安装了 path
在浏览器中使用,就可以使用这个,否则会自动跳过该模块。
¥When bundling for a server environment, Expo's Metro config automatically supports externalizing Node.js built-in modules (fs
, path
, node:crypto
, and more) based on the current Node.js version. If the CLI is bundling for a browser environment, then built-ins will first check if the module is installed locally, then fallback on an empty shim. For example, if you install path
for use in the browser, this can be used, otherwise, the module will automatically be skipped.
¥Environment settings
Expo 的 Metro 配置注入了可通过环境变量在客户端包中使用的构建设置。所有变量都将被内联,并且不能动态使用。例如,process.env["EXPO_BASE_URL"]
将不起作用。
¥Expo's Metro config injects build settings that can be used in the client bundle via environment variables. All variables will be inlined and cannot be used dynamically. For example, process.env["EXPO_BASE_URL"]
won't work.
process.env.EXPO_BASE_URL
公开 experiments.baseUrl
中定义的基本 URL。这在 Expo Router 中用于尊重部署的生产基础 URL。
¥process.env.EXPO_BASE_URL
exposes the base URL defined in experiments.baseUrl
. This is used in Expo Router to respect the production base URL for deployment.
这些环境变量不会在测试环境中定义!
¥These environment variables will not be defined in test environments!
¥Bundle splitting
此功能在 SDK 50 中仅在 Web 上提供。
¥This feature is web-only in SDK 50.
在 SDK 50 中,Expo CLI 根据生产中的异步导入自动将 Web 包拆分为多个块。此功能需要在入门包中安装并导入 @expo/metro-runtime
(默认情况下在 Expo Router 中可用)。
¥In SDK 50, Expo CLI automatically splits web bundles into multiple chunks based on async imports in production. This feature requires @expo/metro-runtime
to be installed and imported somewhere in the entry bundle (available by default in Expo Router).
异步包的共享依赖被合并到单个块中,以减少请求数量。例如,如果你有两个导入 lodash
的异步包,则该库将合并到单个初始块中。
¥Shared dependencies of async bundles are merged into a single chunk to reduce the number of requests. For example, if you have two async bundles that import lodash
, then the library is merged into a single initial chunk.
从 SDK 50 开始,无法自定义块分割启发式。例如:
¥As of SDK 50, the chunk splitting heuristic cannot be customized. For example:
math.js
index.js
export function add(a, b) {
return a + b;
}
import '@expo/metro-runtime';
// This will be split into a separate chunk.
import('./math').then(math => {
console.log(math.add(1, 2));
});
当你运行 npx expo export -p web
时,打包包将被拆分为多个文件,并且条目打包包将添加到主 HTML 文件中。@expo/metro-runtime
添加了加载和评估异步包的运行时代码。
¥When you run npx expo export -p web
, the bundles are split into multiple files, and the entry bundle is added to the main HTML file. @expo/metro-runtime
adds the runtime code that loads and evaluates the async bundles.
¥Source map debug ID
从 SDK 50 起可在所有平台上使用。
¥Available from SDK 50 on all platforms.
如果使用外部源映射导出包,则 调试 ID 注释将添加到文件末尾,并在源映射中添加匹配的 debugId
以将文件对应在一起。如果没有导出源映射,或者使用内联源映射,则不会添加此注释。
¥If a bundle is exported with an external source map, a Debug ID annotation will be added to the end of the file, along with a matching debugId
in the source map for corresponding the files together. If no source maps are exported, or inline source maps are used then this annotation will not be added.
// <all source code>
//# debugId=<deterministic chunk hash>
关联的 *.js.map
或 *.hbc.map
源映射将是包含等效 debugId
属性的 JSON 文件。debugId
将在 Hermes 字节码生成之前注入,以确保在所有情况下都匹配。
¥The associated *.js.map
or *.hbc.map
source map will be a JSON file containing an equivalent debugId
property. The debugId
will be injected before hermes bytecode generation to ensure matching in all cases.
debugId
是打包包内容的确定性哈希,没有外部打包包拆分引用。这与用于创建块文件名的值相同,但格式为 UUID。例如,431b98e2-c997-4975-a3d9-2987710abd44
。
¥The debugId
is a deterministic hash of the bundle's contents without the external bundle splitting references. This is the same value used to create a chunks filename but formatted as a UUID. For example, 431b98e2-c997-4975-a3d9-2987710abd44
.
@expo/metro-config
在 npx expo export
和 npx expo export:embed
期间注入 debugId
。npx expo export:embed
中的任何其他优化步骤(例如 Hermes 字节码生成)都需要手动注入 debugId
。
¥@expo/metro-config
injects debugId
during npx expo export
and npx expo export:embed
. Any additional optimization steps in npx expo export:embed
like Hermes bytecode generation will need to have the debugId
injected manually.
¥Bare workflow setup
本指南已版本化,升级/降级 Expo 时需要重新访问。或者,使用 Expo 预建 进行全自动设置。
¥This guide is versioned and will need to be revisited when upgrading/downgrading Expo. Alternatively, use Expo Prebuild for fully automated setup.
不使用 Expo 预建 的项目必须配置原生文件,以确保始终使用 Expo Metro 配置来打包项目。
¥Projects that don't use Expo Prebuild must configure native files to ensure the Expo Metro config is always used to bundle the project.
这些修改旨在分别用 npx expo export:embed
和 npx expo start
替换 npx react-native bundle
和 npx react-native start
。
¥These modifications are meant to replace npx react-native bundle
and npx react-native start
with npx expo export:embed
and npx expo start
respectively.
确保 Metro.config.js 扩展 expo/metro-config
:
¥Ensure the metro.config.js extends expo/metro-config
:
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
module.exports = config;
android/app/build.gradle
Android app/build.gradle
必须配置为使用 Expo CLI 进行生产打包。修改 react
配置对象:
¥The Android app/build.gradle
must be configured to use Expo CLI for production bundling. Modify the react
config object:
react {
...
+ // Use Expo CLI to bundle the app, this ensures the Metro config
+ // works correctly with Expo projects.
+ cliFile = new File(["node", "--print", "require.resolve('@expo/cli')"].execute(null, rootDir).text.trim())
+ bundleCommand = "export:embed"
}
ios/<Project>.xcodeproj/project.pbxproj
在 ios/<Project>.xcodeproj/project.pbxproj
文件中,替换以下脚本:
¥In your ios/<Project>.xcodeproj/project.pbxproj
file, replace the following scripts:
"Start Packager"
删除 SDK 50 及更高版本中的 "启动打包器" 脚本。在运行应用之前/之后,必须使用 npx expo
启动开发服务器。
¥Remove the "Start Packager" script in SDK 50 and higher. The dev server must be started with npx expo
before/after running the app.
- FD10A7F022414F080027D42C /* Start Packager */ = {
- isa = PBXShellScriptBuildPhase;
- alwaysOutOfDate = 1;
- buildActionMask = 2147483647;
- files = (
- );
- inputFileListPaths = (
- );
- inputPaths = (
- );
- name = "Start Packager";
- outputFileListPaths = (
- );
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.updates\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.updates\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\nexport RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > `$NODE_BINARY --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/.packager.env'\"`\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open `$NODE_BINARY --print \"require('path').dirname(require.resolve('expo/package.json')) + '/scripts/launchPackager.command'\"` || echo \"Can't start packager automatically\"\n fi\nfi\n";
- showEnvVarsInLog = 0;
- };
¥"Bundle React Native code and images"
+ shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n # Set the entry JS file using the bundler's entry resolution.\n export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" \"$PROJECT_ROOT\" ios absolute | tail -n 1)\"\nfi\n\nif [[ -z \"$CLI_PATH\" ]]; then\n # Use Expo CLI\n export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli')\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n # Default Expo CLI command for bundling\n export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n\n";
或者,在 Xcode 项目中,选择 "打包 React Native 代码和图片" 构建阶段并添加以下修改:
¥Alternatively, in the Xcode project, select the "Bundle React Native code and images" build phase and add the following modifications:
if [[ -f "$PODS_ROOT/../.xcode.env" ]]; then
source "$PODS_ROOT/../.xcode.env"
fi
if [[ -f "$PODS_ROOT/../.xcode.env.local" ]]; then
source "$PODS_ROOT/../.xcode.env.local"
fi
# The project root by default is one level up from the ios directory
export PROJECT_ROOT="$PROJECT_DIR"/..
if [[ "$CONFIGURATION" = *Debug* ]]; then
export SKIP_BUNDLING=1
fi
+ if [[ -z "$ENTRY_FILE" ]]; then
+ # Set the entry JS file using the bundler's entry resolution.
+ export ENTRY_FILE="$("$NODE_BINARY" -e "require('expo/scripts/resolveAppEntry')" "$PROJECT_ROOT" ios absolute | tail -n 1)"
+ fi
+ if [[ -z "$CLI_PATH" ]]; then
+ # Use Expo CLI
+ export CLI_PATH="$("$NODE_BINARY" --print "require.resolve('@expo/cli')")"
+ fi
+ if [[ -z "$BUNDLE_COMMAND" ]]; then
+ # Default Expo CLI command for bundling
+ export BUNDLE_COMMAND="export:embed"
+ fi
`"$NODE_BINARY" --print "require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'"`
你可以设置
CLI_PATH
、BUNDLE_COMMAND
和ENTRY_FILE
环境变量来覆盖这些默认值。¥You can set
CLI_PATH
,BUNDLE_COMMAND
, andENTRY_FILE
environment variables to overwrite these defaults.
¥Custom entry file
默认情况下,React Native 仅支持使用根 index.js
文件作为入口文件(或平台特定的变体,如 index.ios.js
)。Expo 项目允许使用任何条目文件,但这需要额外的简单设置。
¥By default, React Native only supports using a root index.js
file as the entry file (or platform-specific variation like index.ios.js
). Expo projects allow using any entry file, but this requires addition bare setup.
¥Development
可以使用 expo-dev-client
包启用开发模式入口文件。或者你可以添加以下配置:
¥Development mode entry files can be enabled by using the expo-dev-client
package. Alternatively you can add the following configuration:
在 ios/[project]/AppDelegate.mm
文件中:
¥In the ios/[project]/AppDelegate.mm
file:
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
- return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
+ return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@".expo/.virtual-metro-entry"];
#else
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}
在 android/app/src/main/java/**/MainApplication.java
中:
¥In the android/app/src/main/java/**/MainApplication.java
:
@Override
protected String getJSMainModuleName() {
- return "index";
+ return ".expo/.virtual-metro-entry";
}
¥Production
在 ios/<Project>.xcodeproj/project.pbxproj
文件中,替换 "Bundle React Native code and images"
脚本以根据使用 Metro 设置 $ENTRY_FILE
:
¥In your ios/<Project>.xcodeproj/project.pbxproj
file, replace the "Bundle React Native code and images"
script to set $ENTRY_FILE
according using Metro:
+ shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n # Set the entry JS file using the bundler's entry resolution.\n export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" \"$PROJECT_ROOT\" ios absolute | tail -n 1)\"\nfi\n\nif [[ -z \"$CLI_PATH\" ]]; then\n # Use Expo CLI\n export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli')\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n # Default Expo CLI command for bundling\n export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n\n";
Android app/build.gradle
必须配置为使用 Metro 模块解析来查找根条目文件。修改 react
配置对象:
¥The Android app/build.gradle
must be configured to use Metro module resolution to find the root entry file. Modify the react
config object:
+ def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath()
react {
+ entryFile = file(["node", "-e", "require('expo/scripts/resolveAppEntry')", projectRoot, "android", "absolute"].execute(null, rootDir).text.trim())
}