了解如何使用 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/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 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 • 网络) | |
全局 CSS | (SDK 50 • 网络) | |
CSS 模块 | (SDK 50 • 网络) | |
静态字体优化 | (SDK 50 • 网络) | |
API 路由 | (SDK 50) | |
多平台 | ||
快速刷新 | ||
错误叠加 | ||
懒惰打包 | ||
静态生成 | ||
环境变量 | ||
tsconfig.json 条路径 | ||
摇树 | (部分支持) |
¥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.ico
由 https://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 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:
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 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:
{
"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:
{
"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:
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:
-
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:
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:
-
npx expo export -p web
运行向导命令,选择 dist
作为应用的根目录,并选择其他所有内容的默认值...
¥Run the wizard command, selecting dist
as the root of the app, and the defaults for everything else...
-
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:
{
"scripts": {
"build:web": "expo export -p web && npx workbox-cli generateSW workbox-config.js"
}
}
¥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:
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.