从 Expo Webpack 迁移
编辑页面
了解如何将使用 Expo Webpack 的网站迁移到 Expo Router。
For the complete documentation index, see llms.txt. Use this Use this file to discover all available pages.
原始的 Expo for web 版本基于 Webpack 4,主要专注于构建单页应用(SPA)。这种方式基于 Create React App,并支持使用 Expo SDK 和 React Native for web 构建简单的 Web 应用。
Expo Router 是一种全新的方式,用于构建可在 web 和 native 上运行的强大通用应用。本指南将帮助你把现有网站迁移到 Expo Router。
React Navigation 和 Expo Router 都是 Expo 的路由和导航框架。Expo Router 是对 React Navigation 的一层封装,并共享许多概念。
介绍
@expo/webpack-config已弃用,并且不再接收任何新功能更新。
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 start | npx expo start |
| 打包命令 | npx expo export | npx expo export:web |
| 输出目录 | dist | web-build |
| 静态目录 | public | web |
| 配置文件 | metro.config.js | webpack.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:
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 字段,将网站打包后托管在子路径下:
{ "homepage": "/evanbacon/my-website" }
在 Expo Router 中,你可以在项目的 app.json 中使用实验性的 baseUrl 字段:
{ "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:
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
添加 service worker 时要小心,因为它们已知会在 web 上导致意外行为。如果你不小心发布了一个会积极缓存网站的 service worker,用户将无法轻松请求更新。若想获得最佳的离线移动体验,请使用 Expo 创建原生应用。与带有 service worker 的网站不同,原生应用可以通过应用商店更新来清除缓存体验。这类似于重置用户的原生浏览器(如果 service worker 过于激进,他们可能也需要这样做)。有关更多信息,请参阅为什么 service worker 并不是最佳方案。
Expo Webpack 没有内置的 service worker 支持。不过,你可以通过使用 workbox-webpack-plugin 并将其添加到 webpack.config.js 来自行添加。
Workbox 没有 Metro 集成,但由于 Workbox 不需要打包器的核心功能之一(转换、解析、序列化),所以它可以很容易地作为构建后的步骤来使用。请遵循使用 Workbox CLI 的指南,并且在它提到“构建脚本”时,改为使用 npx expo export -p web。
例如,下面是设置 Workbox 的一个可能流程。使用以下命令创建一个新项目:
- npm create expo -t tabs my-app- cd my-app接下来,为应用创建一个根 HTML 文件,并添加 service worker 注册脚本:
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); }); }); } `;
现在,在运行向导之前先构建应用:
- npx expo export -p web运行向导命令,选择 dist 作为应用根目录,并对其他所有选项使用默认值:
- 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 中添加一个构建脚本,以正确顺序运行这两个脚本:
{ "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 标签来链接它:
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 网站部署到各种托管服务提供商。