首页指南参考教程

metro.config.js

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:

.gitignore
# local env files
.env*.local

禁用 dotenv 文件

¥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.

Terminal
# 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.

Terminal
# 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

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.

metro.config.js
/** @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.

metro.config.js
/** @type {import('expo/metro-config').MetroConfig} */
const config = getDefaultConfig(__dirname, {
  isCSSEnabled: true,
});

全局 CSS

¥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:

styles.css
.container {
  background-color: red;
}

然后,我们可以通过导入样式表并使用 .container 在组件中使用类名:

¥We can then use the class name in our component by importing the stylesheet and using .container:

App.js
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:

index.js
// 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 模块

¥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.cssmodule.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:

App.js
// 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 the style prop.

App.js
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>
    </>
  );
}
App.module.css
.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.

PostCSS

更改 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:

postcss.config.json
{
  "plugins": {
    "autoprefixer": {}
  }
}

postcss.config.jsonpostcss.config.js 均受支持,但 postcss.config.json 可以实现更好的缓存。

¥Both postcss.config.json and postcss.config.js are supported, but postcss.config.json enables better caching.

SASS

Expo Metro 部分支持 SCSS/SASS。

¥Expo Metro has partial support for SCSS/SASS.

要进行设置,请在项目中安装 sass 包:

¥To setup, install the sass package in your project:

Terminal
yarn add -D sass

然后,确保 Metro.config.js 文件中的 CSS 已设置

¥Then, ensure CSS is setup in the metro.config.js file.

  • 当安装 sass 时,没有扩展的模块将按以下顺序解析:scsssasscss

    ¥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 不支持原生平台。这是仅限网络的。为了获得通用支持,请使用像 本土风 这样的兼容库。

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:

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:

Terminal
yarn add --dev tailwindcss postcss autoprefixer
npx tailwindcss init

2

tailwindcssautoprefixer 添加到 postcss.config.js 文件中:

¥Add tailwindcss and autoprefixer to your postcss.config.js file:

postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};

3

在 tailwind.config.js 文件中添加所有模板文件的路径:

¥Add the paths to all of your template files in your tailwind.config.js file:

tailwind.config.js
/** @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 in src directory.

4

在项目中创建 global.css 并为 Tailwind 的每个层添加指令:

¥Create global.css in your project and add directives for each of Tailwind's layers:

global.css
/* 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:

index.js
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:

Terminal
npx expo --clear

Tailwind 用法

¥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 故障排除

¥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:

metro.config.js
// 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:

metro.config.js
/** @type {import('expo/metro-config').MetroConfig} */
const config = getDefaultConfig(__dirname, {
  // Do not disable CSS support when using Tailwind.
  isCSSEnabled: true,
});

扩展 Babel 转换器

¥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:

metro.transformer.js
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.

metro.config.js
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:

metro.config.js
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:

metro.config.js
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 caches 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.

babel.config.js
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),
  };
};

如果调用者没有 engineplatformbundler 等,请确保你使用 @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 内置组件

¥Node.js built-ins

打包服务器环境时,Expo 的 Metro 配置会自动支持基于当前 Node.js 版本的外部化 Node.js 内置模块(fspathnode: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
math.js
export function add(a, b) {
  return a + b;
}
index.js
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.

源映射调试 ID

¥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-confignpx expo exportnpx expo export:embed 期间注入 debugIdnpx 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:embednpx expo start 替换 npx react-native bundlenpx 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

确保 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;
-		};

"打包 React Native 代码和图片"

¥"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_PATHBUNDLE_COMMANDENTRY_FILE 环境变量来覆盖这些默认值。

¥You can set CLI_PATH, BUNDLE_COMMAND, and ENTRY_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())
}
Expo 中文网 - 粤ICP备13048890号