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

从 Expo Webpack 迁移

编辑页面

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


原始的 Expo for web 版本基于 Webpack 4,主要专注于构建单页应用(SPA)。这种方式基于 Create React App,并支持使用 Expo SDK 和 React Native for web 构建简单的 Web 应用。

Expo Router 是一种全新的方式,用于构建可在 web 和 native 上运行的强大通用应用。本指南将帮助你把现有网站迁移到 Expo Router。

介绍

Expo Router 支持 Web 静态渲染,这使得搜索引擎优化(SEO)、社交媒体预览以及更快的加载时间成为可能,这与 Expo Webpack 不同。结合 React Navigation 的优点,它支持自动深度链接、类型安全延迟打包模块化 HTML 模板Web 静态渲染 等更多功能。

Expo Router 还旨在通过在 web 和 native 之间共享导航,而不牺牲功能或性能,来解决 Expo Webpack 的主要跨平台问题。

反向介绍

Expo Router 使用基于 Metro 的自定义打包器栈。它与 React Native 使用的是同一个打包器。这对于确保最大的代码复用性非常有帮助,并解决了由于跨平台使用不同打包器而产生的许多分叉行为问题。这也意味着 Expo Router 目前可能还不支持某些打包功能。

归根结底,作为一个完整的通用框架,Expo Router 比 @expo/webpack-config 这种打包器集成方案要强大得多。所有新的 Expo web 项目都应该使用它。

Expo CLI

@expo/webpack-config 不同,Expo Router 在 web 和 native 上使用相同的 CLI 命令和功能。有关 Expo Router 与 @expo/webpack-config 差异的更多信息,请参阅下表。

功能Expo Router@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 • web)
全局 CSS (SDK 50 • web)
CSS 模块 (SDK 50 • web)
静态字体优化 (SDK 50 • web)
API 路由 (SDK 50)
多平台
快速刷新
错误覆盖层
延迟打包
静态生成
环境变量
tsconfig.json 路径
Tree Shaking (部分支持)

HTML 模板

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

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

  • 推荐web.output: "static",它会为应用中的每条路由输出一个新的 HTML 文件。这种方式允许你使用 src/app/+html.tsx 文件动态生成整个 HTML 模板
  • 不推荐web.output: "single",它会输出一个单页应用。这种方式允许你使用 public/index.html 作为模板 HTML 文件。

静态资源

@expo/webpack-config 中,你可以在 web 目录下托管静态文件,这些文件会从网站根路径提供。例如,web/favicon.ico 会以 https://example.com/favicon.ico 的形式提供。

在 Expo Router 中,你可以使用 public 目录来托管静态文件。例如,public/favicon.ico 会以 https://example.com/favicon.ico 的形式提供。与 Webpack 不同,Expo Router 的托管方式在 native 上也有效。请确保在生产环境中使用这些文件之前,先从服务器托管它们。

为生产环境打包

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

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

Babel 配置

和以前一样,根目录的 babel.config.js 文件同时用于 web 和 native。你可以通过在 API 调用者中使用 platform 属性来更改 preset:

babel.config.js
module.exports = api => { // 从 API 调用者中获取平台... const platform = api.caller(caller => caller && caller.platform); return { presets: ['babel-preset-expo'], plugins: [ // 添加一个仅限 web 的插件... platform === 'web' && 'custom-web-only-plugin', ].filter(Boolean), }; };

开发服务器

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

由于 native 上的限制,目前不支持使用伪造的 HTTPS 托管。与 2018 年相比,这项功能现在没那么重要了,因为你可以使用 Chrome 之类的 Web 浏览器在 localhost 上测试相机和定位等安全功能。

Expo 常量

expo-constants 库可用于在应用内访问 app.json。在底层,这是通过将 app.json 文件的字符串化内容设置到 process.env.APP_MANIFEST 来实现的。

在 Expo Router 中,这是使用 babel-preset-expo 通过 Babel 完成的。如果你修改了 app.json,请使用 npx expo start --clear 重新启动 Babel 缓存以查看更新。

基础路径和子路径托管

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

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

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

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

与之前的系统不同,这也会更新路由以考虑基础路径。例如,如果你有一个路由 /profile,并将基础路径设置为 /evanbacon/my-website,那么该路由将变为 /evanbacon/my-website/profile

有关更多信息,请参阅使用子路径托管

快速刷新

@expo/webpack-config 中,你可以安装 @pmmmwh/react-refresh-webpack-plugin,并将以下内容添加到 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); // 在开发模式下使用 React refresh 插件 if (env.mode === 'development') { config.plugins.push(new ReactRefreshWebpackPlugin({ disableRefreshCheck: true })); } return config; };

在 Expo Router 中,默认已启用快速刷新,使用的是 Meta 提供的官方 Fast Refresh 实现。

图标

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

Service Worker

Expo Webpack 没有内置的 service worker 支持。不过,你可以通过使用 workbox-webpack-plugin 并将其添加到 webpack.config.js 来自行添加。

Workbox 没有 Metro 集成,但由于 Workbox 不需要打包器的核心功能之一(转换、解析、序列化),所以它可以很容易地作为构建后的步骤来使用。请遵循使用 Workbox CLI 的指南,并且在它提到“构建脚本”时,改为使用 npx expo export -p web

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

Terminal
npm create expo -t tabs my-app

cd my-app

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

src/app/+html.tsx
import { ScrollViewStyleReset } from 'expo-router/html'; import type { PropsWithChildren } from 'react'; // 此文件仅用于 web,并用于在静态渲染期间为每个 // Web 页面配置根 HTML。 // 这个函数的内容只会在 Node.js 环境中运行, // 无法访问 DOM 或浏览器 API。 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" /> {/* 启动 service worker。 */} <script dangerouslySetInnerHTML={{ __html: sw }} /> {/* 在 web 上禁用 body 滚动。这会让 ScrollView 组件的行为更接近 native。 不过,body 滚动对于移动端 web 来说通常也很有用。如果你想启用它,请删除这一行。 */} <ScrollViewStyleReset /> {/* 添加任何你希望在 web 上全局可用的额外 <head> 元素... */} </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); }); }); } `;

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

Terminal
npx expo export -p web

运行向导命令,选择 dist 作为应用根目录,并对其他所有选项使用默认值:

Terminal
npx workbox-cli wizard

? 你的 Web 应用根目录是什么(也就是你要部署哪个目录)? dist/? 你希望预缓存哪些文件类型? js, html, ttf, ico, json? 你希望将 service worker 文件保存到哪里? dist/sw.js? 你希望将这些配置选项保存到哪里? workbox-config.js? 你的 Web 应用清单中的 'start_url' 是否包含除 'utm_' 或 'fbclid' 之外的查询参数(例如 '?source=pwa')? 否

最后,运行 npx workbox-cli generateSW workbox-config.js 来生成 service worker 配置。从现在开始,你可以在 package.json 中添加一个构建脚本,以正确顺序运行这两个脚本:

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

PWA 清单

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

{ "short_name": "Expo App", "name": "Expo Router 示例", "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" }

你可以在 HTML 文件中使用 link 标签来链接它:

src/app/+html.tsx
import { ScrollViewStyleReset } from 'expo-router/html'; import type { PropsWithChildren } from 'react'; // 此文件仅用于 Web,并用于为静态渲染期间的每个 Web 页面配置根 HTML。 // 此函数中的内容只会在 Node.js 环境中运行, // 且无法访问 DOM 或浏览器 API。 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" /> {/* 链接 PWA 清单文件。 */} <link rel="manifest" href="/manifest.json" /> {/* 禁用 Web 端的 body 滚动。这使得 ScrollView 组件的行为更接近原生端。 但是,body 滚动在移动 Web 上通常很有用。如果你想启用它,请删除这一行。 */} <ScrollViewStyleReset /> {/* 添加任何你希望在 Web 上全局可用的其他 <head> 元素... */} </head> <body>{children}</body> </html> ); }

打包器插件

如果你之前使用过自定义打包器插件,请参阅 Expo Metro 配置 以了解如何为你的打包管道添加自定义功能。

导航

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

部署

查看 发布网站,了解如何将 Expo Router 网站部署到各种托管服务提供商。