首页指南参考教程

从 Expo Webpack 迁移

了解如何使用 Expo Webpack 将网站迁移到 Expo Router。


最初的 Web 版本 Expo 基于 Webpack 4,主要侧重于构建单页应用 (SPA)。该方法基于 创建反应应用,并支持使用 Expo SDK 和 React Native for web 构建简单的 Web 应用。

¥The original Expo for web version was based on Webpack 4 and focused primarily on building single-page applications (SPAs). This approach was based on Create React App and enabled building simple web apps with Expo SDK and React Native for web.

Expo Router 是构建在 Web 和原生上运行的强大通用应用的新方法。本指南将帮助你将现有网站迁移到 Expo Router。

¥Expo Router is the new approach to building powerful universal apps that run on web and native. This guide will help you migrate your existing website to Expo Router.

React Navigation 和 Expo Router 都是用于路由和导航的 Expo 框架。Expo Router 是 React Navigation 的封装器,并且有许多共享概念。

¥Both React Navigation and Expo Router are Expo frameworks for routing and navigation. Expo Router is a wrapper around React Navigation and has many shared concepts.

投入

¥Pitch

@expo/webpack-config 已弃用,并且未收到任何新功能更新。

与 Expo Webpack 不同,Expo Router 支持 Web 上的静态渲染,它支持搜索引擎优化 (SEO)、社交媒体预览和更快的加载时间。除了 React Navigation 的优点之外,它还支持自动深度链接、类型安全延迟打包模块化 HTML 模板Web 上的静态渲染 等。

¥Expo Router supports static rendering on web, which enables search engine optimization (SEO), social media previews, and faster loading times, unlike Expo Webpack. Along with the benefits of React Navigation, it enables automatic deep linking, type safety, deferred bundling, modular HTML templates, static rendering on web, and more.

Expo Router 还旨在通过在 Web 和原生之间共享导航而不影响功能或性能来解决 Expo Webpack 的主要跨平台问题。

¥Expo Router is also designed to fix the main cross-platform issue with Expo Webpack by sharing navigation between web and native without compromising functionality or performance.

反投入

¥Anti-pitch

Expo Router 使用基于 Metro 的自定义打包器堆栈。它与 React Native 使用的打包器相同。这对于确保最大程度的代码可重用性非常有用,并且解决了跨平台使用不同打包器导致的许多分叉行为问题。这也意味着某些打包功能可能在 Expo Router 中尚不可用。

¥Expo Router uses a custom bundler stack based on Metro. It is the same bundler used by React Native. This is great for ensuring maximum code reusability and solves many forked behavior issues from using different bundlers across platforms. This also means certain bundling features may not be available in Expo Router yet.

最终,作为一个完整的通用框架,Expo Router 是一个比 @expo/webpack-config(打包器集成)更强大的解决方案。它应该用于所有新的 Expo Web 项目。

¥Ultimately as a full universal framework, Expo Router is a substantially more robust solution than @expo/webpack-config, which is a bundler integration. It should be used for all new Expo web projects.

Expo CLI

@expo/webpack-config 不同,Expo Router 对 Web 和原生使用相同的 CLI 命令和功能。有关 Expo Router 和 @expo/webpack-config 之间差异的更多信息,请参阅下表。

¥Unlike @expo/webpack-config, Expo Router uses the same CLI commands and features for web and native. Refer to the table below for more information on the differences between Expo Router and @expo/webpack-config.

特性Expo 路由@expo/webpack-config
启动命令npx expo startnpx expo start
打包命令npx expo exportnpx expo export:web
导出目录distweb-build
静态文件夹publicweb
配置文件metro.config.jswebpack.config.js
默认配置@expo/metro-config@expo/webpack-config
打包分裂(SDK 50 • 网络)
全局 CSS(SDK 50 • 网络)
CSS 模块(SDK 50 • 网络)
静态字体优化(SDK 50 • 网络)
API 路由(SDK 50)
多平台
快速刷新
错误叠加
懒惰打包
静态生成
环境变量
tsconfig.json 条路径
摇树(部分支持)

HTML 模板

¥HTML template

@expo/webpack-config 中,所有路由共享一个 HTML 文件。该文件基于 web/index.html 中的模板,然后由 @expo/webpack-config 修改以包含必要的脚本和样式表。

¥In @expo/webpack-config all routes shared a single HTML file. This file was based on the template in web/index.html which was then modified by the @expo/webpack-config to include the necessary scripts and stylesheets.

在 Expo Router 中,有两种不同的渲染模式:

¥In Expo Router, there are two different rendering patterns:

  • 受到推崇的:web.output: "static" 为应用中的每个路由输出一个新的 HTML 文件。这种方法可以让你 使用 app/+html.js 文件动态生成整个 HTML 模板

    ¥Recommended: web.output: "static" which outputs a new HTML file for each route in the app. This approach lets you dynamically generate the entire HTML template using the app/+html.js file.

  • 不建议:web.output: "single" 输出单页应用。这种方法允许你使用 public/index.html 作为 HTML 模板文件。

    ¥Not recommended: web.output: "single" which outputs a single-page application. This approach lets you use public/index.html as the template HTML file.

静态资源

¥Static resources

@expo/webpack-config 中,你可以在 web 目录中托管静态文件,该目录将从网站的根目录提供服务。例如,web/favicon.icohttps://example.com/favicon.ico 提供。

¥In @expo/webpack-config, you could host static files in the web directory, which would be served from the website's root. For example, web/favicon.ico was served from https://example.com/favicon.ico.

在 Expo Router 中,你可以使用公共目录来托管静态文件。例如,public/favicon.ico 由 https://example.com/favicon.ico 提供。与 Webpack 不同,Expo Router 的托管也可以在原生上运行。确保在生产中使用文件之前从服务器托管这些文件。

¥In Expo Router, you can use the public directory to host static files. For example, public/favicon.ico is served from https://example.com/favicon.ico. Unlike Webpack, Expo Router's hosting works on native too. Make sure to host the files from a server before using them in production.

打包用于生产

¥Bundling for production

@expo/webpack-config 中,你可以使用 npx expo export:web 打包你的网站进行生产。这会将包输出到 web-build 目录。

¥In @expo/webpack-config, you could bundle your website for production using npx expo export:web. This would output a bundle to the web-build directory.

在 Expo Router 中,使用 npx expo export --platform web 命令导出到 dist 目录。你可以使用 --dump-sourcemap 标志生成源映射。在构建时,public 目录的内容将被复制到 dist 目录。

¥In Expo Router, use the npx expo export --platform web command to export to the dist directory. You can generate sourcemaps with the --dump-sourcemap flag. On build, the contents of the public directory will be copied to the dist directory.

Babel 配置

¥Babel configuration

和以前一样,根 babel.config.js 文件用于 Web 和原生。你可以使用 API 调用程序中的 platform 属性更改预设:

¥Like before, the root babel.config.js file is used for both web and native. You can change the preset by using the platform property in the API caller:

babel.config.js
module.exports = api => {
  // Get the platform from the API caller...
  const platform = api.caller(caller => caller && caller.platform);

  return {
    presets: ['babel-preset-expo'],
    plugins: [
      // Add a web-only plugin...
      platform === 'web' && 'custom-web-only-plugin',
    ].filter(Boolean),
  };
};

开发服务器

¥Dev server

在 Expo Router 中,所有平台都由同一端口上的同一开发服务器托管。这方便模拟应用的生产行为。所有日志和热模块重新加载也通过同一端口。

¥In Expo Router, all platforms are hosted from the same dev server on the same port. This is convenient for emulating the production behavior of the app. All logs and hot module reloading go through the same port as well.

由于原生的限制,目前不支持使用虚假 HTTPS 进行托管。此功能现在不如 2018 年那么重要,因为你可以使用 Chrome 等网络浏览器在本地主机上测试安全功能,例如摄像头和位置。

¥Due to limitations on native, hosting with fake HTTPS is not currently supported. This feature is less important now than in 2018, as you can test secure features such as camera and location on localhost using a web browser like Chrome.

Expo 常量

¥Expo constants

expo-constants 库可用于访问应用内的 app.json。在幕后,这是通过使用 app.json 文件的字符串化内容设置 process.env.APP_MANIFEST 来完成的。

¥The expo-constants library can be used to access the app.json in-app. Behind the scenes, this is accomplished by setting process.env.APP_MANIFEST with the stringified contents of the app.json file.

在 Expo Router 中,这是使用 Babel 以及 SDK 49 及更低版本中的 expo-router/babel 和 SDK 50 及更高版本中的 babel-preset-expo 来完成的。如果修改了 app.json,请使用 npx expo start --clear 重新启动 Babel 缓存以查看更新。

¥In Expo Router, this is done using Babel with the expo-router/babel in SDK 49 and lower and babel-preset-expo in SDK 50 and higher. If you modify the app.json, restart the Babel cache with npx expo start --clear to see the updates.

基本路径和子路径托管

¥Base path and subpath hosting

实验功能。它将从 SDK 50 开始提供。

¥Experimental functionality. It will be available from SDK 50.

@expo/webpack-config 中,你可以使用 PUBLIC_URL 环境变量或项目的 package.json 中的 homepage 字段将网站打包到子路径托管:

¥In @expo/webpack-config, you could bundle your website to be hosted from a subpath by using the PUBLIC_URL environment variable or the homepage field in the project's package.json:

package.json
{
  "homepage": "/evanbacon/my-website"
}

在 Expo Router 中,你可以使用项目 app.json 中的实验性 baseUrl 字段:

¥In Expo Router, you can use the experimental baseUrl field in the project's app.json:

app.json
{
  "expo": {
    "experiments": {
      "baseUrl": "/evanbacon/my-website"
    }
  }
}

与以前的系统不同,这还将更新路由以考虑基本路径。例如,如果你有一条路由 /profile,并将基本路径设置为 /evanbacon/my-website,则该路由将为 /evanbacon/my-website/profile

¥Unlike the previous system, this will also update the routing to account for the base path. For example, if you have a route /profile and you set the base path to /evanbacon/my-website, then the route will be /evanbacon/my-website/profile.

请参阅 带有子路径的托管 了解更多信息。

¥See hosting with sub-paths for more information.

快速刷新

¥Fast refresh

@expo/webpack-config 中,你可以安装 @pmmmwh/react-refresh-webpack-plugin 并将以下内容添加到 webpack.config.js 中:

¥In @expo/webpack-config you could install @pmmmwh/react-refresh-webpack-plugin and add the following to the webpack.config.js:

webpack.config.js
const createExpoWebpackConfigAsync = require('@expo/webpack-config');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');

module.exports = async function (env, argv) {
  const config = await createExpoWebpackConfigAsync(env, argv);

  // Use the React refresh plugin in development mode
  if (env.mode === 'development') {
    config.plugins.push(new ReactRefreshWebpackPlugin({ disableRefreshCheck: true }));
  }

  return config;
};

在 Expo Router 中,默认情况下使用 Meta 的官方快速刷新实现启用快速刷新。

¥In Expo Router, Fast Refresh is enabled by default using the official Fast Refresh implementation by Meta.

网站图标

¥Favicons

@expo/webpack-config 一样,Expo Router 支持根据 app.json 中的 web.favicon 字段生成 favicon.ico 文件。

¥Like @expo/webpack-config, Expo Router supports generating the favicon.ico file based on the web.favicon field in the app.json.

服务工作线程

¥Service workers

警告:添加 Service Worker 时要小心,因为众所周知,它们会在网络上导致意外行为。如果你不小心发布了积极缓存你网站的 Service Worker,用户将无法轻松请求更新。为了获得最佳的离线移动体验,请使用 Expo 创建原生应用。与具有 Service Worker 的网站不同,原生应用可以通过应用商店进行更新,以清除缓存的体验。这类似于重置用户的原生浏览器(如果服务工作线程足够积极,他们可能必须这样做)。请参阅 为什么服务工作进程的表现不佳 了解更多信息。

¥warning: Be careful adding service workers as they are known to cause unexpected behavior on web. If you accidentally ship a service worker that aggressively caches your website, users cannot request updates easily. For the best offline mobile experience, create a native app with Expo. Unlike websites with service workers, native apps can be updated through the app store to clear the cached experience. This would be similar to resetting the user's native browser (which they may have to do if the service worker is aggressive enough). See why service workers are suboptimal for more information.

Expo Webpack 没有内置的 Service Worker 支持。但是,你可以通过使用 workbox-webpack-plugin 自行添加它并将其添加到 webpack.config.js 中。

¥Expo Webpack didn't have built-in service worker support. However, you could add it yourself by using the workbox-webpack-plugin and adding it to the webpack.config.js.

Workbox 没有 Metro 集成,但由于 Workbox 不需要打包器的核心功能之一(转换、解析、序列化),因此可以轻松地将其用作构建后步骤。请遵循 使用 Workbox CLI 的指南,只要它引用 "构建脚本",请使用 npx expo export -p web

¥Workbox doesn't have a Metro integration, but because Workbox doesn't require one of the core features of a bundler (transformation, resolution, serialization), it can easily be used as a post-build step. Follow the guide for using Workbox CLI, and wherever it refers to a "build script" use npx expo export -p web instead.

例如,以下是设置 Workbox 的可能流程。使用以下命令创建一个新项目:

¥For example, here's a possible flow for setting up Workbox. Create a new project with the following command:

Terminal
npm create expo -t tabs my-app

cd my-app

接下来,为应用创建一个根 HTML 文件并添加 Service Worker 注册脚本:

¥Next, create a root HTML file for the app and add the service worker registration script:

app/+html.tsx
import { ScrollViewStyleReset } from 'expo-router/html';
import type { PropsWithChildren } from 'react';

// This file is web-only and used to configure the root HTML for every
// web page during static rendering.
// The contents of this function only run in Node.js environments and
// do not have access to the DOM or browser APIs.
export default function Root({ children }: PropsWithChildren) {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />

        

{/* Bootstrap the service worker. */}


        <script dangerouslySetInnerHTML={{ __html: sw }} />

        {/*
          Disable body scrolling on web. This makes ScrollView components work closer to how they do on native.
          However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line.
        */}
        <ScrollViewStyleReset />

        

{/* Add any additional <head> elements that you want globally available on web... */}


      </head>
      <body>{children}</body>
    </html>
  );
}

const sw = `
if ('serviceWorker' in navigator) {
    window.addEventListener('load', () => {
        navigator.serviceWorker.register('/sw.js').then(registration => {
            console.log('Service Worker registered with scope:', registration.scope);
        }).catch(error => {
            console.error('Service Worker registration failed:', error);
        });
    });
}
`;

现在,在运行向导之前构建应用:

¥Now build the app before running the wizard:

Terminal
npx expo export -p web

运行向导命令,选择 dist 作为应用的根目录,并选择其他所有内容的默认值...

¥Run the wizard command, selecting dist as the root of the app, and the defaults for everything else...

Terminal
npx workbox-cli wizard

? What is the root of your web app (that is which directory do you deploy)? dist/
? Which file types would you like to precache? js, html, ttf, ico, json
? Where would you like your service worker file to be saved? dist/sw.js
? Where would you like to save these configuration options? workbox-config.js
? Does your web app manifest include search parameter(s) in the 'start_url', other than 'utm_' or 'fbclid' (like '?source=pwa')? No

最后,运行 npx workbox-cli generateSW workbox-config.js 生成 Service Worker 配置。接下来,你可以在 package.json 中添加构建脚本,以按正确的顺序运行这两个脚本:

¥Finally, run npx workbox-cli generateSW workbox-config.js to generate the service worker config. Going forward, you can add a build script in package.json to run both scripts in the correct order:

package.json
{
  "scripts": {
    "build:web": "expo export -p web && npx workbox-cli generateSW workbox-config.js"
  }
}

PWA 清单

¥PWA manifests

@expo/webpack-config 不同,Expo Router 不会自动尝试生成 PWA 清单配置。你可以在 public/manifest.json 中创建一个:

¥Unlike @expo/webpack-config, Expo Router does not automatically attempt to generate the PWA manifest configuration. You can create one in public/manifest.json:

{
  "short_name": "Expo App",
  "name": "Expo Router Sample",
  "icons": [
    {
      "src": "favicon.ico",
      "sizes": "64x64 32x32 24x24 16x16",
      "type": "image/x-icon"
    },
    {
      "src": "logo192.png",
      "type": "image/png",
      "sizes": "192x192"
    },
    {
      "src": "logo512.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ],
  "start_url": ".",
  "display": "standalone",
  "theme_color": "#000000",
  "background_color": "#ffffff"
}

你可以使用 link 标签将其链接到 HTML 文件中:

¥You can link this in your HTML file using the link tag:

app/+html.tsx
import { ScrollViewStyleReset } from 'expo-router/html';
import type { PropsWithChildren } from 'react';

// This file is web-only and used to configure the root HTML for every
// web page during static rendering.
// The contents of this function only run in Node.js environments and
// do not have access to the DOM or browser APIs.
export default function Root({ children }: PropsWithChildren) {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />

        

{/* Link the PWA manifest file. */}


        <link rel="manifest" href="/manifest.json" />

        {/*
          Disable body scrolling on web. This makes ScrollView components work closer to how they do on native.
          However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line.
        */}
        <ScrollViewStyleReset />

        

{/* Add any additional <head> elements that you want globally available on web... */}


      </head>
      <body>{children}</body>
    </html>
  );
}

打包器插件

¥Bundler plugins

如果你使用自定义打包器插件,请参阅 Expo Metro 配置 以向你的打包器管道添加自定义功能。

¥If you were using custom bundler plugins, see Expo Metro config for adding custom functionality to your bundler pipeline.

导航

¥Navigation

如果你使用 React Navigation 在 @expo/webpack-config 中的屏幕之间导航,请参阅 React Navigation 迁移指南

¥If you used React Navigation for navigating between screens in @expo/webpack-config, see the migration guide for React Navigation.

部署

¥Deployment

请参阅 发布网站 并选择 "Expo 路由",了解如何将 Expo Router 网站部署到各种托管提供商。

¥See publishing websites and select "Expo Router" on how to deploy Expo Router websites to various hosting providers.

Expo 中文网 - 粤ICP备13048890号