diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 712765f01f5c4a..f2a07a9fc8c3a9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -126,7 +126,7 @@ Note that we generally do not accept PRs that bump versions of native dependenci All modules should adhere to the style guides which can be found here: -- [Creating Unimodules](guides/Creating%20Unimodules.md) +- [Expo Module Infrastructure](guides/Expo%20Module%20Infrastructure.md) - [Expo JS Style Guide](guides/Expo%20JavaScript%20Style%20Guide.md) (also mostly applies to TypeScript) - [Updating Changelogs](guides/contributing/Updating%20Changelogs.md) @@ -198,7 +198,7 @@ To keep CI green, please make sure of the following: ### If you edited the docs directory: - - Any change to the current SDK version should also be in the unversioned copy as well. Example: + - Any changes to docs for the current SDK version should also be applied to the unversioned copy. The current docs SDK version is defined in [`docs/package.json`](./docs/package.json), and the versioning workflow is described in [docs/README.md](./docs/README.md#update-latest-version-of-api-reference-docs). Example: - You fixed a typo in `docs/pages/versions/vXX.0.0/sdk/app-auth.md` - Ensure you copy that change to: `docs/pages/versions/unversioned/sdk/app-auth.md` - You don't need to run the docs tests locally. Just ensure the links you include aren't broken, the format is correct, and the changes are following our [writing style guide](/guides/Expo%20Documentation%20Writing%20Style%20Guide.md). diff --git a/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/bottomsheet.mdx b/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/bottomsheet.mdx index d7c99f641a83c8..e252b6a5257827 100644 --- a/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/bottomsheet.mdx +++ b/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/bottomsheet.mdx @@ -10,6 +10,8 @@ import APISection from '~/components/plugins/APISection'; import { APIInstallSection } from '~/components/plugins/InstallSection'; import { ComponentDiagram } from '~/ui/components/Diagram'; +> **info** For cross-platform usage, see the universal [`BottomSheet`](../universal/bottomsheet) — it renders the appropriate native component per platform. + Expo UI ModalBottomSheet matches the official Jetpack Compose [Bottom Sheet API](https://developer.android.com/develop/ui/compose/components/bottom-sheets) and displays content in a modal sheet that slides up from the bottom. **info** For cross-platform usage, see the universal [`Button`](../universal/button) — it renders the appropriate native component per platform. + Expo UI provides five button components that match the official Jetpack Compose [Button API](https://developer.android.com/develop/ui/compose/components/button): `Button` (filled), `FilledTonalButton`, `OutlinedButton`, `ElevatedButton`, and `TextButton`. All variants share the same props and accept composable children for content. **info** For cross-platform usage, see the universal [`Checkbox`](../universal/checkbox) — it renders the appropriate native component per platform. + Expo UI Checkbox matches the official Jetpack Compose [Checkbox](https://developer.android.com/develop/ui/compose/components/checkbox) API. **info** For cross-platform usage, see the universal [`Column`](../universal/column) — it renders the appropriate native component per platform. + Expo UI Column matches the official Jetpack Compose [Column](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/package-summary#Column) API and places children vertically with configurable arrangement and alignment. **info** For a cross-platform picker, see [`Picker`](../universal/picker) — built on top of `ExposedDropdownMenuBox` on Android. + Expo UI `ExposedDropdownMenuBox` matches the official Jetpack Compose [`ExposedDropdownMenuBox`](https://kotlinlang.org/api/compose-multiplatform/material3/androidx.compose.material3/-exposed-dropdown-menu-box.html). Use the `menuAnchor()` modifier on the anchor content (typically a read-only `TextField`) and `ExposedDropdownMenu` to wrap `DropdownMenuItem` children. **info** For cross-platform usage, see the universal [`Host`](../universal/host) — it renders the appropriate native component per platform. + The `Host` component is the bridge between React Native and Jetpack Compose. Every Jetpack Compose component from `@expo/ui/jetpack-compose` must be wrapped in a `Host` to render correctly. ## Installation diff --git a/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/icon.mdx b/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/icon.mdx index 0d5c896d60cdcc..ee1f21376998d3 100644 --- a/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/icon.mdx +++ b/docs/pages/versions/unversioned/sdk/ui/jetpack-compose/icon.mdx @@ -10,6 +10,8 @@ import APISection from '~/components/plugins/APISection'; import { APIInstallSection } from '~/components/plugins/InstallSection'; import { ComponentDiagram } from '~/ui/components/Diagram'; +> **info** For cross-platform usage, see the universal [`Icon`](../universal/icon) — it renders the appropriate native component per platform. + An icon component for rendering icons in Jetpack Compose. We recommend downloading icons as XML vector drawables from [Material Symbols](https://fonts.google.com/icons), which is the standard approach for Android development. **info** For a cross-platform list with pull-to-refresh, see [`List`](../universal/list) — built on top of `PullToRefreshBox` on Android. + Expo UI PullToRefreshBox matches the official Jetpack Compose [PullToRefreshBox]() API. It wraps scrollable content and shows a refresh indicator when pulled. **info** For cross-platform usage, see the universal [`Row`](../universal/row) — it renders the appropriate native component per platform. + Expo UI Row matches the official Jetpack Compose [Row](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/package-summary#Row) API and places children horizontally with configurable arrangement and alignment. **info** For cross-platform usage, see the universal [`Slider`](../universal/slider) — it renders the appropriate native component per platform. + Expo UI Slider matches the official Jetpack Compose [Slider API](https://developer.android.com/develop/ui/compose/components/slider) and allows selecting values from a bounded range. **info** For cross-platform usage, see the universal [`Spacer`](../universal/spacer) — it renders the appropriate native component per platform. + Expo UI Spacer matches the official Jetpack Compose [Spacer](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/package-summary#Spacer) API and is used to add flexible or fixed-size space between elements in a layout. **info** For cross-platform usage, see the universal [`Switch`](../universal/switch) — it renders the appropriate native component per platform. + Expo UI Switch matches the official Jetpack Compose [Switch](https://developer.android.com/develop/ui/compose/components/switch) API. **info** For cross-platform usage, see the universal [`Text`](../universal/text) — it renders the appropriate native component per platform. + Expo UI Text matches the official Jetpack Compose [Text styling](https://developer.android.com/develop/ui/compose/text/style-text) API and displays text with Material 3 typography styles, custom fonts, and text formatting options. **info** For cross-platform usage, see the universal [`TextInput`](../universal/textinput) — it renders the appropriate native component per platform. + Expo UI provides two text field components that match the official Jetpack Compose [TextField API](https://developer.android.com/develop/ui/compose/text/user-input): `TextField` (filled) and `OutlinedTextField` (outlined border). Both variants share the same props and support composable slot children for label, placeholder, icons, prefix, suffix, and supporting text. | Type | Appearance | Purpose | diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/bottomsheet.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/bottomsheet.mdx index d2ef15ad5675d7..151bfa484338ae 100644 --- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/bottomsheet.mdx +++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/bottomsheet.mdx @@ -10,6 +10,8 @@ import APISection from '~/components/plugins/APISection'; import { APIInstallSection } from '~/components/plugins/InstallSection'; import { ComponentDiagram } from '~/ui/components/Diagram'; +> **info** For cross-platform usage, see the universal [`BottomSheet`](../universal/bottomsheet) — it renders the appropriate native component per platform. + Expo UI BottomSheet matches the official SwiftUI [sheet API]() and presents content from the bottom of the screen. **info** For cross-platform usage, see the universal [`Button`](../universal/button) — it renders the appropriate native component per platform. + Expo UI Button matches the official SwiftUI [Button API](https://developer.apple.com/documentation/swiftui/button) and supports styling via the [`buttonStyle`](modifiers/#buttonstylestyle), [`controlSize`](modifiers/#controlsizesize), and other modifiers. **info** For cross-platform usage, see the universal [`Host`](../universal/host) — it renders the appropriate native component per platform. + A component that allows you to put the other `@expo/ui/swift-ui` components in React Native. It acts like [``](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/svg) for DOM, [``](https://shopify.github.io/react-native-skia/docs/canvas/overview/) for [`react-native-skia`](https://shopify.github.io/react-native-skia/), which underlying uses [`UIHostingController`](https://developer.apple.com/documentation/swiftui/uihostingcontroller) to render the SwiftUI views in UIKit. Since the `Host` component is a React Native [`View`](https://reactnative.dev/docs/view), you can pass the [`style`](https://reactnative.dev/docs/style) prop to it or `matchContents` prop to make the `Host` component match the contents' size. diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/hstack.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/hstack.mdx index 0f16ad27747ce8..1a312370d079a6 100644 --- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/hstack.mdx +++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/hstack.mdx @@ -10,6 +10,8 @@ import APISection from '~/components/plugins/APISection'; import { APIInstallSection } from '~/components/plugins/InstallSection'; import { ComponentDiagram } from '~/ui/components/Diagram'; +> **info** For cross-platform usage, see the universal [`Row`](../universal/row) — it renders the appropriate native component per platform. + Expo UI HStack matches the official SwiftUI [HStack API](https://developer.apple.com/documentation/swiftui/hstack) and arranges its children horizontally. **info** For cross-platform usage, see the universal [`Icon`](../universal/icon) — it renders the appropriate native component per platform. + Expo UI Image displays SF Symbols using the SwiftUI [Image API](https://developer.apple.com/documentation/swiftui/image). SF Symbols are a library of configurable symbols provided by Apple. **info** For cross-platform usage, see the universal [`List`](../universal/list) — it renders the appropriate native component per platform. + Expo UI List matches the official SwiftUI [List API](https://developer.apple.com/documentation/swiftui/list) and supports styling via the [`listStyle`](modifiers/#liststylestyle) modifier, various row/section modifiers, as well as selection, reordering, and editing capabilities. **info** For cross-platform usage, see the universal [`Picker`](../universal/picker) — it renders the appropriate native component per platform. + Expo UI Picker matches the official SwiftUI [Picker API](https://developer.apple.com/documentation/swiftui/picker) and supports all picker styles via the [`pickerStyle`](modifiers/#pickerstylestyle) modifier. **info** For cross-platform usage, see the universal [`ScrollView`](../universal/scrollview) — it renders the appropriate native component per platform. + Expo UI ScrollView matches the official SwiftUI [ScrollView API](https://developer.apple.com/documentation/swiftui/scrollview) and provides a scrollable container for its children. ## Installation diff --git a/docs/pages/versions/unversioned/sdk/ui/swift-ui/slider.mdx b/docs/pages/versions/unversioned/sdk/ui/swift-ui/slider.mdx index f48508d368155f..ec79d427900642 100644 --- a/docs/pages/versions/unversioned/sdk/ui/swift-ui/slider.mdx +++ b/docs/pages/versions/unversioned/sdk/ui/swift-ui/slider.mdx @@ -10,6 +10,8 @@ import APISection from '~/components/plugins/APISection'; import { APIInstallSection } from '~/components/plugins/InstallSection'; import { ComponentDiagram } from '~/ui/components/Diagram'; +> **info** For cross-platform usage, see the universal [`Slider`](../universal/slider) — it renders the appropriate native component per platform. + Expo UI Slider matches the official SwiftUI [Slider API](https://developer.apple.com/documentation/swiftui/slider) and allows selecting values from a bounded range. **info** For cross-platform usage, see the universal [`Spacer`](../universal/spacer) — it renders the appropriate native component per platform. + Expo UI Spacer matches the official SwiftUI [Spacer API](https://developer.apple.com/documentation/swiftui/spacer) and expands to fill available space in a stack. **info** For cross-platform usage, see the universal [`Text`](../universal/text) — it renders the appropriate native component per platform. + Expo UI Text matches the official SwiftUI [Text API](https://developer.apple.com/documentation/swiftui/text). **info** For cross-platform usage, see the universal [`TextInput`](../universal/textinput) — it renders the appropriate native component per platform. + Expo UI TextField matches the official SwiftUI [TextField API](https://developer.apple.com/documentation/swiftui/textfield) and supports single-line and multiline input, keyboard configuration, submit handling, and an imperative `ref` for programmatic control. **info** For cross-platform usage, see the universal [`Switch`](../universal/switch) — it renders the appropriate native component per platform. + Expo UI Toggle matches the official SwiftUI [Toggle API](https://developer.apple.com/documentation/swiftui/toggle) and supports styling via the [`toggleStyle`](modifiers/#togglestylestyle) modifier. **info** For cross-platform usage, see the universal [`Column`](../universal/column) — it renders the appropriate native component per platform. + Expo UI VStack matches the official SwiftUI [VStack API](https://developer.apple.com/documentation/swiftui/vstack) and arranges its children vertically. **info** For cross-platform usage, see the universal [`BottomSheet`](../universal/bottomsheet) — it renders the appropriate native component per platform. + Expo UI ModalBottomSheet matches the official Jetpack Compose [Bottom Sheet API](https://developer.android.com/develop/ui/compose/components/bottom-sheets) and displays content in a modal sheet that slides up from the bottom. **info** For cross-platform usage, see the universal [`Button`](../universal/button) — it renders the appropriate native component per platform. + Expo UI provides five button components that match the official Jetpack Compose [Button API](https://developer.android.com/develop/ui/compose/components/button): `Button` (filled), `FilledTonalButton`, `OutlinedButton`, `ElevatedButton`, and `TextButton`. All variants share the same props and accept composable children for content. **info** For cross-platform usage, see the universal [`Checkbox`](../universal/checkbox) — it renders the appropriate native component per platform. + Expo UI Checkbox matches the official Jetpack Compose [Checkbox](https://developer.android.com/develop/ui/compose/components/checkbox) API. **info** For cross-platform usage, see the universal [`Column`](../universal/column) — it renders the appropriate native component per platform. + Expo UI Column matches the official Jetpack Compose [Column](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/package-summary#Column) API and places children vertically with configurable arrangement and alignment. **info** For a cross-platform picker, see [`Picker`](../universal/picker) — built on top of `ExposedDropdownMenuBox` on Android. + Expo UI `ExposedDropdownMenuBox` matches the official Jetpack Compose [`ExposedDropdownMenuBox`](https://kotlinlang.org/api/compose-multiplatform/material3/androidx.compose.material3/-exposed-dropdown-menu-box.html). Use the `menuAnchor()` modifier on the anchor content (typically a read-only `TextField`) and `ExposedDropdownMenu` to wrap `DropdownMenuItem` children. **info** For cross-platform usage, see the universal [`Host`](../universal/host) — it renders the appropriate native component per platform. + The `Host` component is the bridge between React Native and Jetpack Compose. Every Jetpack Compose component from `@expo/ui/jetpack-compose` must be wrapped in a `Host` to render correctly. ## Installation diff --git a/docs/pages/versions/v56.0.0/sdk/ui/jetpack-compose/icon.mdx b/docs/pages/versions/v56.0.0/sdk/ui/jetpack-compose/icon.mdx index 60363e6d6139b9..8fdc2ac92c0818 100644 --- a/docs/pages/versions/v56.0.0/sdk/ui/jetpack-compose/icon.mdx +++ b/docs/pages/versions/v56.0.0/sdk/ui/jetpack-compose/icon.mdx @@ -10,6 +10,8 @@ import APISection from '~/components/plugins/APISection'; import { APIInstallSection } from '~/components/plugins/InstallSection'; import { ComponentDiagram } from '~/ui/components/Diagram'; +> **info** For cross-platform usage, see the universal [`Icon`](../universal/icon) — it renders the appropriate native component per platform. + An icon component for rendering icons in Jetpack Compose. We recommend downloading icons as XML vector drawables from [Material Symbols](https://fonts.google.com/icons), which is the standard approach for Android development. **info** For a cross-platform list with pull-to-refresh, see [`List`](../universal/list) — built on top of `PullToRefreshBox` on Android. + Expo UI PullToRefreshBox matches the official Jetpack Compose [PullToRefreshBox]() API. It wraps scrollable content and shows a refresh indicator when pulled. **info** For cross-platform usage, see the universal [`Row`](../universal/row) — it renders the appropriate native component per platform. + Expo UI Row matches the official Jetpack Compose [Row](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/package-summary#Row) API and places children horizontally with configurable arrangement and alignment. **info** For cross-platform usage, see the universal [`Slider`](../universal/slider) — it renders the appropriate native component per platform. + Expo UI Slider matches the official Jetpack Compose [Slider API](https://developer.android.com/develop/ui/compose/components/slider) and allows selecting values from a bounded range. **info** For cross-platform usage, see the universal [`Spacer`](../universal/spacer) — it renders the appropriate native component per platform. + Expo UI Spacer matches the official Jetpack Compose [Spacer](https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/package-summary#Spacer) API and is used to add flexible or fixed-size space between elements in a layout. **info** For cross-platform usage, see the universal [`Switch`](../universal/switch) — it renders the appropriate native component per platform. + Expo UI Switch matches the official Jetpack Compose [Switch](https://developer.android.com/develop/ui/compose/components/switch) API. **info** For cross-platform usage, see the universal [`Text`](../universal/text) — it renders the appropriate native component per platform. + Expo UI Text matches the official Jetpack Compose [Text styling](https://developer.android.com/develop/ui/compose/text/style-text) API and displays text with Material 3 typography styles, custom fonts, and text formatting options. **info** For cross-platform usage, see the universal [`TextInput`](../universal/textinput) — it renders the appropriate native component per platform. + Expo UI provides two text field components that match the official Jetpack Compose [TextField API](https://developer.android.com/develop/ui/compose/text/user-input): `TextField` (filled) and `OutlinedTextField` (outlined border). Both variants share the same props and support composable slot children for label, placeholder, icons, prefix, suffix, and supporting text. | Type | Appearance | Purpose | diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/bottomsheet.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/bottomsheet.mdx index 224f4b1d94f66d..4f68e6a179bddf 100644 --- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/bottomsheet.mdx +++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/bottomsheet.mdx @@ -10,6 +10,8 @@ import APISection from '~/components/plugins/APISection'; import { APIInstallSection } from '~/components/plugins/InstallSection'; import { ComponentDiagram } from '~/ui/components/Diagram'; +> **info** For cross-platform usage, see the universal [`BottomSheet`](../universal/bottomsheet) — it renders the appropriate native component per platform. + Expo UI BottomSheet matches the official SwiftUI [sheet API]() and presents content from the bottom of the screen. **info** For cross-platform usage, see the universal [`Button`](../universal/button) — it renders the appropriate native component per platform. + Expo UI Button matches the official SwiftUI [Button API](https://developer.apple.com/documentation/swiftui/button) and supports styling via the [`buttonStyle`](modifiers/#buttonstylestyle), [`controlSize`](modifiers/#controlsizesize), and other modifiers. **info** For cross-platform usage, see the universal [`Host`](../universal/host) — it renders the appropriate native component per platform. + A component that allows you to put the other `@expo/ui/swift-ui` components in React Native. It acts like [``](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/svg) for DOM, [``](https://shopify.github.io/react-native-skia/docs/canvas/overview/) for [`react-native-skia`](https://shopify.github.io/react-native-skia/), which underlying uses [`UIHostingController`](https://developer.apple.com/documentation/swiftui/uihostingcontroller) to render the SwiftUI views in UIKit. Since the `Host` component is a React Native [`View`](https://reactnative.dev/docs/view), you can pass the [`style`](https://reactnative.dev/docs/style) prop to it or `matchContents` prop to make the `Host` component match the contents' size. diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/hstack.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/hstack.mdx index 7dac89e22406aa..b3aa887c30d061 100644 --- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/hstack.mdx +++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/hstack.mdx @@ -10,6 +10,8 @@ import APISection from '~/components/plugins/APISection'; import { APIInstallSection } from '~/components/plugins/InstallSection'; import { ComponentDiagram } from '~/ui/components/Diagram'; +> **info** For cross-platform usage, see the universal [`Row`](../universal/row) — it renders the appropriate native component per platform. + Expo UI HStack matches the official SwiftUI [HStack API](https://developer.apple.com/documentation/swiftui/hstack) and arranges its children horizontally. **info** For cross-platform usage, see the universal [`Icon`](../universal/icon) — it renders the appropriate native component per platform. + Expo UI Image displays SF Symbols using the SwiftUI [Image API](https://developer.apple.com/documentation/swiftui/image). SF Symbols are a library of configurable symbols provided by Apple. **info** For cross-platform usage, see the universal [`List`](../universal/list) — it renders the appropriate native component per platform. + Expo UI List matches the official SwiftUI [List API](https://developer.apple.com/documentation/swiftui/list) and supports styling via the [`listStyle`](modifiers/#liststylestyle) modifier, various row/section modifiers, as well as selection, reordering, and editing capabilities. **info** For cross-platform usage, see the universal [`Picker`](../universal/picker) — it renders the appropriate native component per platform. + Expo UI Picker matches the official SwiftUI [Picker API](https://developer.apple.com/documentation/swiftui/picker) and supports all picker styles via the [`pickerStyle`](modifiers/#pickerstylestyle) modifier. **info** For cross-platform usage, see the universal [`ScrollView`](../universal/scrollview) — it renders the appropriate native component per platform. + Expo UI ScrollView matches the official SwiftUI [ScrollView API](https://developer.apple.com/documentation/swiftui/scrollview) and provides a scrollable container for its children. ## Installation diff --git a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/slider.mdx b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/slider.mdx index 2864d491ce71ab..16fe0175c4acb8 100644 --- a/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/slider.mdx +++ b/docs/pages/versions/v56.0.0/sdk/ui/swift-ui/slider.mdx @@ -10,6 +10,8 @@ import APISection from '~/components/plugins/APISection'; import { APIInstallSection } from '~/components/plugins/InstallSection'; import { ComponentDiagram } from '~/ui/components/Diagram'; +> **info** For cross-platform usage, see the universal [`Slider`](../universal/slider) — it renders the appropriate native component per platform. + Expo UI Slider matches the official SwiftUI [Slider API](https://developer.apple.com/documentation/swiftui/slider) and allows selecting values from a bounded range. **info** For cross-platform usage, see the universal [`Spacer`](../universal/spacer) — it renders the appropriate native component per platform. + Expo UI Spacer matches the official SwiftUI [Spacer API](https://developer.apple.com/documentation/swiftui/spacer) and expands to fill available space in a stack. **info** For cross-platform usage, see the universal [`Text`](../universal/text) — it renders the appropriate native component per platform. + Expo UI Text matches the official SwiftUI [Text API](https://developer.apple.com/documentation/swiftui/text). **info** For cross-platform usage, see the universal [`TextInput`](../universal/textinput) — it renders the appropriate native component per platform. + Expo UI TextField matches the official SwiftUI [TextField API](https://developer.apple.com/documentation/swiftui/textfield) and supports single-line and multiline input, keyboard configuration, submit handling, and an imperative `ref` for programmatic control. **info** For cross-platform usage, see the universal [`Switch`](../universal/switch) — it renders the appropriate native component per platform. + Expo UI Toggle matches the official SwiftUI [Toggle API](https://developer.apple.com/documentation/swiftui/toggle) and supports styling via the [`toggleStyle`](modifiers/#togglestylestyle) modifier. **info** For cross-platform usage, see the universal [`Column`](../universal/column) — it renders the appropriate native component per platform. + Expo UI VStack matches the official SwiftUI [VStack API](https://developer.apple.com/documentation/swiftui/vstack) and arranges its children vertically. Chat with the community

- Join over 60,000 other developers + Join over 70,000 other developers
on the Expo Community Discord.

diff --git a/packages/@expo/cli/CHANGELOG.md b/packages/@expo/cli/CHANGELOG.md index ebce2c0cb5a5a1..db6c84cf219766 100644 --- a/packages/@expo/cli/CHANGELOG.md +++ b/packages/@expo/cli/CHANGELOG.md @@ -7,14 +7,19 @@ ### 🎉 New features - Add `/_expo/open` middleware for programmatically resolving deep links and disambiguation pages for the running dev server. ([#45804](https://github.com/expo/expo/pull/45804) by [@EvanBacon](https://github.com/EvanBacon)) +- Upgrade react-native-tvos to the correct version on install/fix. ([#45816](https://github.com/expo/expo/pull/45816) by [@douglowder](https://github.com/douglowder)) +- Accept `expo login -p -` argument to read password from stdin ([#45877](https://github.com/expo/expo/pull/45877) by [@kitten](https://github.com/kitten)) ### 🐛 Bug fixes - Restrict `/message` client commands ([#45858](https://github.com/expo/expo/pull/45858) by [@kitten](https://github.com/kitten) - Enforce `routerRoot` to be within `projectRoot` to prevent unexpected errors ([#45892](https://github.com/expo/expo/pull/45892) by [@kitten](https://github.com/kitten)) +- Fix containment check in tar extraction to cover parallel folders with same prefix ([#45882](https://github.com/expo/expo/pull/45882) by [@kitten](https://github.com/kitten)) ### 💡 Others +- [Internal] Use `resolveRouterModule` helper for RSC router module resolution ([#45900](https://github.com/expo/expo/pull/45900) by [@kitten](https://github.com/kitten)) + ## 56.1.5 — 2026-05-15 ### 🐛 Bug fixes @@ -23,6 +28,7 @@ - Fix long project paths overflowing the dev server interstitial page by making the path scroll horizontally. ([#45808](https://github.com/expo/expo/pull/45808) by [@EvanBacon](https://github.com/EvanBacon)) - Serve an unsigned Expo Go manifest instead of failing with HTTP 500 when `expo start` is unauthenticated in a non-interactive shell. ([#45809](https://github.com/expo/expo/pull/45809) by [@EvanBacon](https://github.com/EvanBacon)) - Add troubleshooting guide link to simulator boot failure errors. ([#43786](https://github.com/expo/expo/pull/43786) by [@kadikraman](https://github.com/kadikraman)) +- Disallow devtools plugins to point to `webpageRoot` outside of their own bounds ([#45841](https://github.com/expo/expo/pull/45841) by [@kitten](https://github.com/kitten)) ### 💡 Others diff --git a/packages/@expo/cli/e2e/__tests__/login-test.ts b/packages/@expo/cli/e2e/__tests__/login-test.ts index 4f98dce2fcba84..fadaa4bff9dc6b 100644 --- a/packages/@expo/cli/e2e/__tests__/login-test.ts +++ b/packages/@expo/cli/e2e/__tests__/login-test.ts @@ -38,7 +38,7 @@ it('runs `npx expo login --help`', async () => { Options -u, --username Username - -p, --password Password + -p, --password Password ("-" for stdin) --otp One-time password from your 2FA device -s, --sso Log in with SSO -b, --browser Log in with a browser diff --git a/packages/@expo/cli/e2e/playwright/prod/01-rsc.test.ts b/packages/@expo/cli/e2e/playwright/prod/01-rsc.test.ts index f230e1f6fbf68a..7549ebb964a540 100644 --- a/packages/@expo/cli/e2e/playwright/prod/01-rsc.test.ts +++ b/packages/@expo/cli/e2e/playwright/prod/01-rsc.test.ts @@ -163,7 +163,7 @@ for (const outputMode of outputModes) { 3:I["node_modules/react-native-web/dist/exports/View/index.js",[],"",1] 4:I["packages/expo-router/build/rsc/router/client.js",[],"Link",1] 5:I["node_modules/react-native-web/dist/exports/Text/index.js",[],"",1] -0:{"layout":["$","$L1",null,{"style":{"flex":1},"testID":"layout-child-wrapper","children":[["$","$L2",null,{}],["$","$L3",null,{"testID":"layout-global-style","style":[{"width":100,"height":100},{"$$css":true,"_":"custom-global-style"}]}],["$","$L3",null,{"testID":"layout-module-style","style":[{"width":100,"height":100},{"$$css":true,"_":"zvzhJW_container"}]}],["$","$L3",null,{"style":{"flexDirection":"row","padding":12,"justifyContent":"space-around"},"children":[["$","$L4",null,{"href":"/","style":{},"children":"One"}],["$","$L4",null,{"href":"/second","style":{},"children":"Two"}]]}]]}],"colors/blue/page":["$","$L5",null,{"testID":"color","children":["blue","-","static"]}],"/SHOULD_SKIP":[["layout",[]],["colors/layout",[]],["colors/blue/layout",[]],["colors/blue/page",[]]],"/LOCATION":["/colors/blue",""]}\n`); +0:{"layout":["$","$L1",null,{"style":{"flex":1},"testID":"layout-child-wrapper","children":[["$","$L2",null,{}],["$","$L3",null,{"testID":"layout-global-style","style":[{"width":100,"height":100},{"$$css":true,"_":"custom-global-style"}]}],["$","$L3",null,{"testID":"layout-module-style","style":[{"width":100,"height":100},{"$$css":true,"_":"zvzhJW_container"}]}],["$","$L3",null,{"style":{"flexDirection":"row","padding":12,"justifyContent":"space-around"},"children":[["$","$L4",null,{"href":"/","style":{},"children":"One"}],["$","$L4",null,{"href":"/second","style":{},"children":"Two"}]]}]]}],"colors/blue/page":["$","$L5",null,{"testID":"color","children":["blue","-","static"]}],"/SHOULD_SKIP":[["colors/layout",[]],["colors/blue/layout",[]],["colors/blue/page",[]]],"/LOCATION":["/colors/blue",""]}\n`); await expect(page.locator('[data-testid="color"]')).toHaveText('blue-static'); }); diff --git a/packages/@expo/cli/src/install/__tests__/fixPackages-test.ts b/packages/@expo/cli/src/install/__tests__/fixPackages-test.ts new file mode 100644 index 00000000000000..25027731c36d8f --- /dev/null +++ b/packages/@expo/cli/src/install/__tests__/fixPackages-test.ts @@ -0,0 +1,106 @@ +import * as PackageManager from '@expo/package-manager'; + +import { applyPluginsAsync } from '../applyPlugins'; +import { fixPackagesAsync } from '../fixPackages'; +import { installExpoPackageAsync } from '../installExpoPackage'; + +jest.mock('../../log'); +jest.mock('../applyPlugins', () => ({ + applyPluginsAsync: jest.fn(), +})); +jest.mock('../installExpoPackage', () => ({ + installExpoPackageAsync: jest.fn(), +})); +jest.mock('../../start/doctor/dependencies/getVersionedPackages', () => ({ + getOperationLog: jest.fn(() => []), +})); + +describe(fixPackagesAsync, () => { + beforeEach(() => { + jest.mocked(applyPluginsAsync).mockClear(); + jest.mocked(installExpoPackageAsync).mockClear(); + }); + + it('builds an npm-alias install spec for a TV-corrected react-native dep', async () => { + const packageManager = PackageManager.createForProject('/path/to/project'); + + await fixPackagesAsync('/path/to/project', { + packageManager, + packages: [ + { + packageName: 'react-native', + packageType: 'dependencies', + // Produced by `findIncorrectDependencies` for a TV project. + expectedVersionOrRange: 'npm:react-native-tvos@0.85-stable', + actualVersion: '0.83.0-0', + }, + ], + packageManagerArguments: [], + sdkVersion: '55.0.0', + }); + + expect(packageManager.addAsync).toHaveBeenCalledWith([ + 'react-native@npm:react-native-tvos@0.85-stable', + ]); + // The plugin pass should still operate on the package name, not the install spec. + expect(applyPluginsAsync).toHaveBeenCalledWith('/path/to/project', ['react-native']); + // No expo upgrade required, so installExpoPackageAsync should not have been called. + expect(installExpoPackageAsync).not.toHaveBeenCalled(); + }); + + it('passes packageManagerArguments through to addAsync', async () => { + const packageManager = PackageManager.createForProject('/path/to/project'); + + await fixPackagesAsync('/path/to/project', { + packageManager, + packages: [ + { + packageName: 'react-native', + packageType: 'dependencies', + expectedVersionOrRange: 'npm:react-native-tvos@0.85-stable', + actualVersion: '0.83.0-0', + }, + ], + packageManagerArguments: ['--no-save'], + sdkVersion: '55.0.0', + }); + + expect(packageManager.addAsync).toHaveBeenCalledWith([ + '--no-save', + 'react-native@npm:react-native-tvos@0.85-stable', + ]); + }); + + it('routes through installExpoPackageAsync when expo itself is outdated', async () => { + const packageManager = PackageManager.createForProject('/path/to/project'); + + await fixPackagesAsync('/path/to/project', { + packageManager, + packages: [ + { + packageName: 'expo', + packageType: 'dependencies', + expectedVersionOrRange: '^55.0.0', + actualVersion: '54.0.0', + }, + { + packageName: 'react-native', + packageType: 'dependencies', + expectedVersionOrRange: 'npm:react-native-tvos@0.85-stable', + actualVersion: '0.83.0-0', + }, + ], + packageManagerArguments: [], + sdkVersion: '55.0.0', + }); + + expect(installExpoPackageAsync).toHaveBeenCalledWith('/path/to/project', { + packageManager, + packageManagerArguments: [], + expoPackageToInstall: 'expo@^55.0.0', + followUpCommandArgs: ['--fix'], + }); + // When expo is being upgraded, we bail early and don't run addAsync directly. + expect(packageManager.addAsync).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/@expo/cli/src/login/__tests__/index-test.ts b/packages/@expo/cli/src/login/__tests__/index-test.ts new file mode 100644 index 00000000000000..78483912e653eb --- /dev/null +++ b/packages/@expo/cli/src/login/__tests__/index-test.ts @@ -0,0 +1,46 @@ +import { Readable } from 'stream'; + +import { readWordFromStdin } from '../index'; + +const originalStdin = process.stdin; + +function withFakeStdin(chunks: string[]) { + Object.defineProperty(process, 'stdin', { + value: Readable.from(chunks.map((chunk) => Buffer.from(chunk))), + configurable: true, + }); +} + +afterEach(() => { + Object.defineProperty(process, 'stdin', { value: originalStdin, configurable: true }); +}); + +it('returns the first line of stdin', async () => { + withFakeStdin(['supersecret\nignored\n']); + + expect(await readWordFromStdin()).toBe('supersecret'); +}); + +it('reassembles a line split across chunks', async () => { + withFakeStdin(['super', 'sec', 'ret\n']); + + expect(await readWordFromStdin()).toBe('supersecret'); +}); + +it('strips a trailing CR for CRLF input', async () => { + withFakeStdin(['supersecret\r\n']); + + expect(await readWordFromStdin()).toBe('supersecret'); +}); + +it('returns the buffered content when EOF arrives without a newline', async () => { + withFakeStdin(['supersecret']); + + expect(await readWordFromStdin()).toBe('supersecret'); +}); + +it('returns an empty string when stdin is empty', async () => { + withFakeStdin([]); + + expect(await readWordFromStdin()).toBe(''); +}); diff --git a/packages/@expo/cli/src/login/index.ts b/packages/@expo/cli/src/login/index.ts index 8ae05adba3c7c4..348252282bb768 100644 --- a/packages/@expo/cli/src/login/index.ts +++ b/packages/@expo/cli/src/login/index.ts @@ -29,7 +29,7 @@ export const expoLogin: Command = async (argv) => { `npx expo login`, [ `-u, --username Username`, - `-p, --password Password`, + `-p, --password Password ("-" for stdin)`, `--otp One-time password from your 2FA device`, `-s, --sso Log in with SSO`, `-b, --browser Log in with a browser`, @@ -38,13 +38,27 @@ export const expoLogin: Command = async (argv) => { ); } + const password = args['--password'] === '-' ? await readWordFromStdin() : args['--password']; + const { showLoginPromptAsync } = await import('../api/user/actions.js'); return showLoginPromptAsync({ // Parsed options username: args['--username'], - password: args['--password'], + password, otp: args['--otp'], sso: !!args['--sso'], browser: !!args['--browser'], }).catch(logCmdError); }; + +export async function readWordFromStdin(): Promise { + let buffer = ''; + for await (const chunk of process.stdin) { + buffer += chunk; + const newlineIndex = buffer.indexOf('\n'); + if (newlineIndex !== -1) { + return buffer.slice(0, newlineIndex).replace(/\r$/, ''); + } + } + return buffer; +} diff --git a/packages/@expo/cli/src/start/doctor/dependencies/__tests__/reactNativeTv-test.ts b/packages/@expo/cli/src/start/doctor/dependencies/__tests__/reactNativeTv-test.ts new file mode 100644 index 00000000000000..11b1b43224959e --- /dev/null +++ b/packages/@expo/cli/src/start/doctor/dependencies/__tests__/reactNativeTv-test.ts @@ -0,0 +1,233 @@ +import { vol } from 'memfs'; + +import { fetch } from '../../../../utils/fetch'; +import { + correctReactNativeTvVersion, + isReactNativeTvProjectAsync, + reactNativeTvVersionMatchesBundled, +} from '../reactNativeTv'; + +jest.mock('../../../../utils/fetch', () => ({ + fetch: jest.fn(), +})); + +const mockedFetch = jest.mocked(fetch); + +const okJsonResponse = (body: unknown) => + ({ + ok: true, + status: 200, + text: () => Promise.resolve(JSON.stringify(body)), + }) as any; + +const errorResponse = (status: number, body: string = '') => + ({ + ok: false, + status, + text: () => Promise.resolve(body), + }) as any; + +describe(isReactNativeTvProjectAsync, () => { + const projectRoot = '/test-project'; + + beforeEach(() => { + vol.reset(); + }); + + afterEach(() => { + mockedFetch.mockReset(); + }); + + it('returns true when node_modules/react-native/package.json has name "react-native-tvos"', async () => { + vol.fromJSON( + { + 'node_modules/react-native/package.json': JSON.stringify({ + name: 'react-native-tvos', + version: '0.83.6-0', + }), + }, + projectRoot + ); + + await expect(isReactNativeTvProjectAsync(projectRoot)).resolves.toBe(true); + }); + + it('returns false when the installed react-native is the upstream package', async () => { + vol.fromJSON( + { + 'node_modules/react-native/package.json': JSON.stringify({ + name: 'react-native', + version: '0.85.3', + }), + }, + projectRoot + ); + + await expect(isReactNativeTvProjectAsync(projectRoot)).resolves.toBe(false); + }); + + it('returns false when react-native is not installed at all', async () => { + vol.fromJSON( + { + 'node_modules/expo/package.json': JSON.stringify({ version: '55.0.0' }), + }, + projectRoot + ); + + await expect(isReactNativeTvProjectAsync(projectRoot)).resolves.toBe(false); + }); + + it('returns false when react-native package.json has no name field', async () => { + vol.fromJSON( + { + 'node_modules/react-native/package.json': JSON.stringify({ version: '0.85.3' }), + }, + projectRoot + ); + + await expect(isReactNativeTvProjectAsync(projectRoot)).resolves.toBe(false); + }); +}); + +describe(correctReactNativeTvVersion, () => { + beforeEach(() => { + mockedFetch.mockReset(); + }); + + afterEach(() => { + delete process.env.EXPO_OFFLINE; + }); + + it('returns the derived stable dist-tag when published on npm', async () => { + mockedFetch.mockResolvedValueOnce( + okJsonResponse({ latest: '0.85.3-0', '0.85-stable': '0.85.3-0' }) + ); + + await expect(correctReactNativeTvVersion('0.85.3')).resolves.toBe( + 'npm:react-native-tvos@0.85-stable' + ); + }); + + it('returns the `next` dist-tag for prerelease react-native versions', async () => { + mockedFetch.mockResolvedValueOnce(okJsonResponse({ latest: '0.85.3-0', next: '0.86.0-rc.1' })); + await expect(correctReactNativeTvVersion('0.85.3-rc.1')).resolves.toBe( + 'npm:react-native-tvos@next' + ); + + mockedFetch.mockResolvedValueOnce(okJsonResponse({ latest: '0.85.3-0', next: '0.86.0-rc.1' })); + await expect(correctReactNativeTvVersion('0.86.0-canary.20260514-abc1234')).resolves.toBe( + 'npm:react-native-tvos@next' + ); + }); + + it('handles caret-prefixed stable react-native versions', async () => { + mockedFetch.mockResolvedValueOnce( + okJsonResponse({ latest: '0.85.3-0', '0.85-stable': '0.85.3-0' }) + ); + + await expect(correctReactNativeTvVersion('^0.85.3')).resolves.toBe( + 'npm:react-native-tvos@0.85-stable' + ); + }); + + it('treats a caret range whose minimum is a prerelease as @next', async () => { + mockedFetch.mockResolvedValueOnce(okJsonResponse({ latest: '0.85.3-0', next: '0.86.0-rc.1' })); + + await expect(correctReactNativeTvVersion('^0.85.3-rc.1')).resolves.toBe( + 'npm:react-native-tvos@next' + ); + }); + + it('falls back to @latest when the derived dist-tag is not published', async () => { + // Future SDK whose minor line doesn't have a `-stable` tag published yet. + mockedFetch.mockResolvedValueOnce( + okJsonResponse({ latest: '0.85.3-0', '0.85-stable': '0.85.3-0' }) + ); + + await expect(correctReactNativeTvVersion('0.99.0')).resolves.toBe( + 'npm:react-native-tvos@latest' + ); + }); + + it('falls back to @latest when no dist-tags can be fetched (network failure)', async () => { + mockedFetch.mockRejectedValueOnce(new Error('connection refused')); + + await expect(correctReactNativeTvVersion('0.85.3')).resolves.toBe( + 'npm:react-native-tvos@latest' + ); + }); + + it('falls back to @latest when the npm registry returns a non-2xx', async () => { + mockedFetch.mockResolvedValueOnce(errorResponse(500, 'oops')); + + await expect(correctReactNativeTvVersion('0.85.3')).resolves.toBe( + 'npm:react-native-tvos@latest' + ); + }); + + it('falls back to @latest when the response body is not valid JSON', async () => { + mockedFetch.mockResolvedValueOnce({ + ok: true, + status: 200, + text: () => Promise.resolve('not json'), + } as any); + + await expect(correctReactNativeTvVersion('0.85.3')).resolves.toBe( + 'npm:react-native-tvos@latest' + ); + }); + + it('falls back to @latest for unparseable react-native versions', async () => { + await expect(correctReactNativeTvVersion('not-a-version')).resolves.toBe( + 'npm:react-native-tvos@latest' + ); + await expect(correctReactNativeTvVersion('')).resolves.toBe('npm:react-native-tvos@latest'); + // The offline short-circuit (and unparseable handling) shouldn't hit the + // registry at all. + expect(mockedFetch).not.toHaveBeenCalled(); + }); + + it('skips the npm dist-tags lookup when EXPO_OFFLINE is set', async () => { + process.env.EXPO_OFFLINE = '1'; + + await expect(correctReactNativeTvVersion('0.85.3')).resolves.toBe( + 'npm:react-native-tvos@0.85-stable' + ); + await expect(correctReactNativeTvVersion('0.85.3-rc.1')).resolves.toBe( + 'npm:react-native-tvos@next' + ); + // Asserting the network was untouched is the whole point of the offline + // short-circuit — keep this explicit. + expect(mockedFetch).not.toHaveBeenCalled(); + }); + + it('still falls back to @latest in offline mode when the version is unparseable', async () => { + process.env.EXPO_OFFLINE = '1'; + + await expect(correctReactNativeTvVersion('not-a-version')).resolves.toBe( + 'npm:react-native-tvos@latest' + ); + expect(mockedFetch).not.toHaveBeenCalled(); + }); +}); + +describe(reactNativeTvVersionMatchesBundled, () => { + it('returns true when installed and bundled share major.minor', () => { + expect(reactNativeTvVersionMatchesBundled('0.85.3-0', '0.85.3')).toBe(true); + expect(reactNativeTvVersionMatchesBundled('0.85.0-0', '0.85.3')).toBe(true); + expect(reactNativeTvVersionMatchesBundled('0.85.7-rc.4', '0.85.3')).toBe(true); + }); + + it('returns false when minors differ', () => { + expect(reactNativeTvVersionMatchesBundled('0.83.0-0', '0.85.3')).toBe(false); + }); + + it('returns false when majors differ', () => { + expect(reactNativeTvVersionMatchesBundled('1.85.0', '0.85.3')).toBe(false); + }); + + it('returns false for unparseable inputs', () => { + expect(reactNativeTvVersionMatchesBundled('not-a-version', '0.85.3')).toBe(false); + expect(reactNativeTvVersionMatchesBundled('0.85.3-0', 'not-a-version')).toBe(false); + }); +}); diff --git a/packages/@expo/cli/src/start/doctor/dependencies/__tests__/validateDependenciesVersions-test.ts b/packages/@expo/cli/src/start/doctor/dependencies/__tests__/validateDependenciesVersions-test.ts index 643825187d90b4..2da78c4e4add85 100644 --- a/packages/@expo/cli/src/start/doctor/dependencies/__tests__/validateDependenciesVersions-test.ts +++ b/packages/@expo/cli/src/start/doctor/dependencies/__tests__/validateDependenciesVersions-test.ts @@ -1,9 +1,12 @@ +import nodeFs from 'fs'; import { vol } from 'memfs'; +// eslint-disable-next-line no-restricted-imports import path from 'path'; import resolveFrom from 'resolve-from'; import * as Log from '../../../../log'; import { + getVersionedDependenciesAsync, isDependencyVersionIncorrect, logIncorrectDependencies, validateDependenciesVersionsAsync, @@ -15,6 +18,7 @@ jest.mock('../bundledNativeModules', () => ({ 'expo-splash-screen': '~1.2.3', 'expo-updates': '~2.3.4', firebase: '9.1.0', + 'react-native': '0.85.3', }), })); jest.mock('../getVersionedPackages', () => ({ @@ -23,6 +27,7 @@ jest.mock('../getVersionedPackages', () => ({ 'expo-updates': '~2.3.4', firebase: '9.1.0', expo: '49.0.7', + 'react-native': '0.85.3', })), })); @@ -294,6 +299,118 @@ describe(validateDependenciesVersionsAsync, () => { }); }); +describe('getVersionedDependenciesAsync (TV)', () => { + const projectRoot = '/test-project'; + + beforeEach(() => { + vol.reset(); + // Reset to flush any queued `mockImplementationOnce` from earlier tests in + // this file, then re-install a memfs-backed implementation matching the + // manual mock at `__mocks__/resolve-from.ts`. + // `resolveFrom.silent` is a separate `jest.fn` in the manual mock, so we + // have to re-implement it alongside the main function. We use the `fs` + // imported at the top of this file so we share the same memfs instance as + // `vol` — re-requiring `fs` inside this closure can return a fresh memfs + // after earlier tests' `jest.resetModules()`. + const resolveSilent = (fromDirectory: string, request: string): string | undefined => { + const candidate = path.join(fromDirectory, 'node_modules', request); + return nodeFs.existsSync(candidate) ? candidate : undefined; + }; + jest.mocked(resolveFrom).mockReset(); + jest.mocked(resolveFrom).mockImplementation((fromDirectory, request) => { + const result = resolveSilent(fromDirectory, request); + if (!result) { + const err: any = new Error(`Cannot find module '${request}'`); + err.code = 'MODULE_NOT_FOUND'; + throw err; + } + return result; + }); + const silentMock = jest.mocked(resolveFrom).silent as unknown as jest.Mock; + silentMock.mockReset(); + silentMock.mockImplementation(resolveSilent); + delete process.env.EXPO_OFFLINE; + }); + + it('flags react-native in TV projects whose minor lags the bundled react-native', async () => { + // For TV projects the alias is `react-native: npm:react-native-tvos@`, and the + // file at `node_modules/react-native/package.json` is the upstream react-native-tvos + // package, so the installed version reads as a plain version string. + vol.fromJSON( + { + 'node_modules/expo/package.json': JSON.stringify({ version: '55.0.0' }), + 'node_modules/react-native/package.json': JSON.stringify({ + name: 'react-native-tvos', + version: '0.83.0-0', + }), + }, + projectRoot + ); + const exp = { sdkVersion: '55.0.0' }; + const pkg = { + dependencies: { 'react-native': 'npm:react-native-tvos@0.83.0-0' }, + }; + + const incorrect = await getVersionedDependenciesAsync(projectRoot, exp as any, pkg); + + expect(incorrect).toEqual([ + { + packageName: 'react-native', + packageType: 'dependencies', + expectedVersionOrRange: 'npm:react-native-tvos@0.85-stable', + actualVersion: '0.83.0-0', + }, + ]); + }); + + it('does not flag react-native when the TV project shares the bundled minor', async () => { + vol.fromJSON( + { + 'node_modules/expo/package.json': JSON.stringify({ version: '55.0.0' }), + 'node_modules/react-native/package.json': JSON.stringify({ + name: 'react-native-tvos', + version: '0.85.2-0', + }), + }, + projectRoot + ); + const exp = { sdkVersion: '55.0.0' }; + const pkg = { + dependencies: { 'react-native': 'npm:react-native-tvos@0.85-stable' }, + }; + + const incorrect = await getVersionedDependenciesAsync(projectRoot, exp as any, pkg); + expect(incorrect).toEqual([]); + }); + + it('leaves the expected version untouched for non-TV projects with an outdated react-native', async () => { + vol.fromJSON( + { + 'node_modules/expo/package.json': JSON.stringify({ version: '55.0.0' }), + 'node_modules/react-native/package.json': JSON.stringify({ + name: 'react-native', + version: '0.84.0', + }), + }, + projectRoot + ); + const exp = { sdkVersion: '55.0.0' }; + const pkg = { + dependencies: { 'react-native': '0.84.0' }, + }; + + const incorrect = await getVersionedDependenciesAsync(projectRoot, exp as any, pkg); + expect(incorrect).toEqual([ + { + packageName: 'react-native', + packageType: 'dependencies', + expectedVersionOrRange: '0.85.3', + actualVersion: '0.84.0', + }, + ]); + }); +}); + describe(isDependencyVersionIncorrect, () => { const testCases = [ ['3.9.0-rc.1', '~3.9.0-rc.1', false, 'prerelease with tilde range'], diff --git a/packages/@expo/cli/src/start/doctor/dependencies/reactNativeTv.ts b/packages/@expo/cli/src/start/doctor/dependencies/reactNativeTv.ts new file mode 100644 index 00000000000000..51095f01b6628f --- /dev/null +++ b/packages/@expo/cli/src/start/doctor/dependencies/reactNativeTv.ts @@ -0,0 +1,150 @@ +import JsonFile from '@expo/json-file'; +import { resolveFrom } from '@expo/require-utils'; +import semver from 'semver'; + +import { env } from '../../../utils/env'; +import { fetch } from '../../../utils/fetch'; + +export const REACT_NATIVE_TVOS_PACKAGE_NAME = 'react-native-tvos'; + +const debug = require('debug')('expo:doctor:reactNativeTv') as typeof console.log; + +const NPM_DIST_TAGS_URL = `https://registry.npmjs.org/-/package/${REACT_NATIVE_TVOS_PACKAGE_NAME}/dist-tags`; + +const LATEST_FALLBACK_SPEC = `npm:${REACT_NATIVE_TVOS_PACKAGE_NAME}@latest`; + +/** + * Detects whether this is a TV project by inspecting the installed + * `react-native` package's `name` field. When the project's `package.json` + * aliases `react-native` to `react-native-tvos` (e.g. + * `"react-native": "npm:react-native-tvos@0.83.0-0"`), the installed package + * at `node_modules/react-native/package.json` is the upstream + * `react-native-tvos` manifest, whose `name` is `"react-native-tvos"`. + * + * Reading the installed manifest is more reliable than reading the project's + * `package.json` spec string, which can vary across package managers and + * lockfile rewrites. + */ +export async function isReactNativeTvProjectAsync(projectRoot: string): Promise { + const reactNativePackageJsonPath = resolveFrom(projectRoot, 'react-native/package.json'); + if (!reactNativePackageJsonPath) { + return false; + } + try { + const installedPkg = await JsonFile.readAsync<{ name?: string }>(reactNativePackageJsonPath); + return installedPkg.name === REACT_NATIVE_TVOS_PACKAGE_NAME; + } catch { + return false; + } +} + +/** + * Returns the install spec to use for `react-native-tvos` given the bundled + * `react-native` version. + * + * `react-native-tvos` ships a dist-tag per minor line in the form + * `.-stable` and a rolling `next` tag for prereleases. We derive + * the expected tag from the bundled `react-native` version (stable → `-stable`, + * prerelease → `next`) and then verify the tag is actually published by + * fetching the package's `dist-tags` from the npm registry. If the derived tag + * isn't published (or the registry can't be reached), we fall back to + * `npm:react-native-tvos@latest` so the install still resolves to something + * usable. + */ +export async function correctReactNativeTvVersion( + bundledReactNativeVersion: string +): Promise { + const derivedTag = deriveDistTag(bundledReactNativeVersion); + if (!derivedTag) { + debug( + `Could not derive a react-native-tvos dist-tag from "${bundledReactNativeVersion}"; falling back to @latest` + ); + return LATEST_FALLBACK_SPEC; + } + // In offline mode skip the npm dist-tags lookup and trust the derived tag — + // any other CLI code path that needs a network request also bails on + // `EXPO_OFFLINE` (see `validateDependenciesVersionsAsync`). + if (env.EXPO_OFFLINE) { + debug(`EXPO_OFFLINE is set; skipping npm dist-tags lookup for react-native-tvos`); + return `npm:${REACT_NATIVE_TVOS_PACKAGE_NAME}@${derivedTag}`; + } + const publishedTags = await fetchReactNativeTvDistTagsAsync(); + if (publishedTags.has(derivedTag)) { + return `npm:${REACT_NATIVE_TVOS_PACKAGE_NAME}@${derivedTag}`; + } + debug( + `Derived react-native-tvos dist-tag "${derivedTag}" is not published; falling back to @latest` + ); + return LATEST_FALLBACK_SPEC; +} + +/** + * Returns true when an installed `react-native` (aliased to `react-native-tvos`) + * version's `major.minor` lines up with the expected bundled `react-native` + * version. `react-native-tvos` releases follow the upstream minor versions, so a + * matching `major.minor` means the TV variant is already current. + */ +export function reactNativeTvVersionMatchesBundled( + actualVersion: string, + bundledReactNativeVersion: string +): boolean { + const actual = semver.coerce(actualVersion); + const bundled = semver.coerce(bundledReactNativeVersion); + if (!actual || !bundled) { + return false; + } + return actual.major === bundled.major && actual.minor === bundled.minor; +} + +function deriveDistTag(reactNativeVersion: string): string | undefined { + if (!reactNativeVersion) { + return undefined; + } + let minVersion: semver.SemVer | null = null; + try { + minVersion = semver.minVersion(reactNativeVersion); + } catch { + minVersion = null; + } + if (!minVersion) { + return undefined; + } + if (minVersion.prerelease.length > 0) { + return 'next'; + } + return `${minVersion.major}.${minVersion.minor}-stable`; +} + +async function fetchReactNativeTvDistTagsAsync(): Promise> { + let response; + try { + response = await fetch(NPM_DIST_TAGS_URL); + } catch (error: any) { + debug(`npm dist-tags lookup threw: ${error?.message ?? error}`); + return new Set(); + } + // Always read the body to release the underlying stream — even on a non-2xx — + // before deciding what to do with it. Parse JSON manually so a malformed + // body never escapes as a rejected promise. + let body = ''; + try { + body = await response.text(); + } catch (error: any) { + debug(`npm dist-tags body read threw: ${error?.message ?? error}`); + return new Set(); + } + if (!response.ok) { + debug(`npm dist-tags lookup failed with status ${response.status}`); + return new Set(); + } + let json: unknown; + try { + json = JSON.parse(body); + } catch { + return new Set(); + } + if (!json || typeof json !== 'object') { + return new Set(); + } + return new Set(Object.keys(json as Record)); +} diff --git a/packages/@expo/cli/src/start/doctor/dependencies/validateDependenciesVersions.ts b/packages/@expo/cli/src/start/doctor/dependencies/validateDependenciesVersions.ts index 8f5d7f058b4dcd..ffa2d0c5f847de 100644 --- a/packages/@expo/cli/src/start/doctor/dependencies/validateDependenciesVersions.ts +++ b/packages/@expo/cli/src/start/doctor/dependencies/validateDependenciesVersions.ts @@ -7,13 +7,18 @@ import semverRangeSubset from 'semver/ranges/subset'; import type { BundledNativeModules } from './bundledNativeModules'; import { getCombinedKnownVersionsAsync } from './getVersionedPackages'; +import { + correctReactNativeTvVersion, + isReactNativeTvProjectAsync, + reactNativeTvVersionMatchesBundled, +} from './reactNativeTv'; import { resolveAllPackageVersionsAsync } from './resolvePackages'; import * as Log from '../../../log'; import { env } from '../../../utils/env'; const debug = require('debug')('expo:doctor:dependencies:validate') as typeof console.log; -type IncorrectDependency = { +export type IncorrectDependency = { packageName: string; packageType: 'dependencies' | 'devDependencies'; expectedVersionOrRange: string; @@ -123,8 +128,17 @@ export async function getVersionedDependenciesAsync( resolvedPackagesToCheck ); debug(`Package versions: %O`, packageVersions); + // Detect TV projects via the installed `react-native` package's `name`, since + // `pkg.dependencies['react-native']` can vary across package managers. + const isReactNativeTvProject = await isReactNativeTvProjectAsync(projectRoot); + debug(`react-native-tvos project: %O`, isReactNativeTvProject); // find incorrect dependencies by comparing the actual package versions with the bundled native module version ranges - let incorrectDeps = findIncorrectDependencies(pkg, packageVersions, combinedKnownPackages); + let incorrectDeps = await findIncorrectDependencies( + pkg, + packageVersions, + combinedKnownPackages, + isReactNativeTvProject + ); debug(`Incorrect dependencies: %O`, incorrectDeps); if (pkg?.expo?.install?.exclude) { @@ -214,16 +228,47 @@ function getPackagesToCheck( return { known, unknown }; } -function findIncorrectDependencies( +async function findIncorrectDependencies( pkg: PackageJSONConfig, packageVersions: Record, - bundledNativeModules: BundledNativeModules -): IncorrectDependency[] { + bundledNativeModules: BundledNativeModules, + isReactNativeTvProject: boolean +): Promise { + // For TV projects, compare the installed `major.minor` against the bundled + // `react-native` `major.minor` — `react-native-tvos` follows the upstream + // minor lines via a `.-stable` dist-tag, so a matching minor + // means the TV variant is up to date. + // + // Resolve the install spec for the TV variant once up front so the inner + // loop stays synchronous and we don't hit the npm registry per dependency. + const bundledReactNativeVersion = bundledNativeModules['react-native']; + const reactNativeTvExpectedVersionOrRange = + isReactNativeTvProject && bundledReactNativeVersion + ? await correctReactNativeTvVersion(bundledReactNativeVersion) + : undefined; + const packages = Object.keys(packageVersions); const incorrectDeps: IncorrectDependency[] = []; for (const packageName of packages) { - const expectedVersionOrRange = bundledNativeModules[packageName]!; const actualVersion = packageVersions[packageName]!; + + if (isReactNativeTvProject && packageName === 'react-native') { + if ( + bundledReactNativeVersion && + reactNativeTvExpectedVersionOrRange && + !reactNativeTvVersionMatchesBundled(actualVersion, bundledReactNativeVersion) + ) { + incorrectDeps.push({ + packageName, + packageType: findDependencyType(pkg, packageName), + expectedVersionOrRange: reactNativeTvExpectedVersionOrRange, + actualVersion, + }); + } + continue; + } + + const expectedVersionOrRange = bundledNativeModules[packageName]!; if (isDependencyVersionIncorrect(packageName, actualVersion, expectedVersionOrRange)) { incorrectDeps.push({ packageName, diff --git a/packages/@expo/cli/src/start/interface/__tests__/createDevToolsMenuItems-test.ts b/packages/@expo/cli/src/start/interface/__tests__/createDevToolsMenuItems-test.ts index d7e1f710033a24..d4d30208a0f604 100644 --- a/packages/@expo/cli/src/start/interface/__tests__/createDevToolsMenuItems-test.ts +++ b/packages/@expo/cli/src/start/interface/__tests__/createDevToolsMenuItems-test.ts @@ -18,7 +18,7 @@ describe('createInteractiveMenuItems', () => { { packageName: 'test-plugin', packageRoot: 'path/to/test-plugin', - webpageRoot: '/test/plugin', + webpageRoot: 'path/to/test-plugin/web', }, DEFAULT_PROJECT_ROOT ); @@ -103,7 +103,7 @@ describe('createInteractiveMenuItems', () => { { packageName: 'test-plugin', packageRoot: 'path/to/test-plugin', - webpageRoot: '/test/plugin', + webpageRoot: 'path/to/test-plugin/web', cliExtensions: { description: 'Test CLI Extension', entryPoint: 'index.js', diff --git a/packages/@expo/cli/src/start/server/DevToolsPlugin.ts b/packages/@expo/cli/src/start/server/DevToolsPlugin.ts index 11acfb37cb56fb..07dfbd5bc1bbf4 100644 --- a/packages/@expo/cli/src/start/server/DevToolsPlugin.ts +++ b/packages/@expo/cli/src/start/server/DevToolsPlugin.ts @@ -1,7 +1,18 @@ +import fs from 'node:fs'; + import type { DevToolsPluginInfo } from './DevToolsPlugin.schema'; import { PluginSchema } from './DevToolsPlugin.schema'; import { DevToolsPluginCliExtensionExecutor } from './DevToolsPluginCliExtensionExecutor'; import { DevToolsPluginEndpoint } from './DevToolsPluginManager'; +import { isPathInside } from '../../utils/dir'; + +const maybeRealpath = (target: string): string => { + try { + return fs.realpathSync(target); + } catch { + return target; + } +}; /** * Class that represents a DevTools plugin with CLI and/or web extensions @@ -19,13 +30,21 @@ export class DevToolsPlugin { private plugin: DevToolsPluginInfo, public readonly projectRoot: string ) { - // Validate configuration schema const result = PluginSchema.safeParse(plugin); if (!result.success) { throw new Error(`Invalid plugin configuration: ${result.error.message}`, { cause: result.error, }); } + + if (plugin.webpageRoot != null) { + const webpageRoot = maybeRealpath(plugin.webpageRoot); + if (!isPathInside(webpageRoot, plugin.packageRoot)) { + throw new Error( + `webpageRoot (${plugin.webpageRoot}) is not inside packageRoot (${plugin.packageRoot}).` + ); + } + } } private _executor: DevToolsPluginCliExtensionExecutor | undefined = undefined; diff --git a/packages/@expo/cli/src/start/server/__tests__/DevToolsPlugin-test.ts b/packages/@expo/cli/src/start/server/__tests__/DevToolsPlugin-test.ts index 935db940a6e35f..f05af30321a2d2 100644 --- a/packages/@expo/cli/src/start/server/__tests__/DevToolsPlugin-test.ts +++ b/packages/@expo/cli/src/start/server/__tests__/DevToolsPlugin-test.ts @@ -5,17 +5,31 @@ describe('DevToolsPlugin', () => { const pluginDescriptor = { packageName: 'example-plugin', packageRoot: '/path/to/example-plugin', - webpageRoot: 'https://example.com/plugin', + webpageRoot: '/path/to/example-plugin/web', }; const projectRoot = '/path/to/project'; const plugin = new DevToolsPlugin(pluginDescriptor, projectRoot); expect(plugin.packageName).toBe('example-plugin'); - expect(plugin.webpageRoot).toBe('https://example.com/plugin'); + expect(plugin.webpageRoot).toBe('/path/to/example-plugin/web'); expect(plugin.executor).toBeUndefined(); expect(plugin.description).toBe(''); }); + it('should reject a webpageRoot that escapes the package directory', () => { + expect( + () => + new DevToolsPlugin( + { + packageName: 'malicious-plugin', + packageRoot: '/path/to/project/node_modules/malicious-plugin', + webpageRoot: '/path/to/project', + }, + '/path/to/project' + ) + ).toThrow(/is not inside packageRoot/); + }); + it('should create an instance from a plugin with only the cli extension set', () => { const commands = [ { diff --git a/packages/@expo/cli/src/start/server/__tests__/DevToolsPluginManager-test.ts b/packages/@expo/cli/src/start/server/__tests__/DevToolsPluginManager-test.ts index ccb08c182ef84c..f671e67ff1fdbb 100644 --- a/packages/@expo/cli/src/start/server/__tests__/DevToolsPluginManager-test.ts +++ b/packages/@expo/cli/src/start/server/__tests__/DevToolsPluginManager-test.ts @@ -38,7 +38,7 @@ describe('DevToolsPluginManager', () => { { packageName: 'valid-plugin', packageRoot: '/path/to/valid-plugin', - webpageRoot: '/web', + webpageRoot: '/path/to/valid-plugin/web', }, ]); @@ -49,12 +49,32 @@ describe('DevToolsPluginManager', () => { expect(plugins[0].packageName).toBe('valid-plugin'); }); + it('should skip a plugin whose webpageRoot escapes the package directory', async () => { + mockAutolinkingPlugins([ + { + packageName: 'malicious-plugin', + packageRoot: '/path/to/project/node_modules/malicious-plugin', + // The autolinking-side check should reject this before we get here, + // but this guard catches it if a bad descriptor is supplied directly. + webpageRoot: '/path/to/project', + }, + ]); + + const manager = new DevToolsPluginManager('/project'); + const plugins = await manager.queryPluginsAsync(); + + expect(plugins.length).toBe(0); + expect(Log.warn).toHaveBeenCalledWith( + expect.stringContaining('Skipping plugin "malicious-plugin"') + ); + }); + it('should skip a plugin with an invalid config without affecting other valid plugins', async () => { mockAutolinkingPlugins([ { packageName: 'valid-plugin', packageRoot: '/path/to/valid-plugin', - webpageRoot: '/web', + webpageRoot: '/path/to/valid-plugin/web', }, { packageName: 'invalid-plugin', diff --git a/packages/@expo/cli/src/start/server/metro/createServerComponentsMiddleware.ts b/packages/@expo/cli/src/start/server/metro/createServerComponentsMiddleware.ts index 3efc1df4ae6a51..5afc8b82f8fc38 100644 --- a/packages/@expo/cli/src/start/server/metro/createServerComponentsMiddleware.ts +++ b/packages/@expo/cli/src/start/server/metro/createServerComponentsMiddleware.ts @@ -6,6 +6,7 @@ */ import { getMetroServerRoot } from '@expo/config/paths'; import type { SerialAsset } from '@expo/metro-config/build/serializer/serializerAssets'; +import { resolveRouterModule, type RouterModule } from '@expo/router-server/build/rsc/router'; import type { EntriesDev } from '@expo/router-server/build/rsc/server'; import assert from 'assert'; import { getRscMiddleware } from 'expo-server/private'; @@ -65,9 +66,7 @@ export function createServerComponentsMiddleware( routerOptions: Record; } ) { - const routerModule = useClientRouter - ? require.resolve('@expo/router-server/build/rsc/router/noopRouter') - : require.resolve('@expo/router-server/build/rsc/router/expo-definedRouter'); + const routerModule = resolveRouterModule(useClientRouter); const rscMiddleware = getRscMiddleware({ config: {}, @@ -308,9 +307,7 @@ export function createServerComponentsMiddleware( return routerCache.get(platform)!; } - const router = await ssrLoadModule< - typeof import('@expo/router-server/build/rsc/router/expo-definedRouter') - >( + const router = await ssrLoadModule( routerModule, { environment: 'react-server', diff --git a/packages/@expo/cli/src/utils/tar.ts b/packages/@expo/cli/src/utils/tar.ts index 3dc90639bf6fe0..2fb8626013e703 100644 --- a/packages/@expo/cli/src/utils/tar.ts +++ b/packages/@expo/cli/src/utils/tar.ts @@ -34,10 +34,10 @@ export interface ExtractOptions { export async function extractStream( input: ReadableStream, - output: string, + targetOutput: string, options: ExtractOptions = {} ): Promise { - output = path.resolve(output); + const output = path.resolve(targetOutput) + path.sep; await fs.promises.mkdir(output, { recursive: true }); const { checksumAlgorithm, strip = 0, rename, filter } = options; diff --git a/packages/@expo/router-server/CHANGELOG.md b/packages/@expo/router-server/CHANGELOG.md index b097d5c4bc0ee2..4d74d3f88e5bd8 100644 --- a/packages/@expo/router-server/CHANGELOG.md +++ b/packages/@expo/router-server/CHANGELOG.md @@ -10,6 +10,9 @@ ### 💡 Others +- [Internal] Align RSC server routing with expo-server/expo-router's canonical matchers ([#45900](https://github.com/expo/expo/pull/45900) by [@kitten](https://github.com/kitten)) +- Enforce that RSC `skip` parameter cannot skip layouts anymore ([#45900](https://github.com/expo/expo/pull/45900) by [@kitten](https://github.com/kitten)) + ## 56.0.8 — 2026-05-13 _This version does not introduce any user-facing changes._ diff --git a/packages/@expo/router-server/build/getNamedParametrizedRoute.d.ts b/packages/@expo/router-server/build/getNamedParametrizedRoute.d.ts new file mode 100644 index 00000000000000..858b87dcd1eb87 --- /dev/null +++ b/packages/@expo/router-server/build/getNamedParametrizedRoute.d.ts @@ -0,0 +1,12 @@ +export declare function getNamedParametrizedRoute(route: string): { + namedParameterizedRoute: string; + routeKeys: Record; + /** Cleaned route-key names whose captures should be split into arrays (wildcards). */ + wildcardKeys: Set; +}; +export declare function parseParameter(param: string): { + name: string; + repeat: boolean; + optional: boolean; +}; +//# sourceMappingURL=getNamedParametrizedRoute.d.ts.map \ No newline at end of file diff --git a/packages/@expo/router-server/build/getNamedParametrizedRoute.d.ts.map b/packages/@expo/router-server/build/getNamedParametrizedRoute.d.ts.map new file mode 100644 index 00000000000000..2e89655532a013 --- /dev/null +++ b/packages/@expo/router-server/build/getNamedParametrizedRoute.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"getNamedParametrizedRoute.d.ts","sourceRoot":"","sources":["../src/getNamedParametrizedRoute.ts"],"names":[],"mappings":"AA0CA,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG;IACxD,uBAAuB,EAAE,MAAM,CAAC;IAChC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,sFAAsF;IACtF,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC3B,CA6DA;AAcD,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM;;;;EAgB3C"} \ No newline at end of file diff --git a/packages/@expo/router-server/build/getNamedParametrizedRoute.js b/packages/@expo/router-server/build/getNamedParametrizedRoute.js new file mode 100644 index 00000000000000..1f60b6a022f96c --- /dev/null +++ b/packages/@expo/router-server/build/getNamedParametrizedRoute.js @@ -0,0 +1,127 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getNamedParametrizedRoute = getNamedParametrizedRoute; +exports.parseParameter = parseParameter; +const routing_1 = require("expo-router/internal/routing"); +/** + * Builds a function to generate a minimal routeKey using only a-z and minimal + * number of characters. + */ +function buildGetSafeRouteKey() { + let currentCharCode = 96; // Starting one before 'a' to make the increment logic simpler + let currentLength = 1; + return () => { + let result = ''; + let incrementNext = true; + // Iterate from right to left to build the key + for (let i = 0; i < currentLength; i++) { + if (incrementNext) { + currentCharCode++; + if (currentCharCode > 122) { + currentCharCode = 97; // Reset to 'a' + incrementNext = true; // Continue to increment the next character + } + else { + incrementNext = false; + } + } + result = String.fromCharCode(currentCharCode) + result; + } + // If all characters are 'z', increase the length of the key + if (incrementNext) { + currentLength++; + currentCharCode = 96; // This will make the next key start with 'a' + } + return result; + }; +} +function removeTrailingSlash(route) { + return route.replace(/\/$/, '') || '/'; +} +function getNamedParametrizedRoute(route) { + const segments = removeTrailingSlash(route).slice(1).split('/'); + const getSafeRouteKey = buildGetSafeRouteKey(); + const routeKeys = {}; + const wildcardKeys = new Set(); + const namedParameterizedRoute = segments + .map((segment, index) => { + if (segment === '+not-found' && index === segments.length - 1) { + segment = '[...not-found]'; + } + if (/^\[.*\]$/.test(segment)) { + const { name, optional, repeat } = parseParameter(segment); + // replace any non-word characters since they can break + // the named regex + let cleanedKey = name.replace(/\W/g, ''); + let invalidKey = false; + // check if the key is still invalid and fallback to using a known + // safe key + if (cleanedKey.length === 0 || cleanedKey.length > 30) { + invalidKey = true; + } + if (!isNaN(parseInt(cleanedKey.slice(0, 1), 10))) { + invalidKey = true; + } + // Prevent duplicates after sanitizing the key + if (cleanedKey in routeKeys) { + invalidKey = true; + } + if (invalidKey) { + cleanedKey = getSafeRouteKey(); + } + routeKeys[cleanedKey] = name; + if (repeat) + wildcardKeys.add(cleanedKey); + return repeat + ? optional + ? `(?:/(?<${cleanedKey}>.+?))?` + : `/(?<${cleanedKey}>.+?)` + : `/(?<${cleanedKey}>[^/]+?)`; + } + else if (/^\(.*\)$/.test(segment)) { + const groupName = (0, routing_1.matchGroupName)(segment) + .split(',') + .map((group) => group.trim()) + .filter(Boolean); + if (groupName.length > 1) { + const optionalSegment = `\\((?:${groupName.map(escapeStringRegexp).join('|')})\\)`; + // Make section optional + return `(?:/${optionalSegment})?`; + } + else { + // Use simpler regex for single groups + return `(?:/${escapeStringRegexp(segment)})?`; + } + } + else { + return `/${escapeStringRegexp(segment)}`; + } + }) + .join(''); + return { namedParameterizedRoute, routeKeys, wildcardKeys }; +} +// regexp is based on https://github.com/sindresorhus/escape-string-regexp +const reHasRegExp = /[|\\{}()[\]^$+*?.-]/; +const reReplaceRegExp = /[|\\{}()[\]^$+*?.-]/g; +function escapeStringRegexp(str) { + // see also: https://github.com/lodash/lodash/blob/2da024c3b4f9947a48517639de7560457cd4ec6c/escapeRegExp.js#L23 + if (reHasRegExp.test(str)) { + return str.replace(reReplaceRegExp, '\\$&'); + } + return str; +} +function parseParameter(param) { + let repeat = false; + let optional = false; + let name = param; + if (/^\[.*\]$/.test(name)) { + optional = true; + name = name.slice(1, -1); + } + if (/^\.\.\./.test(name)) { + repeat = true; + name = name.slice(3); + } + return { name, repeat, optional }; +} +//# sourceMappingURL=getNamedParametrizedRoute.js.map \ No newline at end of file diff --git a/packages/@expo/router-server/build/getNamedParametrizedRoute.js.map b/packages/@expo/router-server/build/getNamedParametrizedRoute.js.map new file mode 100644 index 00000000000000..90f1c389463894 --- /dev/null +++ b/packages/@expo/router-server/build/getNamedParametrizedRoute.js.map @@ -0,0 +1 @@ +{"version":3,"file":"getNamedParametrizedRoute.js","sourceRoot":"","sources":["../src/getNamedParametrizedRoute.ts"],"names":[],"mappings":"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","sourcesContent":["import { matchGroupName } from 'expo-router/internal/routing';\n\n/**\n * Builds a function to generate a minimal routeKey using only a-z and minimal\n * number of characters.\n */\nfunction buildGetSafeRouteKey() {\n let currentCharCode = 96; // Starting one before 'a' to make the increment logic simpler\n let currentLength = 1;\n\n return () => {\n let result = '';\n let incrementNext = true;\n\n // Iterate from right to left to build the key\n for (let i = 0; i < currentLength; i++) {\n if (incrementNext) {\n currentCharCode++;\n if (currentCharCode > 122) {\n currentCharCode = 97; // Reset to 'a'\n incrementNext = true; // Continue to increment the next character\n } else {\n incrementNext = false;\n }\n }\n result = String.fromCharCode(currentCharCode) + result;\n }\n\n // If all characters are 'z', increase the length of the key\n if (incrementNext) {\n currentLength++;\n currentCharCode = 96; // This will make the next key start with 'a'\n }\n\n return result;\n };\n}\n\nfunction removeTrailingSlash(route: string): string {\n return route.replace(/\\/$/, '') || '/';\n}\n\nexport function getNamedParametrizedRoute(route: string): {\n namedParameterizedRoute: string;\n routeKeys: Record;\n /** Cleaned route-key names whose captures should be split into arrays (wildcards). */\n wildcardKeys: Set;\n} {\n const segments = removeTrailingSlash(route).slice(1).split('/');\n const getSafeRouteKey = buildGetSafeRouteKey();\n const routeKeys: Record = {};\n const wildcardKeys = new Set();\n const namedParameterizedRoute = segments\n .map((segment, index) => {\n if (segment === '+not-found' && index === segments.length - 1) {\n segment = '[...not-found]';\n }\n if (/^\\[.*\\]$/.test(segment)) {\n const { name, optional, repeat } = parseParameter(segment);\n // replace any non-word characters since they can break\n // the named regex\n let cleanedKey = name.replace(/\\W/g, '');\n let invalidKey = false;\n\n // check if the key is still invalid and fallback to using a known\n // safe key\n if (cleanedKey.length === 0 || cleanedKey.length > 30) {\n invalidKey = true;\n }\n if (!isNaN(parseInt(cleanedKey.slice(0, 1), 10))) {\n invalidKey = true;\n }\n\n // Prevent duplicates after sanitizing the key\n if (cleanedKey in routeKeys) {\n invalidKey = true;\n }\n\n if (invalidKey) {\n cleanedKey = getSafeRouteKey();\n }\n\n routeKeys[cleanedKey] = name;\n if (repeat) wildcardKeys.add(cleanedKey);\n return repeat\n ? optional\n ? `(?:/(?<${cleanedKey}>.+?))?`\n : `/(?<${cleanedKey}>.+?)`\n : `/(?<${cleanedKey}>[^/]+?)`;\n } else if (/^\\(.*\\)$/.test(segment)) {\n const groupName = matchGroupName(segment)!\n .split(',')\n .map((group) => group.trim())\n .filter(Boolean);\n if (groupName.length > 1) {\n const optionalSegment = `\\\\((?:${groupName.map(escapeStringRegexp).join('|')})\\\\)`;\n // Make section optional\n return `(?:/${optionalSegment})?`;\n } else {\n // Use simpler regex for single groups\n return `(?:/${escapeStringRegexp(segment)})?`;\n }\n } else {\n return `/${escapeStringRegexp(segment)}`;\n }\n })\n .join('');\n return { namedParameterizedRoute, routeKeys, wildcardKeys };\n}\n\n// regexp is based on https://github.com/sindresorhus/escape-string-regexp\nconst reHasRegExp = /[|\\\\{}()[\\]^$+*?.-]/;\nconst reReplaceRegExp = /[|\\\\{}()[\\]^$+*?.-]/g;\n\nfunction escapeStringRegexp(str: string) {\n // see also: https://github.com/lodash/lodash/blob/2da024c3b4f9947a48517639de7560457cd4ec6c/escapeRegExp.js#L23\n if (reHasRegExp.test(str)) {\n return str.replace(reReplaceRegExp, '\\\\$&');\n }\n return str;\n}\n\nexport function parseParameter(param: string) {\n let repeat = false;\n let optional = false;\n let name = param;\n\n if (/^\\[.*\\]$/.test(name)) {\n optional = true;\n name = name.slice(1, -1);\n }\n\n if (/^\\.\\.\\./.test(name)) {\n repeat = true;\n name = name.slice(3);\n }\n\n return { name, repeat, optional };\n}\n"]} \ No newline at end of file diff --git a/packages/@expo/router-server/build/getServerManifest.d.ts b/packages/@expo/router-server/build/getServerManifest.d.ts index 9af3373be99f96..ab602f98db9b62 100644 --- a/packages/@expo/router-server/build/getServerManifest.d.ts +++ b/packages/@expo/router-server/build/getServerManifest.d.ts @@ -1,12 +1,3 @@ -/** - * Copyright © 2023 650 Industries. - * Copyright © 2023 Vercel, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * Based on https://github.com/vercel/next.js/blob/1df2686bc9964f1a86c444701fa5cbf178669833/packages/next/src/shared/lib/router/utils/route-regex.ts - */ import { type RouteNode } from 'expo-router/internal/routing'; import type { RoutesManifest } from 'expo-server/private'; export interface Group { @@ -22,10 +13,5 @@ type GetServerManifestOptions = { headers?: Record; }; export declare function getServerManifest(route: RouteNode | null, options: GetServerManifestOptions | undefined): RoutesManifest; -export declare function parseParameter(param: string): { - name: string; - repeat: boolean; - optional: boolean; -}; export {}; //# sourceMappingURL=getServerManifest.d.ts.map \ No newline at end of file diff --git a/packages/@expo/router-server/build/getServerManifest.d.ts.map b/packages/@expo/router-server/build/getServerManifest.d.ts.map index ce3dd320f7fe8c..dafa23f3bb24b7 100644 --- a/packages/@expo/router-server/build/getServerManifest.d.ts.map +++ b/packages/@expo/router-server/build/getServerManifest.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"getServerManifest.d.ts","sourceRoot":"","sources":["../src/getServerManifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAa,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErE,MAAM,WAAW,KAAK;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC9B,EAAE,EAAE,MAAM,CAAC;CACZ;AA2BD,KAAK,wBAAwB,GAAG;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;CAC7C,CAAC;AAGF,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,GAAG,IAAI,EACvB,OAAO,EAAE,wBAAwB,GAAG,SAAS,GAC5C,cAAc,CAAC,MAAM,CAAC,CAuGxB;AAuJD,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM;;;;EAgB3C"} \ No newline at end of file +{"version":3,"file":"getServerManifest.d.ts","sourceRoot":"","sources":["../src/getServerManifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAEzF,OAAO,KAAK,EAAa,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAIrE,MAAM,WAAW,KAAK;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC9B,EAAE,EAAE,MAAM,CAAC;CACZ;AA2BD,KAAK,wBAAwB,GAAG;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;CAC7C,CAAC;AAGF,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,SAAS,GAAG,IAAI,EACvB,OAAO,EAAE,wBAAwB,GAAG,SAAS,GAC5C,cAAc,CAAC,MAAM,CAAC,CAuGxB"} \ No newline at end of file diff --git a/packages/@expo/router-server/build/getServerManifest.js b/packages/@expo/router-server/build/getServerManifest.js index c329dd48cf3027..8785f914c308c2 100644 --- a/packages/@expo/router-server/build/getServerManifest.js +++ b/packages/@expo/router-server/build/getServerManifest.js @@ -1,18 +1,9 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getServerManifest = getServerManifest; -exports.parseParameter = parseParameter; -/** - * Copyright © 2023 650 Industries. - * Copyright © 2023 Vercel, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * Based on https://github.com/vercel/next.js/blob/1df2686bc9964f1a86c444701fa5cbf178669833/packages/next/src/shared/lib/router/utils/route-regex.ts - */ const routing_1 = require("expo-router/internal/routing"); const utils_1 = require("expo-router/internal/utils"); +const getNamedParametrizedRoute_1 = require("./getNamedParametrizedRoute"); function isNotFoundRoute(route) { return route.dynamic != null && (route.dynamic[route.dynamic.length - 1]?.notFound ?? false); } @@ -120,7 +111,7 @@ function getMatchableManifestForPaths(paths) { }); } function getNamedRouteRegex(normalizedRoute, page, file) { - const result = getNamedParametrizedRoute(normalizedRoute); + const result = (0, getNamedParametrizedRoute_1.getNamedParametrizedRoute)(normalizedRoute); return { file, page, @@ -128,126 +119,6 @@ function getNamedRouteRegex(normalizedRoute, page, file) { routeKeys: result.routeKeys, }; } -/** - * Builds a function to generate a minimal routeKey using only a-z and minimal - * number of characters. - */ -function buildGetSafeRouteKey() { - let currentCharCode = 96; // Starting one before 'a' to make the increment logic simpler - let currentLength = 1; - return () => { - let result = ''; - let incrementNext = true; - // Iterate from right to left to build the key - for (let i = 0; i < currentLength; i++) { - if (incrementNext) { - currentCharCode++; - if (currentCharCode > 122) { - currentCharCode = 97; // Reset to 'a' - incrementNext = true; // Continue to increment the next character - } - else { - incrementNext = false; - } - } - result = String.fromCharCode(currentCharCode) + result; - } - // If all characters are 'z', increase the length of the key - if (incrementNext) { - currentLength++; - currentCharCode = 96; // This will make the next key start with 'a' - } - return result; - }; -} -function removeTrailingSlash(route) { - return route.replace(/\/$/, '') || '/'; -} -function getNamedParametrizedRoute(route) { - const segments = removeTrailingSlash(route).slice(1).split('/'); - const getSafeRouteKey = buildGetSafeRouteKey(); - const routeKeys = {}; - return { - namedParameterizedRoute: segments - .map((segment, index) => { - if (segment === '+not-found' && index === segments.length - 1) { - segment = '[...not-found]'; - } - if (/^\[.*\]$/.test(segment)) { - const { name, optional, repeat } = parseParameter(segment); - // replace any non-word characters since they can break - // the named regex - let cleanedKey = name.replace(/\W/g, ''); - let invalidKey = false; - // check if the key is still invalid and fallback to using a known - // safe key - if (cleanedKey.length === 0 || cleanedKey.length > 30) { - invalidKey = true; - } - if (!isNaN(parseInt(cleanedKey.slice(0, 1), 10))) { - invalidKey = true; - } - // Prevent duplicates after sanitizing the key - if (cleanedKey in routeKeys) { - invalidKey = true; - } - if (invalidKey) { - cleanedKey = getSafeRouteKey(); - } - routeKeys[cleanedKey] = name; - return repeat - ? optional - ? `(?:/(?<${cleanedKey}>.+?))?` - : `/(?<${cleanedKey}>.+?)` - : `/(?<${cleanedKey}>[^/]+?)`; - } - else if (/^\(.*\)$/.test(segment)) { - const groupName = (0, routing_1.matchGroupName)(segment) - .split(',') - .map((group) => group.trim()) - .filter(Boolean); - if (groupName.length > 1) { - const optionalSegment = `\\((?:${groupName.map(escapeStringRegexp).join('|')})\\)`; - // Make section optional - return `(?:/${optionalSegment})?`; - } - else { - // Use simpler regex for single groups - return `(?:/${escapeStringRegexp(segment)})?`; - } - } - else { - return `/${escapeStringRegexp(segment)}`; - } - }) - .join(''), - routeKeys, - }; -} -// regexp is based on https://github.com/sindresorhus/escape-string-regexp -const reHasRegExp = /[|\\{}()[\]^$+*?.-]/; -const reReplaceRegExp = /[|\\{}()[\]^$+*?.-]/g; -function escapeStringRegexp(str) { - // see also: https://github.com/lodash/lodash/blob/2da024c3b4f9947a48517639de7560457cd4ec6c/escapeRegExp.js#L23 - if (reHasRegExp.test(str)) { - return str.replace(reReplaceRegExp, '\\$&'); - } - return str; -} -function parseParameter(param) { - let repeat = false; - let optional = false; - let name = param; - if (/^\[.*\]$/.test(name)) { - optional = true; - name = name.slice(1, -1); - } - if (/^\.\.\./.test(name)) { - repeat = true; - name = name.slice(3); - } - return { name, repeat, optional }; -} function getNormalizedContextKey(contextKey) { return (0, routing_1.getContextKey)(contextKey).replace(/\/index$/, '') ?? '/'; } diff --git a/packages/@expo/router-server/build/getServerManifest.js.map b/packages/@expo/router-server/build/getServerManifest.js.map index b8a3fab3749a25..4d59654a88883e 100644 --- a/packages/@expo/router-server/build/getServerManifest.js.map +++ b/packages/@expo/router-server/build/getServerManifest.js.map @@ -1 +1 @@ -{"version":3,"file":"getServerManifest.js","sourceRoot":"","sources":["../src/getServerManifest.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Copyright © 2023 650 Industries.\n * Copyright © 2023 Vercel, Inc.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * Based on https://github.com/vercel/next.js/blob/1df2686bc9964f1a86c444701fa5cbf178669833/packages/next/src/shared/lib/router/utils/route-regex.ts\n */\nimport {\n getContextKey,\n matchGroupName,\n sortRoutes,\n type RouteNode,\n} from 'expo-router/internal/routing';\nimport { shouldLinkExternally } from 'expo-router/internal/utils';\nimport type { RouteInfo, RoutesManifest } from 'expo-server/private';\n\nexport interface Group {\n pos: number;\n repeat: boolean;\n optional: boolean;\n}\n\nexport interface RouteRegex {\n groups: Record;\n re: RegExp;\n}\n\nfunction isNotFoundRoute(route: RouteNode): boolean {\n return route.dynamic != null && (route.dynamic[route.dynamic.length - 1]?.notFound ?? false);\n}\n\nfunction uniqueBy(arr: T[], key: (item: T) => string): T[] {\n const seen = new Set();\n return arr.filter((item) => {\n const id = key(item);\n if (seen.has(id)) {\n return false;\n }\n seen.add(id);\n return true;\n });\n}\n\ntype FlatNode = {\n /** The context key, normalized to remove `/index` */\n normalizedContextKey: string;\n /** The complete route path, including all parent route paths */\n absoluteRoutePath: string;\n /** The route node that maps to this flattened node */\n route: RouteNode;\n};\n\ntype GetServerManifestOptions = {\n headers?: Record;\n};\n\n// Given a nested route tree, return a flattened array of all routes that can be matched.\nexport function getServerManifest(\n route: RouteNode | null,\n options: GetServerManifestOptions | undefined\n): RoutesManifest {\n function getFlatNodes(route: RouteNode, parentRoute: string = ''): FlatNode[] {\n // Use a recreated route instead of contextKey because we duplicate nodes to support array syntax.\n const absoluteRoute = [parentRoute, route.route].filter(Boolean).join('/');\n\n if (route.children.length) {\n return route.children.map((child) => getFlatNodes(child, absoluteRoute)).flat();\n }\n\n // API Routes are handled differently to HTML routes because they have no nested behavior.\n // An HTML route can be different based on parent segments due to layout routes, therefore multiple\n // copies should be rendered. However, an API route is always the same regardless of parent segments.\n let key: string;\n if (route.type.includes('api')) {\n key = getNormalizedContextKey(route.contextKey);\n } else {\n key = getNormalizedContextKey(absoluteRoute);\n }\n\n return [\n {\n normalizedContextKey: key,\n absoluteRoutePath: '/' + absoluteRoute,\n route,\n },\n ];\n }\n\n // Remove duplicates from the runtime manifest which expands array syntax.\n const flat = route\n ? getFlatNodes(route)\n .sort(({ route: a }, { route: b }) => sortRoutes(b, a))\n .reverse()\n : [];\n\n const apiRoutes = uniqueBy(\n flat.filter(({ route }) => route.type === 'api'),\n ({ normalizedContextKey }) => normalizedContextKey\n );\n\n const otherRoutes = uniqueBy(\n flat.filter(\n ({ route }) =>\n route.type === 'route' ||\n (route.type === 'rewrite' && (route.methods === undefined || route.methods.includes('GET')))\n ),\n ({ normalizedContextKey }) => normalizedContextKey\n );\n\n const redirects = uniqueBy(\n flat.filter(({ route }) => route.type === 'redirect'),\n ({ normalizedContextKey }) => normalizedContextKey\n )\n .map((redirect) => {\n // TODO(@hassankhan): ENG-16577\n // For external redirects, use `destinationContextKey` as the destination URL\n if (shouldLinkExternally(redirect.route.destinationContextKey!)) {\n redirect.absoluteRoutePath = redirect.route.destinationContextKey!;\n } else {\n redirect.absoluteRoutePath =\n flat.find(({ route }) => route.contextKey === redirect.route.destinationContextKey)\n ?.normalizedContextKey ?? '/';\n }\n\n return redirect;\n })\n .reverse();\n\n const rewrites = uniqueBy(\n flat.filter(({ route }) => route.type === 'rewrite'),\n ({ normalizedContextKey }) => normalizedContextKey\n )\n .map((rewrite) => {\n rewrite.absoluteRoutePath =\n flat.find(({ route }) => route.contextKey === rewrite.route.destinationContextKey)\n ?.normalizedContextKey ?? '/';\n\n return rewrite;\n })\n .reverse();\n\n const standardRoutes = otherRoutes.filter(({ route }) => !isNotFoundRoute(route));\n const notFoundRoutes = otherRoutes.filter(({ route }) => isNotFoundRoute(route));\n\n const manifest: RoutesManifest = {\n apiRoutes: getMatchableManifestForPaths(apiRoutes),\n htmlRoutes: getMatchableManifestForPaths(standardRoutes),\n notFoundRoutes: getMatchableManifestForPaths(notFoundRoutes),\n redirects: getMatchableManifestForPaths(redirects),\n rewrites: getMatchableManifestForPaths(rewrites),\n };\n\n if (route?.middleware) {\n manifest.middleware = {\n file: route.middleware.contextKey,\n };\n }\n\n if (options?.headers) {\n manifest.headers = options.headers;\n }\n\n return manifest;\n}\n\nfunction getMatchableManifestForPaths(paths: FlatNode[]): RouteInfo[] {\n return paths.map(({ normalizedContextKey, absoluteRoutePath, route }) => {\n const matcher = getNamedRouteRegex(normalizedContextKey, absoluteRoutePath, route.contextKey);\n\n if (route.generated) {\n matcher.generated = true;\n }\n\n if (route.permanent) {\n matcher.permanent = true;\n }\n\n if (route.methods) {\n matcher.methods = route.methods;\n }\n\n return matcher;\n });\n}\n\nfunction getNamedRouteRegex(\n normalizedRoute: string,\n page: string,\n file: string\n): RouteInfo {\n const result = getNamedParametrizedRoute(normalizedRoute);\n return {\n file,\n page,\n namedRegex: `^${result.namedParameterizedRoute}(?:/)?$`,\n routeKeys: result.routeKeys,\n };\n}\n\n/**\n * Builds a function to generate a minimal routeKey using only a-z and minimal\n * number of characters.\n */\nfunction buildGetSafeRouteKey() {\n let currentCharCode = 96; // Starting one before 'a' to make the increment logic simpler\n let currentLength = 1;\n\n return () => {\n let result = '';\n let incrementNext = true;\n\n // Iterate from right to left to build the key\n for (let i = 0; i < currentLength; i++) {\n if (incrementNext) {\n currentCharCode++;\n if (currentCharCode > 122) {\n currentCharCode = 97; // Reset to 'a'\n incrementNext = true; // Continue to increment the next character\n } else {\n incrementNext = false;\n }\n }\n result = String.fromCharCode(currentCharCode) + result;\n }\n\n // If all characters are 'z', increase the length of the key\n if (incrementNext) {\n currentLength++;\n currentCharCode = 96; // This will make the next key start with 'a'\n }\n\n return result;\n };\n}\n\nfunction removeTrailingSlash(route: string): string {\n return route.replace(/\\/$/, '') || '/';\n}\n\nfunction getNamedParametrizedRoute(route: string) {\n const segments = removeTrailingSlash(route).slice(1).split('/');\n const getSafeRouteKey = buildGetSafeRouteKey();\n const routeKeys: Record = {};\n return {\n namedParameterizedRoute: segments\n .map((segment, index) => {\n if (segment === '+not-found' && index === segments.length - 1) {\n segment = '[...not-found]';\n }\n if (/^\\[.*\\]$/.test(segment)) {\n const { name, optional, repeat } = parseParameter(segment);\n // replace any non-word characters since they can break\n // the named regex\n let cleanedKey = name.replace(/\\W/g, '');\n let invalidKey = false;\n\n // check if the key is still invalid and fallback to using a known\n // safe key\n if (cleanedKey.length === 0 || cleanedKey.length > 30) {\n invalidKey = true;\n }\n if (!isNaN(parseInt(cleanedKey.slice(0, 1), 10))) {\n invalidKey = true;\n }\n\n // Prevent duplicates after sanitizing the key\n if (cleanedKey in routeKeys) {\n invalidKey = true;\n }\n\n if (invalidKey) {\n cleanedKey = getSafeRouteKey();\n }\n\n routeKeys[cleanedKey] = name;\n return repeat\n ? optional\n ? `(?:/(?<${cleanedKey}>.+?))?`\n : `/(?<${cleanedKey}>.+?)`\n : `/(?<${cleanedKey}>[^/]+?)`;\n } else if (/^\\(.*\\)$/.test(segment)) {\n const groupName = matchGroupName(segment)!\n .split(',')\n .map((group) => group.trim())\n .filter(Boolean);\n if (groupName.length > 1) {\n const optionalSegment = `\\\\((?:${groupName.map(escapeStringRegexp).join('|')})\\\\)`;\n // Make section optional\n return `(?:/${optionalSegment})?`;\n } else {\n // Use simpler regex for single groups\n return `(?:/${escapeStringRegexp(segment)})?`;\n }\n } else {\n return `/${escapeStringRegexp(segment)}`;\n }\n })\n .join(''),\n routeKeys,\n };\n}\n\n// regexp is based on https://github.com/sindresorhus/escape-string-regexp\nconst reHasRegExp = /[|\\\\{}()[\\]^$+*?.-]/;\nconst reReplaceRegExp = /[|\\\\{}()[\\]^$+*?.-]/g;\n\nfunction escapeStringRegexp(str: string) {\n // see also: https://github.com/lodash/lodash/blob/2da024c3b4f9947a48517639de7560457cd4ec6c/escapeRegExp.js#L23\n if (reHasRegExp.test(str)) {\n return str.replace(reReplaceRegExp, '\\\\$&');\n }\n return str;\n}\n\nexport function parseParameter(param: string) {\n let repeat = false;\n let optional = false;\n let name = param;\n\n if (/^\\[.*\\]$/.test(name)) {\n optional = true;\n name = name.slice(1, -1);\n }\n\n if (/^\\.\\.\\./.test(name)) {\n repeat = true;\n name = name.slice(3);\n }\n\n return { name, repeat, optional };\n}\n\nfunction getNormalizedContextKey(contextKey: string): string {\n return getContextKey(contextKey).replace(/\\/index$/, '') ?? '/';\n}\n"]} \ No newline at end of file +{"version":3,"file":"getServerManifest.js","sourceRoot":"","sources":["../src/getServerManifest.ts"],"names":[],"mappings":"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","sourcesContent":["import { getContextKey, sortRoutes, type RouteNode } from 'expo-router/internal/routing';\nimport { shouldLinkExternally } from 'expo-router/internal/utils';\nimport type { RouteInfo, RoutesManifest } from 'expo-server/private';\n\nimport { getNamedParametrizedRoute } from './getNamedParametrizedRoute';\n\nexport interface Group {\n pos: number;\n repeat: boolean;\n optional: boolean;\n}\n\nexport interface RouteRegex {\n groups: Record;\n re: RegExp;\n}\n\nfunction isNotFoundRoute(route: RouteNode): boolean {\n return route.dynamic != null && (route.dynamic[route.dynamic.length - 1]?.notFound ?? false);\n}\n\nfunction uniqueBy(arr: T[], key: (item: T) => string): T[] {\n const seen = new Set();\n return arr.filter((item) => {\n const id = key(item);\n if (seen.has(id)) {\n return false;\n }\n seen.add(id);\n return true;\n });\n}\n\ntype FlatNode = {\n /** The context key, normalized to remove `/index` */\n normalizedContextKey: string;\n /** The complete route path, including all parent route paths */\n absoluteRoutePath: string;\n /** The route node that maps to this flattened node */\n route: RouteNode;\n};\n\ntype GetServerManifestOptions = {\n headers?: Record;\n};\n\n// Given a nested route tree, return a flattened array of all routes that can be matched.\nexport function getServerManifest(\n route: RouteNode | null,\n options: GetServerManifestOptions | undefined\n): RoutesManifest {\n function getFlatNodes(route: RouteNode, parentRoute: string = ''): FlatNode[] {\n // Use a recreated route instead of contextKey because we duplicate nodes to support array syntax.\n const absoluteRoute = [parentRoute, route.route].filter(Boolean).join('/');\n\n if (route.children.length) {\n return route.children.map((child) => getFlatNodes(child, absoluteRoute)).flat();\n }\n\n // API Routes are handled differently to HTML routes because they have no nested behavior.\n // An HTML route can be different based on parent segments due to layout routes, therefore multiple\n // copies should be rendered. However, an API route is always the same regardless of parent segments.\n let key: string;\n if (route.type.includes('api')) {\n key = getNormalizedContextKey(route.contextKey);\n } else {\n key = getNormalizedContextKey(absoluteRoute);\n }\n\n return [\n {\n normalizedContextKey: key,\n absoluteRoutePath: '/' + absoluteRoute,\n route,\n },\n ];\n }\n\n // Remove duplicates from the runtime manifest which expands array syntax.\n const flat = route\n ? getFlatNodes(route)\n .sort(({ route: a }, { route: b }) => sortRoutes(b, a))\n .reverse()\n : [];\n\n const apiRoutes = uniqueBy(\n flat.filter(({ route }) => route.type === 'api'),\n ({ normalizedContextKey }) => normalizedContextKey\n );\n\n const otherRoutes = uniqueBy(\n flat.filter(\n ({ route }) =>\n route.type === 'route' ||\n (route.type === 'rewrite' && (route.methods === undefined || route.methods.includes('GET')))\n ),\n ({ normalizedContextKey }) => normalizedContextKey\n );\n\n const redirects = uniqueBy(\n flat.filter(({ route }) => route.type === 'redirect'),\n ({ normalizedContextKey }) => normalizedContextKey\n )\n .map((redirect) => {\n // TODO(@hassankhan): ENG-16577\n // For external redirects, use `destinationContextKey` as the destination URL\n if (shouldLinkExternally(redirect.route.destinationContextKey!)) {\n redirect.absoluteRoutePath = redirect.route.destinationContextKey!;\n } else {\n redirect.absoluteRoutePath =\n flat.find(({ route }) => route.contextKey === redirect.route.destinationContextKey)\n ?.normalizedContextKey ?? '/';\n }\n\n return redirect;\n })\n .reverse();\n\n const rewrites = uniqueBy(\n flat.filter(({ route }) => route.type === 'rewrite'),\n ({ normalizedContextKey }) => normalizedContextKey\n )\n .map((rewrite) => {\n rewrite.absoluteRoutePath =\n flat.find(({ route }) => route.contextKey === rewrite.route.destinationContextKey)\n ?.normalizedContextKey ?? '/';\n\n return rewrite;\n })\n .reverse();\n\n const standardRoutes = otherRoutes.filter(({ route }) => !isNotFoundRoute(route));\n const notFoundRoutes = otherRoutes.filter(({ route }) => isNotFoundRoute(route));\n\n const manifest: RoutesManifest = {\n apiRoutes: getMatchableManifestForPaths(apiRoutes),\n htmlRoutes: getMatchableManifestForPaths(standardRoutes),\n notFoundRoutes: getMatchableManifestForPaths(notFoundRoutes),\n redirects: getMatchableManifestForPaths(redirects),\n rewrites: getMatchableManifestForPaths(rewrites),\n };\n\n if (route?.middleware) {\n manifest.middleware = {\n file: route.middleware.contextKey,\n };\n }\n\n if (options?.headers) {\n manifest.headers = options.headers;\n }\n\n return manifest;\n}\n\nfunction getMatchableManifestForPaths(paths: FlatNode[]): RouteInfo[] {\n return paths.map(({ normalizedContextKey, absoluteRoutePath, route }) => {\n const matcher = getNamedRouteRegex(normalizedContextKey, absoluteRoutePath, route.contextKey);\n\n if (route.generated) {\n matcher.generated = true;\n }\n\n if (route.permanent) {\n matcher.permanent = true;\n }\n\n if (route.methods) {\n matcher.methods = route.methods;\n }\n\n return matcher;\n });\n}\n\nfunction getNamedRouteRegex(\n normalizedRoute: string,\n page: string,\n file: string\n): RouteInfo {\n const result = getNamedParametrizedRoute(normalizedRoute);\n return {\n file,\n page,\n namedRegex: `^${result.namedParameterizedRoute}(?:/)?$`,\n routeKeys: result.routeKeys,\n };\n}\n\nfunction getNormalizedContextKey(contextKey: string): string {\n return getContextKey(contextKey).replace(/\\/index$/, '') ?? '/';\n}\n"]} \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/middleware.d.ts b/packages/@expo/router-server/build/rsc/middleware.d.ts index 33f88c45ff4b85..c6eb4219e8cf5a 100644 --- a/packages/@expo/router-server/build/rsc/middleware.d.ts +++ b/packages/@expo/router-server/build/rsc/middleware.d.ts @@ -1,6 +1,6 @@ import type { RenderRscArgs } from 'expo-server/private'; type ImportMap = { - router: () => Promise; + router: () => Promise; }; export declare function renderRscWithImportsAsync(distFolder: string, imports: ImportMap, { body, platform, searchParams, config, method, input, contentType, headers }: RenderRscArgs): Promise>; export declare function renderRscAsync(distFolder: string, args: RenderRscArgs): Promise>; diff --git a/packages/@expo/router-server/build/rsc/middleware.d.ts.map b/packages/@expo/router-server/build/rsc/middleware.d.ts.map index 039dff75d911a0..f7201c29a2fe14 100644 --- a/packages/@expo/router-server/build/rsc/middleware.d.ts.map +++ b/packages/@expo/router-server/build/rsc/middleware.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/rsc/middleware.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAkEzD,KAAK,SAAS,GAAG;IACf,MAAM,EAAE,MAAM,OAAO,CAAC,cAAc,6BAA6B,CAAC,CAAC,CAAC;CACrE,CAAC;AAEF,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,SAAS,EAClB,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,aAAa,GAC3F,OAAO,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAqE9B;AAED,wBAAsB,cAAc,CAClC,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAY9B"} \ No newline at end of file +{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/rsc/middleware.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAkEzD,KAAK,SAAS,GAAG;IACf,MAAM,EAAE,MAAM,OAAO,CAAC,OAAO,UAAU,EAAE,YAAY,CAAC,CAAC;CACxD,CAAC;AAEF,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,SAAS,EAClB,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,aAAa,GAC3F,OAAO,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAqE9B;AAED,wBAAsB,cAAc,CAClC,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAY9B"} \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/middleware.js.map b/packages/@expo/router-server/build/rsc/middleware.js.map index d79457af8b56da..f91abaef555f45 100644 --- a/packages/@expo/router-server/build/rsc/middleware.js.map +++ b/packages/@expo/router-server/build/rsc/middleware.js.map @@ -1 +1 @@ -{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/rsc/middleware.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Copyright © 2024 650 Industries.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n// This module is bundled with Metro in web/react-server mode and redirects to platform specific renderers.\nimport Constants from 'expo-constants';\nimport type { RenderRscArgs } from 'expo-server/private';\nimport path from 'node:path';\n\nimport { renderRsc } from './rsc-renderer';\nimport { createDebug } from '../utils/debug';\n\ndeclare const $$require_external: typeof require;\n\nconst debug = createDebug('expo:router:server:rsc-renderer');\n\n// Tracking the implementation in expo/cli's MetroBundlerDevServer\nconst rscRenderContext = new Map();\n\nfunction serverRequire(...targetOutputModulePath: string[]): T {\n // NOTE(@kitten): This `__dirname` will be located in the output file system, e.g. `dist/server/*`\n const filePath = path.join(__dirname, ...targetOutputModulePath);\n return $$require_external(filePath);\n}\n\nfunction getRscRenderContext(platform: string) {\n // NOTE(EvanBacon): We memoize this now that there's a persistent server storage cache for Server Actions.\n if (rscRenderContext.has(platform)) {\n return rscRenderContext.get(platform)!;\n }\n\n const context = {};\n\n rscRenderContext.set(platform, context);\n return context;\n}\n\nfunction getServerActionManifest(\n _distFolder: string,\n platform: string\n): Record<\n // Input ID\n string,\n [\n // Metro ID\n string,\n // Chunk location.\n string,\n ]\n> {\n const filePath = `../../rsc/${platform}/action-manifest.js`;\n return serverRequire(filePath);\n}\n\nfunction getSSRManifest(\n _distFolder: string,\n platform: string\n): Record<\n // Input ID\n string,\n [\n // Metro ID\n string,\n // Chunk location.\n string,\n ]\n> {\n const filePath = `../../rsc/${platform}/ssr-manifest.js`;\n return serverRequire(filePath);\n}\n\n// The import map allows us to use external modules from different bundling contexts.\ntype ImportMap = {\n router: () => Promise;\n};\n\nexport async function renderRscWithImportsAsync(\n distFolder: string,\n imports: ImportMap,\n { body, platform, searchParams, config, method, input, contentType, headers }: RenderRscArgs\n): Promise> {\n globalThis.__expo_platform_header = platform;\n if (method === 'POST' && !body) {\n throw new Error('Server request must be provided when method is POST (server actions)');\n }\n\n const context = getRscRenderContext(platform);\n context['__expo_requestHeaders'] = headers;\n\n const router = await imports.router();\n const entries = router.default({\n redirects: Constants.expoConfig?.extra?.router?.redirects,\n rewrites: Constants.expoConfig?.extra?.router?.rewrites,\n });\n\n const ssrManifest = getSSRManifest(distFolder, platform);\n const actionManifest = getServerActionManifest(distFolder, platform);\n return renderRsc(\n {\n body: body ?? undefined,\n context,\n config,\n input,\n contentType,\n decodedBody: searchParams.get('x-expo-params'),\n },\n {\n isExporting: true,\n\n resolveClientEntry(file: string, isServer: boolean) {\n debug('resolveClientEntry', file, { isServer });\n\n if (isServer) {\n const actionManifestFile = actionManifest[file];\n\n if (actionManifestFile == null) {\n throw new Error(\n `Could not find file in server action manifest: ${file}. ${JSON.stringify(actionManifest)}`\n );\n }\n\n const [id, chunk] = actionManifestFile;\n return {\n id,\n chunks: chunk ? [chunk] : [],\n };\n }\n\n const ssrManifestFile = ssrManifest[file];\n\n if (ssrManifestFile == null) {\n throw new Error(`Could not find file in SSR manifest: ${file}`);\n }\n\n const [id, chunk] = ssrManifestFile;\n return {\n id,\n chunks: chunk ? [chunk] : [],\n };\n },\n async loadServerModuleRsc(file) {\n debug('loadServerModuleRsc', file);\n // NOTE(@kitten): [WORKAROUND] Assumes __dirname is at `dist/server/_expo/functions/_flight`\n return serverRequire('../../../', file);\n },\n\n entries: entries!,\n }\n );\n}\n\nexport async function renderRscAsync(\n distFolder: string,\n args: RenderRscArgs\n): Promise> {\n const platform = args.platform;\n return renderRscWithImportsAsync(\n distFolder,\n {\n router: () => {\n // NOTE(@kitten): [WORKAROUND] Assumes __dirname is at `dist/server/_expo/functions/_flight`\n return serverRequire(`../../rsc/${platform}/router.js`);\n },\n },\n args\n );\n}\n"]} \ No newline at end of file +{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/rsc/middleware.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Copyright © 2024 650 Industries.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n// This module is bundled with Metro in web/react-server mode and redirects to platform specific renderers.\nimport Constants from 'expo-constants';\nimport type { RenderRscArgs } from 'expo-server/private';\nimport path from 'node:path';\n\nimport { renderRsc } from './rsc-renderer';\nimport { createDebug } from '../utils/debug';\n\ndeclare const $$require_external: typeof require;\n\nconst debug = createDebug('expo:router:server:rsc-renderer');\n\n// Tracking the implementation in expo/cli's MetroBundlerDevServer\nconst rscRenderContext = new Map();\n\nfunction serverRequire(...targetOutputModulePath: string[]): T {\n // NOTE(@kitten): This `__dirname` will be located in the output file system, e.g. `dist/server/*`\n const filePath = path.join(__dirname, ...targetOutputModulePath);\n return $$require_external(filePath);\n}\n\nfunction getRscRenderContext(platform: string) {\n // NOTE(EvanBacon): We memoize this now that there's a persistent server storage cache for Server Actions.\n if (rscRenderContext.has(platform)) {\n return rscRenderContext.get(platform)!;\n }\n\n const context = {};\n\n rscRenderContext.set(platform, context);\n return context;\n}\n\nfunction getServerActionManifest(\n _distFolder: string,\n platform: string\n): Record<\n // Input ID\n string,\n [\n // Metro ID\n string,\n // Chunk location.\n string,\n ]\n> {\n const filePath = `../../rsc/${platform}/action-manifest.js`;\n return serverRequire(filePath);\n}\n\nfunction getSSRManifest(\n _distFolder: string,\n platform: string\n): Record<\n // Input ID\n string,\n [\n // Metro ID\n string,\n // Chunk location.\n string,\n ]\n> {\n const filePath = `../../rsc/${platform}/ssr-manifest.js`;\n return serverRequire(filePath);\n}\n\n// The import map allows us to use external modules from different bundling contexts.\ntype ImportMap = {\n router: () => Promise;\n};\n\nexport async function renderRscWithImportsAsync(\n distFolder: string,\n imports: ImportMap,\n { body, platform, searchParams, config, method, input, contentType, headers }: RenderRscArgs\n): Promise> {\n globalThis.__expo_platform_header = platform;\n if (method === 'POST' && !body) {\n throw new Error('Server request must be provided when method is POST (server actions)');\n }\n\n const context = getRscRenderContext(platform);\n context['__expo_requestHeaders'] = headers;\n\n const router = await imports.router();\n const entries = router.default({\n redirects: Constants.expoConfig?.extra?.router?.redirects,\n rewrites: Constants.expoConfig?.extra?.router?.rewrites,\n });\n\n const ssrManifest = getSSRManifest(distFolder, platform);\n const actionManifest = getServerActionManifest(distFolder, platform);\n return renderRsc(\n {\n body: body ?? undefined,\n context,\n config,\n input,\n contentType,\n decodedBody: searchParams.get('x-expo-params'),\n },\n {\n isExporting: true,\n\n resolveClientEntry(file: string, isServer: boolean) {\n debug('resolveClientEntry', file, { isServer });\n\n if (isServer) {\n const actionManifestFile = actionManifest[file];\n\n if (actionManifestFile == null) {\n throw new Error(\n `Could not find file in server action manifest: ${file}. ${JSON.stringify(actionManifest)}`\n );\n }\n\n const [id, chunk] = actionManifestFile;\n return {\n id,\n chunks: chunk ? [chunk] : [],\n };\n }\n\n const ssrManifestFile = ssrManifest[file];\n\n if (ssrManifestFile == null) {\n throw new Error(`Could not find file in SSR manifest: ${file}`);\n }\n\n const [id, chunk] = ssrManifestFile;\n return {\n id,\n chunks: chunk ? [chunk] : [],\n };\n },\n async loadServerModuleRsc(file) {\n debug('loadServerModuleRsc', file);\n // NOTE(@kitten): [WORKAROUND] Assumes __dirname is at `dist/server/_expo/functions/_flight`\n return serverRequire('../../../', file);\n },\n\n entries: entries!,\n }\n );\n}\n\nexport async function renderRscAsync(\n distFolder: string,\n args: RenderRscArgs\n): Promise> {\n const platform = args.platform;\n return renderRscWithImportsAsync(\n distFolder,\n {\n router: () => {\n // NOTE(@kitten): [WORKAROUND] Assumes __dirname is at `dist/server/_expo/functions/_flight`\n return serverRequire(`../../rsc/${platform}/router.js`);\n },\n },\n args\n );\n}\n"]} \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/path.d.ts b/packages/@expo/router-server/build/rsc/path.d.ts index 3d57d139d0ff39..6fd5a790b47f2b 100644 --- a/packages/@expo/router-server/build/rsc/path.d.ts +++ b/packages/@expo/router-server/build/rsc/path.d.ts @@ -14,21 +14,4 @@ export declare const filePathToFileURL: (filePath: string) => string; export declare const fileURLToFilePath: (fileURL: string) => string; export declare const joinPath: (...paths: string[]) => string; export declare const extname: (filePath: string) => string; -export type PathSpecItem = { - type: 'literal'; - name: string; -} | { - type: 'group'; - name?: string; -} | { - type: 'wildcard'; - name?: string; -}; -export type PathSpec = readonly PathSpecItem[]; -export declare const parsePathWithSlug: (path: string) => PathSpec; -export declare const getPathMapping: (pathSpec: PathSpec, pathname: string) => Record | null; -/** - * Transform a path spec to a regular expression. - */ -export declare const path2regexp: (path: PathSpec) => string; //# sourceMappingURL=path.d.ts.map \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/path.d.ts.map b/packages/@expo/router-server/build/rsc/path.d.ts.map index c28ff4cf0d18a7..e9903b48734655 100644 --- a/packages/@expo/router-server/build/rsc/path.d.ts.map +++ b/packages/@expo/router-server/build/rsc/path.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/rsc/path.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAYH,eAAO,MAAM,wBAAwB,GAAI,UAAU,MAAM,WAQxD,CAAC;AAEF,eAAO,MAAM,0BAA0B,GAAI,UAAU,MAAM,WAK1D,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,UAAU,MAAM,WAAoC,CAAC;AAEvF,yDAAyD;AACzD,eAAO,MAAM,iBAAiB,GAAI,SAAS,MAAM,WAahD,CAAC;AAGF,eAAO,MAAM,QAAQ,GAAI,GAAG,OAAO,MAAM,EAAE,WAmB1C,CAAC;AAEF,eAAO,MAAM,OAAO,GAAI,UAAU,MAAM,WAGvC,CAAC;AAEF,MAAM,MAAM,YAAY,GACpB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AACxC,MAAM,MAAM,QAAQ,GAAG,SAAS,YAAY,EAAE,CAAC;AAE/C,eAAO,MAAM,iBAAiB,GAAI,MAAM,MAAM,KAAG,QAiB3C,CAAC;AAEP,eAAO,MAAM,cAAc,GACzB,UAAU,QAAQ,EAClB,UAAU,MAAM,KACf,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,GAAG,IAgDtC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,QAAQ,WAWzC,CAAC"} \ No newline at end of file +{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/rsc/path.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAYH,eAAO,MAAM,wBAAwB,GAAI,UAAU,MAAM,WAQxD,CAAC;AAEF,eAAO,MAAM,0BAA0B,GAAI,UAAU,MAAM,WAK1D,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,UAAU,MAAM,WAAoC,CAAC;AAEvF,yDAAyD;AACzD,eAAO,MAAM,iBAAiB,GAAI,SAAS,MAAM,WAahD,CAAC;AAGF,eAAO,MAAM,QAAQ,GAAI,GAAG,OAAO,MAAM,EAAE,WAmB1C,CAAC;AAEF,eAAO,MAAM,OAAO,GAAI,UAAU,MAAM,WAGvC,CAAC"} \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/path.js b/packages/@expo/router-server/build/rsc/path.js index b1afd281e8775b..5a797c93309213 100644 --- a/packages/@expo/router-server/build/rsc/path.js +++ b/packages/@expo/router-server/build/rsc/path.js @@ -9,7 +9,7 @@ * https://github.com/dai-shi/waku/blob/32d52242c1450b5f5965860e671ff73c42da8bd0/packages/waku/src/lib/utils/path.ts#L1 */ Object.defineProperty(exports, "__esModule", { value: true }); -exports.path2regexp = exports.getPathMapping = exports.parsePathWithSlug = exports.extname = exports.joinPath = exports.fileURLToFilePath = exports.filePathToFileURL = exports.decodeFilePathFromAbsolute = exports.encodeFilePathToAbsolute = void 0; +exports.extname = exports.joinPath = exports.fileURLToFilePath = exports.filePathToFileURL = exports.decodeFilePathFromAbsolute = exports.encodeFilePathToAbsolute = void 0; // Terminology: // - filePath: posix-like file path, e.g. `/foo/bar.js` or `c:/foo/bar.js` // This is used by Vite. @@ -81,94 +81,4 @@ const extname = (filePath) => { return index > 0 ? filePath.slice(index) : ''; }; exports.extname = extname; -const parsePathWithSlug = (path) => path - .split('/') - .filter(Boolean) - .map((name) => { - let type = 'literal'; - const isSlug = name.startsWith('[') && name.endsWith(']'); - if (isSlug) { - type = 'group'; - name = name.slice(1, -1); - } - const isWildcard = name.startsWith('...'); - if (isWildcard) { - type = 'wildcard'; - name = name.slice(3); - } - return { type, name }; -}); -exports.parsePathWithSlug = parsePathWithSlug; -const getPathMapping = (pathSpec, pathname) => { - const actual = pathname.split('/').filter(Boolean); - if (pathSpec.length > actual.length) { - return null; - } - const mapping = {}; - let wildcardStartIndex = -1; - for (let i = 0; i < pathSpec.length; i++) { - const { type, name } = pathSpec[i]; - if (type === 'literal') { - if (name !== actual[i]) { - return null; - } - } - else if (type === 'wildcard') { - wildcardStartIndex = i; - break; - } - else if (name) { - mapping[name] = actual[i]; - } - } - if (wildcardStartIndex === -1) { - if (pathSpec.length !== actual.length) { - return null; - } - return mapping; - } - let wildcardEndIndex = -1; - for (let i = 0; i < pathSpec.length; i++) { - const { type, name } = pathSpec[pathSpec.length - i - 1]; - if (type === 'literal') { - if (name !== actual[actual.length - i - 1]) { - return null; - } - } - else if (type === 'wildcard') { - wildcardEndIndex = actual.length - i - 1; - break; - } - else if (name) { - mapping[name] = actual[actual.length - i - 1]; - } - } - if (wildcardStartIndex === -1 || wildcardEndIndex === -1) { - throw new Error('Invalid wildcard path'); - } - const wildcardName = pathSpec[wildcardStartIndex].name; - if (wildcardName) { - mapping[wildcardName] = actual.slice(wildcardStartIndex, wildcardEndIndex + 1); - } - return mapping; -}; -exports.getPathMapping = getPathMapping; -/** - * Transform a path spec to a regular expression. - */ -const path2regexp = (path) => { - const parts = path.map(({ type, name }) => { - if (type === 'literal') { - return name; - } - else if (type === 'group') { - return `([^/]+)`; - } - else { - return `(.*)`; - } - }); - return `^/${parts.join('/')}$`; -}; -exports.path2regexp = path2regexp; //# sourceMappingURL=path.js.map \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/path.js.map b/packages/@expo/router-server/build/rsc/path.js.map index 9971071f25270c..bc60f548eee971 100644 --- a/packages/@expo/router-server/build/rsc/path.js.map +++ b/packages/@expo/router-server/build/rsc/path.js.map @@ -1 +1 @@ -{"version":3,"file":"path.js","sourceRoot":"","sources":["../../src/rsc/path.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Copyright © 2024 650 Industries.\n * Copyright © 2024 2023 Daishi Kato\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * https://github.com/dai-shi/waku/blob/32d52242c1450b5f5965860e671ff73c42da8bd0/packages/waku/src/lib/utils/path.ts#L1\n */\n\n// Terminology:\n// - filePath: posix-like file path, e.g. `/foo/bar.js` or `c:/foo/bar.js`\n// This is used by Vite.\n// - fileURL: file URL, e.g. `file:///foo/bar.js` or `file:///c:/foo/bar.js`\n// This is used by import().\n// - osPath: os dependent path, e.g. `/foo/bar.js` or `c:\\foo\\bar.js`\n// This is used by node:fs.\n\nconst ABSOLUTE_WIN32_PATH_REGEXP = /^\\/[a-zA-Z]:\\//;\n\nexport const encodeFilePathToAbsolute = (filePath: string) => {\n if (ABSOLUTE_WIN32_PATH_REGEXP.test(filePath)) {\n throw new Error('Unsupported absolute file path');\n }\n if (filePath.startsWith('/')) {\n return filePath;\n }\n return '/' + filePath;\n};\n\nexport const decodeFilePathFromAbsolute = (filePath: string) => {\n if (ABSOLUTE_WIN32_PATH_REGEXP.test(filePath)) {\n return filePath.slice(1);\n }\n return filePath;\n};\n\nexport const filePathToFileURL = (filePath: string) => 'file://' + encodeURI(filePath);\n\n/** Return the original \"osPath\" based on the file URL */\nexport const fileURLToFilePath = (fileURL: string) => {\n if (!fileURL.startsWith('file://')) {\n throw new Error('Not a file URL');\n }\n\n const filePath = decodeURI(fileURL.slice('file://'.length));\n\n // File URLs are always formatted in POSIX, using a leading `/` (URL pathname) separator.\n // On POSIX systems, this leading `/` is the root directory, which is valid for absolute file paths.\n // On UNIX systems, this leading `/` needs to be stripped, and the actual UNIX formatted path is returned - to match Metro's behavior\n return ABSOLUTE_WIN32_PATH_REGEXP.test(filePath)\n ? filePath.slice(1).replace(/\\//g, '\\\\')\n : filePath;\n};\n\n// for filePath\nexport const joinPath = (...paths: string[]) => {\n const isAbsolute = paths[0]?.startsWith('/');\n const items = ([] as string[]).concat(...paths.map((path) => path.split('/')));\n let i = 0;\n while (i < items.length) {\n if (items[i] === '.' || items[i] === '') {\n items.splice(i, 1);\n } else if (items[i] === '..') {\n if (i > 0) {\n items.splice(i - 1, 2);\n --i;\n } else {\n items.splice(i, 1);\n }\n } else {\n ++i;\n }\n }\n return (isAbsolute ? '/' : '') + items.join('/') || '.';\n};\n\nexport const extname = (filePath: string) => {\n const index = filePath.lastIndexOf('.');\n return index > 0 ? filePath.slice(index) : '';\n};\n\nexport type PathSpecItem =\n | { type: 'literal'; name: string }\n | { type: 'group'; name?: string }\n | { type: 'wildcard'; name?: string };\nexport type PathSpec = readonly PathSpecItem[];\n\nexport const parsePathWithSlug = (path: string): PathSpec =>\n path\n .split('/')\n .filter(Boolean)\n .map((name) => {\n let type: 'literal' | 'group' | 'wildcard' = 'literal';\n const isSlug = name.startsWith('[') && name.endsWith(']');\n if (isSlug) {\n type = 'group';\n name = name.slice(1, -1);\n }\n const isWildcard = name.startsWith('...');\n if (isWildcard) {\n type = 'wildcard';\n name = name.slice(3);\n }\n return { type, name };\n });\n\nexport const getPathMapping = (\n pathSpec: PathSpec,\n pathname: string\n): Record | null => {\n const actual = pathname.split('/').filter(Boolean);\n if (pathSpec.length > actual.length) {\n return null;\n }\n const mapping: Record = {};\n let wildcardStartIndex = -1;\n for (let i = 0; i < pathSpec.length; i++) {\n const { type, name } = pathSpec[i]!;\n if (type === 'literal') {\n if (name !== actual[i]) {\n return null;\n }\n } else if (type === 'wildcard') {\n wildcardStartIndex = i;\n break;\n } else if (name) {\n mapping[name] = actual[i]!;\n }\n }\n if (wildcardStartIndex === -1) {\n if (pathSpec.length !== actual.length) {\n return null;\n }\n return mapping;\n }\n let wildcardEndIndex = -1;\n for (let i = 0; i < pathSpec.length; i++) {\n const { type, name } = pathSpec[pathSpec.length - i - 1]!;\n if (type === 'literal') {\n if (name !== actual[actual.length - i - 1]) {\n return null;\n }\n } else if (type === 'wildcard') {\n wildcardEndIndex = actual.length - i - 1;\n break;\n } else if (name) {\n mapping[name] = actual[actual.length - i - 1]!;\n }\n }\n if (wildcardStartIndex === -1 || wildcardEndIndex === -1) {\n throw new Error('Invalid wildcard path');\n }\n const wildcardName = pathSpec[wildcardStartIndex]!.name;\n if (wildcardName) {\n mapping[wildcardName] = actual.slice(wildcardStartIndex, wildcardEndIndex + 1);\n }\n return mapping;\n};\n\n/**\n * Transform a path spec to a regular expression.\n */\nexport const path2regexp = (path: PathSpec) => {\n const parts = path.map(({ type, name }) => {\n if (type === 'literal') {\n return name;\n } else if (type === 'group') {\n return `([^/]+)`;\n } else {\n return `(.*)`;\n }\n });\n return `^/${parts.join('/')}$`;\n};\n"]} \ No newline at end of file +{"version":3,"file":"path.js","sourceRoot":"","sources":["../../src/rsc/path.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Copyright © 2024 650 Industries.\n * Copyright © 2024 2023 Daishi Kato\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * https://github.com/dai-shi/waku/blob/32d52242c1450b5f5965860e671ff73c42da8bd0/packages/waku/src/lib/utils/path.ts#L1\n */\n\n// Terminology:\n// - filePath: posix-like file path, e.g. `/foo/bar.js` or `c:/foo/bar.js`\n// This is used by Vite.\n// - fileURL: file URL, e.g. `file:///foo/bar.js` or `file:///c:/foo/bar.js`\n// This is used by import().\n// - osPath: os dependent path, e.g. `/foo/bar.js` or `c:\\foo\\bar.js`\n// This is used by node:fs.\n\nconst ABSOLUTE_WIN32_PATH_REGEXP = /^\\/[a-zA-Z]:\\//;\n\nexport const encodeFilePathToAbsolute = (filePath: string) => {\n if (ABSOLUTE_WIN32_PATH_REGEXP.test(filePath)) {\n throw new Error('Unsupported absolute file path');\n }\n if (filePath.startsWith('/')) {\n return filePath;\n }\n return '/' + filePath;\n};\n\nexport const decodeFilePathFromAbsolute = (filePath: string) => {\n if (ABSOLUTE_WIN32_PATH_REGEXP.test(filePath)) {\n return filePath.slice(1);\n }\n return filePath;\n};\n\nexport const filePathToFileURL = (filePath: string) => 'file://' + encodeURI(filePath);\n\n/** Return the original \"osPath\" based on the file URL */\nexport const fileURLToFilePath = (fileURL: string) => {\n if (!fileURL.startsWith('file://')) {\n throw new Error('Not a file URL');\n }\n\n const filePath = decodeURI(fileURL.slice('file://'.length));\n\n // File URLs are always formatted in POSIX, using a leading `/` (URL pathname) separator.\n // On POSIX systems, this leading `/` is the root directory, which is valid for absolute file paths.\n // On UNIX systems, this leading `/` needs to be stripped, and the actual UNIX formatted path is returned - to match Metro's behavior\n return ABSOLUTE_WIN32_PATH_REGEXP.test(filePath)\n ? filePath.slice(1).replace(/\\//g, '\\\\')\n : filePath;\n};\n\n// for filePath\nexport const joinPath = (...paths: string[]) => {\n const isAbsolute = paths[0]?.startsWith('/');\n const items = ([] as string[]).concat(...paths.map((path) => path.split('/')));\n let i = 0;\n while (i < items.length) {\n if (items[i] === '.' || items[i] === '') {\n items.splice(i, 1);\n } else if (items[i] === '..') {\n if (i > 0) {\n items.splice(i - 1, 2);\n --i;\n } else {\n items.splice(i, 1);\n }\n } else {\n ++i;\n }\n }\n return (isAbsolute ? '/' : '') + items.join('/') || '.';\n};\n\nexport const extname = (filePath: string) => {\n const index = filePath.lastIndexOf('.');\n return index > 0 ? filePath.slice(index) : '';\n};\n"]} \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/router/create-expo-pages.d.ts b/packages/@expo/router-server/build/rsc/router/create-expo-pages.d.ts deleted file mode 100644 index b0d3fea342da5b..00000000000000 --- a/packages/@expo/router-server/build/rsc/router/create-expo-pages.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { GetRoutesOptions } from 'expo-router/internal/routing'; -import { createPages } from './create-pages'; -import type { EntriesDev } from '../server'; -type CreatePagesFn = Parameters[0]; -type CreatePagesFns = Parameters[0]; -type CreatePagesOptions = Parameters[1] & { - getRouteOptions?: GetRoutesOptions; -}; -/** - * Wrapper around `createPages` to pass data from the server to the fn - * - * This is separated from the `createPages` function allowing us to keep the createPages - * in sync with the original Waku implementation. - * - * @param fn - * @returns - */ -export declare function createExpoPages(fn: (fn: CreatePagesFns, options: CreatePagesOptions) => ReturnType): (getRouteOptions?: GetRoutesOptions) => EntriesDev; -export {}; -//# sourceMappingURL=create-expo-pages.d.ts.map \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/router/create-expo-pages.d.ts.map b/packages/@expo/router-server/build/rsc/router/create-expo-pages.d.ts.map deleted file mode 100644 index 54a2f256f5e9f1..00000000000000 --- a/packages/@expo/router-server/build/rsc/router/create-expo-pages.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"create-expo-pages.d.ts","sourceRoot":"","sources":["../../../src/rsc/router/create-expo-pages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAErE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE5C,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;AACvD,KAAK,cAAc,GAAG,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;AACnD,KAAK,kBAAkB,GAAG,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,GAAG;IACvD,eAAe,CAAC,EAAE,gBAAgB,CAAC;CACpC,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,EAAE,EAAE,CAAC,EAAE,EAAE,cAAc,EAAE,OAAO,EAAE,kBAAkB,KAAK,UAAU,CAAC,aAAa,CAAC,IAE1E,kBAAkB,gBAAgB,KAAG,UAAU,CAKxD"} \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/router/create-expo-pages.js b/packages/@expo/router-server/build/rsc/router/create-expo-pages.js deleted file mode 100644 index 3b41fff6a6b4ed..00000000000000 --- a/packages/@expo/router-server/build/rsc/router/create-expo-pages.js +++ /dev/null @@ -1,21 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.createExpoPages = createExpoPages; -const create_pages_1 = require("./create-pages"); -/** - * Wrapper around `createPages` to pass data from the server to the fn - * - * This is separated from the `createPages` function allowing us to keep the createPages - * in sync with the original Waku implementation. - * - * @param fn - * @returns - */ -function createExpoPages(fn) { - return (getRouteOptions) => { - return { - default: (0, create_pages_1.createPages)((a, b) => fn(a, { ...b, getRouteOptions })), - }; - }; -} -//# sourceMappingURL=create-expo-pages.js.map \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/router/create-expo-pages.js.map b/packages/@expo/router-server/build/rsc/router/create-expo-pages.js.map deleted file mode 100644 index dcc4db14838e46..00000000000000 --- a/packages/@expo/router-server/build/rsc/router/create-expo-pages.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"create-expo-pages.js","sourceRoot":"","sources":["../../../src/rsc/router/create-expo-pages.ts"],"names":[],"mappings":";;AAoBA,0CAQC;AA1BD,iDAA6C;AAS7C;;;;;;;;GAQG;AACH,SAAgB,eAAe,CAC7B,EAAkF;IAElF,OAAO,CAAC,eAAkC,EAAc,EAAE;QACxD,OAAO;YACL,OAAO,EAAE,IAAA,0BAAW,EAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,eAAe,EAAE,CAAC,CAAC;SACjE,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import type { GetRoutesOptions } from 'expo-router/internal/routing';\n\nimport { createPages } from './create-pages';\nimport type { EntriesDev } from '../server';\n\ntype CreatePagesFn = Parameters[0];\ntype CreatePagesFns = Parameters[0];\ntype CreatePagesOptions = Parameters[1] & {\n getRouteOptions?: GetRoutesOptions;\n};\n\n/**\n * Wrapper around `createPages` to pass data from the server to the fn\n *\n * This is separated from the `createPages` function allowing us to keep the createPages\n * in sync with the original Waku implementation.\n *\n * @param fn\n * @returns\n */\nexport function createExpoPages(\n fn: (fn: CreatePagesFns, options: CreatePagesOptions) => ReturnType\n) {\n return (getRouteOptions?: GetRoutesOptions): EntriesDev => {\n return {\n default: createPages((a, b) => fn(a, { ...b, getRouteOptions })),\n };\n };\n}\n"]} \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/router/create-pages.d.ts b/packages/@expo/router-server/build/rsc/router/create-pages.d.ts deleted file mode 100644 index 2492c5f1b95ded..00000000000000 --- a/packages/@expo/router-server/build/rsc/router/create-pages.d.ts +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Copyright © 2024 650 Industries. - * Copyright © 2024 2023 Daishi Kato - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * https://github.com/dai-shi/waku/blob/3d1cc7d714b67b142c847e879c30f0724fc457a7/packages/waku/src/router/create-pages.ts#L1 - */ -import type { RouteProps } from 'expo-router/internal/rsc'; -import type { FunctionComponent, ReactNode } from 'react'; -import type { BuildConfig } from '../server'; -/** - * Type version of `String.prototype.split()`. Splits the first string argument by the second string argument - * @example - * ```ts - * // ['a', 'b', 'c'] - * type Case1 = Split<'abc', ''> - * // ['a', 'b', 'c'] - * type Case2 = Split<'a,b,c', ','> - * ``` - */ -type Split = string extends Str ? string[] : '' extends Str ? [] : Str extends `${infer T}${Del}${infer U}` ? [T, ...Split] : [Str]; -/** Assumes that the path is a part of a slug path. */ -type IsValidPathItem = T extends `/${infer _}` ? false : T extends '[]' | '' ? false : true; -/** - * This is a helper type to check if a path is valid in a slug path. - */ -export type IsValidPathInSlugPath = T extends `/${infer L}/${infer R}` ? IsValidPathItem extends true ? IsValidPathInSlugPath<`/${R}`> : false : T extends `/${infer U}` ? IsValidPathItem : false; -/** Checks if a particular slug name exists in a path. */ -export type HasSlugInPath = T extends `/[${K}]/${infer _}` ? true : T extends `/${infer _}/${infer U}` ? HasSlugInPath<`/${U}`, K> : T extends `/[${K}]` ? true : false; -export type HasWildcardInPath = T extends `/[...${string}]/${string}` ? true : T extends `/${infer _}/${infer U}` ? HasWildcardInPath<`/${U}`> : T extends `/[...${string}]` ? true : false; -export type PathWithSlug = IsValidPathInSlugPath extends true ? (HasSlugInPath extends true ? T : never) : never; -type _GetSlugs, Result extends string[] = []> = SplitRoute extends [] ? Result : SplitRoute extends [`${infer MaybeSlug}`, ...infer Rest] ? Rest extends string[] ? MaybeSlug extends `[${infer Slug}]` ? _GetSlugs : _GetSlugs : never : Result; -export type GetSlugs = _GetSlugs; -export type StaticSlugRoutePathsTuple, Result extends string[] = []> = Slugs extends [] ? Result : Slugs extends [infer _, ...infer Rest] ? StaticSlugRoutePathsTuple : never; -type StaticSlugRoutePaths = HasWildcardInPath extends true ? string[] | string[][] : StaticSlugRoutePathsTuple extends [string] ? string[] : StaticSlugRoutePathsTuple[]; -export type PathWithoutSlug = T extends '/' ? T : IsValidPathInSlugPath extends true ? HasSlugInPath extends true ? never : T : never; -type PathWithStaticSlugs = T extends `/` ? T : IsValidPathInSlugPath extends true ? T : never; -export type PathWithWildcard = PathWithSlug; -export type CreatePage = (page: ({ - render: 'static'; - path: PathWithoutSlug; - component: FunctionComponent; -} | { - render: 'static'; - path: PathWithStaticSlugs; - staticPaths: StaticSlugRoutePaths; - component: FunctionComponent>; -} | { - render: 'dynamic'; - path: PathWithoutSlug; - component: FunctionComponent; -} | { - render: 'dynamic'; - path: PathWithWildcard; - component: FunctionComponent & Record>; -}) & { - unstable_disableSSR?: boolean; -}) => void; -export type CreateLayout = (layout: { - render: 'static' | 'dynamic'; - path: PathWithoutSlug; - component: FunctionComponent & { - children: ReactNode; - }>; -}) => void; -export declare function createPages(fn: (fns: { - createPage: CreatePage; - createLayout: CreateLayout; - unstable_setBuildData: (path: string, data: unknown) => void; -}, opts: { - unstable_buildConfig: BuildConfig | undefined; -}) => Promise): { - renderEntries: import("../server").RenderEntries; - getBuildConfig: import("../server").GetBuildConfig | undefined; - getSsrConfig: import("../server").GetSsrConfig | undefined; -}; -export {}; -//# sourceMappingURL=create-pages.d.ts.map \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/router/create-pages.d.ts.map b/packages/@expo/router-server/build/rsc/router/create-pages.d.ts.map deleted file mode 100644 index d8b26997acf666..00000000000000 --- a/packages/@expo/router-server/build/rsc/router/create-pages.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"create-pages.d.ts","sourceRoot":"","sources":["../../../src/rsc/router/create-pages.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/router/create-pages.js b/packages/@expo/router-server/build/rsc/router/create-pages.js deleted file mode 100644 index 2b3c4eff9b0959..00000000000000 --- a/packages/@expo/router-server/build/rsc/router/create-pages.js +++ /dev/null @@ -1,232 +0,0 @@ -"use strict"; -/* eslint-disable @typescript-eslint/no-unused-vars */ -/** - * Copyright © 2024 650 Industries. - * Copyright © 2024 2023 Daishi Kato - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * https://github.com/dai-shi/waku/blob/3d1cc7d714b67b142c847e879c30f0724fc457a7/packages/waku/src/router/create-pages.ts#L1 - */ -Object.defineProperty(exports, "__esModule", { value: true }); -exports.createPages = createPages; -const react_1 = require("react"); -const defineRouter_1 = require("./defineRouter"); -const path_1 = require("../path"); -const hasPathSpecPrefix = (prefix, path) => { - for (let i = 0; i < prefix.length; i++) { - if (i >= path.length || - prefix[i].type !== path[i].type || - prefix[i].name !== path[i].name) { - return false; - } - } - return true; -}; -const sanitizeSlug = (slug) => slug.replace(/\./g, '').replace(/ /g, '-'); -function createPages(fn) { - let configured = false; - // TODO I think there's room for improvement to refactor these structures - const staticPathSet = new Set(); - const dynamicPagePathMap = new Map(); - const wildcardPagePathMap = new Map(); - const dynamicLayoutPathMap = new Map(); - const staticComponentMap = new Map(); - const noSsrSet = new WeakSet(); - const buildDataMap = new Map(); - const registerStaticComponent = (id, component) => { - if (staticComponentMap.has(id) && staticComponentMap.get(id) !== component) { - throw new Error(`Duplicated component for: ${id}`); - } - staticComponentMap.set(id, component); - }; - const createPage = (page) => { - if (configured) { - throw new Error('no longer available'); - } - const pathSpec = (0, path_1.parsePathWithSlug)(page.path); - if (page.unstable_disableSSR) { - noSsrSet.add(pathSpec); - } - const { numSlugs, numWildcards } = (() => { - let numSlugs = 0; - let numWildcards = 0; - for (const slug of pathSpec) { - if (slug.type !== 'literal') { - numSlugs++; - } - if (slug.type === 'wildcard') { - numWildcards++; - } - } - return { numSlugs, numWildcards }; - })(); - if (page.render === 'static' && numSlugs === 0) { - staticPathSet.add([page.path, pathSpec]); - const id = (0, path_1.joinPath)(page.path, 'page').replace(/^\//, ''); - registerStaticComponent(id, page.component); - } - else if (page.render === 'static' && numSlugs > 0 && 'staticPaths' in page) { - const staticPaths = page.staticPaths.map((item) => (Array.isArray(item) ? item : [item]).map(sanitizeSlug)); - for (const staticPath of staticPaths) { - if (staticPath.length !== numSlugs && numWildcards === 0) { - throw new Error('staticPaths does not match with slug pattern'); - } - const mapping = {}; - let slugIndex = 0; - const pathItems = []; - pathSpec.forEach(({ type, name }) => { - switch (type) { - case 'literal': - pathItems.push(name); - break; - case 'wildcard': - mapping[name] = staticPath.slice(slugIndex); - staticPath.slice(slugIndex++).forEach((slug) => { - pathItems.push(slug); - }); - break; - case 'group': - pathItems.push(staticPath[slugIndex++]); - mapping[name] = pathItems[pathItems.length - 1]; - break; - } - }); - staticPathSet.add([page.path, pathItems.map((name) => ({ type: 'literal', name }))]); - const id = (0, path_1.joinPath)(...pathItems, 'page'); - const WrappedComponent = (props) => (0, react_1.createElement)(page.component, { ...props, ...mapping }); - registerStaticComponent(id, WrappedComponent); - } - } - else if (page.render === 'dynamic' && numWildcards === 0) { - if (dynamicPagePathMap.has(page.path)) { - throw new Error(`Duplicated dynamic path: ${page.path}`); - } - dynamicPagePathMap.set(page.path, [pathSpec, page.component]); - } - else if (page.render === 'dynamic' && numWildcards === 1) { - if (wildcardPagePathMap.has(page.path)) { - throw new Error(`Duplicated dynamic path: ${page.path}`); - } - wildcardPagePathMap.set(page.path, [pathSpec, page.component]); - } - else { - throw new Error('Invalid page configuration: ' + page.path); - } - }; - const createLayout = (layout) => { - if (configured) { - throw new Error('no longer available'); - } - if (layout.render === 'static') { - const id = (0, path_1.joinPath)(layout.path, 'layout').replace(/^\//, ''); - registerStaticComponent(id, layout.component); - } - else if (layout.render === 'dynamic') { - if (dynamicLayoutPathMap.has(layout.path)) { - throw new Error(`Duplicated dynamic path: ${layout.path}`); - } - const pathSpec = (0, path_1.parsePathWithSlug)(layout.path); - dynamicLayoutPathMap.set(layout.path, [pathSpec, layout.component]); - } - else { - throw new Error('Invalid layout configuration'); - } - }; - const unstable_setBuildData = (path, data) => { - buildDataMap.set(path, data); - }; - let ready; - const configure = async (buildConfig) => { - if (!configured && !ready) { - ready = fn({ createPage, createLayout, unstable_setBuildData }, { unstable_buildConfig: buildConfig }); - await ready; - configured = true; - } - await ready; - }; - return (0, defineRouter_1.unstable_defineRouter)(async () => { - await configure(); - const paths = []; - for (const [path, pathSpec] of staticPathSet) { - const noSsr = noSsrSet.has(pathSpec); - const isStatic = (() => { - for (const [_, [layoutPathSpec]] of dynamicLayoutPathMap) { - if (hasPathSpecPrefix(layoutPathSpec, pathSpec)) { - return false; - } - } - return true; - })(); - paths.push({ - pattern: (0, path_1.path2regexp)((0, path_1.parsePathWithSlug)(path)), - path: pathSpec, - isStatic, - noSsr, - data: buildDataMap.get(path), - }); - } - for (const [path, [pathSpec]] of dynamicPagePathMap) { - const noSsr = noSsrSet.has(pathSpec); - paths.push({ - pattern: (0, path_1.path2regexp)((0, path_1.parsePathWithSlug)(path)), - path: pathSpec, - isStatic: false, - noSsr, - data: buildDataMap.get(path), - }); - } - for (const [path, [pathSpec]] of wildcardPagePathMap) { - const noSsr = noSsrSet.has(pathSpec); - paths.push({ - pattern: (0, path_1.path2regexp)((0, path_1.parsePathWithSlug)(path)), - path: pathSpec, - isStatic: false, - noSsr, - data: buildDataMap.get(path), - }); - } - return paths; - }, async (id, { unstable_setShouldSkip, unstable_buildConfig }) => { - await configure(unstable_buildConfig); - const staticComponent = staticComponentMap.get(id); - if (staticComponent) { - unstable_setShouldSkip([]); - return staticComponent; - } - for (const [_, [pathSpec, Component]] of dynamicPagePathMap) { - const mapping = (0, path_1.getPathMapping)([...pathSpec, { type: 'literal', name: 'page' }], id); - if (mapping) { - if (Object.keys(mapping).length === 0) { - unstable_setShouldSkip(); - return Component; - } - const WrappedComponent = (props) => (0, react_1.createElement)(Component, { ...props, ...mapping }); - unstable_setShouldSkip(); - return WrappedComponent; - } - } - for (const [_, [pathSpec, Component]] of wildcardPagePathMap) { - const mapping = (0, path_1.getPathMapping)([...pathSpec, { type: 'literal', name: 'page' }], id); - if (mapping) { - const WrappedComponent = (props) => (0, react_1.createElement)(Component, { ...props, ...mapping }); - unstable_setShouldSkip(); - return WrappedComponent; - } - } - for (const [_, [pathSpec, Component]] of dynamicLayoutPathMap) { - const mapping = (0, path_1.getPathMapping)([...pathSpec, { type: 'literal', name: 'layout' }], id); - if (mapping) { - if (Object.keys(mapping).length) { - throw new Error('[Bug] layout should not have slugs'); - } - unstable_setShouldSkip(); - return Component; - } - } - unstable_setShouldSkip([]); // negative cache - return null; // not found - }); -} -//# sourceMappingURL=create-pages.js.map \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/router/create-pages.js.map b/packages/@expo/router-server/build/rsc/router/create-pages.js.map deleted file mode 100644 index cd2906fc8bacaf..00000000000000 --- a/packages/@expo/router-server/build/rsc/router/create-pages.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"create-pages.js","sourceRoot":"","sources":["../../../src/rsc/router/create-pages.ts"],"names":[],"mappings":"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","sourcesContent":["/* eslint-disable @typescript-eslint/no-unused-vars */\n/**\n * Copyright © 2024 650 Industries.\n * Copyright © 2024 2023 Daishi Kato\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * https://github.com/dai-shi/waku/blob/3d1cc7d714b67b142c847e879c30f0724fc457a7/packages/waku/src/router/create-pages.ts#L1\n */\n\nimport type { RouteProps } from 'expo-router/internal/rsc';\nimport { createElement } from 'react';\nimport type { FunctionComponent, ReactNode } from 'react';\n\nimport { unstable_defineRouter } from './defineRouter';\nimport { joinPath, parsePathWithSlug, getPathMapping, path2regexp } from '../path';\nimport type { PathSpec } from '../path';\nimport type { BuildConfig } from '../server';\n\nconst hasPathSpecPrefix = (prefix: PathSpec, path: PathSpec) => {\n for (let i = 0; i < prefix.length; i++) {\n if (\n i >= path.length ||\n prefix[i]!.type !== path[i]!.type ||\n prefix[i]!.name !== path[i]!.name\n ) {\n return false;\n }\n }\n return true;\n};\n\nconst sanitizeSlug = (slug: string) => slug.replace(/\\./g, '').replace(/ /g, '-');\n\n// createPages API (a wrapper around unstable_defineRouter)\n\n/**\n * Type version of `String.prototype.split()`. Splits the first string argument by the second string argument\n * @example\n * ```ts\n * // ['a', 'b', 'c']\n * type Case1 = Split<'abc', ''>\n * // ['a', 'b', 'c']\n * type Case2 = Split<'a,b,c', ','>\n * ```\n */\ntype Split = string extends Str\n ? string[]\n : '' extends Str\n ? []\n : Str extends `${infer T}${Del}${infer U}`\n ? [T, ...Split]\n : [Str];\n\n/** Assumes that the path is a part of a slug path. */\ntype IsValidPathItem = T extends `/${infer _}` ? false : T extends '[]' | '' ? false : true;\n/**\n * This is a helper type to check if a path is valid in a slug path.\n */\nexport type IsValidPathInSlugPath = T extends `/${infer L}/${infer R}`\n ? IsValidPathItem extends true\n ? IsValidPathInSlugPath<`/${R}`>\n : false\n : T extends `/${infer U}`\n ? IsValidPathItem\n : false;\n/** Checks if a particular slug name exists in a path. */\nexport type HasSlugInPath = T extends `/[${K}]/${infer _}`\n ? true\n : T extends `/${infer _}/${infer U}`\n ? HasSlugInPath<`/${U}`, K>\n : T extends `/[${K}]`\n ? true\n : false;\n\nexport type HasWildcardInPath = T extends `/[...${string}]/${string}`\n ? true\n : T extends `/${infer _}/${infer U}`\n ? HasWildcardInPath<`/${U}`>\n : T extends `/[...${string}]`\n ? true\n : false;\n\nexport type PathWithSlug =\n IsValidPathInSlugPath extends true ? (HasSlugInPath extends true ? T : never) : never;\n\ntype _GetSlugs<\n Route extends string,\n SplitRoute extends string[] = Split,\n Result extends string[] = [],\n> = SplitRoute extends []\n ? Result\n : SplitRoute extends [`${infer MaybeSlug}`, ...infer Rest]\n ? Rest extends string[]\n ? MaybeSlug extends `[${infer Slug}]`\n ? _GetSlugs\n : _GetSlugs\n : never\n : Result;\n\nexport type GetSlugs = _GetSlugs;\n\nexport type StaticSlugRoutePathsTuple<\n T extends string,\n Slugs extends unknown[] = GetSlugs,\n Result extends string[] = [],\n> = Slugs extends []\n ? Result\n : Slugs extends [infer _, ...infer Rest]\n ? StaticSlugRoutePathsTuple\n : never;\n\ntype StaticSlugRoutePaths =\n HasWildcardInPath extends true\n ? string[] | string[][]\n : StaticSlugRoutePathsTuple extends [string]\n ? string[]\n : StaticSlugRoutePathsTuple[];\n\nexport type PathWithoutSlug = T extends '/'\n ? T\n : IsValidPathInSlugPath extends true\n ? HasSlugInPath extends true\n ? never\n : T\n : never;\n\ntype PathWithStaticSlugs = T extends `/`\n ? T\n : IsValidPathInSlugPath extends true\n ? T\n : never;\n\nexport type PathWithWildcard<\n Path,\n SlugKey extends string,\n WildSlugKey extends string,\n> = PathWithSlug;\n\nexport type CreatePage = (\n page: (\n | {\n render: 'static';\n path: PathWithoutSlug;\n component: FunctionComponent;\n }\n | {\n render: 'static';\n path: PathWithStaticSlugs;\n staticPaths: StaticSlugRoutePaths;\n component: FunctionComponent>;\n }\n | {\n render: 'dynamic';\n path: PathWithoutSlug;\n component: FunctionComponent;\n }\n | {\n render: 'dynamic';\n path: PathWithWildcard;\n component: FunctionComponent<\n RouteProps & Record & Record\n >;\n }\n ) & { unstable_disableSSR?: boolean }\n) => void;\n\nexport type CreateLayout = (layout: {\n render: 'static' | 'dynamic';\n path: PathWithoutSlug;\n component: FunctionComponent & { children: ReactNode }>;\n}) => void;\n\nexport function createPages(\n fn: (\n fns: {\n createPage: CreatePage;\n createLayout: CreateLayout;\n unstable_setBuildData: (path: string, data: unknown) => void;\n },\n opts: {\n unstable_buildConfig: BuildConfig | undefined;\n }\n ) => Promise\n) {\n let configured = false;\n\n // TODO I think there's room for improvement to refactor these structures\n const staticPathSet = new Set<[string, PathSpec]>();\n const dynamicPagePathMap = new Map]>();\n const wildcardPagePathMap = new Map]>();\n const dynamicLayoutPathMap = new Map]>();\n const staticComponentMap = new Map>();\n const noSsrSet = new WeakSet();\n const buildDataMap = new Map();\n\n const registerStaticComponent = (id: string, component: FunctionComponent) => {\n if (staticComponentMap.has(id) && staticComponentMap.get(id) !== component) {\n throw new Error(`Duplicated component for: ${id}`);\n }\n staticComponentMap.set(id, component);\n };\n\n const createPage: CreatePage = (page) => {\n if (configured) {\n throw new Error('no longer available');\n }\n const pathSpec = parsePathWithSlug(page.path);\n if (page.unstable_disableSSR) {\n noSsrSet.add(pathSpec);\n }\n const { numSlugs, numWildcards } = (() => {\n let numSlugs = 0;\n let numWildcards = 0;\n for (const slug of pathSpec) {\n if (slug.type !== 'literal') {\n numSlugs++;\n }\n if (slug.type === 'wildcard') {\n numWildcards++;\n }\n }\n return { numSlugs, numWildcards };\n })();\n if (page.render === 'static' && numSlugs === 0) {\n staticPathSet.add([page.path, pathSpec]);\n const id = joinPath(page.path, 'page').replace(/^\\//, '');\n registerStaticComponent(id, page.component);\n } else if (page.render === 'static' && numSlugs > 0 && 'staticPaths' in page) {\n const staticPaths = page.staticPaths.map((item) =>\n (Array.isArray(item) ? item : [item]).map(sanitizeSlug)\n );\n for (const staticPath of staticPaths) {\n if (staticPath.length !== numSlugs && numWildcards === 0) {\n throw new Error('staticPaths does not match with slug pattern');\n }\n const mapping: Record = {};\n let slugIndex = 0;\n const pathItems: string[] = [];\n pathSpec.forEach(({ type, name }) => {\n switch (type) {\n case 'literal':\n pathItems.push(name!);\n break;\n case 'wildcard':\n mapping[name!] = staticPath.slice(slugIndex);\n staticPath.slice(slugIndex++).forEach((slug) => {\n pathItems.push(slug);\n });\n break;\n case 'group':\n pathItems.push(staticPath[slugIndex++]!);\n mapping[name!] = pathItems[pathItems.length - 1]!;\n break;\n }\n });\n staticPathSet.add([page.path, pathItems.map((name) => ({ type: 'literal', name }))]);\n const id = joinPath(...pathItems, 'page');\n const WrappedComponent = (props: Record) =>\n createElement(page.component as any, { ...props, ...mapping });\n registerStaticComponent(id, WrappedComponent);\n }\n } else if (page.render === 'dynamic' && numWildcards === 0) {\n if (dynamicPagePathMap.has(page.path)) {\n throw new Error(`Duplicated dynamic path: ${page.path}`);\n }\n dynamicPagePathMap.set(page.path, [pathSpec, page.component]);\n } else if (page.render === 'dynamic' && numWildcards === 1) {\n if (wildcardPagePathMap.has(page.path)) {\n throw new Error(`Duplicated dynamic path: ${page.path}`);\n }\n wildcardPagePathMap.set(page.path, [pathSpec, page.component]);\n } else {\n throw new Error('Invalid page configuration: ' + page.path);\n }\n };\n\n const createLayout: CreateLayout = (layout) => {\n if (configured) {\n throw new Error('no longer available');\n }\n if (layout.render === 'static') {\n const id = joinPath(layout.path, 'layout').replace(/^\\//, '');\n registerStaticComponent(id, layout.component);\n } else if (layout.render === 'dynamic') {\n if (dynamicLayoutPathMap.has(layout.path)) {\n throw new Error(`Duplicated dynamic path: ${layout.path}`);\n }\n const pathSpec = parsePathWithSlug(layout.path);\n dynamicLayoutPathMap.set(layout.path, [pathSpec, layout.component]);\n } else {\n throw new Error('Invalid layout configuration');\n }\n };\n\n const unstable_setBuildData = (path: string, data: unknown) => {\n buildDataMap.set(path, data);\n };\n\n let ready: Promise | undefined;\n const configure = async (buildConfig?: BuildConfig) => {\n if (!configured && !ready) {\n ready = fn(\n { createPage, createLayout, unstable_setBuildData },\n { unstable_buildConfig: buildConfig }\n );\n await ready;\n configured = true;\n }\n await ready;\n };\n\n return unstable_defineRouter(\n async () => {\n await configure();\n const paths: {\n pattern: string;\n path: PathSpec;\n isStatic: boolean;\n noSsr: boolean;\n data: unknown;\n }[] = [];\n for (const [path, pathSpec] of staticPathSet) {\n const noSsr = noSsrSet.has(pathSpec);\n const isStatic = (() => {\n for (const [_, [layoutPathSpec]] of dynamicLayoutPathMap) {\n if (hasPathSpecPrefix(layoutPathSpec, pathSpec)) {\n return false;\n }\n }\n return true;\n })();\n\n paths.push({\n pattern: path2regexp(parsePathWithSlug(path)),\n path: pathSpec,\n isStatic,\n noSsr,\n data: buildDataMap.get(path),\n });\n }\n for (const [path, [pathSpec]] of dynamicPagePathMap) {\n const noSsr = noSsrSet.has(pathSpec);\n paths.push({\n pattern: path2regexp(parsePathWithSlug(path)),\n path: pathSpec,\n isStatic: false,\n noSsr,\n data: buildDataMap.get(path),\n });\n }\n for (const [path, [pathSpec]] of wildcardPagePathMap) {\n const noSsr = noSsrSet.has(pathSpec);\n paths.push({\n pattern: path2regexp(parsePathWithSlug(path)),\n path: pathSpec,\n isStatic: false,\n noSsr,\n data: buildDataMap.get(path),\n });\n }\n return paths;\n },\n async (id, { unstable_setShouldSkip, unstable_buildConfig }) => {\n await configure(unstable_buildConfig);\n const staticComponent = staticComponentMap.get(id);\n if (staticComponent) {\n unstable_setShouldSkip([]);\n return staticComponent;\n }\n for (const [_, [pathSpec, Component]] of dynamicPagePathMap) {\n const mapping = getPathMapping([...pathSpec, { type: 'literal', name: 'page' }], id);\n if (mapping) {\n if (Object.keys(mapping).length === 0) {\n unstable_setShouldSkip();\n return Component;\n }\n const WrappedComponent = (props: Record) =>\n createElement(Component, { ...props, ...mapping });\n unstable_setShouldSkip();\n return WrappedComponent;\n }\n }\n for (const [_, [pathSpec, Component]] of wildcardPagePathMap) {\n const mapping = getPathMapping([...pathSpec, { type: 'literal', name: 'page' }], id);\n if (mapping) {\n const WrappedComponent = (props: Record) =>\n createElement(Component, { ...props, ...mapping });\n unstable_setShouldSkip();\n return WrappedComponent;\n }\n }\n for (const [_, [pathSpec, Component]] of dynamicLayoutPathMap) {\n const mapping = getPathMapping([...pathSpec, { type: 'literal', name: 'layout' }], id);\n if (mapping) {\n if (Object.keys(mapping).length) {\n throw new Error('[Bug] layout should not have slugs');\n }\n unstable_setShouldSkip();\n return Component;\n }\n }\n unstable_setShouldSkip([]); // negative cache\n return null; // not found\n }\n );\n}\n"]} \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/router/createPages.d.ts b/packages/@expo/router-server/build/rsc/router/createPages.d.ts new file mode 100644 index 00000000000000..88a82b067c86c6 --- /dev/null +++ b/packages/@expo/router-server/build/rsc/router/createPages.d.ts @@ -0,0 +1,35 @@ +import type { RouteProps } from 'expo-router/internal/rsc'; +import type { FunctionComponent, ReactNode } from 'react'; +import { unstable_defineRouter } from './defineRouter'; +import type { BuildConfig } from '../server'; +export type CreatePageInput = { + path: string; + component: FunctionComponent; + render: 'static' | 'dynamic'; + staticPaths?: (string | string[])[]; + unstable_disableSSR?: boolean; +}; +export type CreateLayoutInput = { + path: string; + component: FunctionComponent & { + children: ReactNode; + }>; + render: 'static' | 'dynamic'; +}; +export type CreatePagesApi = { + createPage: (page: CreatePageInput) => void; + createLayout: (layout: CreateLayoutInput) => void; + unstable_setBuildData: (path: string, data: unknown) => void; +}; +export type CreatePagesFn = (api: CreatePagesApi, opts: { + unstable_buildConfig: BuildConfig | undefined; +}) => Promise; +/** + * Build an RSC router from a registration callback. Imitates `expo-server`'s + * URL routing: each registered component carries a regex matcher, and the + * resolver iterates the registry in specificity order, first match wins. + * No exact-key lookup: this lets paths with `(group)` segments match runtime + * IDs that don't include them. + */ +export declare function createPages(fn: CreatePagesFn): ReturnType; +//# sourceMappingURL=createPages.d.ts.map \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/router/createPages.d.ts.map b/packages/@expo/router-server/build/rsc/router/createPages.d.ts.map new file mode 100644 index 00000000000000..dc78e23c475afe --- /dev/null +++ b/packages/@expo/router-server/build/rsc/router/createPages.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"createPages.d.ts","sourceRoot":"","sources":["../../../src/rsc/router/createPages.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAE1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAoB7C,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,iBAAiB,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC7B,WAAW,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,EAAE,CAAC,EAAE,CAAC;IACpC,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,iBAAiB,CAAC,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,GAAG;QAAE,QAAQ,EAAE,SAAS,CAAA;KAAE,CAAC,CAAC;IACzF,MAAM,EAAE,QAAQ,GAAG,SAAS,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,IAAI,CAAC;IAC5C,YAAY,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAClD,qBAAqB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CAC9D,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,CAC1B,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE;IAAE,oBAAoB,EAAE,WAAW,GAAG,SAAS,CAAA;CAAE,KACpD,OAAO,CAAC,IAAI,CAAC,CAAC;AAgDnB;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,aAAa,GAAG,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAgOvF"} \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/router/createPages.js b/packages/@expo/router-server/build/rsc/router/createPages.js new file mode 100644 index 00000000000000..03ecbea9702126 --- /dev/null +++ b/packages/@expo/router-server/build/rsc/router/createPages.js @@ -0,0 +1,258 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.createPages = createPages; +const routing_1 = require("expo-router/internal/routing"); +const react_1 = require("react"); +const defineRouter_1 = require("./defineRouter"); +const getNamedParametrizedRoute_1 = require("../../getNamedParametrizedRoute"); +function compilePathMatcher(path, suffix) { + // Reuse expo-router-server's canonical regex builder so RSC matching agrees with + // the URL manifest about brackets, group routes (optional), wildcards, and +not-found. + const { namedParameterizedRoute, routeKeys, wildcardKeys } = (0, getNamedParametrizedRoute_1.getNamedParametrizedRoute)(path); + // Strip a trailing slash before appending the suffix so the root path doesn't + // produce `//page` when concatenated. + const base = namedParameterizedRoute.replace(/\/$/, ''); + const regex = new RegExp(`^${suffix ? `${base}/${suffix}` : base}/?$`); + return (target) => { + // Targets are either IDs (`posts/123/page`) or pathnames (`/posts/123`). + // The canonical regex expects a leading slash, so add one for IDs. + const match = regex.exec(target.startsWith('/') ? target : '/' + target); + if (!match) + return null; + const params = {}; + for (const [cleanedKey, originalName] of Object.entries(routeKeys)) { + const value = match.groups?.[cleanedKey]; + if (value === undefined) + continue; + params[originalName] = wildcardKeys.has(cleanedKey) ? value.split('/') : value; + } + return params; + }; +} +function buildMatchesPathname(path) { + const matcher = compilePathMatcher(path); + return (pathname) => matcher(pathname) != null; +} +function isDynamicPath(path) { + return path.split('/').some((segment) => (0, routing_1.matchDynamicName)(segment) != null); +} +function hasPathPrefix(prefix, path) { + return path === prefix || path.startsWith(prefix + '/'); +} +/** Normalize a registration path to a URL-shaped pathname (always starts with `/`). */ +function normalizePath(path) { + if (path === '' || path === '/') + return '/'; + return path.startsWith('/') ? path : '/' + path; +} +function sanitizeSlug(slug) { + return slug.replace(/\./g, '').replace(/ /g, '-'); +} +/** + * Build an RSC router from a registration callback. Imitates `expo-server`'s + * URL routing: each registered component carries a regex matcher, and the + * resolver iterates the registry in specificity order, first match wins. + * No exact-key lookup: this lets paths with `(group)` segments match runtime + * IDs that don't include them. + */ +function createPages(fn) { + let configured = false; + const entriesByKey = new Map(); + const buildDataMap = new Map(); + let sortedEntries = []; + const register = (entry) => { + const key = `${entry.kind}:${entry.path}`; + const existing = entriesByKey.get(key); + if (existing && existing.component !== entry.component) { + throw new Error(`Duplicated component for ${entry.kind}: ${entry.path}`); + } + entriesByKey.set(key, entry); + }; + const createPage = (page) => { + if (configured) { + throw new Error('no longer available'); + } + // Normalize once up-front: top-level `./index.tsx` arrives as `''`, and + // everything downstream (the matcher, the registry key, the resolver) wants + // a URL-shaped pathname. + const path = normalizePath(page.path); + const noSsr = !!page.unstable_disableSSR; + const segments = path.split('/').filter(Boolean); + let numSlugs = 0; + let numWildcards = 0; + for (const segment of segments) { + const dynamic = (0, routing_1.matchDynamicName)(segment); + if (!dynamic) + continue; + numSlugs++; + if (dynamic.deep) + numWildcards++; + } + if (page.render === 'static' && numSlugs === 0) { + register({ + path, + component: page.component, + kind: 'page', + isDynamic: false, + isWildcard: false, + noSsr, + matchId: compilePathMatcher(path, 'page'), + matchesPathname: buildMatchesPathname(path), + }); + return; + } + if (page.render === 'static' && numSlugs > 0) { + if (!page.staticPaths) { + throw new Error('staticPaths is required for static pages with slugs'); + } + const staticPaths = page.staticPaths.map((item) => (Array.isArray(item) ? item : [item]).map(sanitizeSlug)); + for (const staticPath of staticPaths) { + if (staticPath.length !== numSlugs && numWildcards === 0) { + throw new Error('staticPaths does not match with slug pattern'); + } + const mapping = {}; + let slugIndex = 0; + const pathItems = []; + for (const segment of segments) { + const dynamic = (0, routing_1.matchDynamicName)(segment); + if (!dynamic) { + pathItems.push(segment); + continue; + } + if (dynamic.deep) { + mapping[dynamic.name] = staticPath.slice(slugIndex); + staticPath.slice(slugIndex++).forEach((slug) => pathItems.push(slug)); + } + else { + pathItems.push(staticPath[slugIndex++]); + mapping[dynamic.name] = pathItems[pathItems.length - 1]; + } + } + const concretePath = '/' + pathItems.join('/'); + const WrappedComponent = (props) => (0, react_1.createElement)(page.component, { ...props, ...mapping }); + register({ + path: concretePath, + component: WrappedComponent, + kind: 'page', + isDynamic: false, + isWildcard: false, + noSsr, + matchId: compilePathMatcher(concretePath, 'page'), + matchesPathname: buildMatchesPathname(concretePath), + }); + } + return; + } + if (page.render === 'dynamic') { + if (numWildcards > 1) { + throw new Error('Invalid page configuration: ' + path); + } + register({ + path, + component: page.component, + kind: 'page', + isDynamic: true, + isWildcard: numWildcards === 1, + noSsr, + matchId: compilePathMatcher(path, 'page'), + matchesPathname: buildMatchesPathname(path), + }); + return; + } + throw new Error('Invalid page configuration: ' + path); + }; + const createLayout = (layout) => { + if (configured) { + throw new Error('no longer available'); + } + if (layout.render !== 'static' && layout.render !== 'dynamic') { + throw new Error('Invalid layout configuration'); + } + const path = normalizePath(layout.path); + register({ + path, + component: layout.component, + kind: 'layout', + isDynamic: layout.render === 'dynamic' || isDynamicPath(path), + isWildcard: false, + noSsr: false, + matchId: compilePathMatcher(path, 'layout'), + matchesPathname: buildMatchesPathname(path), + }); + }; + const unstable_setBuildData = (path, data) => { + // Key by the same normalized pathname `register` uses, so the lookup at + // `buildDataMap.get(entry.path)` finds it regardless of caller convention. + buildDataMap.set(normalizePath(path), data); + }; + let ready; + const configure = async (buildConfig) => { + if (!configured && !ready) { + ready = fn({ createPage, createLayout, unstable_setBuildData }, { unstable_buildConfig: buildConfig }); + await ready; + configured = true; + // Resolver iterates this once per request and takes the first matchId hit. + // Non-wildcard pages must out-rank wildcards so a more specific path wins; the + // matcher's `/page` vs `/layout` suffix prevents cross-kind false matches, so + // page-vs-layout order is irrelevant. + sortedEntries = Array.from(entriesByKey.values()).sort((a, b) => Number(a.isWildcard) - Number(b.isWildcard)); + } + await ready; + }; + return (0, defineRouter_1.unstable_defineRouter)(async () => { + await configure(); + const dynamicLayoutPaths = []; + for (const entry of sortedEntries) { + if (entry.kind === 'layout' && entry.isDynamic) + dynamicLayoutPaths.push(entry.path); + } + const isUnderDynamicLayout = (pagePath) => dynamicLayoutPaths.some((lp) => hasPathPrefix(lp, pagePath)); + const paths = []; + for (const entry of sortedEntries) { + if (entry.kind !== 'page') + continue; + paths.push({ + path: entry.path, + matchesPathname: entry.matchesPathname, + isStatic: !entry.isDynamic && !isUnderDynamicLayout(entry.path), + noSsr: entry.noSsr, + data: buildDataMap.get(entry.path), + }); + } + return paths; + }, async (id, { unstable_setShouldSkip, unstable_buildConfig }) => { + await configure(unstable_buildConfig); + for (const entry of sortedEntries) { + const mapping = entry.matchId(id); + if (!mapping) + continue; + if (entry.kind === 'layout') { + if (Object.keys(mapping).length) { + throw new Error('[Bug] layout should not have slugs'); + } + // Layouts never opt into shouldSkipObj — they must render on every request to + // enforce their auth/loader effects. + return { + component: entry.component, + kind: 'layout', + }; + } + // Static pages opt into shouldSkipObj so the client can cache them across + // navigations. Dynamic pages don't (their content depends on slugs). + if (entry.isDynamic) { + unstable_setShouldSkip(); + } + else { + unstable_setShouldSkip([]); + } + if (Object.keys(mapping).length === 0) { + return { component: entry.component, kind: 'page' }; + } + const WrappedComponent = (props) => (0, react_1.createElement)(entry.component, { ...props, ...mapping }); + return { component: WrappedComponent, kind: 'page' }; + } + unstable_setShouldSkip([]); + return null; + }); +} +//# sourceMappingURL=createPages.js.map \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/router/createPages.js.map b/packages/@expo/router-server/build/rsc/router/createPages.js.map new file mode 100644 index 00000000000000..d6939d318744a1 --- /dev/null +++ b/packages/@expo/router-server/build/rsc/router/createPages.js.map @@ -0,0 +1 @@ +{"version":3,"file":"createPages.js","sourceRoot":"","sources":["../../../src/rsc/router/createPages.ts"],"names":[],"mappings":"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","sourcesContent":["import { matchDynamicName } from 'expo-router/internal/routing';\nimport type { RouteProps } from 'expo-router/internal/rsc';\nimport { createElement } from 'react';\nimport type { FunctionComponent, ReactNode } from 'react';\n\nimport { unstable_defineRouter } from './defineRouter';\nimport { getNamedParametrizedRoute } from '../../getNamedParametrizedRoute';\nimport type { BuildConfig } from '../server';\n\ntype ComponentKind = 'page' | 'layout';\ntype SlugMapping = Record;\ntype IdMatcher = (id: string) => SlugMapping | null;\n\ntype Entry = {\n /** Registered path, may contain `[slug]`, `[...wildcard]`, or `(group)` segments. */\n path: string;\n component: FunctionComponent;\n kind: ComponentKind;\n /** True if the path contains any slug or wildcard segments. */\n isDynamic: boolean;\n /** True if the path contains a wildcard `[...rest]` segment. */\n isWildcard: boolean;\n noSsr: boolean;\n matchId: IdMatcher;\n matchesPathname: (pathname: string) => boolean;\n};\n\nexport type CreatePageInput = {\n path: string;\n component: FunctionComponent;\n render: 'static' | 'dynamic';\n staticPaths?: (string | string[])[];\n unstable_disableSSR?: boolean;\n};\n\nexport type CreateLayoutInput = {\n path: string;\n component: FunctionComponent & { children: ReactNode }>;\n render: 'static' | 'dynamic';\n};\n\nexport type CreatePagesApi = {\n createPage: (page: CreatePageInput) => void;\n createLayout: (layout: CreateLayoutInput) => void;\n unstable_setBuildData: (path: string, data: unknown) => void;\n};\n\nexport type CreatePagesFn = (\n api: CreatePagesApi,\n opts: { unstable_buildConfig: BuildConfig | undefined }\n) => Promise;\n\nfunction compilePathMatcher(path: string, suffix?: 'page' | 'layout'): IdMatcher {\n // Reuse expo-router-server's canonical regex builder so RSC matching agrees with\n // the URL manifest about brackets, group routes (optional), wildcards, and +not-found.\n const { namedParameterizedRoute, routeKeys, wildcardKeys } = getNamedParametrizedRoute(path);\n // Strip a trailing slash before appending the suffix so the root path doesn't\n // produce `//page` when concatenated.\n const base = namedParameterizedRoute.replace(/\\/$/, '');\n const regex = new RegExp(`^${suffix ? `${base}/${suffix}` : base}/?$`);\n return (target) => {\n // Targets are either IDs (`posts/123/page`) or pathnames (`/posts/123`).\n // The canonical regex expects a leading slash, so add one for IDs.\n const match = regex.exec(target.startsWith('/') ? target : '/' + target);\n if (!match) return null;\n const params: SlugMapping = {};\n for (const [cleanedKey, originalName] of Object.entries(routeKeys)) {\n const value = match.groups?.[cleanedKey];\n if (value === undefined) continue;\n params[originalName] = wildcardKeys.has(cleanedKey) ? value.split('/') : value;\n }\n return params;\n };\n}\n\nfunction buildMatchesPathname(path: string): (pathname: string) => boolean {\n const matcher = compilePathMatcher(path);\n return (pathname) => matcher(pathname) != null;\n}\n\nfunction isDynamicPath(path: string): boolean {\n return path.split('/').some((segment) => matchDynamicName(segment) != null);\n}\n\nfunction hasPathPrefix(prefix: string, path: string): boolean {\n return path === prefix || path.startsWith(prefix + '/');\n}\n\n/** Normalize a registration path to a URL-shaped pathname (always starts with `/`). */\nfunction normalizePath(path: string): string {\n if (path === '' || path === '/') return '/';\n return path.startsWith('/') ? path : '/' + path;\n}\n\nfunction sanitizeSlug(slug: string): string {\n return slug.replace(/\\./g, '').replace(/ /g, '-');\n}\n\n/**\n * Build an RSC router from a registration callback. Imitates `expo-server`'s\n * URL routing: each registered component carries a regex matcher, and the\n * resolver iterates the registry in specificity order, first match wins.\n * No exact-key lookup: this lets paths with `(group)` segments match runtime\n * IDs that don't include them.\n */\nexport function createPages(fn: CreatePagesFn): ReturnType {\n let configured = false;\n const entriesByKey = new Map();\n const buildDataMap = new Map();\n let sortedEntries: Entry[] = [];\n\n const register = (entry: Entry) => {\n const key = `${entry.kind}:${entry.path}`;\n const existing = entriesByKey.get(key);\n if (existing && existing.component !== entry.component) {\n throw new Error(`Duplicated component for ${entry.kind}: ${entry.path}`);\n }\n entriesByKey.set(key, entry);\n };\n\n const createPage = (page: CreatePageInput): void => {\n if (configured) {\n throw new Error('no longer available');\n }\n // Normalize once up-front: top-level `./index.tsx` arrives as `''`, and\n // everything downstream (the matcher, the registry key, the resolver) wants\n // a URL-shaped pathname.\n const path = normalizePath(page.path);\n const noSsr = !!page.unstable_disableSSR;\n const segments = path.split('/').filter(Boolean);\n let numSlugs = 0;\n let numWildcards = 0;\n for (const segment of segments) {\n const dynamic = matchDynamicName(segment);\n if (!dynamic) continue;\n numSlugs++;\n if (dynamic.deep) numWildcards++;\n }\n\n if (page.render === 'static' && numSlugs === 0) {\n register({\n path,\n component: page.component,\n kind: 'page',\n isDynamic: false,\n isWildcard: false,\n noSsr,\n matchId: compilePathMatcher(path, 'page'),\n matchesPathname: buildMatchesPathname(path),\n });\n return;\n }\n\n if (page.render === 'static' && numSlugs > 0) {\n if (!page.staticPaths) {\n throw new Error('staticPaths is required for static pages with slugs');\n }\n const staticPaths = page.staticPaths.map((item) =>\n (Array.isArray(item) ? item : [item]).map(sanitizeSlug)\n );\n for (const staticPath of staticPaths) {\n if (staticPath.length !== numSlugs && numWildcards === 0) {\n throw new Error('staticPaths does not match with slug pattern');\n }\n const mapping: SlugMapping = {};\n let slugIndex = 0;\n const pathItems: string[] = [];\n for (const segment of segments) {\n const dynamic = matchDynamicName(segment);\n if (!dynamic) {\n pathItems.push(segment);\n continue;\n }\n if (dynamic.deep) {\n mapping[dynamic.name] = staticPath.slice(slugIndex);\n staticPath.slice(slugIndex++).forEach((slug) => pathItems.push(slug));\n } else {\n pathItems.push(staticPath[slugIndex++]!);\n mapping[dynamic.name] = pathItems[pathItems.length - 1]!;\n }\n }\n const concretePath = '/' + pathItems.join('/');\n const WrappedComponent = (props: Record) =>\n createElement(page.component as any, { ...props, ...mapping });\n register({\n path: concretePath,\n component: WrappedComponent,\n kind: 'page',\n isDynamic: false,\n isWildcard: false,\n noSsr,\n matchId: compilePathMatcher(concretePath, 'page'),\n matchesPathname: buildMatchesPathname(concretePath),\n });\n }\n return;\n }\n\n if (page.render === 'dynamic') {\n if (numWildcards > 1) {\n throw new Error('Invalid page configuration: ' + path);\n }\n register({\n path,\n component: page.component,\n kind: 'page',\n isDynamic: true,\n isWildcard: numWildcards === 1,\n noSsr,\n matchId: compilePathMatcher(path, 'page'),\n matchesPathname: buildMatchesPathname(path),\n });\n return;\n }\n\n throw new Error('Invalid page configuration: ' + path);\n };\n\n const createLayout = (layout: CreateLayoutInput): void => {\n if (configured) {\n throw new Error('no longer available');\n }\n if (layout.render !== 'static' && layout.render !== 'dynamic') {\n throw new Error('Invalid layout configuration');\n }\n const path = normalizePath(layout.path);\n register({\n path,\n component: layout.component as FunctionComponent,\n kind: 'layout',\n isDynamic: layout.render === 'dynamic' || isDynamicPath(path),\n isWildcard: false,\n noSsr: false,\n matchId: compilePathMatcher(path, 'layout'),\n matchesPathname: buildMatchesPathname(path),\n });\n };\n\n const unstable_setBuildData = (path: string, data: unknown) => {\n // Key by the same normalized pathname `register` uses, so the lookup at\n // `buildDataMap.get(entry.path)` finds it regardless of caller convention.\n buildDataMap.set(normalizePath(path), data);\n };\n\n let ready: Promise | undefined;\n const configure = async (buildConfig?: BuildConfig) => {\n if (!configured && !ready) {\n ready = fn(\n { createPage, createLayout, unstable_setBuildData },\n { unstable_buildConfig: buildConfig }\n );\n await ready;\n configured = true;\n // Resolver iterates this once per request and takes the first matchId hit.\n // Non-wildcard pages must out-rank wildcards so a more specific path wins; the\n // matcher's `/page` vs `/layout` suffix prevents cross-kind false matches, so\n // page-vs-layout order is irrelevant.\n sortedEntries = Array.from(entriesByKey.values()).sort(\n (a, b) => Number(a.isWildcard) - Number(b.isWildcard)\n );\n }\n await ready;\n };\n\n return unstable_defineRouter(\n async () => {\n await configure();\n const dynamicLayoutPaths: string[] = [];\n for (const entry of sortedEntries) {\n if (entry.kind === 'layout' && entry.isDynamic) dynamicLayoutPaths.push(entry.path);\n }\n const isUnderDynamicLayout = (pagePath: string) =>\n dynamicLayoutPaths.some((lp) => hasPathPrefix(lp, pagePath));\n\n const paths: {\n path: string;\n matchesPathname: (pathname: string) => boolean;\n isStatic: boolean;\n noSsr: boolean;\n data: unknown;\n }[] = [];\n for (const entry of sortedEntries) {\n if (entry.kind !== 'page') continue;\n paths.push({\n path: entry.path,\n matchesPathname: entry.matchesPathname,\n isStatic: !entry.isDynamic && !isUnderDynamicLayout(entry.path),\n noSsr: entry.noSsr,\n data: buildDataMap.get(entry.path),\n });\n }\n return paths;\n },\n async (id, { unstable_setShouldSkip, unstable_buildConfig }) => {\n await configure(unstable_buildConfig);\n for (const entry of sortedEntries) {\n const mapping = entry.matchId(id);\n if (!mapping) continue;\n if (entry.kind === 'layout') {\n if (Object.keys(mapping).length) {\n throw new Error('[Bug] layout should not have slugs');\n }\n // Layouts never opt into shouldSkipObj — they must render on every request to\n // enforce their auth/loader effects.\n return {\n component: entry.component as FunctionComponent<\n Omit & { children: ReactNode }\n >,\n kind: 'layout',\n };\n }\n // Static pages opt into shouldSkipObj so the client can cache them across\n // navigations. Dynamic pages don't (their content depends on slugs).\n if (entry.isDynamic) {\n unstable_setShouldSkip();\n } else {\n unstable_setShouldSkip([]);\n }\n if (Object.keys(mapping).length === 0) {\n return { component: entry.component as FunctionComponent, kind: 'page' };\n }\n const WrappedComponent = (props: Record) =>\n createElement(entry.component, { ...props, ...mapping });\n return { component: WrappedComponent as FunctionComponent, kind: 'page' };\n }\n unstable_setShouldSkip([]);\n return null;\n }\n );\n}\n"]} \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/router/defineRouter.d.ts b/packages/@expo/router-server/build/rsc/router/defineRouter.d.ts index 90a8ffb8257db6..571ae9de8567cc 100644 --- a/packages/@expo/router-server/build/rsc/router/defineRouter.d.ts +++ b/packages/@expo/router-server/build/rsc/router/defineRouter.d.ts @@ -7,23 +7,27 @@ */ import { type RouteProps, type ShouldSkip } from 'expo-router/internal/rsc'; import type { FunctionComponent, ReactNode } from 'react'; -import type { PathSpec } from '../path'; import type { BuildConfig, defineEntries } from '../server'; type RoutePropsForLayout = Omit & { children: ReactNode; }; type ShouldSkipValue = ShouldSkip[number][1]; export declare function unstable_defineRouter(getPathConfig: () => Promise boolean; isStatic?: boolean; noSsr?: boolean; data?: unknown; -}>>, getComponent: (componentId: string, // "**/layout" or "**/page" -options: { +}>>, getComponent: (componentId: string, options: { unstable_setShouldSkip: (val?: ShouldSkipValue) => void; unstable_buildConfig: BuildConfig | undefined; -}) => Promise | FunctionComponent | null>): ReturnType; +}) => Promise<{ + component: FunctionComponent; + kind: 'page'; +} | { + component: FunctionComponent; + kind: 'layout'; +} | null>): ReturnType; export declare function unstable_redirect(pathname: string, searchParams?: URLSearchParams, skip?: string[]): void; export {}; //# sourceMappingURL=defineRouter.d.ts.map \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/router/defineRouter.d.ts.map b/packages/@expo/router-server/build/rsc/router/defineRouter.d.ts.map index cd8dd0b053ac02..8b3a66bbb9d5bc 100644 --- a/packages/@expo/router-server/build/rsc/router/defineRouter.d.ts.map +++ b/packages/@expo/router-server/build/rsc/router/defineRouter.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"defineRouter.d.ts","sourceRoot":"","sources":["../../../src/rsc/router/defineRouter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAUL,KAAK,UAAU,EACf,KAAK,UAAU,EAChB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAkB,iBAAiB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAI1E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC,OAAO,KAAK,EACV,WAAW,EAIX,aAAa,EACd,MAAM,WAAW,CAAC;AAEnB,KAAK,mBAAmB,GAAG,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,GAAG;IAC5D,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF,KAAK,eAAe,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAgB7C,wBAAgB,qBAAqB,CACnC,aAAa,EAAE,MAAM,OAAO,CAC1B,QAAQ,CAAC;IACP,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC,CACH,EACD,YAAY,EAAE,CACZ,WAAW,EAAE,MAAM,EAAE,2BAA2B;AAChD,OAAO,EAAE;IAEP,sBAAsB,EAAE,CAAC,GAAG,CAAC,EAAE,eAAe,KAAK,IAAI,CAAC;IACxD,oBAAoB,EAAE,WAAW,GAAG,SAAS,CAAC;CAC/C,KACE,OAAO,CAAC,iBAAiB,CAAC,UAAU,CAAC,GAAG,iBAAiB,CAAC,mBAAmB,CAAC,GAAG,IAAI,CAAC,GAC1F,UAAU,CAAC,OAAO,aAAa,CAAC,CAmKlC;AAED,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,eAAe,EAC9B,IAAI,CAAC,EAAE,MAAM,EAAE,QAUhB"} \ No newline at end of file +{"version":3,"file":"defineRouter.d.ts","sourceRoot":"","sources":["../../../src/rsc/router/defineRouter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAUL,KAAK,UAAU,EACf,KAAK,UAAU,EAChB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAkB,iBAAiB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAI1E,OAAO,KAAK,EACV,WAAW,EAIX,aAAa,EACd,MAAM,WAAW,CAAC;AAEnB,KAAK,mBAAmB,GAAG,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,GAAG;IAC5D,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF,KAAK,eAAe,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAqB7C,wBAAgB,qBAAqB,CACnC,aAAa,EAAE,MAAM,OAAO,CAC1B,QAAQ,CAAC;IACP,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;IAC/C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC,CACH,EACD,YAAY,EAAE,CACZ,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE;IAEP,sBAAsB,EAAE,CAAC,GAAG,CAAC,EAAE,eAAe,KAAK,IAAI,CAAC;IACxD,oBAAoB,EAAE,WAAW,GAAG,SAAS,CAAC;CAC/C,KACE,OAAO,CACR;IAAE,SAAS,EAAE,iBAAiB,CAAC,UAAU,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC1D;IAAE,SAAS,EAAE,iBAAiB,CAAC,mBAAmB,CAAC,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GACrE,IAAI,CACP,GACA,UAAU,CAAC,OAAO,aAAa,CAAC,CAsKlC;AAED,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,eAAe,EAC9B,IAAI,CAAC,EAAE,MAAM,EAAE,QAUhB"} \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/router/defineRouter.js b/packages/@expo/router-server/build/rsc/router/defineRouter.js index 0a8962cd4183fa..34d7de59ec0529 100644 --- a/packages/@expo/router-server/build/rsc/router/defineRouter.js +++ b/packages/@expo/router-server/build/rsc/router/defineRouter.js @@ -11,7 +11,6 @@ exports.unstable_redirect = unstable_redirect; */ const rsc_1 = require("expo-router/internal/rsc"); const react_1 = require("react"); -const path_1 = require("../path"); const server_1 = require("../server"); const safeJsonParse = (str) => { if (typeof str === 'string') { @@ -27,6 +26,11 @@ const safeJsonParse = (str) => { } return undefined; }; +const parseSkipList = (value) => { + if (!Array.isArray(value)) + return new Set(); + return new Set(value.filter((v) => typeof v === 'string')); +}; function unstable_defineRouter(getPathConfig, getComponent) { let cachedPathConfig; const getMyPathConfig = async (buildConfig) => { @@ -34,23 +38,22 @@ function unstable_defineRouter(getPathConfig, getComponent) { return buildConfig; } if (!cachedPathConfig) { - cachedPathConfig = Array.from(await getPathConfig()).map((item) => { - const is404 = item.path.length === 1 && - item.path[0].type === 'literal' && - item.path[0].name === '404'; - return { - pattern: item.pattern, - pathname: item.path, - isStatic: item.isStatic, - customData: { is404, noSsr: !!item.noSsr, data: item.data }, - }; - }); + cachedPathConfig = Array.from(await getPathConfig()).map((item) => ({ + pathname: item.path, + matchesPathname: item.matchesPathname, + isStatic: item.isStatic, + customData: { + is404: item.path === '/404', + noSsr: !!item.noSsr, + data: item.data, + }, + })); } return cachedPathConfig; }; const existsPath = async (pathname, buildConfig) => { const pathConfig = await getMyPathConfig(buildConfig); - const found = pathConfig.find(({ pathname: pathSpec }) => (0, path_1.getPathMapping)(pathSpec, pathname)); + const found = pathConfig.find(({ matchesPathname }) => matchesPathname(pathname)); return found ? found.customData.noSsr ? ['FOUND', 'NO_SSR'] @@ -67,10 +70,14 @@ function unstable_defineRouter(getPathConfig, getComponent) { const shouldSkipObj = {}; const parsedParams = safeJsonParse(params); const query = typeof parsedParams?.query === 'string' ? parsedParams.query : ''; - const skip = Array.isArray(parsedParams?.skip) ? parsedParams?.skip : []; - const componentIds = (0, rsc_1.getComponentIds)(pathname); - const entries = (await Promise.all(componentIds.map(async (id) => { - if (skip?.includes(id)) { + const skip = parseSkipList(parsedParams?.skip); + const { layouts, page } = (0, rsc_1.getComponentIds)(pathname); + const entries = (await Promise.all([...layouts, page].map(async (id) => { + // `getComponentIds` separates the terminal page from layouts. Use that + // structural distinction as a fast-path skip (kind isn't known until we + // resolve); the kind-based check after resolve is the authoritative defense. + const isPage = id === page; + if (isPage && skip.has(id)) { return []; } const setShouldSkip = (val) => { @@ -81,14 +88,18 @@ function unstable_defineRouter(getPathConfig, getComponent) { delete shouldSkipObj[id]; } }; - const component = await getComponent(id, { + const resolved = await getComponent(id, { unstable_setShouldSkip: setShouldSkip, unstable_buildConfig: buildConfig, }); - if (!component) { + if (!resolved) { + return []; + } + // Honor skip only for entries the resolver opted into shouldSkipObj; layouts are never skippable. + if (skip.has(id) && resolved.kind !== 'layout' && shouldSkipObj[id] != null) { return []; } - const element = (0, react_1.createElement)(component, id.endsWith('/layout') ? { path: pathname } : { path: pathname, query }, (0, react_1.createElement)(rsc_1.Children)); + const element = (0, react_1.createElement)(resolved.component, isPage ? { path: pathname, query } : { path: pathname }, (0, react_1.createElement)(rsc_1.Children)); return [[id, element]]; }))).flat(); entries.push([rsc_1.SHOULD_SKIP_ID, Object.entries(shouldSkipObj)]); @@ -98,11 +109,10 @@ function unstable_defineRouter(getPathConfig, getComponent) { const getBuildConfig = async (unstable_collectClientModules) => { const pathConfig = await getMyPathConfig(); const path2moduleIds = {}; - for (const { pathname: pathSpec } of pathConfig) { - if (pathSpec.some(({ type }) => type !== 'literal')) { + for (const { pathname } of pathConfig) { + if (pathname.includes('[')) { continue; } - const pathname = '/' + pathSpec.map(({ name }) => name).join('/'); const input = (0, rsc_1.getInputString)(pathname); const moduleIds = await unstable_collectClientModules(input); path2moduleIds[pathname] = moduleIds; @@ -115,15 +125,14 @@ globalThis.__EXPO_ROUTER_PREFETCH__ = (path) => { } };`; const buildConfig = []; - for (const { pathname: pathSpec, isStatic, customData } of pathConfig) { + for (const { pathname, isStatic, customData } of pathConfig) { const entries = []; - if (pathSpec.every(({ type }) => type === 'literal')) { - const pathname = '/' + pathSpec.map(({ name }) => name).join('/'); + if (!pathname.includes('[')) { const input = (0, rsc_1.getInputString)(pathname); entries.push({ input, isStatic }); } buildConfig.push({ - pathname: pathSpec, + pathname, isStatic, entries, customCode, @@ -145,9 +154,9 @@ globalThis.__EXPO_ROUTER_PREFETCH__ = (path) => { return null; } } - const componentIds = (0, rsc_1.getComponentIds)(pathname); + const { layouts, page } = (0, rsc_1.getComponentIds)(pathname); const input = (0, rsc_1.getInputString)(pathname); - const html = (0, react_1.createElement)(rsc_1.ServerRouter, { route: { path: pathname, query: searchParams.toString(), hash: '' } }, componentIds.reduceRight((acc, id) => (0, react_1.createElement)(rsc_1.Slot, { id, fallback: acc }, acc), null)); + const html = (0, react_1.createElement)(rsc_1.ServerRouter, { route: { path: pathname, query: searchParams.toString(), hash: '' } }, [...layouts, page].reduceRight((acc, id) => (0, react_1.createElement)(rsc_1.Slot, { id, fallback: acc }, acc), null)); return { input, params: JSON.stringify({ query: searchParams.toString() }), diff --git a/packages/@expo/router-server/build/rsc/router/defineRouter.js.map b/packages/@expo/router-server/build/rsc/router/defineRouter.js.map index 72523c3ecf08b1..e74635ee4dc719 100644 --- a/packages/@expo/router-server/build/rsc/router/defineRouter.js.map +++ b/packages/@expo/router-server/build/rsc/router/defineRouter.js.map @@ -1 +1 @@ -{"version":3,"file":"defineRouter.js","sourceRoot":"","sources":["../../../src/rsc/router/defineRouter.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Copyright © 2024 650 Industries.\n * Copyright © 2024 2023 Daishi Kato\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\nimport {\n getComponentIds,\n getInputString,\n parseInputString,\n PARAM_KEY_SKIP,\n SHOULD_SKIP_ID,\n LOCATION_ID,\n Children,\n ServerRouter,\n Slot,\n type RouteProps,\n type ShouldSkip,\n} from 'expo-router/internal/rsc';\nimport type { ComponentProps, FunctionComponent, ReactNode } from 'react';\nimport { createElement } from 'react';\n\nimport { getPathMapping } from '../path';\nimport type { PathSpec } from '../path';\nimport { rerender } from '../server';\nimport type {\n BuildConfig,\n RenderEntries,\n GetBuildConfig,\n GetSsrConfig,\n defineEntries,\n} from '../server';\n\ntype RoutePropsForLayout = Omit & {\n children: ReactNode;\n};\n\ntype ShouldSkipValue = ShouldSkip[number][1];\n\nconst safeJsonParse = (str: unknown) => {\n if (typeof str === 'string') {\n try {\n const obj = JSON.parse(str);\n if (typeof obj === 'object') {\n return obj as Record;\n }\n } catch {\n // ignore\n }\n }\n return undefined;\n};\n\nexport function unstable_defineRouter(\n getPathConfig: () => Promise<\n Iterable<{\n pattern: string;\n path: PathSpec;\n isStatic?: boolean;\n noSsr?: boolean;\n data?: unknown; // For build: put in customData\n }>\n >,\n getComponent: (\n componentId: string, // \"**/layout\" or \"**/page\"\n options: {\n // TODO setShouldSkip API is too hard to understand\n unstable_setShouldSkip: (val?: ShouldSkipValue) => void;\n unstable_buildConfig: BuildConfig | undefined;\n }\n ) => Promise | FunctionComponent | null>\n): ReturnType {\n type MyPathConfig = {\n pattern: string;\n pathname: PathSpec;\n isStatic?: boolean | undefined;\n customData: { noSsr?: boolean; is404: boolean; data: unknown };\n }[];\n let cachedPathConfig: MyPathConfig | undefined;\n const getMyPathConfig = async (buildConfig?: BuildConfig): Promise => {\n if (buildConfig) {\n return buildConfig as MyPathConfig;\n }\n if (!cachedPathConfig) {\n cachedPathConfig = Array.from(await getPathConfig()).map((item) => {\n const is404 =\n item.path.length === 1 &&\n item.path[0]!.type === 'literal' &&\n item.path[0]!.name === '404';\n return {\n pattern: item.pattern,\n pathname: item.path,\n isStatic: item.isStatic,\n customData: { is404, noSsr: !!item.noSsr, data: item.data },\n };\n });\n }\n return cachedPathConfig;\n };\n const existsPath = async (\n pathname: string,\n buildConfig: BuildConfig | undefined\n ): Promise<['FOUND', 'NO_SSR'?] | ['NOT_FOUND', 'HAS_404'?]> => {\n const pathConfig = await getMyPathConfig(buildConfig);\n const found = pathConfig.find(({ pathname: pathSpec }) => getPathMapping(pathSpec, pathname));\n return found\n ? found.customData.noSsr\n ? ['FOUND', 'NO_SSR']\n : ['FOUND']\n : pathConfig.some(({ customData: { is404 } }) => is404) // FIXMEs should avoid re-computation\n ? ['NOT_FOUND', 'HAS_404']\n : ['NOT_FOUND'];\n };\n const renderEntries: RenderEntries = async (input, { params, buildConfig }) => {\n const pathname = parseInputString(input);\n if ((await existsPath(pathname, buildConfig))[0] === 'NOT_FOUND') {\n return null;\n }\n const shouldSkipObj: {\n [componentId: ShouldSkip[number][0]]: ShouldSkip[number][1];\n } = {};\n\n const parsedParams = safeJsonParse(params);\n\n const query = typeof parsedParams?.query === 'string' ? parsedParams.query : '';\n const skip = Array.isArray(parsedParams?.skip) ? (parsedParams?.skip as unknown[]) : [];\n const componentIds = getComponentIds(pathname);\n const entries: (readonly [string, ReactNode])[] = (\n await Promise.all(\n componentIds.map(async (id) => {\n if (skip?.includes(id)) {\n return [];\n }\n const setShouldSkip = (val?: ShouldSkipValue) => {\n if (val) {\n shouldSkipObj[id] = val;\n } else {\n delete shouldSkipObj[id];\n }\n };\n const component = await getComponent(id, {\n unstable_setShouldSkip: setShouldSkip,\n unstable_buildConfig: buildConfig,\n });\n if (!component) {\n return [];\n }\n const element = createElement(\n component as FunctionComponent<{\n path: string;\n query?: string;\n }>,\n id.endsWith('/layout') ? { path: pathname } : { path: pathname, query },\n createElement(Children)\n );\n return [[id, element]] as const;\n })\n )\n ).flat();\n entries.push([SHOULD_SKIP_ID, Object.entries(shouldSkipObj)]);\n entries.push([LOCATION_ID, [pathname, query]]);\n return Object.fromEntries(entries);\n };\n\n const getBuildConfig: GetBuildConfig = async (unstable_collectClientModules) => {\n const pathConfig = await getMyPathConfig();\n const path2moduleIds: Record = {};\n\n for (const { pathname: pathSpec } of pathConfig) {\n if (pathSpec.some(({ type }) => type !== 'literal')) {\n continue;\n }\n\n const pathname = '/' + pathSpec.map(({ name }) => name).join('/');\n const input = getInputString(pathname);\n const moduleIds = await unstable_collectClientModules(input);\n path2moduleIds[pathname] = moduleIds;\n }\n const customCode = `\nglobalThis.__EXPO_ROUTER_PREFETCH__ = (path) => {\n const path2ids = ${JSON.stringify(path2moduleIds)};\n for (const id of path2ids[path] || []) {\n import(id);\n }\n};`;\n const buildConfig: BuildConfig = [];\n for (const { pathname: pathSpec, isStatic, customData } of pathConfig) {\n const entries: BuildConfig[number]['entries'] = [];\n if (pathSpec.every(({ type }) => type === 'literal')) {\n const pathname = '/' + pathSpec.map(({ name }) => name).join('/');\n const input = getInputString(pathname);\n entries.push({ input, isStatic });\n }\n buildConfig.push({\n pathname: pathSpec,\n isStatic,\n entries,\n customCode,\n customData,\n });\n }\n return buildConfig;\n };\n\n const getSsrConfig: GetSsrConfig = async (pathname, { searchParams, buildConfig }) => {\n const pathStatus = await existsPath(pathname, buildConfig);\n if (pathStatus[1] === 'NO_SSR') {\n return null;\n }\n if (pathStatus[0] === 'NOT_FOUND') {\n if (pathStatus[1] === 'HAS_404') {\n pathname = '/404';\n } else {\n return null;\n }\n }\n const componentIds = getComponentIds(pathname);\n const input = getInputString(pathname);\n const html = createElement(\n ServerRouter as FunctionComponent, 'children'>>,\n { route: { path: pathname, query: searchParams.toString(), hash: '' } },\n componentIds.reduceRight(\n (acc: ReactNode, id) => createElement(Slot, { id, fallback: acc }, acc),\n null\n )\n );\n return {\n input,\n params: JSON.stringify({ query: searchParams.toString() }),\n html,\n };\n };\n\n return { renderEntries, getBuildConfig, getSsrConfig };\n}\n\nexport function unstable_redirect(\n pathname: string,\n searchParams?: URLSearchParams,\n skip?: string[]\n) {\n if (skip) {\n searchParams = new URLSearchParams(searchParams);\n for (const id of skip) {\n searchParams.append(PARAM_KEY_SKIP, id);\n }\n }\n const input = getInputString(pathname);\n rerender(input, searchParams);\n}\n"]} \ No newline at end of file +{"version":3,"file":"defineRouter.js","sourceRoot":"","sources":["../../../src/rsc/router/defineRouter.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Copyright © 2024 650 Industries.\n * Copyright © 2024 2023 Daishi Kato\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\nimport {\n getComponentIds,\n getInputString,\n parseInputString,\n PARAM_KEY_SKIP,\n SHOULD_SKIP_ID,\n LOCATION_ID,\n Children,\n ServerRouter,\n Slot,\n type RouteProps,\n type ShouldSkip,\n} from 'expo-router/internal/rsc';\nimport type { ComponentProps, FunctionComponent, ReactNode } from 'react';\nimport { createElement } from 'react';\n\nimport { rerender } from '../server';\nimport type {\n BuildConfig,\n RenderEntries,\n GetBuildConfig,\n GetSsrConfig,\n defineEntries,\n} from '../server';\n\ntype RoutePropsForLayout = Omit & {\n children: ReactNode;\n};\n\ntype ShouldSkipValue = ShouldSkip[number][1];\n\nconst safeJsonParse = (str: unknown) => {\n if (typeof str === 'string') {\n try {\n const obj = JSON.parse(str);\n if (typeof obj === 'object') {\n return obj as Record;\n }\n } catch {\n // ignore\n }\n }\n return undefined;\n};\n\nconst parseSkipList = (value: unknown): Set => {\n if (!Array.isArray(value)) return new Set();\n return new Set(value.filter((v): v is string => typeof v === 'string'));\n};\n\nexport function unstable_defineRouter(\n getPathConfig: () => Promise<\n Iterable<{\n path: string;\n matchesPathname: (pathname: string) => boolean;\n isStatic?: boolean;\n noSsr?: boolean;\n data?: unknown; // For build: put in customData\n }>\n >,\n getComponent: (\n componentId: string,\n options: {\n // TODO setShouldSkip API is too hard to understand\n unstable_setShouldSkip: (val?: ShouldSkipValue) => void;\n unstable_buildConfig: BuildConfig | undefined;\n }\n ) => Promise<\n | { component: FunctionComponent; kind: 'page' }\n | { component: FunctionComponent; kind: 'layout' }\n | null\n >\n): ReturnType {\n type MyPathConfig = {\n pathname: string;\n matchesPathname: (pathname: string) => boolean;\n isStatic?: boolean | undefined;\n customData: { noSsr?: boolean; is404: boolean; data: unknown };\n }[];\n let cachedPathConfig: MyPathConfig | undefined;\n const getMyPathConfig = async (buildConfig?: BuildConfig): Promise => {\n if (buildConfig) {\n return buildConfig as MyPathConfig;\n }\n if (!cachedPathConfig) {\n cachedPathConfig = Array.from(await getPathConfig()).map((item) => ({\n pathname: item.path,\n matchesPathname: item.matchesPathname,\n isStatic: item.isStatic,\n customData: {\n is404: item.path === '/404',\n noSsr: !!item.noSsr,\n data: item.data,\n },\n }));\n }\n return cachedPathConfig;\n };\n const existsPath = async (\n pathname: string,\n buildConfig: BuildConfig | undefined\n ): Promise<['FOUND', 'NO_SSR'?] | ['NOT_FOUND', 'HAS_404'?]> => {\n const pathConfig = await getMyPathConfig(buildConfig);\n const found = pathConfig.find(({ matchesPathname }) => matchesPathname(pathname));\n return found\n ? found.customData.noSsr\n ? ['FOUND', 'NO_SSR']\n : ['FOUND']\n : pathConfig.some(({ customData: { is404 } }) => is404) // FIXMEs should avoid re-computation\n ? ['NOT_FOUND', 'HAS_404']\n : ['NOT_FOUND'];\n };\n const renderEntries: RenderEntries = async (input, { params, buildConfig }) => {\n const pathname = parseInputString(input);\n if ((await existsPath(pathname, buildConfig))[0] === 'NOT_FOUND') {\n return null;\n }\n const shouldSkipObj: {\n [componentId: ShouldSkip[number][0]]: ShouldSkip[number][1];\n } = {};\n\n const parsedParams = safeJsonParse(params);\n\n const query = typeof parsedParams?.query === 'string' ? parsedParams.query : '';\n const skip = parseSkipList(parsedParams?.skip);\n const { layouts, page } = getComponentIds(pathname);\n const entries: (readonly [string, ReactNode])[] = (\n await Promise.all(\n [...layouts, page].map(async (id) => {\n // `getComponentIds` separates the terminal page from layouts. Use that\n // structural distinction as a fast-path skip (kind isn't known until we\n // resolve); the kind-based check after resolve is the authoritative defense.\n const isPage = id === page;\n if (isPage && skip.has(id)) {\n return [];\n }\n const setShouldSkip = (val?: ShouldSkipValue) => {\n if (val) {\n shouldSkipObj[id] = val;\n } else {\n delete shouldSkipObj[id];\n }\n };\n const resolved = await getComponent(id, {\n unstable_setShouldSkip: setShouldSkip,\n unstable_buildConfig: buildConfig,\n });\n if (!resolved) {\n return [];\n }\n // Honor skip only for entries the resolver opted into shouldSkipObj; layouts are never skippable.\n if (skip.has(id) && resolved.kind !== 'layout' && shouldSkipObj[id] != null) {\n return [];\n }\n const element = createElement(\n resolved.component as FunctionComponent<{\n path: string;\n query?: string;\n }>,\n isPage ? { path: pathname, query } : { path: pathname },\n createElement(Children)\n );\n return [[id, element]] as const;\n })\n )\n ).flat();\n entries.push([SHOULD_SKIP_ID, Object.entries(shouldSkipObj)]);\n entries.push([LOCATION_ID, [pathname, query]]);\n return Object.fromEntries(entries);\n };\n\n const getBuildConfig: GetBuildConfig = async (unstable_collectClientModules) => {\n const pathConfig = await getMyPathConfig();\n const path2moduleIds: Record = {};\n\n for (const { pathname } of pathConfig) {\n if (pathname.includes('[')) {\n continue;\n }\n const input = getInputString(pathname);\n const moduleIds = await unstable_collectClientModules(input);\n path2moduleIds[pathname] = moduleIds;\n }\n const customCode = `\nglobalThis.__EXPO_ROUTER_PREFETCH__ = (path) => {\n const path2ids = ${JSON.stringify(path2moduleIds)};\n for (const id of path2ids[path] || []) {\n import(id);\n }\n};`;\n const buildConfig: BuildConfig = [];\n for (const { pathname, isStatic, customData } of pathConfig) {\n const entries: BuildConfig[number]['entries'] = [];\n if (!pathname.includes('[')) {\n const input = getInputString(pathname);\n entries.push({ input, isStatic });\n }\n buildConfig.push({\n pathname,\n isStatic,\n entries,\n customCode,\n customData,\n });\n }\n return buildConfig;\n };\n\n const getSsrConfig: GetSsrConfig = async (pathname, { searchParams, buildConfig }) => {\n const pathStatus = await existsPath(pathname, buildConfig);\n if (pathStatus[1] === 'NO_SSR') {\n return null;\n }\n if (pathStatus[0] === 'NOT_FOUND') {\n if (pathStatus[1] === 'HAS_404') {\n pathname = '/404';\n } else {\n return null;\n }\n }\n const { layouts, page } = getComponentIds(pathname);\n const input = getInputString(pathname);\n const html = createElement(\n ServerRouter as FunctionComponent, 'children'>>,\n { route: { path: pathname, query: searchParams.toString(), hash: '' } },\n [...layouts, page].reduceRight(\n (acc: ReactNode, id) => createElement(Slot, { id, fallback: acc }, acc),\n null\n )\n );\n return {\n input,\n params: JSON.stringify({ query: searchParams.toString() }),\n html,\n };\n };\n\n return { renderEntries, getBuildConfig, getSsrConfig };\n}\n\nexport function unstable_redirect(\n pathname: string,\n searchParams?: URLSearchParams,\n skip?: string[]\n) {\n if (skip) {\n searchParams = new URLSearchParams(searchParams);\n for (const id of skip) {\n searchParams.append(PARAM_KEY_SKIP, id);\n }\n }\n const input = getInputString(pathname);\n rerender(input, searchParams);\n}\n"]} \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/router/expo-definedRouter.d.ts b/packages/@expo/router-server/build/rsc/router/expo-definedRouter.d.ts index cb66392d83e754..3831228c98b20b 100644 --- a/packages/@expo/router-server/build/rsc/router/expo-definedRouter.d.ts +++ b/packages/@expo/router-server/build/rsc/router/expo-definedRouter.d.ts @@ -1,3 +1,5 @@ -declare const _default: (getRouteOptions?: import("expo-router/build/getRoutes").Options) => import("../server").EntriesDev; +import { getRoutes } from '../../getRoutesSSR'; +import type { EntriesDev } from '../server'; +declare const _default: (getRouteOptions?: Parameters[1]) => EntriesDev; export default _default; //# sourceMappingURL=expo-definedRouter.d.ts.map \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/router/expo-definedRouter.d.ts.map b/packages/@expo/router-server/build/rsc/router/expo-definedRouter.d.ts.map index 97276320a15436..41ca9d48329307 100644 --- a/packages/@expo/router-server/build/rsc/router/expo-definedRouter.d.ts.map +++ b/packages/@expo/router-server/build/rsc/router/expo-definedRouter.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"expo-definedRouter.d.ts","sourceRoot":"","sources":["../../../src/rsc/router/expo-definedRouter.ts"],"names":[],"mappings":";AAiBA,wBA2HG"} \ No newline at end of file +{"version":3,"file":"expo-definedRouter.d.ts","sourceRoot":"","sources":["../../../src/rsc/router/expo-definedRouter.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;yBA6H5B,kBAAkB,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC,KAAG,UAAU;AAA9E,wBAWG"} \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/router/expo-definedRouter.js b/packages/@expo/router-server/build/rsc/router/expo-definedRouter.js index 6c78d829a2fb69..a6ee1b0c7abc2e 100644 --- a/packages/@expo/router-server/build/rsc/router/expo-definedRouter.js +++ b/packages/@expo/router-server/build/rsc/router/expo-definedRouter.js @@ -2,112 +2,108 @@ Object.defineProperty(exports, "__esModule", { value: true }); const _ctx_1 = require("expo-router/_ctx"); const routing_1 = require("expo-router/internal/routing"); -const create_expo_pages_1 = require("./create-expo-pages"); +const createPages_1 = require("./createPages"); const getRoutesSSR_1 = require("../../getRoutesSSR"); const loadStaticParamsAsync_1 = require("../../loadStaticParamsAsync"); +function readSettings(loaded) { + const raw = (loaded.unstable_settings ?? {}); + const render = raw.render === 'static' || raw.render === 'dynamic' ? raw.render : undefined; + const unstable_disableSSR = typeof raw.unstable_disableSSR === 'boolean' ? raw.unstable_disableSSR : undefined; + return { render, unstable_disableSSR }; +} const UNIMPLEMENTED_PARAMS = new Proxy({}, { - // Assert that params is unimplemented when accessed. get() { throw new Error('generateStaticParams(): params is not implemented yet'); }, }); -exports.default = (0, create_expo_pages_1.createExpoPages)(async ({ createPage, createLayout }, { getRouteOptions }) => { - const routes = (0, getRoutesSSR_1.getRoutes)(_ctx_1.ctx, { - ...getRouteOptions, - platform: process.env.EXPO_OS, - skipGenerated: true, - importMode: 'lazy', - }); - if (!routes) - return; - async function loadAndConvertStaticParamsAsync(route) { - const loaded = route.loadRoute(); - let staticPaths = undefined; - if (route.dynamic) { - const params = await (0, loadStaticParamsAsync_1.evalStaticParamsAsync)(route, { parentParams: UNIMPLEMENTED_PARAMS }, loaded.generateStaticParams); - // Sort `params` like `[{a: 'x', b: 'y'}, { a: 'z', b: 'w' }]` for a route.dynamic like `[{name: 'a', deep: false}, {name: 'b', deep: false}]` to `[['a', 'y'], ['z', 'w]]` - staticPaths = params?.map((p) => { - const grouped = []; - for (const dynamic of route.dynamic) { - const defined = p[dynamic.name]; - if (!defined) { - throw new Error('generateStaticParams is missing param: ' + - dynamic.name + - '. In route: ' + - route.contextKey); - } - if (Array.isArray(defined)) { - if (defined.length > 1) { - throw new Error('generateStaticParams does not support returning multiple static paths for deep dynamic routes in React Server Components yet. Update route: ' + - route.contextKey); - } - } - const first = Array.isArray(defined) ? defined[0] : defined; - if (first != null) { - grouped.push(first); - } - } - return grouped; - }); - } - else if (loaded.generateStaticParams) { +async function loadStaticParamsForRoute(route) { + const loaded = route.loadRoute(); + if (!route.dynamic) { + if (loaded.generateStaticParams) { throw new Error('Cannot use generateStaticParams without a dynamic route: ' + route.contextKey); } - return staticPaths; + return undefined; } - async function addLayout(route) { - const normal = (0, routing_1.getContextKey)(route.contextKey).replace(/\/index$/, ''); - const loaded = route.loadRoute(); - if (loaded.generateStaticParams) { - throw new Error('generateStaticParams is not supported in _layout routes with React Server Components enabled yet.'); - } - createLayout({ - // NOTE(EvanBacon): Support routes with top-level "use client" - component: loaded.default, - path: normal, - render: 'static', - ...loaded.unstable_settings, - }); - await Promise.all(route.children.sort(routing_1.sortRoutes).map(async (child) => { - if (child.type === 'layout') { - await addLayout(child); + const params = await (0, loadStaticParamsAsync_1.evalStaticParamsAsync)(route, { parentParams: UNIMPLEMENTED_PARAMS }, loaded.generateStaticParams); + return params?.map((p) => { + const grouped = []; + for (const dynamic of route.dynamic) { + const defined = p[dynamic.name]; + if (!defined) { + throw new Error('generateStaticParams is missing param: ' + + dynamic.name + + '. In route: ' + + route.contextKey); } - else { - const normal = (0, routing_1.getContextKey)(child.contextKey).replace(/\/index$/, ''); - const loaded = child.loadRoute(); - const settings = loaded.unstable_settings; - // Support generateStaticParams for dynamic routes by defining the route twice. - if (loaded.generateStaticParams) { - createPage({ - // NOTE(EvanBacon): Support routes with top-level "use client" - component: loaded.default, - path: normal, - render: 'static', - ...loaded.unstable_settings, - staticPaths: (await loadAndConvertStaticParamsAsync(child)), - }); - if (settings?.render !== 'static') { - createPage({ - // NOTE(EvanBacon): Support routes with top-level "use client" - component: loaded.default, - path: normal, - render: 'dynamic', - ...settings, - }); - } - } - else { - createPage({ - // NOTE(EvanBacon): Support routes with top-level "use client" - component: loaded.default, - path: normal, - render: 'dynamic', - ...settings, - }); - } + if (Array.isArray(defined) && defined.length > 1) { + throw new Error('generateStaticParams does not support returning multiple static paths for deep dynamic routes in React Server Components yet. Update route: ' + + route.contextKey); } - })); + const first = Array.isArray(defined) ? defined[0] : defined; + if (first != null) { + grouped.push(first); + } + } + return grouped; + }); +} +async function registerRouteTree(api, route) { + const layoutPath = (0, routing_1.getContextKey)(route.contextKey).replace(/\/index$/, ''); + const loaded = route.loadRoute(); + if (loaded.generateStaticParams) { + throw new Error('generateStaticParams is not supported in _layout routes with React Server Components enabled yet.'); } - await addLayout(routes); + const layoutSettings = readSettings(loaded); + api.createLayout({ + component: loaded.default, + path: layoutPath, + render: layoutSettings.render ?? 'static', + }); + await Promise.all(route.children.sort(routing_1.sortRoutes).map(async (child) => { + if (child.type === 'layout') { + await registerRouteTree(api, child); + return; + } + const childPath = (0, routing_1.getContextKey)(child.contextKey).replace(/\/index$/, ''); + const childLoaded = child.loadRoute(); + const settings = readSettings(childLoaded); + if (childLoaded.generateStaticParams) { + api.createPage({ + component: childLoaded.default, + path: childPath, + render: 'static', + staticPaths: (await loadStaticParamsForRoute(child)), + unstable_disableSSR: settings.unstable_disableSSR, + }); + if (settings.render !== 'static') { + api.createPage({ + component: childLoaded.default, + path: childPath, + render: 'dynamic', + unstable_disableSSR: settings.unstable_disableSSR, + }); + } + return; + } + api.createPage({ + component: childLoaded.default, + path: childPath, + render: settings.render ?? 'dynamic', + unstable_disableSSR: settings.unstable_disableSSR, + }); + })); +} +exports.default = (getRouteOptions) => ({ + default: (0, createPages_1.createPages)(async ({ createPage, createLayout, unstable_setBuildData }) => { + const routes = (0, getRoutesSSR_1.getRoutes)(_ctx_1.ctx, { + ...getRouteOptions, + platform: process.env.EXPO_OS, + skipGenerated: true, + importMode: 'lazy', + }); + if (!routes) + return; + await registerRouteTree({ createPage, createLayout, unstable_setBuildData }, routes); + }), }); //# sourceMappingURL=expo-definedRouter.js.map \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/router/expo-definedRouter.js.map b/packages/@expo/router-server/build/rsc/router/expo-definedRouter.js.map index 9d163fee2fef05..635347b8f1a4ec 100644 --- a/packages/@expo/router-server/build/rsc/router/expo-definedRouter.js.map +++ b/packages/@expo/router-server/build/rsc/router/expo-definedRouter.js.map @@ -1 +1 @@ -{"version":3,"file":"expo-definedRouter.js","sourceRoot":"","sources":["../../../src/rsc/router/expo-definedRouter.ts"],"names":[],"mappings":"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","sourcesContent":["import { ctx } from 'expo-router/_ctx';\nimport { getContextKey, sortRoutes, type RouteNode } from 'expo-router/internal/routing';\n\nimport { createExpoPages } from './create-expo-pages';\nimport { getRoutes } from '../../getRoutesSSR';\nimport { evalStaticParamsAsync } from '../../loadStaticParamsAsync';\n\nconst UNIMPLEMENTED_PARAMS = new Proxy(\n {},\n {\n // Assert that params is unimplemented when accessed.\n get() {\n throw new Error('generateStaticParams(): params is not implemented yet');\n },\n }\n);\n\nexport default createExpoPages(async ({ createPage, createLayout }, { getRouteOptions }) => {\n const routes = getRoutes(ctx, {\n ...getRouteOptions,\n platform: process.env.EXPO_OS,\n skipGenerated: true,\n importMode: 'lazy',\n });\n\n if (!routes) return;\n\n async function loadAndConvertStaticParamsAsync(route: RouteNode) {\n const loaded = route.loadRoute();\n\n let staticPaths: string[][] | undefined = undefined;\n\n if (route.dynamic) {\n const params = await evalStaticParamsAsync(\n route,\n { parentParams: UNIMPLEMENTED_PARAMS },\n loaded.generateStaticParams\n );\n\n // Sort `params` like `[{a: 'x', b: 'y'}, { a: 'z', b: 'w' }]` for a route.dynamic like `[{name: 'a', deep: false}, {name: 'b', deep: false}]` to `[['a', 'y'], ['z', 'w]]`\n staticPaths = params?.map((p) => {\n const grouped: string[] = [];\n\n for (const dynamic of route.dynamic!) {\n const defined = p[dynamic.name];\n if (!defined) {\n throw new Error(\n 'generateStaticParams is missing param: ' +\n dynamic.name +\n '. In route: ' +\n route.contextKey\n );\n }\n if (Array.isArray(defined)) {\n if (defined.length > 1) {\n throw new Error(\n 'generateStaticParams does not support returning multiple static paths for deep dynamic routes in React Server Components yet. Update route: ' +\n route.contextKey\n );\n }\n }\n\n const first = Array.isArray(defined) ? defined[0] : defined;\n\n if (first != null) {\n grouped.push(first);\n }\n }\n return grouped;\n });\n } else if (loaded.generateStaticParams) {\n throw new Error(\n 'Cannot use generateStaticParams without a dynamic route: ' + route.contextKey\n );\n }\n return staticPaths;\n }\n async function addLayout(route: RouteNode) {\n const normal = getContextKey(route.contextKey).replace(/\\/index$/, '');\n\n const loaded = route.loadRoute();\n\n if (loaded.generateStaticParams) {\n throw new Error(\n 'generateStaticParams is not supported in _layout routes with React Server Components enabled yet.'\n );\n }\n\n createLayout({\n // NOTE(EvanBacon): Support routes with top-level \"use client\"\n component: loaded.default! as any,\n path: normal as any,\n render: 'static',\n ...loaded.unstable_settings,\n });\n\n await Promise.all(\n route.children.sort(sortRoutes).map(async (child) => {\n if (child.type === 'layout') {\n await addLayout(child);\n } else {\n const normal = getContextKey(child.contextKey).replace(/\\/index$/, '');\n const loaded = child.loadRoute();\n const settings = loaded.unstable_settings;\n\n // Support generateStaticParams for dynamic routes by defining the route twice.\n if (loaded.generateStaticParams) {\n createPage({\n // NOTE(EvanBacon): Support routes with top-level \"use client\"\n component: loaded.default as any,\n path: normal as any,\n render: 'static',\n ...loaded.unstable_settings,\n staticPaths: (await loadAndConvertStaticParamsAsync(child)) as any,\n });\n\n if (settings?.render !== 'static') {\n createPage({\n // NOTE(EvanBacon): Support routes with top-level \"use client\"\n component: loaded.default as any,\n path: normal as any,\n render: 'dynamic',\n ...settings,\n });\n }\n } else {\n createPage({\n // NOTE(EvanBacon): Support routes with top-level \"use client\"\n component: loaded.default as any,\n path: normal as any,\n render: 'dynamic',\n ...settings,\n });\n }\n }\n })\n );\n }\n\n await addLayout(routes);\n});\n"]} \ No newline at end of file +{"version":3,"file":"expo-definedRouter.js","sourceRoot":"","sources":["../../../src/rsc/router/expo-definedRouter.ts"],"names":[],"mappings":"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","sourcesContent":["import { ctx } from 'expo-router/_ctx';\nimport { getContextKey, sortRoutes, type RouteNode } from 'expo-router/internal/routing';\n\nimport { createPages, type CreatePagesApi } from './createPages';\nimport { getRoutes } from '../../getRoutesSSR';\nimport { evalStaticParamsAsync } from '../../loadStaticParamsAsync';\nimport type { EntriesDev } from '../server';\n\ntype RouteSettings = {\n render?: 'static' | 'dynamic';\n unstable_disableSSR?: boolean;\n};\n\nfunction readSettings(loaded: { unstable_settings?: unknown }): RouteSettings {\n const raw = (loaded.unstable_settings ?? {}) as Record;\n const render = raw.render === 'static' || raw.render === 'dynamic' ? raw.render : undefined;\n const unstable_disableSSR =\n typeof raw.unstable_disableSSR === 'boolean' ? raw.unstable_disableSSR : undefined;\n return { render, unstable_disableSSR };\n}\n\nconst UNIMPLEMENTED_PARAMS = new Proxy(\n {},\n {\n get() {\n throw new Error('generateStaticParams(): params is not implemented yet');\n },\n }\n);\n\nasync function loadStaticParamsForRoute(route: RouteNode): Promise {\n const loaded = route.loadRoute();\n\n if (!route.dynamic) {\n if (loaded.generateStaticParams) {\n throw new Error(\n 'Cannot use generateStaticParams without a dynamic route: ' + route.contextKey\n );\n }\n return undefined;\n }\n\n const params = await evalStaticParamsAsync(\n route,\n { parentParams: UNIMPLEMENTED_PARAMS },\n loaded.generateStaticParams\n );\n\n return params?.map((p) => {\n const grouped: string[] = [];\n for (const dynamic of route.dynamic!) {\n const defined = p[dynamic.name];\n if (!defined) {\n throw new Error(\n 'generateStaticParams is missing param: ' +\n dynamic.name +\n '. In route: ' +\n route.contextKey\n );\n }\n if (Array.isArray(defined) && defined.length > 1) {\n throw new Error(\n 'generateStaticParams does not support returning multiple static paths for deep dynamic routes in React Server Components yet. Update route: ' +\n route.contextKey\n );\n }\n const first = Array.isArray(defined) ? defined[0] : defined;\n if (first != null) {\n grouped.push(first);\n }\n }\n return grouped;\n });\n}\n\nasync function registerRouteTree(api: CreatePagesApi, route: RouteNode): Promise {\n const layoutPath = getContextKey(route.contextKey).replace(/\\/index$/, '');\n const loaded = route.loadRoute();\n\n if (loaded.generateStaticParams) {\n throw new Error(\n 'generateStaticParams is not supported in _layout routes with React Server Components enabled yet.'\n );\n }\n\n const layoutSettings = readSettings(loaded);\n api.createLayout({\n component: loaded.default! as any,\n path: layoutPath,\n render: layoutSettings.render ?? 'static',\n });\n\n await Promise.all(\n route.children.sort(sortRoutes).map(async (child) => {\n if (child.type === 'layout') {\n await registerRouteTree(api, child);\n return;\n }\n const childPath = getContextKey(child.contextKey).replace(/\\/index$/, '');\n const childLoaded = child.loadRoute();\n const settings = readSettings(childLoaded);\n\n if (childLoaded.generateStaticParams) {\n api.createPage({\n component: childLoaded.default as any,\n path: childPath,\n render: 'static',\n staticPaths: (await loadStaticParamsForRoute(child)) as any,\n unstable_disableSSR: settings.unstable_disableSSR,\n });\n if (settings.render !== 'static') {\n api.createPage({\n component: childLoaded.default as any,\n path: childPath,\n render: 'dynamic',\n unstable_disableSSR: settings.unstable_disableSSR,\n });\n }\n return;\n }\n\n api.createPage({\n component: childLoaded.default as any,\n path: childPath,\n render: settings.render ?? 'dynamic',\n unstable_disableSSR: settings.unstable_disableSSR,\n });\n })\n );\n}\n\nexport default (getRouteOptions?: Parameters[1]): EntriesDev => ({\n default: createPages(async ({ createPage, createLayout, unstable_setBuildData }) => {\n const routes = getRoutes(ctx, {\n ...getRouteOptions,\n platform: process.env.EXPO_OS,\n skipGenerated: true,\n importMode: 'lazy',\n });\n if (!routes) return;\n await registerRouteTree({ createPage, createLayout, unstable_setBuildData }, routes);\n }),\n});\n"]} \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/router/index.d.ts b/packages/@expo/router-server/build/rsc/router/index.d.ts new file mode 100644 index 00000000000000..5ac918f119d7a5 --- /dev/null +++ b/packages/@expo/router-server/build/rsc/router/index.d.ts @@ -0,0 +1,13 @@ +import type { EntriesDev } from '../server'; +/** Shape of a router module loaded via Metro SSR (full or client-only). */ +export type RouterModule = { + default: (getRouteOptions?: any) => EntriesDev; +}; +/** + * Resolve the absolute file path of the router module to load. Two modes: + * full (Expo Router file tree walker) or client-only (no-op). + * + * The cli passes the returned path to Metro's SSR loader. + */ +export declare function resolveRouterModule(clientOnly: boolean): string; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/router/index.d.ts.map b/packages/@expo/router-server/build/rsc/router/index.d.ts.map new file mode 100644 index 00000000000000..882903ab81b70d --- /dev/null +++ b/packages/@expo/router-server/build/rsc/router/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/rsc/router/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE5C,2EAA2E;AAC3E,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,CAAC,eAAe,CAAC,EAAE,GAAG,KAAK,UAAU,CAAC;CAChD,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,OAAO,GAAG,MAAM,CAE/D"} \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/router/index.js b/packages/@expo/router-server/build/rsc/router/index.js new file mode 100644 index 00000000000000..ad206d5e0f80eb --- /dev/null +++ b/packages/@expo/router-server/build/rsc/router/index.js @@ -0,0 +1,13 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.resolveRouterModule = resolveRouterModule; +/** + * Resolve the absolute file path of the router module to load. Two modes: + * full (Expo Router file tree walker) or client-only (no-op). + * + * The cli passes the returned path to Metro's SSR loader. + */ +function resolveRouterModule(clientOnly) { + return clientOnly ? require.resolve('./noopRouter') : require.resolve('./expo-definedRouter'); +} +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/router/index.js.map b/packages/@expo/router-server/build/rsc/router/index.js.map new file mode 100644 index 00000000000000..bfc49691b49a9e --- /dev/null +++ b/packages/@expo/router-server/build/rsc/router/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/rsc/router/index.ts"],"names":[],"mappings":";;AAaA,kDAEC;AARD;;;;;GAKG;AACH,SAAgB,mBAAmB,CAAC,UAAmB;IACrD,OAAO,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAC;AAChG,CAAC","sourcesContent":["import type { EntriesDev } from '../server';\n\n/** Shape of a router module loaded via Metro SSR (full or client-only). */\nexport type RouterModule = {\n default: (getRouteOptions?: any) => EntriesDev;\n};\n\n/**\n * Resolve the absolute file path of the router module to load. Two modes:\n * full (Expo Router file tree walker) or client-only (no-op).\n *\n * The cli passes the returned path to Metro's SSR loader.\n */\nexport function resolveRouterModule(clientOnly: boolean): string {\n return clientOnly ? require.resolve('./noopRouter') : require.resolve('./expo-definedRouter');\n}\n"]} \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/router/noopRouter.d.ts b/packages/@expo/router-server/build/rsc/router/noopRouter.d.ts index 32be1b35e83279..ad067600cdb18c 100644 --- a/packages/@expo/router-server/build/rsc/router/noopRouter.d.ts +++ b/packages/@expo/router-server/build/rsc/router/noopRouter.d.ts @@ -1,3 +1,4 @@ -declare const _default: (getRouteOptions?: import("expo-router/build/getRoutes").Options) => import("../server").EntriesDev; +import type { EntriesDev } from '../server'; +declare const _default: (_getRouteOptions?: unknown) => EntriesDev; export default _default; //# sourceMappingURL=noopRouter.d.ts.map \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/router/noopRouter.d.ts.map b/packages/@expo/router-server/build/rsc/router/noopRouter.d.ts.map index ae7ce5b5d3044e..f5fe96a968aeb0 100644 --- a/packages/@expo/router-server/build/rsc/router/noopRouter.d.ts.map +++ b/packages/@expo/router-server/build/rsc/router/noopRouter.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"noopRouter.d.ts","sourceRoot":"","sources":["../../../src/rsc/router/noopRouter.ts"],"names":[],"mappings":";AAEA,wBAEG"} \ No newline at end of file +{"version":3,"file":"noopRouter.d.ts","sourceRoot":"","sources":["../../../src/rsc/router/noopRouter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;yBAI5B,mBAAmB,OAAO,KAAG,UAAU;AAAvD,wBAEG"} \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/router/noopRouter.js b/packages/@expo/router-server/build/rsc/router/noopRouter.js index e2f0757444de9e..56aab640b9da23 100644 --- a/packages/@expo/router-server/build/rsc/router/noopRouter.js +++ b/packages/@expo/router-server/build/rsc/router/noopRouter.js @@ -1,7 +1,8 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const create_expo_pages_1 = require("./create-expo-pages"); -exports.default = (0, create_expo_pages_1.createExpoPages)(async () => { - // noop the router for client-only mode. This ensures we skip loading the routes in react-server mode. +const createPages_1 = require("./createPages"); +// Used in client-only mode to skip route loading in react-server bundles. +exports.default = (_getRouteOptions) => ({ + default: (0, createPages_1.createPages)(async () => { }), }); //# sourceMappingURL=noopRouter.js.map \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/router/noopRouter.js.map b/packages/@expo/router-server/build/rsc/router/noopRouter.js.map index 6f3d124bba5344..40f0ff06e858aa 100644 --- a/packages/@expo/router-server/build/rsc/router/noopRouter.js.map +++ b/packages/@expo/router-server/build/rsc/router/noopRouter.js.map @@ -1 +1 @@ -{"version":3,"file":"noopRouter.js","sourceRoot":"","sources":["../../../src/rsc/router/noopRouter.ts"],"names":[],"mappings":";;AAAA,2DAAsD;AAEtD,kBAAe,IAAA,mCAAe,EAAC,KAAK,IAAI,EAAE;IACxC,sGAAsG;AACxG,CAAC,CAAC,CAAC","sourcesContent":["import { createExpoPages } from './create-expo-pages';\n\nexport default createExpoPages(async () => {\n // noop the router for client-only mode. This ensures we skip loading the routes in react-server mode.\n});\n"]} \ No newline at end of file +{"version":3,"file":"noopRouter.js","sourceRoot":"","sources":["../../../src/rsc/router/noopRouter.ts"],"names":[],"mappings":";;AACA,+CAA4C;AAE5C,0EAA0E;AAC1E,kBAAe,CAAC,gBAA0B,EAAc,EAAE,CAAC,CAAC;IAC1D,OAAO,EAAE,IAAA,yBAAW,EAAC,KAAK,IAAI,EAAE,GAAE,CAAC,CAAC;CACrC,CAAC,CAAC","sourcesContent":["import type { EntriesDev } from '../server';\nimport { createPages } from './createPages';\n\n// Used in client-only mode to skip route loading in react-server bundles.\nexport default (_getRouteOptions?: unknown): EntriesDev => ({\n default: createPages(async () => {}),\n});\n"]} \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/server.d.ts b/packages/@expo/router-server/build/rsc/server.d.ts index 7998908649f339..cca2c491fee36e 100644 --- a/packages/@expo/router-server/build/rsc/server.d.ts +++ b/packages/@expo/router-server/build/rsc/server.d.ts @@ -6,12 +6,11 @@ * LICENSE file in the root directory of this source tree. */ import type { ReactNode } from 'react'; -import type { PathSpec } from './path'; export declare const REQUEST_HEADERS = "__expo_requestHeaders"; type Config = any; type Elements = Record; export type BuildConfig = { - pathname: string | PathSpec; + pathname: string; isStatic?: boolean | undefined; entries?: { input: string; @@ -46,7 +45,7 @@ export type EntriesDev = { export type EntriesPrd = EntriesDev & { loadConfig: () => Promise; loadModule: (id: string) => Promise; - dynamicHtmlPaths: [pathSpec: PathSpec, htmlHead: string][]; + dynamicHtmlPaths: [pathname: string, htmlHead: string][]; publicIndexHtml: string; }; type RenderStore = { diff --git a/packages/@expo/router-server/build/rsc/server.d.ts.map b/packages/@expo/router-server/build/rsc/server.d.ts.map index 0b0503beb95ac5..4aaa77cad01766 100644 --- a/packages/@expo/router-server/build/rsc/server.d.ts.map +++ b/packages/@expo/router-server/build/rsc/server.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/rsc/server.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/rsc/server.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,eAAO,MAAM,eAAe,0BAA0B,CAAC;AASvD,KAAK,MAAM,GAAG,GAAG,CAAC;AAElB,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAE1C,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC/B,OAAO,CAAC,EAAE;QACR,KAAK,EAAE,MAAM,CAAC;QACd,YAAY,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QACnC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;KAChC,EAAE,CAAC;IACJ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,EAAE,CAAC;AAEJ,MAAM,MAAM,aAAa,GAAG,CAC1B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE;IACP,MAAM,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,WAAW,EAAE,WAAW,GAAG,SAAS,CAAC;CACtC,KACE,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;AAE9B,MAAM,MAAM,cAAc,GAAG,CAC3B,6BAA6B,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,KAChE,OAAO,CAAC,WAAW,CAAC,CAAC;AAE1B,MAAM,MAAM,YAAY,GAAG,CACzB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE;IACP,YAAY,EAAE,eAAe,CAAC;IAC9B,WAAW,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CACvC,KACE,OAAO,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,eAAe,CAAC;IAC/B,IAAI,EAAE,SAAS,CAAC;CACjB,GAAG,IAAI,CAAC,CAAC;AAEV,wBAAgB,aAAa,CAC3B,aAAa,EAAE,aAAa,EAC5B,cAAc,CAAC,EAAE,cAAc,EAC/B,YAAY,CAAC,EAAE,YAAY;;;;EAG5B;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;CAC3C,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG;IACpC,UAAU,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,UAAU,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7C,gBAAgB,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC;IACzD,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACpD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC,CAAC;AA2BF;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAAI,CAAC,EAAE,aAAa,WAAW,EAAE,IAAI,MAAM,CAAC,KAAG,CAY7E,CAAC;AAEF,wBAAsB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,iBAO7D;AAED,wBAAgB,UAAU,CACxB,UAAU,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KACjE,UAAU,CAOd"} \ No newline at end of file diff --git a/packages/@expo/router-server/build/rsc/server.js.map b/packages/@expo/router-server/build/rsc/server.js.map index e57f88b8e22ef3..564852c9d306e0 100644 --- a/packages/@expo/router-server/build/rsc/server.js.map +++ b/packages/@expo/router-server/build/rsc/server.js.map @@ -1 +1 @@ -{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/rsc/server.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;;AAyDH,sCAMC;AA4DD,4BAOC;AAED,gCASC;AA3ID,uDAAqD;AAKxC,QAAA,eAAe,GAAG,uBAAuB,CAAC;AAkDvD,SAAgB,aAAa,CAC3B,aAA4B,EAC5B,cAA+B,EAC/B,YAA2B;IAE3B,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC;AACzD,CAAC;AAkBD,6DAA6D;AAC7D,oIAAoI;AACpI,SAAS,yBAAyB;IAChC,4FAA4F;IAC5F,mGAAmG;IACnG,sBAAsB;IACtB,MAAM,QAAQ,GAAG,UAAU,CAAC,sBAAsB,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;IAC1E,IAAI,CAAC,UAAU,CAAC,kBAAkB,EAAE,CAAC;QACnC,UAAU,CAAC,kBAAkB,GAAG,IAAI,GAAG,EAAE,CAAC;IAC5C,CAAC;IAED,IAAI,UAAU,CAAC,kBAAkB,CAAC,GAAG,CAAC,QAAS,CAAC,EAAE,CAAC;QACjD,OAAO,UAAU,CAAC,kBAAkB,CAAC,GAAG,CAAC,QAAS,CAAE,CAAC;IACvD,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,oCAAiB,EAAe,CAAC;IAEzD,UAAU,CAAC,kBAAkB,CAAC,GAAG,CAAC,QAAS,EAAE,WAAW,CAAC,CAAC;IAE1D,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,IAAI,mBAA4C,CAAC;AACjD,IAAI,kBAA2C,CAAC;AAEhD;;GAEG;AACI,MAAM,kBAAkB,GAAG,CAAI,WAAwB,EAAE,EAAW,EAAK,EAAE;IAChF,MAAM,aAAa,GAAG,yBAAyB,EAAE,CAAC;IAClD,IAAI,aAAa,EAAE,CAAC;QAClB,OAAO,aAAa,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IAC5C,CAAC;IACD,mBAAmB,GAAG,kBAAkB,CAAC;IACzC,kBAAkB,GAAG,WAAW,CAAC;IACjC,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,CAAC;IACd,CAAC;YAAS,CAAC;QACT,kBAAkB,GAAG,mBAAmB,CAAC;IAC3C,CAAC;AACH,CAAC,CAAC;AAZW,QAAA,kBAAkB,sBAY7B;AAEK,KAAK,UAAU,QAAQ,CAAC,KAAa,EAAE,MAAgB;IAC5D,MAAM,aAAa,GAAG,yBAAyB,EAAE,CAAC;IAClD,MAAM,WAAW,GAAG,aAAa,CAAC,QAAQ,EAAE,IAAI,kBAAkB,CAAC;IACnE,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAChE,CAAC;IACD,WAAW,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;AACtC,CAAC;AAED,SAAgB,UAAU;IAGxB,MAAM,aAAa,GAAG,yBAAyB,EAAE,CAAC;IAClD,MAAM,WAAW,GAAG,aAAa,CAAC,QAAQ,EAAE,IAAI,kBAAkB,CAAC;IACnE,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,WAAW,CAAC,OAAqB,CAAC;AAC3C,CAAC","sourcesContent":["/**\n * Copyright © 2024 650 Industries.\n * Copyright © 2024 2023 Daishi Kato\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport type { ReactNode } from 'react';\n\nimport type { PathSpec } from './path';\n\nexport const REQUEST_HEADERS = '__expo_requestHeaders';\n\ndeclare let globalThis: {\n __EXPO_RSC_CACHE__?: Map;\n __expo_platform_header?: string;\n __webpack_chunk_load__: (id: string) => Promise;\n __webpack_require__: (id: string) => any;\n};\n\ntype Config = any;\n\ntype Elements = Record;\n\nexport type BuildConfig = {\n pathname: string | PathSpec; // TODO drop support for string?\n isStatic?: boolean | undefined;\n entries?: {\n input: string;\n skipPrefetch?: boolean | undefined;\n isStatic?: boolean | undefined;\n }[];\n context?: Record;\n customCode?: string; // optional code to inject TODO hope to remove this\n customData?: unknown; // should be serializable with JSON.stringify\n}[];\n\nexport type RenderEntries = (\n input: string,\n options: {\n params: unknown | undefined;\n buildConfig: BuildConfig | undefined;\n }\n) => Promise;\n\nexport type GetBuildConfig = (\n unstable_collectClientModules: (input: string) => Promise\n) => Promise;\n\nexport type GetSsrConfig = (\n pathname: string,\n options: {\n searchParams: URLSearchParams;\n buildConfig?: BuildConfig | undefined;\n }\n) => Promise<{\n input: string;\n searchParams?: URLSearchParams;\n html: ReactNode;\n} | null>;\n\nexport function defineEntries(\n renderEntries: RenderEntries,\n getBuildConfig?: GetBuildConfig,\n getSsrConfig?: GetSsrConfig\n) {\n return { renderEntries, getBuildConfig, getSsrConfig };\n}\n\nexport type EntriesDev = {\n default: ReturnType;\n};\n\nexport type EntriesPrd = EntriesDev & {\n loadConfig: () => Promise;\n loadModule: (id: string) => Promise;\n dynamicHtmlPaths: [pathSpec: PathSpec, htmlHead: string][];\n publicIndexHtml: string;\n};\n\ntype RenderStore = {\n rerender: (input: string, params?: unknown) => void;\n context: Record;\n};\n\n// TODO(EvanBacon): This can leak between platforms and runs.\n// We need to share this module between the server action module and the renderer module, per platform, and invalidate on refreshes.\nfunction getGlobalCacheForPlatform() {\n // HACK: This is a workaround for the shared middleware being shared between web and native.\n // In production the shared middleware is web-only and that causes the first version of this module\n // to be bound to web.\n const platform = globalThis.__expo_platform_header ?? process.env.EXPO_OS;\n if (!globalThis.__EXPO_RSC_CACHE__) {\n globalThis.__EXPO_RSC_CACHE__ = new Map();\n }\n\n if (globalThis.__EXPO_RSC_CACHE__.has(platform!)) {\n return globalThis.__EXPO_RSC_CACHE__.get(platform!)!;\n }\n\n const serverCache = new AsyncLocalStorage();\n\n globalThis.__EXPO_RSC_CACHE__.set(platform!, serverCache);\n\n return serverCache;\n}\n\nlet previousRenderStore: RenderStore | undefined;\nlet currentRenderStore: RenderStore | undefined;\n\n/**\n * This is an internal function and not for public use.\n */\nexport const runWithRenderStore = (renderStore: RenderStore, fn: () => T): T => {\n const renderStorage = getGlobalCacheForPlatform();\n if (renderStorage) {\n return renderStorage.run(renderStore, fn);\n }\n previousRenderStore = currentRenderStore;\n currentRenderStore = renderStore;\n try {\n return fn();\n } finally {\n currentRenderStore = previousRenderStore;\n }\n};\n\nexport async function rerender(input: string, params?: unknown) {\n const renderStorage = getGlobalCacheForPlatform();\n const renderStore = renderStorage.getStore() ?? currentRenderStore;\n if (!renderStore) {\n throw new Error('Render store is not available for rerender');\n }\n renderStore.rerender(input, params);\n}\n\nexport function getContext<\n RscContext extends Record = Record,\n>(): RscContext {\n const renderStorage = getGlobalCacheForPlatform();\n const renderStore = renderStorage.getStore() ?? currentRenderStore;\n if (!renderStore) {\n throw new Error('Render store is not available for accessing context');\n }\n return renderStore.context as RscContext;\n}\n"]} \ No newline at end of file +{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/rsc/server.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;;AAuDH,sCAMC;AA4DD,4BAOC;AAED,gCASC;AAzID,uDAAqD;AAGxC,QAAA,eAAe,GAAG,uBAAuB,CAAC;AAkDvD,SAAgB,aAAa,CAC3B,aAA4B,EAC5B,cAA+B,EAC/B,YAA2B;IAE3B,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC;AACzD,CAAC;AAkBD,6DAA6D;AAC7D,oIAAoI;AACpI,SAAS,yBAAyB;IAChC,4FAA4F;IAC5F,mGAAmG;IACnG,sBAAsB;IACtB,MAAM,QAAQ,GAAG,UAAU,CAAC,sBAAsB,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;IAC1E,IAAI,CAAC,UAAU,CAAC,kBAAkB,EAAE,CAAC;QACnC,UAAU,CAAC,kBAAkB,GAAG,IAAI,GAAG,EAAE,CAAC;IAC5C,CAAC;IAED,IAAI,UAAU,CAAC,kBAAkB,CAAC,GAAG,CAAC,QAAS,CAAC,EAAE,CAAC;QACjD,OAAO,UAAU,CAAC,kBAAkB,CAAC,GAAG,CAAC,QAAS,CAAE,CAAC;IACvD,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,oCAAiB,EAAe,CAAC;IAEzD,UAAU,CAAC,kBAAkB,CAAC,GAAG,CAAC,QAAS,EAAE,WAAW,CAAC,CAAC;IAE1D,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,IAAI,mBAA4C,CAAC;AACjD,IAAI,kBAA2C,CAAC;AAEhD;;GAEG;AACI,MAAM,kBAAkB,GAAG,CAAI,WAAwB,EAAE,EAAW,EAAK,EAAE;IAChF,MAAM,aAAa,GAAG,yBAAyB,EAAE,CAAC;IAClD,IAAI,aAAa,EAAE,CAAC;QAClB,OAAO,aAAa,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IAC5C,CAAC;IACD,mBAAmB,GAAG,kBAAkB,CAAC;IACzC,kBAAkB,GAAG,WAAW,CAAC;IACjC,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,CAAC;IACd,CAAC;YAAS,CAAC;QACT,kBAAkB,GAAG,mBAAmB,CAAC;IAC3C,CAAC;AACH,CAAC,CAAC;AAZW,QAAA,kBAAkB,sBAY7B;AAEK,KAAK,UAAU,QAAQ,CAAC,KAAa,EAAE,MAAgB;IAC5D,MAAM,aAAa,GAAG,yBAAyB,EAAE,CAAC;IAClD,MAAM,WAAW,GAAG,aAAa,CAAC,QAAQ,EAAE,IAAI,kBAAkB,CAAC;IACnE,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAChE,CAAC;IACD,WAAW,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;AACtC,CAAC;AAED,SAAgB,UAAU;IAGxB,MAAM,aAAa,GAAG,yBAAyB,EAAE,CAAC;IAClD,MAAM,WAAW,GAAG,aAAa,CAAC,QAAQ,EAAE,IAAI,kBAAkB,CAAC;IACnE,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,WAAW,CAAC,OAAqB,CAAC;AAC3C,CAAC","sourcesContent":["/**\n * Copyright © 2024 650 Industries.\n * Copyright © 2024 2023 Daishi Kato\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport type { ReactNode } from 'react';\n\nexport const REQUEST_HEADERS = '__expo_requestHeaders';\n\ndeclare let globalThis: {\n __EXPO_RSC_CACHE__?: Map;\n __expo_platform_header?: string;\n __webpack_chunk_load__: (id: string) => Promise;\n __webpack_require__: (id: string) => any;\n};\n\ntype Config = any;\n\ntype Elements = Record;\n\nexport type BuildConfig = {\n pathname: string;\n isStatic?: boolean | undefined;\n entries?: {\n input: string;\n skipPrefetch?: boolean | undefined;\n isStatic?: boolean | undefined;\n }[];\n context?: Record;\n customCode?: string; // optional code to inject TODO hope to remove this\n customData?: unknown; // should be serializable with JSON.stringify\n}[];\n\nexport type RenderEntries = (\n input: string,\n options: {\n params: unknown | undefined;\n buildConfig: BuildConfig | undefined;\n }\n) => Promise;\n\nexport type GetBuildConfig = (\n unstable_collectClientModules: (input: string) => Promise\n) => Promise;\n\nexport type GetSsrConfig = (\n pathname: string,\n options: {\n searchParams: URLSearchParams;\n buildConfig?: BuildConfig | undefined;\n }\n) => Promise<{\n input: string;\n searchParams?: URLSearchParams;\n html: ReactNode;\n} | null>;\n\nexport function defineEntries(\n renderEntries: RenderEntries,\n getBuildConfig?: GetBuildConfig,\n getSsrConfig?: GetSsrConfig\n) {\n return { renderEntries, getBuildConfig, getSsrConfig };\n}\n\nexport type EntriesDev = {\n default: ReturnType;\n};\n\nexport type EntriesPrd = EntriesDev & {\n loadConfig: () => Promise;\n loadModule: (id: string) => Promise;\n dynamicHtmlPaths: [pathname: string, htmlHead: string][];\n publicIndexHtml: string;\n};\n\ntype RenderStore = {\n rerender: (input: string, params?: unknown) => void;\n context: Record;\n};\n\n// TODO(EvanBacon): This can leak between platforms and runs.\n// We need to share this module between the server action module and the renderer module, per platform, and invalidate on refreshes.\nfunction getGlobalCacheForPlatform() {\n // HACK: This is a workaround for the shared middleware being shared between web and native.\n // In production the shared middleware is web-only and that causes the first version of this module\n // to be bound to web.\n const platform = globalThis.__expo_platform_header ?? process.env.EXPO_OS;\n if (!globalThis.__EXPO_RSC_CACHE__) {\n globalThis.__EXPO_RSC_CACHE__ = new Map();\n }\n\n if (globalThis.__EXPO_RSC_CACHE__.has(platform!)) {\n return globalThis.__EXPO_RSC_CACHE__.get(platform!)!;\n }\n\n const serverCache = new AsyncLocalStorage();\n\n globalThis.__EXPO_RSC_CACHE__.set(platform!, serverCache);\n\n return serverCache;\n}\n\nlet previousRenderStore: RenderStore | undefined;\nlet currentRenderStore: RenderStore | undefined;\n\n/**\n * This is an internal function and not for public use.\n */\nexport const runWithRenderStore = (renderStore: RenderStore, fn: () => T): T => {\n const renderStorage = getGlobalCacheForPlatform();\n if (renderStorage) {\n return renderStorage.run(renderStore, fn);\n }\n previousRenderStore = currentRenderStore;\n currentRenderStore = renderStore;\n try {\n return fn();\n } finally {\n currentRenderStore = previousRenderStore;\n }\n};\n\nexport async function rerender(input: string, params?: unknown) {\n const renderStorage = getGlobalCacheForPlatform();\n const renderStore = renderStorage.getStore() ?? currentRenderStore;\n if (!renderStore) {\n throw new Error('Render store is not available for rerender');\n }\n renderStore.rerender(input, params);\n}\n\nexport function getContext<\n RscContext extends Record = Record,\n>(): RscContext {\n const renderStorage = getGlobalCacheForPlatform();\n const renderStore = renderStorage.getStore() ?? currentRenderStore;\n if (!renderStore) {\n throw new Error('Render store is not available for accessing context');\n }\n return renderStore.context as RscContext;\n}\n"]} \ No newline at end of file diff --git a/packages/@expo/router-server/src/__tests__/getNamedParametrizedRoute.test.node.ts b/packages/@expo/router-server/src/__tests__/getNamedParametrizedRoute.test.node.ts new file mode 100644 index 00000000000000..941506fe7a9344 --- /dev/null +++ b/packages/@expo/router-server/src/__tests__/getNamedParametrizedRoute.test.node.ts @@ -0,0 +1,11 @@ +import { parseParameter } from '../getNamedParametrizedRoute'; + +describe(parseParameter, () => { + it(`matches optionals using non-standard from router v1`, () => { + expect(parseParameter('[...all]')).toEqual({ + name: 'all', + optional: true, + repeat: true, + }); + }); +}); diff --git a/packages/@expo/router-server/src/__tests__/getServerManifest.test.node.ts b/packages/@expo/router-server/src/__tests__/getServerManifest.test.node.ts index 89a5b1b35fb6fa..42aec64f99ee70 100644 --- a/packages/@expo/router-server/src/__tests__/getServerManifest.test.node.ts +++ b/packages/@expo/router-server/src/__tests__/getServerManifest.test.node.ts @@ -1,7 +1,7 @@ import type { RequireContext } from 'expo-router'; import { getExactRoutes } from 'expo-router/internal/routing'; -import { getServerManifest, parseParameter } from '../getServerManifest'; +import { getServerManifest } from '../getServerManifest'; function createMockContextModule(map: Record> = {}) { const contextModule = jest.fn((key) => map[key]); @@ -118,16 +118,6 @@ it(`converts a server manifest with nested root group and root layout`, () => { }); }); -describe(parseParameter, () => { - it(`matches optionals using non-standard from router v1`, () => { - expect(parseParameter('[...all]')).toEqual({ - name: 'all', - optional: true, - repeat: true, - }); - }); -}); - it(`sorts deep paths before shallow paths`, () => { const a = getServerManifest(getRoutesFor(['./b/c.tsx', './a.tsx'])); expect(a).toEqual(getServerManifest(getRoutesFor(['./a.tsx', './b/c.tsx']))); diff --git a/packages/@expo/router-server/src/getNamedParametrizedRoute.ts b/packages/@expo/router-server/src/getNamedParametrizedRoute.ts new file mode 100644 index 00000000000000..9e788e2161381b --- /dev/null +++ b/packages/@expo/router-server/src/getNamedParametrizedRoute.ts @@ -0,0 +1,139 @@ +import { matchGroupName } from 'expo-router/internal/routing'; + +/** + * Builds a function to generate a minimal routeKey using only a-z and minimal + * number of characters. + */ +function buildGetSafeRouteKey() { + let currentCharCode = 96; // Starting one before 'a' to make the increment logic simpler + let currentLength = 1; + + return () => { + let result = ''; + let incrementNext = true; + + // Iterate from right to left to build the key + for (let i = 0; i < currentLength; i++) { + if (incrementNext) { + currentCharCode++; + if (currentCharCode > 122) { + currentCharCode = 97; // Reset to 'a' + incrementNext = true; // Continue to increment the next character + } else { + incrementNext = false; + } + } + result = String.fromCharCode(currentCharCode) + result; + } + + // If all characters are 'z', increase the length of the key + if (incrementNext) { + currentLength++; + currentCharCode = 96; // This will make the next key start with 'a' + } + + return result; + }; +} + +function removeTrailingSlash(route: string): string { + return route.replace(/\/$/, '') || '/'; +} + +export function getNamedParametrizedRoute(route: string): { + namedParameterizedRoute: string; + routeKeys: Record; + /** Cleaned route-key names whose captures should be split into arrays (wildcards). */ + wildcardKeys: Set; +} { + const segments = removeTrailingSlash(route).slice(1).split('/'); + const getSafeRouteKey = buildGetSafeRouteKey(); + const routeKeys: Record = {}; + const wildcardKeys = new Set(); + const namedParameterizedRoute = segments + .map((segment, index) => { + if (segment === '+not-found' && index === segments.length - 1) { + segment = '[...not-found]'; + } + if (/^\[.*\]$/.test(segment)) { + const { name, optional, repeat } = parseParameter(segment); + // replace any non-word characters since they can break + // the named regex + let cleanedKey = name.replace(/\W/g, ''); + let invalidKey = false; + + // check if the key is still invalid and fallback to using a known + // safe key + if (cleanedKey.length === 0 || cleanedKey.length > 30) { + invalidKey = true; + } + if (!isNaN(parseInt(cleanedKey.slice(0, 1), 10))) { + invalidKey = true; + } + + // Prevent duplicates after sanitizing the key + if (cleanedKey in routeKeys) { + invalidKey = true; + } + + if (invalidKey) { + cleanedKey = getSafeRouteKey(); + } + + routeKeys[cleanedKey] = name; + if (repeat) wildcardKeys.add(cleanedKey); + return repeat + ? optional + ? `(?:/(?<${cleanedKey}>.+?))?` + : `/(?<${cleanedKey}>.+?)` + : `/(?<${cleanedKey}>[^/]+?)`; + } else if (/^\(.*\)$/.test(segment)) { + const groupName = matchGroupName(segment)! + .split(',') + .map((group) => group.trim()) + .filter(Boolean); + if (groupName.length > 1) { + const optionalSegment = `\\((?:${groupName.map(escapeStringRegexp).join('|')})\\)`; + // Make section optional + return `(?:/${optionalSegment})?`; + } else { + // Use simpler regex for single groups + return `(?:/${escapeStringRegexp(segment)})?`; + } + } else { + return `/${escapeStringRegexp(segment)}`; + } + }) + .join(''); + return { namedParameterizedRoute, routeKeys, wildcardKeys }; +} + +// regexp is based on https://github.com/sindresorhus/escape-string-regexp +const reHasRegExp = /[|\\{}()[\]^$+*?.-]/; +const reReplaceRegExp = /[|\\{}()[\]^$+*?.-]/g; + +function escapeStringRegexp(str: string) { + // see also: https://github.com/lodash/lodash/blob/2da024c3b4f9947a48517639de7560457cd4ec6c/escapeRegExp.js#L23 + if (reHasRegExp.test(str)) { + return str.replace(reReplaceRegExp, '\\$&'); + } + return str; +} + +export function parseParameter(param: string) { + let repeat = false; + let optional = false; + let name = param; + + if (/^\[.*\]$/.test(name)) { + optional = true; + name = name.slice(1, -1); + } + + if (/^\.\.\./.test(name)) { + repeat = true; + name = name.slice(3); + } + + return { name, repeat, optional }; +} diff --git a/packages/@expo/router-server/src/getServerManifest.ts b/packages/@expo/router-server/src/getServerManifest.ts index 3a12d459c25881..990f9f0ec5a8ea 100644 --- a/packages/@expo/router-server/src/getServerManifest.ts +++ b/packages/@expo/router-server/src/getServerManifest.ts @@ -1,21 +1,9 @@ -/** - * Copyright © 2023 650 Industries. - * Copyright © 2023 Vercel, Inc. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * Based on https://github.com/vercel/next.js/blob/1df2686bc9964f1a86c444701fa5cbf178669833/packages/next/src/shared/lib/router/utils/route-regex.ts - */ -import { - getContextKey, - matchGroupName, - sortRoutes, - type RouteNode, -} from 'expo-router/internal/routing'; +import { getContextKey, sortRoutes, type RouteNode } from 'expo-router/internal/routing'; import { shouldLinkExternally } from 'expo-router/internal/utils'; import type { RouteInfo, RoutesManifest } from 'expo-server/private'; +import { getNamedParametrizedRoute } from './getNamedParametrizedRoute'; + export interface Group { pos: number; repeat: boolean; @@ -199,139 +187,6 @@ function getNamedRouteRegex( }; } -/** - * Builds a function to generate a minimal routeKey using only a-z and minimal - * number of characters. - */ -function buildGetSafeRouteKey() { - let currentCharCode = 96; // Starting one before 'a' to make the increment logic simpler - let currentLength = 1; - - return () => { - let result = ''; - let incrementNext = true; - - // Iterate from right to left to build the key - for (let i = 0; i < currentLength; i++) { - if (incrementNext) { - currentCharCode++; - if (currentCharCode > 122) { - currentCharCode = 97; // Reset to 'a' - incrementNext = true; // Continue to increment the next character - } else { - incrementNext = false; - } - } - result = String.fromCharCode(currentCharCode) + result; - } - - // If all characters are 'z', increase the length of the key - if (incrementNext) { - currentLength++; - currentCharCode = 96; // This will make the next key start with 'a' - } - - return result; - }; -} - -function removeTrailingSlash(route: string): string { - return route.replace(/\/$/, '') || '/'; -} - -function getNamedParametrizedRoute(route: string) { - const segments = removeTrailingSlash(route).slice(1).split('/'); - const getSafeRouteKey = buildGetSafeRouteKey(); - const routeKeys: Record = {}; - return { - namedParameterizedRoute: segments - .map((segment, index) => { - if (segment === '+not-found' && index === segments.length - 1) { - segment = '[...not-found]'; - } - if (/^\[.*\]$/.test(segment)) { - const { name, optional, repeat } = parseParameter(segment); - // replace any non-word characters since they can break - // the named regex - let cleanedKey = name.replace(/\W/g, ''); - let invalidKey = false; - - // check if the key is still invalid and fallback to using a known - // safe key - if (cleanedKey.length === 0 || cleanedKey.length > 30) { - invalidKey = true; - } - if (!isNaN(parseInt(cleanedKey.slice(0, 1), 10))) { - invalidKey = true; - } - - // Prevent duplicates after sanitizing the key - if (cleanedKey in routeKeys) { - invalidKey = true; - } - - if (invalidKey) { - cleanedKey = getSafeRouteKey(); - } - - routeKeys[cleanedKey] = name; - return repeat - ? optional - ? `(?:/(?<${cleanedKey}>.+?))?` - : `/(?<${cleanedKey}>.+?)` - : `/(?<${cleanedKey}>[^/]+?)`; - } else if (/^\(.*\)$/.test(segment)) { - const groupName = matchGroupName(segment)! - .split(',') - .map((group) => group.trim()) - .filter(Boolean); - if (groupName.length > 1) { - const optionalSegment = `\\((?:${groupName.map(escapeStringRegexp).join('|')})\\)`; - // Make section optional - return `(?:/${optionalSegment})?`; - } else { - // Use simpler regex for single groups - return `(?:/${escapeStringRegexp(segment)})?`; - } - } else { - return `/${escapeStringRegexp(segment)}`; - } - }) - .join(''), - routeKeys, - }; -} - -// regexp is based on https://github.com/sindresorhus/escape-string-regexp -const reHasRegExp = /[|\\{}()[\]^$+*?.-]/; -const reReplaceRegExp = /[|\\{}()[\]^$+*?.-]/g; - -function escapeStringRegexp(str: string) { - // see also: https://github.com/lodash/lodash/blob/2da024c3b4f9947a48517639de7560457cd4ec6c/escapeRegExp.js#L23 - if (reHasRegExp.test(str)) { - return str.replace(reReplaceRegExp, '\\$&'); - } - return str; -} - -export function parseParameter(param: string) { - let repeat = false; - let optional = false; - let name = param; - - if (/^\[.*\]$/.test(name)) { - optional = true; - name = name.slice(1, -1); - } - - if (/^\.\.\./.test(name)) { - repeat = true; - name = name.slice(3); - } - - return { name, repeat, optional }; -} - function getNormalizedContextKey(contextKey: string): string { return getContextKey(contextKey).replace(/\/index$/, '') ?? '/'; } diff --git a/packages/@expo/router-server/src/rsc/middleware.ts b/packages/@expo/router-server/src/rsc/middleware.ts index 48871e29bcad18..59b805774479e5 100644 --- a/packages/@expo/router-server/src/rsc/middleware.ts +++ b/packages/@expo/router-server/src/rsc/middleware.ts @@ -74,7 +74,7 @@ function getSSRManifest( // The import map allows us to use external modules from different bundling contexts. type ImportMap = { - router: () => Promise; + router: () => Promise; }; export async function renderRscWithImportsAsync( diff --git a/packages/@expo/router-server/src/rsc/path.ts b/packages/@expo/router-server/src/rsc/path.ts index ef36bf47feda12..ae1efdfa1af125 100644 --- a/packages/@expo/router-server/src/rsc/path.ts +++ b/packages/@expo/router-server/src/rsc/path.ts @@ -79,97 +79,3 @@ export const extname = (filePath: string) => { const index = filePath.lastIndexOf('.'); return index > 0 ? filePath.slice(index) : ''; }; - -export type PathSpecItem = - | { type: 'literal'; name: string } - | { type: 'group'; name?: string } - | { type: 'wildcard'; name?: string }; -export type PathSpec = readonly PathSpecItem[]; - -export const parsePathWithSlug = (path: string): PathSpec => - path - .split('/') - .filter(Boolean) - .map((name) => { - let type: 'literal' | 'group' | 'wildcard' = 'literal'; - const isSlug = name.startsWith('[') && name.endsWith(']'); - if (isSlug) { - type = 'group'; - name = name.slice(1, -1); - } - const isWildcard = name.startsWith('...'); - if (isWildcard) { - type = 'wildcard'; - name = name.slice(3); - } - return { type, name }; - }); - -export const getPathMapping = ( - pathSpec: PathSpec, - pathname: string -): Record | null => { - const actual = pathname.split('/').filter(Boolean); - if (pathSpec.length > actual.length) { - return null; - } - const mapping: Record = {}; - let wildcardStartIndex = -1; - for (let i = 0; i < pathSpec.length; i++) { - const { type, name } = pathSpec[i]!; - if (type === 'literal') { - if (name !== actual[i]) { - return null; - } - } else if (type === 'wildcard') { - wildcardStartIndex = i; - break; - } else if (name) { - mapping[name] = actual[i]!; - } - } - if (wildcardStartIndex === -1) { - if (pathSpec.length !== actual.length) { - return null; - } - return mapping; - } - let wildcardEndIndex = -1; - for (let i = 0; i < pathSpec.length; i++) { - const { type, name } = pathSpec[pathSpec.length - i - 1]!; - if (type === 'literal') { - if (name !== actual[actual.length - i - 1]) { - return null; - } - } else if (type === 'wildcard') { - wildcardEndIndex = actual.length - i - 1; - break; - } else if (name) { - mapping[name] = actual[actual.length - i - 1]!; - } - } - if (wildcardStartIndex === -1 || wildcardEndIndex === -1) { - throw new Error('Invalid wildcard path'); - } - const wildcardName = pathSpec[wildcardStartIndex]!.name; - if (wildcardName) { - mapping[wildcardName] = actual.slice(wildcardStartIndex, wildcardEndIndex + 1); - } - return mapping; -}; - -/** - * Transform a path spec to a regular expression. - */ -export const path2regexp = (path: PathSpec) => { - const parts = path.map(({ type, name }) => { - if (type === 'literal') { - return name; - } else if (type === 'group') { - return `([^/]+)`; - } else { - return `(.*)`; - } - }); - return `^/${parts.join('/')}$`; -}; diff --git a/packages/@expo/router-server/src/rsc/router/__tests__/create-pages.test.node.ts b/packages/@expo/router-server/src/rsc/router/__tests__/create-pages.test.node.ts new file mode 100644 index 00000000000000..cd2041959c209b --- /dev/null +++ b/packages/@expo/router-server/src/rsc/router/__tests__/create-pages.test.node.ts @@ -0,0 +1,290 @@ +import type { FunctionComponent } from 'react'; + +import { createPages } from '../createPages'; + +const SHOULD_SKIP_ID = '/SHOULD_SKIP'; +const LOCATION_ID = '/LOCATION'; + +const NullComponent: FunctionComponent = () => null; + +function build(fn: Parameters[0]) { + return createPages(fn); +} + +async function render(router: ReturnType, input: string, params?: unknown) { + return router.renderEntries(input, { + params: params == null ? undefined : JSON.stringify(params), + buildConfig: undefined, + }); +} + +describe('createPages', () => { + it('resolves static page IDs alongside layouts', async () => { + const router = build(async ({ createPage, createLayout }) => { + createLayout({ component: NullComponent, path: '' as any, render: 'static' }); + createPage({ component: NullComponent, path: '/about' as any, render: 'static' }); + }); + + const entries = await render(router, 'about'); + + expect(entries).not.toBeNull(); + expect(Object.keys(entries!).sort()).toEqual( + [LOCATION_ID, SHOULD_SKIP_ID, 'about/page', 'layout'].sort() + ); + }); + + it('resolves slug-based dynamic page IDs', async () => { + const router = build(async ({ createPage, createLayout }) => { + createLayout({ component: NullComponent, path: '' as any, render: 'static' }); + createPage({ component: NullComponent, path: '/posts/[id]' as any, render: 'dynamic' }); + }); + + const entries = await render(router, 'posts/123'); + + expect(entries).not.toBeNull(); + expect(entries).toHaveProperty('posts/123/page'); + expect(entries).toHaveProperty('layout'); + }); + + it('resolves wildcard page IDs', async () => { + const router = build(async ({ createPage, createLayout }) => { + createLayout({ component: NullComponent, path: '' as any, render: 'static' }); + createPage({ + component: NullComponent, + path: '/blog/[...slug]' as any, + render: 'dynamic', + }); + }); + + const entries = await render(router, 'blog/a/b/c'); + + expect(entries).not.toBeNull(); + expect(entries).toHaveProperty('blog/a/b/c/page'); + }); + + it('returns null for an unknown path', async () => { + const router = build(async ({ createPage }) => { + createPage({ component: NullComponent, path: '/about' as any, render: 'static' }); + }); + + const entries = await render(router, 'unknown'); + + expect(entries).toBeNull(); + }); + + it('opts only pages (not layouts) into shouldSkipObj', async () => { + const router = build(async ({ createPage, createLayout }) => { + createLayout({ component: NullComponent, path: '' as any, render: 'static' }); + createPage({ component: NullComponent, path: '/about' as any, render: 'static' }); + }); + + const entries = await render(router, 'about'); + const shouldSkip = entries![SHOULD_SKIP_ID] as [string, unknown][]; + const optedInIds = new Set(shouldSkip.map(([id]) => id)); + + expect(optedInIds.has('about/page')).toBe(true); + expect(optedInIds.has('layout')).toBe(false); + }); + + it('drops a page from the response when the client skips an opted-in entry', async () => { + const router = build(async ({ createPage, createLayout }) => { + createLayout({ component: NullComponent, path: '' as any, render: 'static' }); + createPage({ component: NullComponent, path: '/about' as any, render: 'static' }); + }); + + const entries = await render(router, 'about', { skip: ['about/page'] }); + + expect(entries).not.toHaveProperty('about/page'); + expect(entries).toHaveProperty('layout'); + }); + + it('refuses to skip a layout even when the client requests it', async () => { + const router = build(async ({ createPage, createLayout }) => { + createLayout({ component: NullComponent, path: '' as any, render: 'static' }); + createPage({ component: NullComponent, path: '/about' as any, render: 'static' }); + }); + + const entries = await render(router, 'about', { skip: ['layout'] }); + + expect(entries).toHaveProperty('layout'); + expect(entries).toHaveProperty('about/page'); + }); + + it('matches static pages under group segments', async () => { + // Expo Router uses `(group)` segments for filesystem organization; they + // do not appear in runtime URLs. The matcher must treat them as optional + // so a page at `/(auth)/login` resolves for URL `/login`. + const router = build(async ({ createPage, createLayout }) => { + createLayout({ component: NullComponent, path: '' as any, render: 'static' }); + createPage({ + component: NullComponent, + path: '/(auth)/login' as any, + render: 'static', + }); + }); + + const entries = await render(router, 'login'); + expect(entries).not.toBeNull(); + expect(entries).toHaveProperty('login/page'); + }); + + it('prefers non-wildcard pages over wildcard pages when both could match', async () => { + const NonWildcardPage: FunctionComponent = () => null; + const WildcardPage: FunctionComponent = () => null; + + const router = build(async ({ createPage, createLayout }) => { + createLayout({ component: NullComponent, path: '' as any, render: 'static' }); + // Register wildcard FIRST so registration order can't accidentally satisfy the test. + createPage({ + component: WildcardPage, + path: '/posts/[...rest]' as any, + render: 'dynamic', + }); + createPage({ + component: NonWildcardPage, + path: '/posts/[id]' as any, + render: 'dynamic', + }); + }); + + const entries = await render(router, 'posts/123'); + expect(entries).not.toBeNull(); + expect(entries).toHaveProperty('posts/123/page'); + + const pageElement = entries!['posts/123/page'] as { type: FunctionComponent }; + // The matched component should be the non-wildcard page (single segment) even though + // the wildcard would also match. + expect(pageElement.type.name).toBe('WrappedComponent'); + }); + + it('renders a root page registered with empty path', async () => { + // Regression: top-level `./index.tsx` gets registered with path `''` after + // `getContextKey(...).replace(/\/index$/, '')`. The resolver and getBuildConfig + // must both handle this without throwing. + const router = build(async ({ createPage, createLayout }) => { + createLayout({ component: NullComponent, path: '' as any, render: 'static' }); + createPage({ component: NullComponent, path: '' as any, render: 'static' }); + }); + + const entries = await render(router, ''); + expect(entries).not.toBeNull(); + expect(entries).toHaveProperty('page'); + expect(entries).toHaveProperty('layout'); + }); + + it('produces a buildConfig entry for a root page', async () => { + // Regression: getInputString rejects paths that don't start with `/`. A root + // page must surface with pathname `/` in buildConfig, not the raw `''`. + const router = build(async ({ createPage }) => { + createPage({ component: NullComponent, path: '' as any, render: 'static' }); + }); + + const buildConfig = await router.getBuildConfig!(async () => []); + const root = buildConfig.find(({ pathname }) => pathname === '/'); + expect(root).toBeDefined(); + expect(root!.entries).toEqual([{ input: '', isStatic: true }]); + }); + + it('produces a buildConfig entry for a literal static page', async () => { + const router = build(async ({ createPage }) => { + createPage({ component: NullComponent, path: '/about' as any, render: 'static' }); + }); + + const buildConfig = await router.getBuildConfig!(async () => []); + const about = buildConfig.find(({ pathname }) => pathname === '/about'); + expect(about).toBeDefined(); + expect(about!.isStatic).toBe(true); + expect(about!.entries).toEqual([{ input: 'about', isStatic: true }]); + }); + + it('marks dynamic pages as non-static in buildConfig and skips their entries', async () => { + const router = build(async ({ createPage }) => { + createPage({ component: NullComponent, path: '/posts/[id]' as any, render: 'dynamic' }); + }); + + const buildConfig = await router.getBuildConfig!(async () => []); + const posts = buildConfig.find(({ pathname }) => pathname.includes('[')); + expect(posts).toBeDefined(); + expect(posts!.isStatic).toBe(false); + // No `entries[]` for dynamic paths — they can't be pre-rendered. + expect(posts!.entries).toEqual([]); + }); + + it('expands staticPaths into one buildConfig entry per concrete path', async () => { + const router = build(async ({ createPage }) => { + createPage({ + component: NullComponent, + path: '/posts/[id]' as any, + render: 'static', + staticPaths: [['a'], ['b'], ['c']], + }); + }); + + const buildConfig = await router.getBuildConfig!(async () => []); + const concretePaths = buildConfig + .map(({ pathname }) => pathname) + .filter((p) => p.startsWith('/posts/')) + .sort(); + expect(concretePaths).toEqual(['/posts/a', '/posts/b', '/posts/c']); + }); + + it('marks pages under a dynamic layout as non-static', async () => { + const router = build(async ({ createPage, createLayout }) => { + createLayout({ + component: NullComponent, + path: '/dashboard' as any, + render: 'dynamic', + }); + createPage({ + component: NullComponent, + path: '/dashboard/about' as any, + render: 'static', + }); + }); + + const buildConfig = await router.getBuildConfig!(async () => []); + const about = buildConfig.find(({ pathname }) => pathname === '/dashboard/about'); + expect(about).toBeDefined(); + expect(about!.isStatic).toBe(false); + }); + + it('propagates unstable_disableSSR onto buildConfig as customData.noSsr', async () => { + const router = build(async ({ createPage }) => { + createPage({ + component: NullComponent, + path: '/private' as any, + render: 'static', + unstable_disableSSR: true, + }); + }); + + const buildConfig = await router.getBuildConfig!(async () => []); + const priv = buildConfig.find(({ pathname }) => pathname === '/private'); + expect(priv!.customData).toMatchObject({ noSsr: true }); + }); + + it('throws on duplicate components for the same path+kind', async () => { + const ComponentA: FunctionComponent = () => null; + const ComponentB: FunctionComponent = () => null; + const router = build(async ({ createPage }) => { + createPage({ component: ComponentA, path: '/about' as any, render: 'static' }); + createPage({ component: ComponentB, path: '/about' as any, render: 'static' }); + }); + + await expect(render(router, 'about')).rejects.toThrow(/Duplicated/); + }); + + it('ignores non-string skip entries without crashing', async () => { + const router = build(async ({ createPage, createLayout }) => { + createLayout({ component: NullComponent, path: '' as any, render: 'static' }); + createPage({ component: NullComponent, path: '/about' as any, render: 'static' }); + }); + + const entries = await render(router, 'about', { + skip: [42, null, { malicious: true }, 'about/page'], + }); + + expect(entries).not.toHaveProperty('about/page'); + expect(entries).toHaveProperty('layout'); + }); +}); diff --git a/packages/@expo/router-server/src/rsc/router/create-expo-pages.ts b/packages/@expo/router-server/src/rsc/router/create-expo-pages.ts deleted file mode 100644 index d7443453bf1598..00000000000000 --- a/packages/@expo/router-server/src/rsc/router/create-expo-pages.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { GetRoutesOptions } from 'expo-router/internal/routing'; - -import { createPages } from './create-pages'; -import type { EntriesDev } from '../server'; - -type CreatePagesFn = Parameters[0]; -type CreatePagesFns = Parameters[0]; -type CreatePagesOptions = Parameters[1] & { - getRouteOptions?: GetRoutesOptions; -}; - -/** - * Wrapper around `createPages` to pass data from the server to the fn - * - * This is separated from the `createPages` function allowing us to keep the createPages - * in sync with the original Waku implementation. - * - * @param fn - * @returns - */ -export function createExpoPages( - fn: (fn: CreatePagesFns, options: CreatePagesOptions) => ReturnType -) { - return (getRouteOptions?: GetRoutesOptions): EntriesDev => { - return { - default: createPages((a, b) => fn(a, { ...b, getRouteOptions })), - }; - }; -} diff --git a/packages/@expo/router-server/src/rsc/router/create-pages.ts b/packages/@expo/router-server/src/rsc/router/create-pages.ts deleted file mode 100644 index 0ed9141b1edb50..00000000000000 --- a/packages/@expo/router-server/src/rsc/router/create-pages.ts +++ /dev/null @@ -1,408 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -/** - * Copyright © 2024 650 Industries. - * Copyright © 2024 2023 Daishi Kato - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * https://github.com/dai-shi/waku/blob/3d1cc7d714b67b142c847e879c30f0724fc457a7/packages/waku/src/router/create-pages.ts#L1 - */ - -import type { RouteProps } from 'expo-router/internal/rsc'; -import { createElement } from 'react'; -import type { FunctionComponent, ReactNode } from 'react'; - -import { unstable_defineRouter } from './defineRouter'; -import { joinPath, parsePathWithSlug, getPathMapping, path2regexp } from '../path'; -import type { PathSpec } from '../path'; -import type { BuildConfig } from '../server'; - -const hasPathSpecPrefix = (prefix: PathSpec, path: PathSpec) => { - for (let i = 0; i < prefix.length; i++) { - if ( - i >= path.length || - prefix[i]!.type !== path[i]!.type || - prefix[i]!.name !== path[i]!.name - ) { - return false; - } - } - return true; -}; - -const sanitizeSlug = (slug: string) => slug.replace(/\./g, '').replace(/ /g, '-'); - -// createPages API (a wrapper around unstable_defineRouter) - -/** - * Type version of `String.prototype.split()`. Splits the first string argument by the second string argument - * @example - * ```ts - * // ['a', 'b', 'c'] - * type Case1 = Split<'abc', ''> - * // ['a', 'b', 'c'] - * type Case2 = Split<'a,b,c', ','> - * ``` - */ -type Split = string extends Str - ? string[] - : '' extends Str - ? [] - : Str extends `${infer T}${Del}${infer U}` - ? [T, ...Split] - : [Str]; - -/** Assumes that the path is a part of a slug path. */ -type IsValidPathItem = T extends `/${infer _}` ? false : T extends '[]' | '' ? false : true; -/** - * This is a helper type to check if a path is valid in a slug path. - */ -export type IsValidPathInSlugPath = T extends `/${infer L}/${infer R}` - ? IsValidPathItem extends true - ? IsValidPathInSlugPath<`/${R}`> - : false - : T extends `/${infer U}` - ? IsValidPathItem - : false; -/** Checks if a particular slug name exists in a path. */ -export type HasSlugInPath = T extends `/[${K}]/${infer _}` - ? true - : T extends `/${infer _}/${infer U}` - ? HasSlugInPath<`/${U}`, K> - : T extends `/[${K}]` - ? true - : false; - -export type HasWildcardInPath = T extends `/[...${string}]/${string}` - ? true - : T extends `/${infer _}/${infer U}` - ? HasWildcardInPath<`/${U}`> - : T extends `/[...${string}]` - ? true - : false; - -export type PathWithSlug = - IsValidPathInSlugPath extends true ? (HasSlugInPath extends true ? T : never) : never; - -type _GetSlugs< - Route extends string, - SplitRoute extends string[] = Split, - Result extends string[] = [], -> = SplitRoute extends [] - ? Result - : SplitRoute extends [`${infer MaybeSlug}`, ...infer Rest] - ? Rest extends string[] - ? MaybeSlug extends `[${infer Slug}]` - ? _GetSlugs - : _GetSlugs - : never - : Result; - -export type GetSlugs = _GetSlugs; - -export type StaticSlugRoutePathsTuple< - T extends string, - Slugs extends unknown[] = GetSlugs, - Result extends string[] = [], -> = Slugs extends [] - ? Result - : Slugs extends [infer _, ...infer Rest] - ? StaticSlugRoutePathsTuple - : never; - -type StaticSlugRoutePaths = - HasWildcardInPath extends true - ? string[] | string[][] - : StaticSlugRoutePathsTuple extends [string] - ? string[] - : StaticSlugRoutePathsTuple[]; - -export type PathWithoutSlug = T extends '/' - ? T - : IsValidPathInSlugPath extends true - ? HasSlugInPath extends true - ? never - : T - : never; - -type PathWithStaticSlugs = T extends `/` - ? T - : IsValidPathInSlugPath extends true - ? T - : never; - -export type PathWithWildcard< - Path, - SlugKey extends string, - WildSlugKey extends string, -> = PathWithSlug; - -export type CreatePage = ( - page: ( - | { - render: 'static'; - path: PathWithoutSlug; - component: FunctionComponent; - } - | { - render: 'static'; - path: PathWithStaticSlugs; - staticPaths: StaticSlugRoutePaths; - component: FunctionComponent>; - } - | { - render: 'dynamic'; - path: PathWithoutSlug; - component: FunctionComponent; - } - | { - render: 'dynamic'; - path: PathWithWildcard; - component: FunctionComponent< - RouteProps & Record & Record - >; - } - ) & { unstable_disableSSR?: boolean } -) => void; - -export type CreateLayout = (layout: { - render: 'static' | 'dynamic'; - path: PathWithoutSlug; - component: FunctionComponent & { children: ReactNode }>; -}) => void; - -export function createPages( - fn: ( - fns: { - createPage: CreatePage; - createLayout: CreateLayout; - unstable_setBuildData: (path: string, data: unknown) => void; - }, - opts: { - unstable_buildConfig: BuildConfig | undefined; - } - ) => Promise -) { - let configured = false; - - // TODO I think there's room for improvement to refactor these structures - const staticPathSet = new Set<[string, PathSpec]>(); - const dynamicPagePathMap = new Map]>(); - const wildcardPagePathMap = new Map]>(); - const dynamicLayoutPathMap = new Map]>(); - const staticComponentMap = new Map>(); - const noSsrSet = new WeakSet(); - const buildDataMap = new Map(); - - const registerStaticComponent = (id: string, component: FunctionComponent) => { - if (staticComponentMap.has(id) && staticComponentMap.get(id) !== component) { - throw new Error(`Duplicated component for: ${id}`); - } - staticComponentMap.set(id, component); - }; - - const createPage: CreatePage = (page) => { - if (configured) { - throw new Error('no longer available'); - } - const pathSpec = parsePathWithSlug(page.path); - if (page.unstable_disableSSR) { - noSsrSet.add(pathSpec); - } - const { numSlugs, numWildcards } = (() => { - let numSlugs = 0; - let numWildcards = 0; - for (const slug of pathSpec) { - if (slug.type !== 'literal') { - numSlugs++; - } - if (slug.type === 'wildcard') { - numWildcards++; - } - } - return { numSlugs, numWildcards }; - })(); - if (page.render === 'static' && numSlugs === 0) { - staticPathSet.add([page.path, pathSpec]); - const id = joinPath(page.path, 'page').replace(/^\//, ''); - registerStaticComponent(id, page.component); - } else if (page.render === 'static' && numSlugs > 0 && 'staticPaths' in page) { - const staticPaths = page.staticPaths.map((item) => - (Array.isArray(item) ? item : [item]).map(sanitizeSlug) - ); - for (const staticPath of staticPaths) { - if (staticPath.length !== numSlugs && numWildcards === 0) { - throw new Error('staticPaths does not match with slug pattern'); - } - const mapping: Record = {}; - let slugIndex = 0; - const pathItems: string[] = []; - pathSpec.forEach(({ type, name }) => { - switch (type) { - case 'literal': - pathItems.push(name!); - break; - case 'wildcard': - mapping[name!] = staticPath.slice(slugIndex); - staticPath.slice(slugIndex++).forEach((slug) => { - pathItems.push(slug); - }); - break; - case 'group': - pathItems.push(staticPath[slugIndex++]!); - mapping[name!] = pathItems[pathItems.length - 1]!; - break; - } - }); - staticPathSet.add([page.path, pathItems.map((name) => ({ type: 'literal', name }))]); - const id = joinPath(...pathItems, 'page'); - const WrappedComponent = (props: Record) => - createElement(page.component as any, { ...props, ...mapping }); - registerStaticComponent(id, WrappedComponent); - } - } else if (page.render === 'dynamic' && numWildcards === 0) { - if (dynamicPagePathMap.has(page.path)) { - throw new Error(`Duplicated dynamic path: ${page.path}`); - } - dynamicPagePathMap.set(page.path, [pathSpec, page.component]); - } else if (page.render === 'dynamic' && numWildcards === 1) { - if (wildcardPagePathMap.has(page.path)) { - throw new Error(`Duplicated dynamic path: ${page.path}`); - } - wildcardPagePathMap.set(page.path, [pathSpec, page.component]); - } else { - throw new Error('Invalid page configuration: ' + page.path); - } - }; - - const createLayout: CreateLayout = (layout) => { - if (configured) { - throw new Error('no longer available'); - } - if (layout.render === 'static') { - const id = joinPath(layout.path, 'layout').replace(/^\//, ''); - registerStaticComponent(id, layout.component); - } else if (layout.render === 'dynamic') { - if (dynamicLayoutPathMap.has(layout.path)) { - throw new Error(`Duplicated dynamic path: ${layout.path}`); - } - const pathSpec = parsePathWithSlug(layout.path); - dynamicLayoutPathMap.set(layout.path, [pathSpec, layout.component]); - } else { - throw new Error('Invalid layout configuration'); - } - }; - - const unstable_setBuildData = (path: string, data: unknown) => { - buildDataMap.set(path, data); - }; - - let ready: Promise | undefined; - const configure = async (buildConfig?: BuildConfig) => { - if (!configured && !ready) { - ready = fn( - { createPage, createLayout, unstable_setBuildData }, - { unstable_buildConfig: buildConfig } - ); - await ready; - configured = true; - } - await ready; - }; - - return unstable_defineRouter( - async () => { - await configure(); - const paths: { - pattern: string; - path: PathSpec; - isStatic: boolean; - noSsr: boolean; - data: unknown; - }[] = []; - for (const [path, pathSpec] of staticPathSet) { - const noSsr = noSsrSet.has(pathSpec); - const isStatic = (() => { - for (const [_, [layoutPathSpec]] of dynamicLayoutPathMap) { - if (hasPathSpecPrefix(layoutPathSpec, pathSpec)) { - return false; - } - } - return true; - })(); - - paths.push({ - pattern: path2regexp(parsePathWithSlug(path)), - path: pathSpec, - isStatic, - noSsr, - data: buildDataMap.get(path), - }); - } - for (const [path, [pathSpec]] of dynamicPagePathMap) { - const noSsr = noSsrSet.has(pathSpec); - paths.push({ - pattern: path2regexp(parsePathWithSlug(path)), - path: pathSpec, - isStatic: false, - noSsr, - data: buildDataMap.get(path), - }); - } - for (const [path, [pathSpec]] of wildcardPagePathMap) { - const noSsr = noSsrSet.has(pathSpec); - paths.push({ - pattern: path2regexp(parsePathWithSlug(path)), - path: pathSpec, - isStatic: false, - noSsr, - data: buildDataMap.get(path), - }); - } - return paths; - }, - async (id, { unstable_setShouldSkip, unstable_buildConfig }) => { - await configure(unstable_buildConfig); - const staticComponent = staticComponentMap.get(id); - if (staticComponent) { - unstable_setShouldSkip([]); - return staticComponent; - } - for (const [_, [pathSpec, Component]] of dynamicPagePathMap) { - const mapping = getPathMapping([...pathSpec, { type: 'literal', name: 'page' }], id); - if (mapping) { - if (Object.keys(mapping).length === 0) { - unstable_setShouldSkip(); - return Component; - } - const WrappedComponent = (props: Record) => - createElement(Component, { ...props, ...mapping }); - unstable_setShouldSkip(); - return WrappedComponent; - } - } - for (const [_, [pathSpec, Component]] of wildcardPagePathMap) { - const mapping = getPathMapping([...pathSpec, { type: 'literal', name: 'page' }], id); - if (mapping) { - const WrappedComponent = (props: Record) => - createElement(Component, { ...props, ...mapping }); - unstable_setShouldSkip(); - return WrappedComponent; - } - } - for (const [_, [pathSpec, Component]] of dynamicLayoutPathMap) { - const mapping = getPathMapping([...pathSpec, { type: 'literal', name: 'layout' }], id); - if (mapping) { - if (Object.keys(mapping).length) { - throw new Error('[Bug] layout should not have slugs'); - } - unstable_setShouldSkip(); - return Component; - } - } - unstable_setShouldSkip([]); // negative cache - return null; // not found - } - ); -} diff --git a/packages/@expo/router-server/src/rsc/router/createPages.ts b/packages/@expo/router-server/src/rsc/router/createPages.ts new file mode 100644 index 00000000000000..7d51c9af663982 --- /dev/null +++ b/packages/@expo/router-server/src/rsc/router/createPages.ts @@ -0,0 +1,330 @@ +import { matchDynamicName } from 'expo-router/internal/routing'; +import type { RouteProps } from 'expo-router/internal/rsc'; +import { createElement } from 'react'; +import type { FunctionComponent, ReactNode } from 'react'; + +import { unstable_defineRouter } from './defineRouter'; +import { getNamedParametrizedRoute } from '../../getNamedParametrizedRoute'; +import type { BuildConfig } from '../server'; + +type ComponentKind = 'page' | 'layout'; +type SlugMapping = Record; +type IdMatcher = (id: string) => SlugMapping | null; + +type Entry = { + /** Registered path, may contain `[slug]`, `[...wildcard]`, or `(group)` segments. */ + path: string; + component: FunctionComponent; + kind: ComponentKind; + /** True if the path contains any slug or wildcard segments. */ + isDynamic: boolean; + /** True if the path contains a wildcard `[...rest]` segment. */ + isWildcard: boolean; + noSsr: boolean; + matchId: IdMatcher; + matchesPathname: (pathname: string) => boolean; +}; + +export type CreatePageInput = { + path: string; + component: FunctionComponent; + render: 'static' | 'dynamic'; + staticPaths?: (string | string[])[]; + unstable_disableSSR?: boolean; +}; + +export type CreateLayoutInput = { + path: string; + component: FunctionComponent & { children: ReactNode }>; + render: 'static' | 'dynamic'; +}; + +export type CreatePagesApi = { + createPage: (page: CreatePageInput) => void; + createLayout: (layout: CreateLayoutInput) => void; + unstable_setBuildData: (path: string, data: unknown) => void; +}; + +export type CreatePagesFn = ( + api: CreatePagesApi, + opts: { unstable_buildConfig: BuildConfig | undefined } +) => Promise; + +function compilePathMatcher(path: string, suffix?: 'page' | 'layout'): IdMatcher { + // Reuse expo-router-server's canonical regex builder so RSC matching agrees with + // the URL manifest about brackets, group routes (optional), wildcards, and +not-found. + const { namedParameterizedRoute, routeKeys, wildcardKeys } = getNamedParametrizedRoute(path); + // Strip a trailing slash before appending the suffix so the root path doesn't + // produce `//page` when concatenated. + const base = namedParameterizedRoute.replace(/\/$/, ''); + const regex = new RegExp(`^${suffix ? `${base}/${suffix}` : base}/?$`); + return (target) => { + // Targets are either IDs (`posts/123/page`) or pathnames (`/posts/123`). + // The canonical regex expects a leading slash, so add one for IDs. + const match = regex.exec(target.startsWith('/') ? target : '/' + target); + if (!match) return null; + const params: SlugMapping = {}; + for (const [cleanedKey, originalName] of Object.entries(routeKeys)) { + const value = match.groups?.[cleanedKey]; + if (value === undefined) continue; + params[originalName] = wildcardKeys.has(cleanedKey) ? value.split('/') : value; + } + return params; + }; +} + +function buildMatchesPathname(path: string): (pathname: string) => boolean { + const matcher = compilePathMatcher(path); + return (pathname) => matcher(pathname) != null; +} + +function isDynamicPath(path: string): boolean { + return path.split('/').some((segment) => matchDynamicName(segment) != null); +} + +function hasPathPrefix(prefix: string, path: string): boolean { + return path === prefix || path.startsWith(prefix + '/'); +} + +/** Normalize a registration path to a URL-shaped pathname (always starts with `/`). */ +function normalizePath(path: string): string { + if (path === '' || path === '/') return '/'; + return path.startsWith('/') ? path : '/' + path; +} + +function sanitizeSlug(slug: string): string { + return slug.replace(/\./g, '').replace(/ /g, '-'); +} + +/** + * Build an RSC router from a registration callback. Imitates `expo-server`'s + * URL routing: each registered component carries a regex matcher, and the + * resolver iterates the registry in specificity order, first match wins. + * No exact-key lookup: this lets paths with `(group)` segments match runtime + * IDs that don't include them. + */ +export function createPages(fn: CreatePagesFn): ReturnType { + let configured = false; + const entriesByKey = new Map(); + const buildDataMap = new Map(); + let sortedEntries: Entry[] = []; + + const register = (entry: Entry) => { + const key = `${entry.kind}:${entry.path}`; + const existing = entriesByKey.get(key); + if (existing && existing.component !== entry.component) { + throw new Error(`Duplicated component for ${entry.kind}: ${entry.path}`); + } + entriesByKey.set(key, entry); + }; + + const createPage = (page: CreatePageInput): void => { + if (configured) { + throw new Error('no longer available'); + } + // Normalize once up-front: top-level `./index.tsx` arrives as `''`, and + // everything downstream (the matcher, the registry key, the resolver) wants + // a URL-shaped pathname. + const path = normalizePath(page.path); + const noSsr = !!page.unstable_disableSSR; + const segments = path.split('/').filter(Boolean); + let numSlugs = 0; + let numWildcards = 0; + for (const segment of segments) { + const dynamic = matchDynamicName(segment); + if (!dynamic) continue; + numSlugs++; + if (dynamic.deep) numWildcards++; + } + + if (page.render === 'static' && numSlugs === 0) { + register({ + path, + component: page.component, + kind: 'page', + isDynamic: false, + isWildcard: false, + noSsr, + matchId: compilePathMatcher(path, 'page'), + matchesPathname: buildMatchesPathname(path), + }); + return; + } + + if (page.render === 'static' && numSlugs > 0) { + if (!page.staticPaths) { + throw new Error('staticPaths is required for static pages with slugs'); + } + const staticPaths = page.staticPaths.map((item) => + (Array.isArray(item) ? item : [item]).map(sanitizeSlug) + ); + for (const staticPath of staticPaths) { + if (staticPath.length !== numSlugs && numWildcards === 0) { + throw new Error('staticPaths does not match with slug pattern'); + } + const mapping: SlugMapping = {}; + let slugIndex = 0; + const pathItems: string[] = []; + for (const segment of segments) { + const dynamic = matchDynamicName(segment); + if (!dynamic) { + pathItems.push(segment); + continue; + } + if (dynamic.deep) { + mapping[dynamic.name] = staticPath.slice(slugIndex); + staticPath.slice(slugIndex++).forEach((slug) => pathItems.push(slug)); + } else { + pathItems.push(staticPath[slugIndex++]!); + mapping[dynamic.name] = pathItems[pathItems.length - 1]!; + } + } + const concretePath = '/' + pathItems.join('/'); + const WrappedComponent = (props: Record) => + createElement(page.component as any, { ...props, ...mapping }); + register({ + path: concretePath, + component: WrappedComponent, + kind: 'page', + isDynamic: false, + isWildcard: false, + noSsr, + matchId: compilePathMatcher(concretePath, 'page'), + matchesPathname: buildMatchesPathname(concretePath), + }); + } + return; + } + + if (page.render === 'dynamic') { + if (numWildcards > 1) { + throw new Error('Invalid page configuration: ' + path); + } + register({ + path, + component: page.component, + kind: 'page', + isDynamic: true, + isWildcard: numWildcards === 1, + noSsr, + matchId: compilePathMatcher(path, 'page'), + matchesPathname: buildMatchesPathname(path), + }); + return; + } + + throw new Error('Invalid page configuration: ' + path); + }; + + const createLayout = (layout: CreateLayoutInput): void => { + if (configured) { + throw new Error('no longer available'); + } + if (layout.render !== 'static' && layout.render !== 'dynamic') { + throw new Error('Invalid layout configuration'); + } + const path = normalizePath(layout.path); + register({ + path, + component: layout.component as FunctionComponent, + kind: 'layout', + isDynamic: layout.render === 'dynamic' || isDynamicPath(path), + isWildcard: false, + noSsr: false, + matchId: compilePathMatcher(path, 'layout'), + matchesPathname: buildMatchesPathname(path), + }); + }; + + const unstable_setBuildData = (path: string, data: unknown) => { + // Key by the same normalized pathname `register` uses, so the lookup at + // `buildDataMap.get(entry.path)` finds it regardless of caller convention. + buildDataMap.set(normalizePath(path), data); + }; + + let ready: Promise | undefined; + const configure = async (buildConfig?: BuildConfig) => { + if (!configured && !ready) { + ready = fn( + { createPage, createLayout, unstable_setBuildData }, + { unstable_buildConfig: buildConfig } + ); + await ready; + configured = true; + // Resolver iterates this once per request and takes the first matchId hit. + // Non-wildcard pages must out-rank wildcards so a more specific path wins; the + // matcher's `/page` vs `/layout` suffix prevents cross-kind false matches, so + // page-vs-layout order is irrelevant. + sortedEntries = Array.from(entriesByKey.values()).sort( + (a, b) => Number(a.isWildcard) - Number(b.isWildcard) + ); + } + await ready; + }; + + return unstable_defineRouter( + async () => { + await configure(); + const dynamicLayoutPaths: string[] = []; + for (const entry of sortedEntries) { + if (entry.kind === 'layout' && entry.isDynamic) dynamicLayoutPaths.push(entry.path); + } + const isUnderDynamicLayout = (pagePath: string) => + dynamicLayoutPaths.some((lp) => hasPathPrefix(lp, pagePath)); + + const paths: { + path: string; + matchesPathname: (pathname: string) => boolean; + isStatic: boolean; + noSsr: boolean; + data: unknown; + }[] = []; + for (const entry of sortedEntries) { + if (entry.kind !== 'page') continue; + paths.push({ + path: entry.path, + matchesPathname: entry.matchesPathname, + isStatic: !entry.isDynamic && !isUnderDynamicLayout(entry.path), + noSsr: entry.noSsr, + data: buildDataMap.get(entry.path), + }); + } + return paths; + }, + async (id, { unstable_setShouldSkip, unstable_buildConfig }) => { + await configure(unstable_buildConfig); + for (const entry of sortedEntries) { + const mapping = entry.matchId(id); + if (!mapping) continue; + if (entry.kind === 'layout') { + if (Object.keys(mapping).length) { + throw new Error('[Bug] layout should not have slugs'); + } + // Layouts never opt into shouldSkipObj — they must render on every request to + // enforce their auth/loader effects. + return { + component: entry.component as FunctionComponent< + Omit & { children: ReactNode } + >, + kind: 'layout', + }; + } + // Static pages opt into shouldSkipObj so the client can cache them across + // navigations. Dynamic pages don't (their content depends on slugs). + if (entry.isDynamic) { + unstable_setShouldSkip(); + } else { + unstable_setShouldSkip([]); + } + if (Object.keys(mapping).length === 0) { + return { component: entry.component as FunctionComponent, kind: 'page' }; + } + const WrappedComponent = (props: Record) => + createElement(entry.component, { ...props, ...mapping }); + return { component: WrappedComponent as FunctionComponent, kind: 'page' }; + } + unstable_setShouldSkip([]); + return null; + } + ); +} diff --git a/packages/@expo/router-server/src/rsc/router/defineRouter.ts b/packages/@expo/router-server/src/rsc/router/defineRouter.ts index 95048fa7bea16b..b6deba446780ea 100644 --- a/packages/@expo/router-server/src/rsc/router/defineRouter.ts +++ b/packages/@expo/router-server/src/rsc/router/defineRouter.ts @@ -21,8 +21,6 @@ import { import type { ComponentProps, FunctionComponent, ReactNode } from 'react'; import { createElement } from 'react'; -import { getPathMapping } from '../path'; -import type { PathSpec } from '../path'; import { rerender } from '../server'; import type { BuildConfig, @@ -52,28 +50,37 @@ const safeJsonParse = (str: unknown) => { return undefined; }; +const parseSkipList = (value: unknown): Set => { + if (!Array.isArray(value)) return new Set(); + return new Set(value.filter((v): v is string => typeof v === 'string')); +}; + export function unstable_defineRouter( getPathConfig: () => Promise< Iterable<{ - pattern: string; - path: PathSpec; + path: string; + matchesPathname: (pathname: string) => boolean; isStatic?: boolean; noSsr?: boolean; data?: unknown; // For build: put in customData }> >, getComponent: ( - componentId: string, // "**/layout" or "**/page" + componentId: string, options: { // TODO setShouldSkip API is too hard to understand unstable_setShouldSkip: (val?: ShouldSkipValue) => void; unstable_buildConfig: BuildConfig | undefined; } - ) => Promise | FunctionComponent | null> + ) => Promise< + | { component: FunctionComponent; kind: 'page' } + | { component: FunctionComponent; kind: 'layout' } + | null + > ): ReturnType { type MyPathConfig = { - pattern: string; - pathname: PathSpec; + pathname: string; + matchesPathname: (pathname: string) => boolean; isStatic?: boolean | undefined; customData: { noSsr?: boolean; is404: boolean; data: unknown }; }[]; @@ -83,18 +90,16 @@ export function unstable_defineRouter( return buildConfig as MyPathConfig; } if (!cachedPathConfig) { - cachedPathConfig = Array.from(await getPathConfig()).map((item) => { - const is404 = - item.path.length === 1 && - item.path[0]!.type === 'literal' && - item.path[0]!.name === '404'; - return { - pattern: item.pattern, - pathname: item.path, - isStatic: item.isStatic, - customData: { is404, noSsr: !!item.noSsr, data: item.data }, - }; - }); + cachedPathConfig = Array.from(await getPathConfig()).map((item) => ({ + pathname: item.path, + matchesPathname: item.matchesPathname, + isStatic: item.isStatic, + customData: { + is404: item.path === '/404', + noSsr: !!item.noSsr, + data: item.data, + }, + })); } return cachedPathConfig; }; @@ -103,7 +108,7 @@ export function unstable_defineRouter( buildConfig: BuildConfig | undefined ): Promise<['FOUND', 'NO_SSR'?] | ['NOT_FOUND', 'HAS_404'?]> => { const pathConfig = await getMyPathConfig(buildConfig); - const found = pathConfig.find(({ pathname: pathSpec }) => getPathMapping(pathSpec, pathname)); + const found = pathConfig.find(({ matchesPathname }) => matchesPathname(pathname)); return found ? found.customData.noSsr ? ['FOUND', 'NO_SSR'] @@ -124,12 +129,16 @@ export function unstable_defineRouter( const parsedParams = safeJsonParse(params); const query = typeof parsedParams?.query === 'string' ? parsedParams.query : ''; - const skip = Array.isArray(parsedParams?.skip) ? (parsedParams?.skip as unknown[]) : []; - const componentIds = getComponentIds(pathname); + const skip = parseSkipList(parsedParams?.skip); + const { layouts, page } = getComponentIds(pathname); const entries: (readonly [string, ReactNode])[] = ( await Promise.all( - componentIds.map(async (id) => { - if (skip?.includes(id)) { + [...layouts, page].map(async (id) => { + // `getComponentIds` separates the terminal page from layouts. Use that + // structural distinction as a fast-path skip (kind isn't known until we + // resolve); the kind-based check after resolve is the authoritative defense. + const isPage = id === page; + if (isPage && skip.has(id)) { return []; } const setShouldSkip = (val?: ShouldSkipValue) => { @@ -139,19 +148,23 @@ export function unstable_defineRouter( delete shouldSkipObj[id]; } }; - const component = await getComponent(id, { + const resolved = await getComponent(id, { unstable_setShouldSkip: setShouldSkip, unstable_buildConfig: buildConfig, }); - if (!component) { + if (!resolved) { + return []; + } + // Honor skip only for entries the resolver opted into shouldSkipObj; layouts are never skippable. + if (skip.has(id) && resolved.kind !== 'layout' && shouldSkipObj[id] != null) { return []; } const element = createElement( - component as FunctionComponent<{ + resolved.component as FunctionComponent<{ path: string; query?: string; }>, - id.endsWith('/layout') ? { path: pathname } : { path: pathname, query }, + isPage ? { path: pathname, query } : { path: pathname }, createElement(Children) ); return [[id, element]] as const; @@ -167,12 +180,10 @@ export function unstable_defineRouter( const pathConfig = await getMyPathConfig(); const path2moduleIds: Record = {}; - for (const { pathname: pathSpec } of pathConfig) { - if (pathSpec.some(({ type }) => type !== 'literal')) { + for (const { pathname } of pathConfig) { + if (pathname.includes('[')) { continue; } - - const pathname = '/' + pathSpec.map(({ name }) => name).join('/'); const input = getInputString(pathname); const moduleIds = await unstable_collectClientModules(input); path2moduleIds[pathname] = moduleIds; @@ -185,15 +196,14 @@ globalThis.__EXPO_ROUTER_PREFETCH__ = (path) => { } };`; const buildConfig: BuildConfig = []; - for (const { pathname: pathSpec, isStatic, customData } of pathConfig) { + for (const { pathname, isStatic, customData } of pathConfig) { const entries: BuildConfig[number]['entries'] = []; - if (pathSpec.every(({ type }) => type === 'literal')) { - const pathname = '/' + pathSpec.map(({ name }) => name).join('/'); + if (!pathname.includes('[')) { const input = getInputString(pathname); entries.push({ input, isStatic }); } buildConfig.push({ - pathname: pathSpec, + pathname, isStatic, entries, customCode, @@ -215,12 +225,12 @@ globalThis.__EXPO_ROUTER_PREFETCH__ = (path) => { return null; } } - const componentIds = getComponentIds(pathname); + const { layouts, page } = getComponentIds(pathname); const input = getInputString(pathname); const html = createElement( ServerRouter as FunctionComponent, 'children'>>, { route: { path: pathname, query: searchParams.toString(), hash: '' } }, - componentIds.reduceRight( + [...layouts, page].reduceRight( (acc: ReactNode, id) => createElement(Slot, { id, fallback: acc }, acc), null ) diff --git a/packages/@expo/router-server/src/rsc/router/expo-definedRouter.ts b/packages/@expo/router-server/src/rsc/router/expo-definedRouter.ts index d29ac55f350af4..24f68ee105a5c1 100644 --- a/packages/@expo/router-server/src/rsc/router/expo-definedRouter.ts +++ b/packages/@expo/router-server/src/rsc/router/expo-definedRouter.ts @@ -1,141 +1,143 @@ import { ctx } from 'expo-router/_ctx'; import { getContextKey, sortRoutes, type RouteNode } from 'expo-router/internal/routing'; -import { createExpoPages } from './create-expo-pages'; +import { createPages, type CreatePagesApi } from './createPages'; import { getRoutes } from '../../getRoutesSSR'; import { evalStaticParamsAsync } from '../../loadStaticParamsAsync'; +import type { EntriesDev } from '../server'; + +type RouteSettings = { + render?: 'static' | 'dynamic'; + unstable_disableSSR?: boolean; +}; + +function readSettings(loaded: { unstable_settings?: unknown }): RouteSettings { + const raw = (loaded.unstable_settings ?? {}) as Record; + const render = raw.render === 'static' || raw.render === 'dynamic' ? raw.render : undefined; + const unstable_disableSSR = + typeof raw.unstable_disableSSR === 'boolean' ? raw.unstable_disableSSR : undefined; + return { render, unstable_disableSSR }; +} const UNIMPLEMENTED_PARAMS = new Proxy( {}, { - // Assert that params is unimplemented when accessed. get() { throw new Error('generateStaticParams(): params is not implemented yet'); }, } ); -export default createExpoPages(async ({ createPage, createLayout }, { getRouteOptions }) => { - const routes = getRoutes(ctx, { - ...getRouteOptions, - platform: process.env.EXPO_OS, - skipGenerated: true, - importMode: 'lazy', - }); - - if (!routes) return; - - async function loadAndConvertStaticParamsAsync(route: RouteNode) { - const loaded = route.loadRoute(); - - let staticPaths: string[][] | undefined = undefined; - - if (route.dynamic) { - const params = await evalStaticParamsAsync( - route, - { parentParams: UNIMPLEMENTED_PARAMS }, - loaded.generateStaticParams - ); - - // Sort `params` like `[{a: 'x', b: 'y'}, { a: 'z', b: 'w' }]` for a route.dynamic like `[{name: 'a', deep: false}, {name: 'b', deep: false}]` to `[['a', 'y'], ['z', 'w]]` - staticPaths = params?.map((p) => { - const grouped: string[] = []; - - for (const dynamic of route.dynamic!) { - const defined = p[dynamic.name]; - if (!defined) { - throw new Error( - 'generateStaticParams is missing param: ' + - dynamic.name + - '. In route: ' + - route.contextKey - ); - } - if (Array.isArray(defined)) { - if (defined.length > 1) { - throw new Error( - 'generateStaticParams does not support returning multiple static paths for deep dynamic routes in React Server Components yet. Update route: ' + - route.contextKey - ); - } - } - - const first = Array.isArray(defined) ? defined[0] : defined; +async function loadStaticParamsForRoute(route: RouteNode): Promise { + const loaded = route.loadRoute(); - if (first != null) { - grouped.push(first); - } - } - return grouped; - }); - } else if (loaded.generateStaticParams) { + if (!route.dynamic) { + if (loaded.generateStaticParams) { throw new Error( 'Cannot use generateStaticParams without a dynamic route: ' + route.contextKey ); } - return staticPaths; + return undefined; } - async function addLayout(route: RouteNode) { - const normal = getContextKey(route.contextKey).replace(/\/index$/, ''); - const loaded = route.loadRoute(); - - if (loaded.generateStaticParams) { - throw new Error( - 'generateStaticParams is not supported in _layout routes with React Server Components enabled yet.' - ); + const params = await evalStaticParamsAsync( + route, + { parentParams: UNIMPLEMENTED_PARAMS }, + loaded.generateStaticParams + ); + + return params?.map((p) => { + const grouped: string[] = []; + for (const dynamic of route.dynamic!) { + const defined = p[dynamic.name]; + if (!defined) { + throw new Error( + 'generateStaticParams is missing param: ' + + dynamic.name + + '. In route: ' + + route.contextKey + ); + } + if (Array.isArray(defined) && defined.length > 1) { + throw new Error( + 'generateStaticParams does not support returning multiple static paths for deep dynamic routes in React Server Components yet. Update route: ' + + route.contextKey + ); + } + const first = Array.isArray(defined) ? defined[0] : defined; + if (first != null) { + grouped.push(first); + } } + return grouped; + }); +} - createLayout({ - // NOTE(EvanBacon): Support routes with top-level "use client" - component: loaded.default! as any, - path: normal as any, - render: 'static', - ...loaded.unstable_settings, - }); - - await Promise.all( - route.children.sort(sortRoutes).map(async (child) => { - if (child.type === 'layout') { - await addLayout(child); - } else { - const normal = getContextKey(child.contextKey).replace(/\/index$/, ''); - const loaded = child.loadRoute(); - const settings = loaded.unstable_settings; - - // Support generateStaticParams for dynamic routes by defining the route twice. - if (loaded.generateStaticParams) { - createPage({ - // NOTE(EvanBacon): Support routes with top-level "use client" - component: loaded.default as any, - path: normal as any, - render: 'static', - ...loaded.unstable_settings, - staticPaths: (await loadAndConvertStaticParamsAsync(child)) as any, - }); +async function registerRouteTree(api: CreatePagesApi, route: RouteNode): Promise { + const layoutPath = getContextKey(route.contextKey).replace(/\/index$/, ''); + const loaded = route.loadRoute(); - if (settings?.render !== 'static') { - createPage({ - // NOTE(EvanBacon): Support routes with top-level "use client" - component: loaded.default as any, - path: normal as any, - render: 'dynamic', - ...settings, - }); - } - } else { - createPage({ - // NOTE(EvanBacon): Support routes with top-level "use client" - component: loaded.default as any, - path: normal as any, - render: 'dynamic', - ...settings, - }); - } - } - }) + if (loaded.generateStaticParams) { + throw new Error( + 'generateStaticParams is not supported in _layout routes with React Server Components enabled yet.' ); } - await addLayout(routes); + const layoutSettings = readSettings(loaded); + api.createLayout({ + component: loaded.default! as any, + path: layoutPath, + render: layoutSettings.render ?? 'static', + }); + + await Promise.all( + route.children.sort(sortRoutes).map(async (child) => { + if (child.type === 'layout') { + await registerRouteTree(api, child); + return; + } + const childPath = getContextKey(child.contextKey).replace(/\/index$/, ''); + const childLoaded = child.loadRoute(); + const settings = readSettings(childLoaded); + + if (childLoaded.generateStaticParams) { + api.createPage({ + component: childLoaded.default as any, + path: childPath, + render: 'static', + staticPaths: (await loadStaticParamsForRoute(child)) as any, + unstable_disableSSR: settings.unstable_disableSSR, + }); + if (settings.render !== 'static') { + api.createPage({ + component: childLoaded.default as any, + path: childPath, + render: 'dynamic', + unstable_disableSSR: settings.unstable_disableSSR, + }); + } + return; + } + + api.createPage({ + component: childLoaded.default as any, + path: childPath, + render: settings.render ?? 'dynamic', + unstable_disableSSR: settings.unstable_disableSSR, + }); + }) + ); +} + +export default (getRouteOptions?: Parameters[1]): EntriesDev => ({ + default: createPages(async ({ createPage, createLayout, unstable_setBuildData }) => { + const routes = getRoutes(ctx, { + ...getRouteOptions, + platform: process.env.EXPO_OS, + skipGenerated: true, + importMode: 'lazy', + }); + if (!routes) return; + await registerRouteTree({ createPage, createLayout, unstable_setBuildData }, routes); + }), }); diff --git a/packages/@expo/router-server/src/rsc/router/index.ts b/packages/@expo/router-server/src/rsc/router/index.ts new file mode 100644 index 00000000000000..6c567f16c24335 --- /dev/null +++ b/packages/@expo/router-server/src/rsc/router/index.ts @@ -0,0 +1,16 @@ +import type { EntriesDev } from '../server'; + +/** Shape of a router module loaded via Metro SSR (full or client-only). */ +export type RouterModule = { + default: (getRouteOptions?: any) => EntriesDev; +}; + +/** + * Resolve the absolute file path of the router module to load. Two modes: + * full (Expo Router file tree walker) or client-only (no-op). + * + * The cli passes the returned path to Metro's SSR loader. + */ +export function resolveRouterModule(clientOnly: boolean): string { + return clientOnly ? require.resolve('./noopRouter') : require.resolve('./expo-definedRouter'); +} diff --git a/packages/@expo/router-server/src/rsc/router/noopRouter.ts b/packages/@expo/router-server/src/rsc/router/noopRouter.ts index c45dc843d61574..6f934112b70919 100644 --- a/packages/@expo/router-server/src/rsc/router/noopRouter.ts +++ b/packages/@expo/router-server/src/rsc/router/noopRouter.ts @@ -1,5 +1,7 @@ -import { createExpoPages } from './create-expo-pages'; +import type { EntriesDev } from '../server'; +import { createPages } from './createPages'; -export default createExpoPages(async () => { - // noop the router for client-only mode. This ensures we skip loading the routes in react-server mode. +// Used in client-only mode to skip route loading in react-server bundles. +export default (_getRouteOptions?: unknown): EntriesDev => ({ + default: createPages(async () => {}), }); diff --git a/packages/@expo/router-server/src/rsc/server.ts b/packages/@expo/router-server/src/rsc/server.ts index 1ed99979d85a71..844d2c64128b25 100644 --- a/packages/@expo/router-server/src/rsc/server.ts +++ b/packages/@expo/router-server/src/rsc/server.ts @@ -9,8 +9,6 @@ import { AsyncLocalStorage } from 'node:async_hooks'; import type { ReactNode } from 'react'; -import type { PathSpec } from './path'; - export const REQUEST_HEADERS = '__expo_requestHeaders'; declare let globalThis: { @@ -25,7 +23,7 @@ type Config = any; type Elements = Record; export type BuildConfig = { - pathname: string | PathSpec; // TODO drop support for string? + pathname: string; isStatic?: boolean | undefined; entries?: { input: string; @@ -76,7 +74,7 @@ export type EntriesDev = { export type EntriesPrd = EntriesDev & { loadConfig: () => Promise; loadModule: (id: string) => Promise; - dynamicHtmlPaths: [pathSpec: PathSpec, htmlHead: string][]; + dynamicHtmlPaths: [pathname: string, htmlHead: string][]; publicIndexHtml: string; }; diff --git a/packages/create-expo-module/src/utils/tar.ts b/packages/create-expo-module/src/utils/tar.ts index 6c7e54317824c6..04364d7520653c 100644 --- a/packages/create-expo-module/src/utils/tar.ts +++ b/packages/create-expo-module/src/utils/tar.ts @@ -38,10 +38,10 @@ export interface ExtractOptions { export async function extractStream( input: ReadableStream, - output: string, + targetOutput: string, options: ExtractOptions = {} ): Promise { - output = path.resolve(output); + const output = path.resolve(targetOutput) + path.sep; await fs.promises.mkdir(output, { recursive: true }); const { checksumAlgorithm, strip = 0, rename, filter } = options; diff --git a/packages/create-expo/CHANGELOG.md b/packages/create-expo/CHANGELOG.md index 9bfaae2b6934e1..bd094be706a868 100644 --- a/packages/create-expo/CHANGELOG.md +++ b/packages/create-expo/CHANGELOG.md @@ -8,6 +8,8 @@ ### 🐛 Bug fixes +- Fix containment check in tar extraction to cover parallel folders with same prefix ([#45882](https://github.com/expo/expo/pull/45882) by [@kitten](https://github.com/kitten)) + ### 💡 Others ## 3.8.0 — 2026-05-13 diff --git a/packages/create-expo/src/utils/tar.ts b/packages/create-expo/src/utils/tar.ts index 896afac3d7136d..3e0b6fcf465960 100644 --- a/packages/create-expo/src/utils/tar.ts +++ b/packages/create-expo/src/utils/tar.ts @@ -38,10 +38,10 @@ export interface ExtractOptions { export async function extractStream( input: ReadableStream, - output: string, + targetOutput: string, options: ExtractOptions = {} ): Promise { - output = path.resolve(output); + const output = path.resolve(targetOutput) + path.sep; await fs.promises.mkdir(output, { recursive: true }); const { checksumAlgorithm, strip = 0, rename, filter } = options; diff --git a/packages/expo-modules-autolinking/CHANGELOG.md b/packages/expo-modules-autolinking/CHANGELOG.md index a484f7fce3a9d2..06fd77631cb234 100644 --- a/packages/expo-modules-autolinking/CHANGELOG.md +++ b/packages/expo-modules-autolinking/CHANGELOG.md @@ -17,6 +17,7 @@ - [iOS] Fixed `pod install` failing with `bad component (expected absolute path component)` for precompiled Expo modules when the project lives under a path containing non-ASCII characters (e.g. emoji). ([#45779](https://github.com/expo/expo/pull/45779) by [@tsapeta](https://github.com/tsapeta)) - [iOS] Cache prebuilt module status lookups to reduce repeated `File.exist?` calls during `pod install`. ([#45742](https://github.com/expo/expo/pull/45742) by [@chrfalch](https://github.com/chrfalch)) - [iOS] Wire the macro plugin flag into `ExpoModulesCore`'s own xcconfig so SourceKit can resolve `#externalMacro` references in core source files. ([#45778](https://github.com/expo/expo/pull/45778) by [@tsapeta](https://github.com/tsapeta)) +- Disallow devtools plugins to point to `webpageRoot` outside of their own bounds ([#45841](https://github.com/expo/expo/pull/45841) by [@kitten](https://github.com/kitten)) ## 56.0.6 — 2026-05-13 diff --git a/packages/expo-modules-autolinking/build/platforms/devtools.js b/packages/expo-modules-autolinking/build/platforms/devtools.js index b0f76f3950fa4c..ec005e44a38c0d 100644 --- a/packages/expo-modules-autolinking/build/platforms/devtools.js +++ b/packages/expo-modules-autolinking/build/platforms/devtools.js @@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.resolveModuleAsync = resolveModuleAsync; exports.resolveExtraBuildDependenciesAsync = resolveExtraBuildDependenciesAsync; const path_1 = __importDefault(require("path")); +const utils_1 = require("../utils"); async function resolveModuleAsync(packageName, revision) { const devtoolsConfig = revision.config?.toJSON().devtools; if (devtoolsConfig == null) { @@ -14,12 +15,19 @@ async function resolveModuleAsync(packageName, revision) { return { packageName, packageRoot: revision.path, - webpageRoot: devtoolsConfig.webpageRoot - ? path_1.default.join(revision.path, devtoolsConfig.webpageRoot) - : undefined, + webpageRoot: await resolveWebpageRoot(revision.path, devtoolsConfig.webpageRoot), cliExtensions: devtoolsConfig.cliExtensions, }; } +async function resolveWebpageRoot(packageRoot, configuredWebpageRoot) { + if (!configuredWebpageRoot) { + return undefined; + } + const resolvedWebpageRoot = path_1.default.resolve(packageRoot, configuredWebpageRoot); + // NOTE(@kitten): Failing realpath-ing, typically due to ENOENT, results in the original value + const webpageRoot = (await (0, utils_1.maybeRealpath)(resolvedWebpageRoot)) ?? resolvedWebpageRoot; + return (0, utils_1.isPathInside)(webpageRoot, packageRoot) ? webpageRoot : undefined; +} async function resolveExtraBuildDependenciesAsync(_projectNativeRoot) { return null; } diff --git a/packages/expo-modules-autolinking/build/platforms/devtools.js.map b/packages/expo-modules-autolinking/build/platforms/devtools.js.map index e6a5bc41d0330d..0379b14cb2d41e 100644 --- a/packages/expo-modules-autolinking/build/platforms/devtools.js.map +++ b/packages/expo-modules-autolinking/build/platforms/devtools.js.map @@ -1 +1 @@ -{"version":3,"file":"devtools.js","sourceRoot":"","sources":["../../src/platforms/devtools.ts"],"names":[],"mappings":";;;;;AAIA,gDAiBC;AAED,gFAIC;AA3BD,gDAAwB;AAIjB,KAAK,UAAU,kBAAkB,CACtC,WAAmB,EACnB,QAAyB;IAEzB,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,QAAQ,CAAC;IAC1D,IAAI,cAAc,IAAI,IAAI,EAAE,CAAC;QAC3B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO;QACL,WAAW;QACX,WAAW,EAAE,QAAQ,CAAC,IAAI;QAC1B,WAAW,EAAE,cAAc,CAAC,WAAW;YACrC,CAAC,CAAC,cAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC,WAAW,CAAC;YACtD,CAAC,CAAC,SAAS;QACb,aAAa,EAAE,cAAc,CAAC,aAAa;KAC5C,CAAC;AACJ,CAAC;AAEM,KAAK,UAAU,kCAAkC,CACtD,kBAA0B;IAE1B,OAAO,IAAI,CAAC;AACd,CAAC"} \ No newline at end of file +{"version":3,"file":"devtools.js","sourceRoot":"","sources":["../../src/platforms/devtools.ts"],"names":[],"mappings":";;;;;AAKA,gDAeC;AAeD,gFAIC;AAvCD,gDAAwB;AAGxB,oCAAuD;AAEhD,KAAK,UAAU,kBAAkB,CACtC,WAAmB,EACnB,QAAyB;IAEzB,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,QAAQ,CAAC;IAC1D,IAAI,cAAc,IAAI,IAAI,EAAE,CAAC;QAC3B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO;QACL,WAAW;QACX,WAAW,EAAE,QAAQ,CAAC,IAAI;QAC1B,WAAW,EAAE,MAAM,kBAAkB,CAAC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC,WAAW,CAAC;QAChF,aAAa,EAAE,cAAc,CAAC,aAAa;KAC5C,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,kBAAkB,CAC/B,WAAmB,EACnB,qBAAyC;IAEzC,IAAI,CAAC,qBAAqB,EAAE,CAAC;QAC3B,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,mBAAmB,GAAG,cAAI,CAAC,OAAO,CAAC,WAAW,EAAE,qBAAqB,CAAC,CAAC;IAC7E,8FAA8F;IAC9F,MAAM,WAAW,GAAG,CAAC,MAAM,IAAA,qBAAa,EAAC,mBAAmB,CAAC,CAAC,IAAI,mBAAmB,CAAC;IACtF,OAAO,IAAA,oBAAY,EAAC,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC;AAC1E,CAAC;AAEM,KAAK,UAAU,kCAAkC,CACtD,kBAA0B;IAE1B,OAAO,IAAI,CAAC;AACd,CAAC"} \ No newline at end of file diff --git a/packages/expo-modules-autolinking/build/utils.d.ts b/packages/expo-modules-autolinking/build/utils.d.ts index 22a32c7988412c..02e6308d7e0170 100644 --- a/packages/expo-modules-autolinking/build/utils.d.ts +++ b/packages/expo-modules-autolinking/build/utils.d.ts @@ -11,6 +11,7 @@ export declare function scanFilesRecursively(parentPath: string, includeDirector export declare const fileExistsAsync: (file: string) => Promise; export declare const fastJoin: (from: string, append: string) => string; export declare const maybeRealpath: (target: string) => Promise; +export declare function isPathInside(child: string, parent: string): boolean; export type PackageJson = Record & { name?: string; version?: string; diff --git a/packages/expo-modules-autolinking/build/utils.js b/packages/expo-modules-autolinking/build/utils.js index 3cc18272d828af..98592379c98a2a 100644 --- a/packages/expo-modules-autolinking/build/utils.js +++ b/packages/expo-modules-autolinking/build/utils.js @@ -7,6 +7,7 @@ exports.loadPackageJson = exports.maybeRealpath = exports.fastJoin = exports.fil exports.listFilesSorted = listFilesSorted; exports.listFilesInDirectories = listFilesInDirectories; exports.scanFilesRecursively = scanFilesRecursively; +exports.isPathInside = isPathInside; const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const memoize_1 = require("./memoize"); @@ -84,6 +85,10 @@ const maybeRealpath = async (target) => { } }; exports.maybeRealpath = maybeRealpath; +function isPathInside(child, parent) { + const relative = path_1.default.relative(parent, child); + return !!relative && !relative.startsWith('..') && !path_1.default.isAbsolute(relative); +} exports.loadPackageJson = (0, memoize_1.memoize)(async function loadPackageJson(jsonPath) { try { const packageJsonText = await fs_1.default.promises.readFile(jsonPath, 'utf8'); diff --git a/packages/expo-modules-autolinking/build/utils.js.map b/packages/expo-modules-autolinking/build/utils.js.map index 172c57448ded8c..25b7b2e359de36 100644 --- a/packages/expo-modules-autolinking/build/utils.js.map +++ b/packages/expo-modules-autolinking/build/utils.js.map @@ -1 +1 @@ -{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;;;;AAMA,0CAaC;AAGD,wDAoBC;AAGD,oDA+BC;AAqBD,oCAGC;AApGD,4CAAoB;AACpB,gDAAwB;AAExB,uCAAoC;AAEpC,6EAA6E;AACtE,KAAK,UAAU,eAAe,CACnC,UAAkB,EAClB,MAAqC;IAErC,IAAI,CAAC;QACH,qDAAqD;QACrD,OAAO,CAAC,MAAM,YAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;aACpE,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;aACvD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;aAC5C,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,cAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,0FAA0F;AACnF,KAAK,UAAU,sBAAsB,CAC1C,UAAkB,EAClB,MAAqC;IAErC,OAAO,CACL,MAAM,OAAO,CAAC,GAAG,CACf,CAAC,MAAM,YAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;SAC7D,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,CAAC;SACvE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;SAC5C,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE;QACvB,MAAM,OAAO,GAAG,MAAM,YAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,cAAI,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,IAAI,CAAC,EAAE;YAC/E,aAAa,EAAE,IAAI;SACpB,CAAC,CAAC;QACH,OAAO,OAAO;aACX,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;aACvD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;aAC5C,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,cAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAC3D,CAAC,CAAC,CACL,CACF,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACZ,CAAC;AAED,kGAAkG;AAC3F,KAAK,SAAS,CAAC,CAAC,oBAAoB,CACzC,UAAkB,EAClB,gBAAgE,EAChE,IAAI,GAAG,CAAC,YAAE,CAAC,OAAO;IAElB,MAAM,KAAK,GAAG,CAAC,UAAU,CAAC,CAAC;IAC3B,IAAI,UAA8B,CAAC;IACnC,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC;QAChE,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI;gBAClB,CAAC,CAAC,CAAC,MAAM,YAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAC7E,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAC7B;gBACH,CAAC,CAAC,MAAM,YAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;YAC1C,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;gBAClC,IAAI,KAAK,CAAC,WAAW,EAAE,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;oBACzD,IAAI,CAAC,gBAAgB,IAAI,gBAAgB,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;wBAClE,KAAK,CAAC,IAAI,CAAC,cAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;oBAChD,CAAC;gBACH,CAAC;qBAAM,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;oBAC1B,MAAM;wBACJ,IAAI,EAAE,cAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC;wBACvC,UAAU,EAAE,UAAU;wBACtB,IAAI,EAAE,KAAK,CAAC,IAAI;qBACR,CAAC;gBACb,CAAC;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;IACH,CAAC;AACH,CAAC;AAEM,MAAM,eAAe,GAAG,KAAK,EAAE,IAAY,EAA0B,EAAE;IAC5E,MAAM,IAAI,GAAG,MAAM,YAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAC5D,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AACtC,CAAC,CAAC;AAHW,QAAA,eAAe,mBAG1B;AAEW,QAAA,QAAQ,GACnB,cAAI,CAAC,GAAG,KAAK,GAAG;IACd,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,GAAG,IAAI,GAAG,cAAI,CAAC,GAAG,GAAG,MAAM,EAAE;IACjD,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CACf,GAAG,IAAI,GAAG,cAAI,CAAC,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,cAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;AAEnF,MAAM,aAAa,GAAG,KAAK,EAAE,MAAc,EAA0B,EAAE;IAC5E,IAAI,CAAC;QACH,OAAO,MAAM,YAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC,CAAC;AANW,QAAA,aAAa,iBAMxB;AAEF,SAAgB,YAAY,CAAC,KAAa,EAAE,MAAc;IACxD,MAAM,QAAQ,GAAG,cAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC9C,OAAO,CAAC,CAAC,QAAQ,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,cAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;AAChF,CAAC;AASY,QAAA,eAAe,GAAG,IAAA,iBAAO,EAAC,KAAK,UAAU,eAAe,CACnE,QAAgB;IAEhB,IAAI,CAAC;QACH,MAAM,eAAe,GAAG,MAAM,YAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACrE,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;QACzC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;YAC7C,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC,CAAC,CAAC"} \ No newline at end of file diff --git a/packages/expo-modules-autolinking/src/platforms/__tests__/devtools-test.ts b/packages/expo-modules-autolinking/src/platforms/__tests__/devtools-test.ts new file mode 100644 index 00000000000000..01090ec07e6223 --- /dev/null +++ b/packages/expo-modules-autolinking/src/platforms/__tests__/devtools-test.ts @@ -0,0 +1,50 @@ +import { vol } from 'memfs'; +import * as path from 'path'; + +import { ExpoModuleConfig } from '../../ExpoModuleConfig'; +import { resolveModuleAsync } from '../devtools'; + +jest.mock('fs/promises'); + +afterEach(() => { + vol.reset(); + jest.resetAllMocks(); +}); + +function createRevision(pkgDir: string, webpageRoot: string | undefined) { + return { + name: 'example-devtools', + path: pkgDir, + version: '0.0.1', + config: new ExpoModuleConfig({ + platforms: ['devtools'], + devtools: webpageRoot != null ? { webpageRoot } : {}, + }), + }; +} + +describe(resolveModuleAsync, () => { + it('returns null when devtools config is absent', async () => { + const result = await resolveModuleAsync('example-devtools', { + name: 'example-devtools', + path: '/pkg', + version: '0.0.1', + config: new ExpoModuleConfig({ platforms: ['devtools'] }), + }); + expect(result).toBeNull(); + }); + + it('resolves a package-local webpageRoot to an absolute path inside the package', async () => { + const pkgDir = path.resolve('/node_modules/example-devtools'); + const result = await resolveModuleAsync('example-devtools', createRevision(pkgDir, 'web')); + expect(result).not.toBeNull(); + expect(result!.webpageRoot).toBe(path.join(pkgDir, 'web')); + }); + + it('drops webpageRoot when it traverses out of the package directory', async () => { + const pkgDir = path.resolve('/project/node_modules/malicious'); + const result = await resolveModuleAsync('malicious', createRevision(pkgDir, '../..')); + expect(result).not.toBeNull(); + expect(result!.webpageRoot).toBeUndefined(); + }); +}); diff --git a/packages/expo-modules-autolinking/src/platforms/devtools.ts b/packages/expo-modules-autolinking/src/platforms/devtools.ts index 010d6f9f4130e0..15c54c88ea5cf8 100644 --- a/packages/expo-modules-autolinking/src/platforms/devtools.ts +++ b/packages/expo-modules-autolinking/src/platforms/devtools.ts @@ -1,6 +1,7 @@ import path from 'path'; import type { ExtraDependencies, ModuleDescriptorDevTools, PackageRevision } from '../types'; +import { isPathInside, maybeRealpath } from '../utils'; export async function resolveModuleAsync( packageName: string, @@ -14,13 +15,24 @@ export async function resolveModuleAsync( return { packageName, packageRoot: revision.path, - webpageRoot: devtoolsConfig.webpageRoot - ? path.join(revision.path, devtoolsConfig.webpageRoot) - : undefined, + webpageRoot: await resolveWebpageRoot(revision.path, devtoolsConfig.webpageRoot), cliExtensions: devtoolsConfig.cliExtensions, }; } +async function resolveWebpageRoot( + packageRoot: string, + configuredWebpageRoot: string | undefined +): Promise { + if (!configuredWebpageRoot) { + return undefined; + } + const resolvedWebpageRoot = path.resolve(packageRoot, configuredWebpageRoot); + // NOTE(@kitten): Failing realpath-ing, typically due to ENOENT, results in the original value + const webpageRoot = (await maybeRealpath(resolvedWebpageRoot)) ?? resolvedWebpageRoot; + return isPathInside(webpageRoot, packageRoot) ? webpageRoot : undefined; +} + export async function resolveExtraBuildDependenciesAsync( _projectNativeRoot: string ): Promise { diff --git a/packages/expo-modules-autolinking/src/utils.ts b/packages/expo-modules-autolinking/src/utils.ts index e1bb866057c387..9e1a22ab42eb20 100644 --- a/packages/expo-modules-autolinking/src/utils.ts +++ b/packages/expo-modules-autolinking/src/utils.ts @@ -95,6 +95,11 @@ export const maybeRealpath = async (target: string): Promise => { } }; +export function isPathInside(child: string, parent: string): boolean { + const relative = path.relative(parent, child); + return !!relative && !relative.startsWith('..') && !path.isAbsolute(relative); +} + export type PackageJson = Record & { name?: string; version?: string; diff --git a/packages/expo-modules-jsi/CHANGELOG.md b/packages/expo-modules-jsi/CHANGELOG.md index 5b8ebae3e7392e..92996d66df592f 100644 --- a/packages/expo-modules-jsi/CHANGELOG.md +++ b/packages/expo-modules-jsi/CHANGELOG.md @@ -8,6 +8,8 @@ ### 🐛 Bug fixes +- [iOS] Fixed `no such module 'jsi'` build error when the package path contains `=` (pnpm virtual store with patched dependencies). ([#45956](https://github.com/expo/expo/pull/45956) by [@tsapeta](https://github.com/tsapeta)) + ### 💡 Others ## 56.0.5 — 2026-05-15 diff --git a/packages/expo-modules-jsi/apple/scripts/build-xcframework.sh b/packages/expo-modules-jsi/apple/scripts/build-xcframework.sh index 768ae12f9590df..4d987260cb72c6 100755 --- a/packages/expo-modules-jsi/apple/scripts/build-xcframework.sh +++ b/packages/expo-modules-jsi/apple/scripts/build-xcframework.sh @@ -278,8 +278,14 @@ if [[ -f "${PODS_ROOT}/Local Podspecs/React-Core.podspec.json" ]]; then SOURCE_FILES+=("${PODS_ROOT}/Local Podspecs/React-Core.podspec.json") fi -# Generate the module map for the `jsi` Clang module. -env PODS_ROOT="$PODS_ROOT" RN_ROOT="$RN_ROOT" "${PACKAGE_DIR}/scripts/generate-modulemap.sh" +# Generate the module map for the `jsi` Clang module. Set the env vars inline +# instead of via `env`: pnpm's virtual-store paths contain `=` characters +# (e.g. `patch_hash=…`), and BSD `env` parses positional args containing `=` +# as additional NAME=VALUE assignments — so `env FOO=bar /pnpm/path=with/equals` +# never finds a command, silently dumps the environment, and exits 0. The +# parent `set -eo pipefail` doesn't catch that, the modulemap never gets +# written, and xcodebuild later fails with `no such module 'jsi'`. +PODS_ROOT="$PODS_ROOT" RN_ROOT="$RN_ROOT" "${PACKAGE_DIR}/scripts/generate-modulemap.sh" GENERATED_MODULE_MAP="${PACKAGE_DIR}/.generated/module.modulemap" SOURCE_FILES+=("$GENERATED_MODULE_MAP") diff --git a/packages/expo-modules-jsi/apple/scripts/test.sh b/packages/expo-modules-jsi/apple/scripts/test.sh index a4f16bdf7b20cd..140779701987be 100755 --- a/packages/expo-modules-jsi/apple/scripts/test.sh +++ b/packages/expo-modules-jsi/apple/scripts/test.sh @@ -64,7 +64,7 @@ link_xcframework "ReactNativeDependencies" \ # --- Generate the jsi module map --- -env PODS_ROOT="$PODS_ROOT" "${PACKAGE_DIR}/scripts/generate-modulemap.sh" +PODS_ROOT="$PODS_ROOT" "${PACKAGE_DIR}/scripts/generate-modulemap.sh" # --- Pick a simulator destination --- diff --git a/packages/expo-router/CHANGELOG.md b/packages/expo-router/CHANGELOG.md index a3bfbd379e32b7..2f61a8bb0f80f1 100644 --- a/packages/expo-router/CHANGELOG.md +++ b/packages/expo-router/CHANGELOG.md @@ -10,6 +10,8 @@ ### 💡 Others +- [Internal] Canonicalize RSC component ID minting ([#45900](https://github.com/expo/expo/pull/45900) by [@kitten](https://github.com/kitten)) + ## 56.2.1 — 2026-05-15 ### 🎉 New features diff --git a/packages/expo-router/build/internal/routing.d.ts b/packages/expo-router/build/internal/routing.d.ts index 7a516b83b964a1..b9a2651ca1650a 100644 --- a/packages/expo-router/build/internal/routing.d.ts +++ b/packages/expo-router/build/internal/routing.d.ts @@ -1,7 +1,7 @@ export { getReactNavigationConfig } from '../getReactNavigationConfig'; export { getRoutes, getExactRoutes, type Options as GetRoutesOptions } from '../getRoutes'; export { extrapolateGroups, generateDynamic, getRoutes as getRoutesCore, type Options as GetRoutesCoreOptions, type RewriteConfig, } from '../getRoutesCore'; -export { getContextKey, isTypedRoute, matchGroupName, removeSupportedExtensions, stripGroupSegmentsFromPath, } from '../matchers'; +export { getContextKey, isTypedRoute, matchDynamicName, matchGroupName, removeSupportedExtensions, stripGroupSegmentsFromPath, } from '../matchers'; export type { DynamicConvention, RouteNode } from '../Route'; export { sortRoutes } from '../sortRoutes'; //# sourceMappingURL=routing.d.ts.map \ No newline at end of file diff --git a/packages/expo-router/build/internal/routing.d.ts.map b/packages/expo-router/build/internal/routing.d.ts.map index 54d978ffd9999f..e019c3cad82665 100644 --- a/packages/expo-router/build/internal/routing.d.ts.map +++ b/packages/expo-router/build/internal/routing.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/internal/routing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,KAAK,OAAO,IAAI,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAC3F,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,SAAS,IAAI,aAAa,EAC1B,KAAK,OAAO,IAAI,oBAAoB,EACpC,KAAK,aAAa,GACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,aAAa,EACb,YAAY,EACZ,cAAc,EACd,yBAAyB,EACzB,0BAA0B,GAC3B,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC"} \ No newline at end of file +{"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/internal/routing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,KAAK,OAAO,IAAI,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAC3F,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,SAAS,IAAI,aAAa,EAC1B,KAAK,OAAO,IAAI,oBAAoB,EACpC,KAAK,aAAa,GACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,yBAAyB,EACzB,0BAA0B,GAC3B,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC"} \ No newline at end of file diff --git a/packages/expo-router/build/internal/routing.js b/packages/expo-router/build/internal/routing.js index 8d4f73e2f19f14..1a4c5dcfd05d73 100644 --- a/packages/expo-router/build/internal/routing.js +++ b/packages/expo-router/build/internal/routing.js @@ -1,6 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.sortRoutes = exports.stripGroupSegmentsFromPath = exports.removeSupportedExtensions = exports.matchGroupName = exports.isTypedRoute = exports.getContextKey = exports.getRoutesCore = exports.generateDynamic = exports.extrapolateGroups = exports.getExactRoutes = exports.getRoutes = exports.getReactNavigationConfig = void 0; +exports.sortRoutes = exports.stripGroupSegmentsFromPath = exports.removeSupportedExtensions = exports.matchGroupName = exports.matchDynamicName = exports.isTypedRoute = exports.getContextKey = exports.getRoutesCore = exports.generateDynamic = exports.extrapolateGroups = exports.getExactRoutes = exports.getRoutes = exports.getReactNavigationConfig = void 0; var getReactNavigationConfig_1 = require("../getReactNavigationConfig"); Object.defineProperty(exports, "getReactNavigationConfig", { enumerable: true, get: function () { return getReactNavigationConfig_1.getReactNavigationConfig; } }); var getRoutes_1 = require("../getRoutes"); @@ -13,6 +13,7 @@ Object.defineProperty(exports, "getRoutesCore", { enumerable: true, get: functio var matchers_1 = require("../matchers"); Object.defineProperty(exports, "getContextKey", { enumerable: true, get: function () { return matchers_1.getContextKey; } }); Object.defineProperty(exports, "isTypedRoute", { enumerable: true, get: function () { return matchers_1.isTypedRoute; } }); +Object.defineProperty(exports, "matchDynamicName", { enumerable: true, get: function () { return matchers_1.matchDynamicName; } }); Object.defineProperty(exports, "matchGroupName", { enumerable: true, get: function () { return matchers_1.matchGroupName; } }); Object.defineProperty(exports, "removeSupportedExtensions", { enumerable: true, get: function () { return matchers_1.removeSupportedExtensions; } }); Object.defineProperty(exports, "stripGroupSegmentsFromPath", { enumerable: true, get: function () { return matchers_1.stripGroupSegmentsFromPath; } }); diff --git a/packages/expo-router/build/internal/routing.js.map b/packages/expo-router/build/internal/routing.js.map index 079e5fcafa9672..8e7081dbe3803e 100644 --- a/packages/expo-router/build/internal/routing.js.map +++ b/packages/expo-router/build/internal/routing.js.map @@ -1 +1 @@ -{"version":3,"file":"routing.js","sourceRoot":"","sources":["../../src/internal/routing.ts"],"names":[],"mappings":";;;AAAA,wEAAuE;AAA9D,oIAAA,wBAAwB,OAAA;AACjC,0CAA2F;AAAlF,sGAAA,SAAS,OAAA;AAAE,2GAAA,cAAc,OAAA;AAClC,kDAM0B;AALxB,kHAAA,iBAAiB,OAAA;AACjB,gHAAA,eAAe,OAAA;AACf,8GAAA,SAAS,OAAiB;AAI5B,wCAMqB;AALnB,yGAAA,aAAa,OAAA;AACb,wGAAA,YAAY,OAAA;AACZ,0GAAA,cAAc,OAAA;AACd,qHAAA,yBAAyB,OAAA;AACzB,sHAAA,0BAA0B,OAAA;AAG5B,4CAA2C;AAAlC,wGAAA,UAAU,OAAA","sourcesContent":["export { getReactNavigationConfig } from '../getReactNavigationConfig';\nexport { getRoutes, getExactRoutes, type Options as GetRoutesOptions } from '../getRoutes';\nexport {\n extrapolateGroups,\n generateDynamic,\n getRoutes as getRoutesCore,\n type Options as GetRoutesCoreOptions,\n type RewriteConfig,\n} from '../getRoutesCore';\nexport {\n getContextKey,\n isTypedRoute,\n matchGroupName,\n removeSupportedExtensions,\n stripGroupSegmentsFromPath,\n} from '../matchers';\nexport type { DynamicConvention, RouteNode } from '../Route';\nexport { sortRoutes } from '../sortRoutes';\n"]} \ No newline at end of file +{"version":3,"file":"routing.js","sourceRoot":"","sources":["../../src/internal/routing.ts"],"names":[],"mappings":";;;AAAA,wEAAuE;AAA9D,oIAAA,wBAAwB,OAAA;AACjC,0CAA2F;AAAlF,sGAAA,SAAS,OAAA;AAAE,2GAAA,cAAc,OAAA;AAClC,kDAM0B;AALxB,kHAAA,iBAAiB,OAAA;AACjB,gHAAA,eAAe,OAAA;AACf,8GAAA,SAAS,OAAiB;AAI5B,wCAOqB;AANnB,yGAAA,aAAa,OAAA;AACb,wGAAA,YAAY,OAAA;AACZ,4GAAA,gBAAgB,OAAA;AAChB,0GAAA,cAAc,OAAA;AACd,qHAAA,yBAAyB,OAAA;AACzB,sHAAA,0BAA0B,OAAA;AAG5B,4CAA2C;AAAlC,wGAAA,UAAU,OAAA","sourcesContent":["export { getReactNavigationConfig } from '../getReactNavigationConfig';\nexport { getRoutes, getExactRoutes, type Options as GetRoutesOptions } from '../getRoutes';\nexport {\n extrapolateGroups,\n generateDynamic,\n getRoutes as getRoutesCore,\n type Options as GetRoutesCoreOptions,\n type RewriteConfig,\n} from '../getRoutesCore';\nexport {\n getContextKey,\n isTypedRoute,\n matchDynamicName,\n matchGroupName,\n removeSupportedExtensions,\n stripGroupSegmentsFromPath,\n} from '../matchers';\nexport type { DynamicConvention, RouteNode } from '../Route';\nexport { sortRoutes } from '../sortRoutes';\n"]} \ No newline at end of file diff --git a/packages/expo-router/build/internal/rsc.d.ts b/packages/expo-router/build/internal/rsc.d.ts index 9a4ff0a0c1d10a..2963f0ea6282dd 100644 --- a/packages/expo-router/build/internal/rsc.d.ts +++ b/packages/expo-router/build/internal/rsc.d.ts @@ -1,5 +1,5 @@ export { ServerRouter } from '../rsc/router/client'; -export { getComponentIds, getInputString, parseInputString, PARAM_KEY_SKIP, SHOULD_SKIP_ID, LOCATION_ID, type RouteProps, type ShouldSkip, } from '../rsc/router/common'; +export { getComponentIds, getInputString, parseInputString, mintComponentId, type ComponentIdKind, type ComponentIds, PARAM_KEY_SKIP, SHOULD_SKIP_ID, LOCATION_ID, type RouteProps, type ShouldSkip, } from '../rsc/router/common'; export { Children, Slot } from '../rsc/router/host'; export { decodeActionId } from '../rsc/router/utils'; //# sourceMappingURL=rsc.d.ts.map \ No newline at end of file diff --git a/packages/expo-router/build/internal/rsc.d.ts.map b/packages/expo-router/build/internal/rsc.d.ts.map index c4e087c9b47bce..5f711d5383e063 100644 --- a/packages/expo-router/build/internal/rsc.d.ts.map +++ b/packages/expo-router/build/internal/rsc.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"rsc.d.ts","sourceRoot":"","sources":["../../src/internal/rsc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EACL,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,WAAW,EACX,KAAK,UAAU,EACf,KAAK,UAAU,GAChB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC"} \ No newline at end of file +{"version":3,"file":"rsc.d.ts","sourceRoot":"","sources":["../../src/internal/rsc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EACL,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,cAAc,EACd,cAAc,EACd,WAAW,EACX,KAAK,UAAU,EACf,KAAK,UAAU,GAChB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC"} \ No newline at end of file diff --git a/packages/expo-router/build/internal/rsc.js b/packages/expo-router/build/internal/rsc.js index ce394cbb4cc549..ede30f67ab12ee 100644 --- a/packages/expo-router/build/internal/rsc.js +++ b/packages/expo-router/build/internal/rsc.js @@ -1,12 +1,13 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.decodeActionId = exports.Slot = exports.Children = exports.LOCATION_ID = exports.SHOULD_SKIP_ID = exports.PARAM_KEY_SKIP = exports.parseInputString = exports.getInputString = exports.getComponentIds = exports.ServerRouter = void 0; +exports.decodeActionId = exports.Slot = exports.Children = exports.LOCATION_ID = exports.SHOULD_SKIP_ID = exports.PARAM_KEY_SKIP = exports.mintComponentId = exports.parseInputString = exports.getInputString = exports.getComponentIds = exports.ServerRouter = void 0; var client_1 = require("../rsc/router/client"); Object.defineProperty(exports, "ServerRouter", { enumerable: true, get: function () { return client_1.ServerRouter; } }); var common_1 = require("../rsc/router/common"); Object.defineProperty(exports, "getComponentIds", { enumerable: true, get: function () { return common_1.getComponentIds; } }); Object.defineProperty(exports, "getInputString", { enumerable: true, get: function () { return common_1.getInputString; } }); Object.defineProperty(exports, "parseInputString", { enumerable: true, get: function () { return common_1.parseInputString; } }); +Object.defineProperty(exports, "mintComponentId", { enumerable: true, get: function () { return common_1.mintComponentId; } }); Object.defineProperty(exports, "PARAM_KEY_SKIP", { enumerable: true, get: function () { return common_1.PARAM_KEY_SKIP; } }); Object.defineProperty(exports, "SHOULD_SKIP_ID", { enumerable: true, get: function () { return common_1.SHOULD_SKIP_ID; } }); Object.defineProperty(exports, "LOCATION_ID", { enumerable: true, get: function () { return common_1.LOCATION_ID; } }); diff --git a/packages/expo-router/build/internal/rsc.js.map b/packages/expo-router/build/internal/rsc.js.map index 00655ea0243112..ac0273637dc0ad 100644 --- a/packages/expo-router/build/internal/rsc.js.map +++ b/packages/expo-router/build/internal/rsc.js.map @@ -1 +1 @@ -{"version":3,"file":"rsc.js","sourceRoot":"","sources":["../../src/internal/rsc.ts"],"names":[],"mappings":";;;AAAA,+CAAoD;AAA3C,sGAAA,YAAY,OAAA;AACrB,+CAS8B;AAR5B,yGAAA,eAAe,OAAA;AACf,wGAAA,cAAc,OAAA;AACd,0GAAA,gBAAgB,OAAA;AAChB,wGAAA,cAAc,OAAA;AACd,wGAAA,cAAc,OAAA;AACd,qGAAA,WAAW,OAAA;AAIb,2CAAoD;AAA3C,gGAAA,QAAQ,OAAA;AAAE,4FAAA,IAAI,OAAA;AACvB,6CAAqD;AAA5C,uGAAA,cAAc,OAAA","sourcesContent":["export { ServerRouter } from '../rsc/router/client';\nexport {\n getComponentIds,\n getInputString,\n parseInputString,\n PARAM_KEY_SKIP,\n SHOULD_SKIP_ID,\n LOCATION_ID,\n type RouteProps,\n type ShouldSkip,\n} from '../rsc/router/common';\nexport { Children, Slot } from '../rsc/router/host';\nexport { decodeActionId } from '../rsc/router/utils';\n"]} \ No newline at end of file +{"version":3,"file":"rsc.js","sourceRoot":"","sources":["../../src/internal/rsc.ts"],"names":[],"mappings":";;;AAAA,+CAAoD;AAA3C,sGAAA,YAAY,OAAA;AACrB,+CAY8B;AAX5B,yGAAA,eAAe,OAAA;AACf,wGAAA,cAAc,OAAA;AACd,0GAAA,gBAAgB,OAAA;AAChB,yGAAA,eAAe,OAAA;AAGf,wGAAA,cAAc,OAAA;AACd,wGAAA,cAAc,OAAA;AACd,qGAAA,WAAW,OAAA;AAIb,2CAAoD;AAA3C,gGAAA,QAAQ,OAAA;AAAE,4FAAA,IAAI,OAAA;AACvB,6CAAqD;AAA5C,uGAAA,cAAc,OAAA","sourcesContent":["export { ServerRouter } from '../rsc/router/client';\nexport {\n getComponentIds,\n getInputString,\n parseInputString,\n mintComponentId,\n type ComponentIdKind,\n type ComponentIds,\n PARAM_KEY_SKIP,\n SHOULD_SKIP_ID,\n LOCATION_ID,\n type RouteProps,\n type ShouldSkip,\n} from '../rsc/router/common';\nexport { Children, Slot } from '../rsc/router/host';\nexport { decodeActionId } from '../rsc/router/utils';\n"]} \ No newline at end of file diff --git a/packages/expo-router/build/rsc/router/client.d.ts.map b/packages/expo-router/build/rsc/router/client.d.ts.map index 9e5bb2a3aa7568..b5f3a6c644e93a 100644 --- a/packages/expo-router/build/rsc/router/client.d.ts.map +++ b/packages/expo-router/build/rsc/router/client.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/rsc/router/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAmBH,OAAO,KAAK,EAGV,SAAS,EAET,oBAAoB,EAGrB,MAAM,OAAO,CAAC;AAIf,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,OAAO,KAAK,EAAE,gBAAgB,IAAI,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACtF,OAAO,KAAK,EAAE,SAAS,IAAI,gBAAgB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGvF,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AA+O3C,wBAAgB,kBAAkB,IAAI,qBAAqB,GACzD,UAAU,GAAG;IACX,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;CAChC,CA4FF;AAoDD,KAAK,UAAU,GAAG,CAAC,SAAS;IAC1B,MAAM;IACN,SAAS;QACP,OAAO;QACP,MAAM,EAAE;KACT;CACF,CAAC,EAAE,CAAC;AAGL,KAAK,UAAU,GAAG;IAChB,UAAU,CAAC,EAAE,UAAU;IACvB,iBAAiB,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/D,CAAC;AAIF,wBAAgB,MAAM,CAAC,EAAE,UAAgC,EAAE;;CAAA;;;;;;;;;;;;;;;;;;gBAU1D;AAKD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,2EAgB3F;AAED,MAAM,MAAM,SAAS,GAAG,gBAAgB,GAAG;IACzC,IAAI,EAAE,MAAM,CAAC;CAMd,GAAG,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC,CAAC;AAE1D,eAAO,MAAM,IAAI,EAA4C,aAAa,CAAC"} \ No newline at end of file +{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/rsc/router/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAmBH,OAAO,KAAK,EAGV,SAAS,EAET,oBAAoB,EAGrB,MAAM,OAAO,CAAC;AAIf,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,OAAO,KAAK,EAAE,gBAAgB,IAAI,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACtF,OAAO,KAAK,EAAE,SAAS,IAAI,gBAAgB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGvF,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAuP3C,wBAAgB,kBAAkB,IAAI,qBAAqB,GACzD,UAAU,GAAG;IACX,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;CAChC,CA4FF;AAoDD,KAAK,UAAU,GAAG,CAAC,SAAS;IAC1B,MAAM;IACN,SAAS;QACP,OAAO;QACP,MAAM,EAAE;KACT;CACF,CAAC,EAAE,CAAC;AAGL,KAAK,UAAU,GAAG;IAChB,UAAU,CAAC,EAAE,UAAU;IACvB,iBAAiB,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/D,CAAC;AAIF,wBAAgB,MAAM,CAAC,EAAE,UAAgC,EAAE;;CAAA;;;;;;;;;;;;;;;;;;gBAU1D;AAKD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,2EAgB3F;AAED,MAAM,MAAM,SAAS,GAAG,gBAAgB,GAAG;IACzC,IAAI,EAAE,MAAM,CAAC;CAMd,GAAG,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC,CAAC;AAE1D,eAAO,MAAM,IAAI,EAA4C,aAAa,CAAC"} \ No newline at end of file diff --git a/packages/expo-router/build/rsc/router/client.js b/packages/expo-router/build/rsc/router/client.js index 0cb43844cefb01..a9e6ba61c4f951 100644 --- a/packages/expo-router/build/rsc/router/client.js +++ b/packages/expo-router/build/rsc/router/client.js @@ -92,7 +92,13 @@ const InnerRouter = ({ routerData }) => { return initialRoute; }); }, []); - const componentIds = (0, common_js_1.getComponentIds)(route.path); + // TODO: Mint IDs from the active RouteNode's `contextKey` (via expo-router's + // RouterStore) so they're group-aware and canonical. Today they're URL-derived + // and omit `(group)` segments — the server's iterating resolver bridges this via + // optional-group regex matching, but the asymmetry should go away if/when the + // canonical-ID convention needs to include groups (e.g. signed skip tokens). + const { layouts, page } = (0, common_js_1.getComponentIds)(route.path); + const componentIds = [...layouts, page]; // const refetchRoute = () => { // const loc = parseRoute(new URL(getHref())); // const input = getInputString(loc.path); @@ -112,7 +118,8 @@ const InnerRouter = ({ routerData }) => { (0, react_1.startTransition)(() => { setRoute(route); }); - const componentIds = (0, common_js_1.getComponentIds)(route.path); + const { layouts, page } = (0, common_js_1.getComponentIds)(route.path); + const componentIds = [...layouts, page]; if (checkCache && componentIds.every((id) => { const cachedLoc = cachedRef.current[id]; @@ -137,7 +144,8 @@ const InnerRouter = ({ routerData }) => { }); }, [refetch, routerData]); const prefetchRoute = (0, react_1.useCallback)((route) => { - const componentIds = (0, common_js_1.getComponentIds)(route.path); + const { layouts, page } = (0, common_js_1.getComponentIds)(route.path); + const componentIds = [...layouts, page]; const shouldSkip = routerData[0]; const skip = getSkipList(shouldSkip, componentIds, route, cachedRef.current); if (componentIds.every((id) => skip.includes(id))) { diff --git a/packages/expo-router/build/rsc/router/client.js.map b/packages/expo-router/build/rsc/router/client.js.map index f0adf2778a7541..c5256fc32869dc 100644 --- a/packages/expo-router/build/rsc/router/client.js.map +++ b/packages/expo-router/build/rsc/router/client.js.map @@ -1 +1 @@ -{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/rsc/router/client.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Copyright © 2024 650 Industries.\n * Copyright © 2024 2023 Daishi Kato\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * https://github.com/dai-shi/waku/blob/3d1cc7d714b67b142c847e879c30f0724fc457a7/packages/waku/src/router/client.ts#L1\n */\n\n'use client';\n\nimport { Slot as ReactSlot } from '@radix-ui/react-slot';\nimport {\n startTransition,\n useCallback,\n use,\n useEffect,\n useRef,\n useTransition,\n createElement,\n createContext,\n useState,\n Fragment,\n forwardRef,\n useMemo,\n} from 'react';\nimport type {\n ComponentProps,\n FunctionComponent,\n ReactNode,\n MutableRefObject,\n AnchorHTMLAttributes,\n MouseEvent,\n ForwardedRef,\n} from 'react';\nimport { Text } from 'react-native';\n\nimport { PARAM_KEY_SKIP, getComponentIds, getInputString } from './common.js';\nimport type { RouteProps } from './common.js';\nimport { prefetchRSC, Root, Slot, useRefetch } from './host.js';\nimport type { NavigationOptions } from '../../global-state/routing.js';\nimport type { ImperativeRouter as ClassicExpoRouterType } from '../../imperative-api';\nimport type { LinkProps as ClassicLinkProps, LinkComponent } from '../../link/Link.js';\nimport { resolveHref } from '../../link/href';\nimport { useInteropClassName, useHrefAttrs } from '../../link/useLinkHooks';\nimport type { Href } from '../../types.js';\n\nconst normalizeRoutePath = (path: string) => {\n for (const suffix of ['/', '/index.html']) {\n if (path.endsWith(suffix)) {\n return path.slice(0, -suffix.length) || '/';\n }\n }\n return path;\n};\n\nconst parseRoute = (url: URL): RouteProps => {\n if ((globalThis as any).__EXPO_ROUTER_404__) {\n return { path: '/404', query: '', hash: '' };\n }\n const { pathname, searchParams, hash } = url;\n if (searchParams.has(PARAM_KEY_SKIP)) {\n console.warn(`The search param \"${PARAM_KEY_SKIP}\" is reserved`);\n }\n return {\n path: normalizeRoutePath(pathname),\n query: searchParams.toString(),\n hash,\n };\n};\nconst getHref = () =>\n process.env.EXPO_OS === 'web'\n ? window.location.href\n : // TODO: This is hardcoded on native to simplify the initial PR.\n 'http://localhost:8081/';\n\ntype ChangeRoute = (\n route: RouteProps,\n options?: {\n checkCache?: boolean;\n skipRefetch?: boolean;\n }\n) => void;\n\ntype PrefetchRoute = (route: RouteProps) => void;\n\nconst equalRouteProps = (a: RouteProps, b: RouteProps) => {\n if (a.path !== b.path) {\n return false;\n }\n if (a.query !== b.query) {\n return false;\n }\n return true;\n};\n\nconst RouterContext = createContext<{\n route: RouteProps;\n changeRoute: ChangeRoute;\n prefetchRoute: PrefetchRoute;\n} | null>(null);\n\nconst InnerRouter = ({ routerData }: { routerData: RouterData }) => {\n const refetch = useRefetch();\n\n const initialRouteRef = useRef(null);\n if (!initialRouteRef.current) {\n initialRouteRef.current = parseRoute(new URL(getHref()));\n }\n const [route, setRoute] = useState(() => ({\n // This is the first initialization of the route, and it has\n // to ignore the hash, because on server side there is none.\n // Otherwise there will be a hydration error.\n // The client side route, including the hash, will be updated in the effect below.\n ...initialRouteRef.current!,\n hash: '',\n }));\n\n // Update the route post-load to include the current hash.\n useEffect(() => {\n const initialRoute = initialRouteRef.current!;\n setRoute((prev) => {\n if (\n prev.path === initialRoute.path &&\n prev.query === initialRoute.query &&\n prev.hash === initialRoute.hash\n ) {\n return prev;\n }\n return initialRoute;\n });\n }, []);\n\n const componentIds = getComponentIds(route.path);\n\n // const refetchRoute = () => {\n // const loc = parseRoute(new URL(getHref()));\n // const input = getInputString(loc.path);\n // refetch(input, loc.query);\n // refetch(input, JSON.stringify({ query: route.query }));\n // };\n // globalThis.__EXPO_REFETCH_ROUTE_NO_CACHE__ = refetchRoute;\n\n const [cached, setCached] = useState>(() => {\n return Object.fromEntries(componentIds.map((id) => [id, route]));\n });\n const cachedRef = useRef(cached);\n useEffect(() => {\n cachedRef.current = cached;\n }, [cached]);\n\n const changeRoute: ChangeRoute = useCallback(\n (route, options) => {\n const { checkCache, skipRefetch } = options || {};\n startTransition(() => {\n setRoute(route);\n });\n const componentIds = getComponentIds(route.path);\n if (\n checkCache &&\n componentIds.every((id) => {\n const cachedLoc = cachedRef.current[id];\n return cachedLoc && equalRouteProps(cachedLoc, route);\n })\n ) {\n return; // everything is cached\n }\n const shouldSkip = routerData[0];\n const skip = getSkipList(shouldSkip, componentIds, route, cachedRef.current);\n if (componentIds.every((id) => skip.includes(id))) {\n return; // everything is skipped\n }\n const input = getInputString(route.path);\n if (!skipRefetch) {\n refetch(input, JSON.stringify({ query: route.query, skip }));\n }\n startTransition(() => {\n setCached((prev) => ({\n ...prev,\n ...Object.fromEntries(\n componentIds.flatMap((id) => (skip.includes(id) ? [] : [[id, route]]))\n ),\n }));\n });\n },\n [refetch, routerData]\n );\n\n const prefetchRoute: PrefetchRoute = useCallback(\n (route) => {\n const componentIds = getComponentIds(route.path);\n const shouldSkip = routerData[0];\n const skip = getSkipList(shouldSkip, componentIds, route, cachedRef.current);\n if (componentIds.every((id) => skip.includes(id))) {\n return; // everything is cached\n }\n const input = getInputString(route.path);\n prefetchRSC(input, JSON.stringify({ query: route.query, skip }));\n (globalThis as any).__EXPO_ROUTER_PREFETCH__?.(route.path);\n },\n [routerData]\n );\n\n useEffect(() => {\n const callback = () => {\n const route = parseRoute(new URL(getHref()));\n changeRoute(route, { checkCache: true });\n };\n if (window.addEventListener) {\n window.addEventListener('popstate', callback);\n return () => {\n window.removeEventListener('popstate', callback);\n };\n }\n return () => {};\n }, [changeRoute]);\n\n useEffect(() => {\n const callback = (pathname: string, searchParamsString: string) => {\n const url = new URL(getHref());\n url.pathname = pathname;\n url.search = searchParamsString;\n url.hash = '';\n getHistory().pushState(\n {\n ...getHistory().state,\n expo_new_path: url.pathname !== window.location.pathname,\n },\n '',\n url\n );\n changeRoute(parseRoute(url), { skipRefetch: true });\n };\n // eslint-disable-next-line no-multi-assign\n const listeners = (routerData[1] ||= new Set());\n listeners.add(callback);\n return () => {\n listeners.delete(callback);\n };\n }, [changeRoute, routerData]);\n\n useEffect(() => {\n const { hash } = window.location;\n const { state } = getHistory();\n const element = hash && document.getElementById(hash.slice(1));\n if (window.scrollTo) {\n window.scrollTo({\n left: 0,\n top: element ? element.getBoundingClientRect().top + window.scrollY : 0,\n behavior: state?.expo_new_path ? 'instant' : 'auto',\n });\n } else {\n // TODO: Native\n // console.log('window.scrollTo is not available');\n }\n });\n\n const children = componentIds.reduceRight(\n (acc: ReactNode, id) =>\n createElement(RouterSlot, { route, routerData, cachedRef, id, fallback: acc }, acc),\n null\n );\n\n return createElement(\n RouterContext.Provider,\n { value: { route, changeRoute, prefetchRoute } },\n children\n );\n};\n\nfunction getHistory() {\n if (process.env.EXPO_OS === 'web') {\n return window.history;\n }\n // Native shim\n return {\n pushState: () => {},\n replaceState: () => {},\n back: () => {},\n forward: () => {},\n state: {},\n };\n}\n\nexport function useRouter_UNSTABLE(): ClassicExpoRouterType &\n RouteProps & {\n forward: () => void;\n prefetch: (href: Href) => void;\n } {\n const router = use(RouterContext);\n if (!router) {\n throw new Error('Missing Router');\n }\n const { route, changeRoute, prefetchRoute } = router;\n const push: ClassicExpoRouterType['push'] = useCallback(\n (href: Href, options?: NavigationOptions) => {\n if (options) {\n // TODO(Bacon): Implement options\n console.warn(\n 'options prop of router.push() is not supported in React Server Components yet'\n );\n }\n\n const url = new URL(resolveHref(href), getHref());\n getHistory().pushState(\n {\n ...getHistory().state,\n expo_new_path: url.pathname !== window.location.pathname,\n },\n '',\n url\n );\n changeRoute(parseRoute(url));\n },\n [changeRoute]\n );\n const replace: ClassicExpoRouterType['replace'] = useCallback(\n (href: Href, options?: NavigationOptions) => {\n if (options) {\n // TODO(Bacon): Implement options\n console.warn(\n 'options prop of router.replace() is not supported in React Server Components yet'\n );\n }\n\n const url = new URL(resolveHref(href), getHref());\n getHistory().replaceState(getHistory().state, '', url);\n changeRoute(parseRoute(url));\n },\n [changeRoute]\n );\n const reload = useCallback(() => {\n const url = new URL(getHref());\n changeRoute(parseRoute(url));\n }, [changeRoute]);\n const back = useCallback(() => {\n // FIXME is this correct?\n getHistory().back();\n }, []);\n const forward = useCallback(() => {\n // FIXME is this correct?\n getHistory().forward();\n }, []);\n const prefetch = useCallback(\n (href: Href) => {\n const url = new URL(resolveHref(href), getHref());\n prefetchRoute(parseRoute(url));\n },\n [prefetchRoute]\n );\n return {\n ...route,\n canDismiss() {\n throw new Error('router.canDismiss() is not supported in React Server Components yet');\n },\n canGoBack() {\n throw new Error('router.canGoBack() is not supported in React Server Components yet');\n },\n dismiss() {\n throw new Error('router.dismiss() is not supported in React Server Components yet');\n },\n dismissTo() {\n throw new Error('router.dismissTo() is not supported in React Server Components yet');\n },\n dismissAll() {\n throw new Error('router.dismissAll() is not supported in React Server Components yet');\n },\n setParams() {\n throw new Error('router.setParams() is not supported in React Server Components yet');\n },\n\n // TODO: The behavior here is not the same as before.\n navigate: push,\n push,\n replace,\n reload,\n back,\n forward,\n prefetch,\n };\n}\n\nconst RouterSlot = ({\n route,\n routerData,\n cachedRef,\n id,\n fallback,\n children,\n}: {\n route: RouteProps;\n routerData: RouterData;\n cachedRef: MutableRefObject>;\n id: string;\n fallback?: ReactNode;\n children?: ReactNode;\n}) => {\n // const unstable_shouldRenderPrev = (_err: unknown) => {\n // const shouldSkip = routerData[0];\n // const skip = getSkipList(shouldSkip, [id], route, cachedRef.current);\n // return skip.length > 0;\n // };\n return createElement(Slot, { id, fallback }, children);\n};\n\nconst getSkipList = (\n shouldSkip: ShouldSkip | undefined,\n componentIds: readonly string[],\n route: RouteProps,\n cached: Record\n): string[] => {\n const shouldSkipObj = Object.fromEntries(shouldSkip || []);\n return componentIds.filter((id) => {\n const prevProps = cached[id];\n if (!prevProps) {\n return false;\n }\n const shouldCheck = shouldSkipObj[id];\n if (!shouldCheck) {\n return false;\n }\n if (shouldCheck[0] && route.path !== prevProps.path) {\n return false;\n }\n if (shouldCheck[0] && route.query !== prevProps.query) {\n return false;\n }\n return true;\n });\n};\n\n// TODO revisit shouldSkip API\ntype ShouldSkip = (readonly [\n string,\n readonly [\n boolean, // if we compare path\n string[], // searchParams keys to compare\n ],\n])[];\n\n// Note: The router data must be a stable mutable object (array).\ntype RouterData = [\n shouldSkip?: ShouldSkip,\n locationListeners?: Set<(path: string, query: string) => void>,\n];\n\nconst DEFAULT_ROUTER_DATA: RouterData = [];\n\nexport function Router({ routerData = DEFAULT_ROUTER_DATA }) {\n const route = parseRoute(new URL(getHref()));\n const initialInput = getInputString(route.path);\n const initialParams = JSON.stringify({ query: route.query });\n const unstable_onFetchData = () => {};\n return createElement(\n Root as FunctionComponent, 'children'>>,\n { initialInput, initialParams, unstable_onFetchData },\n createElement(InnerRouter, { routerData })\n );\n}\n\nconst notAvailableInServer = (name: string) => () => {\n throw new Error(`${name} is not in the server`);\n};\n/**\n * ServerRouter for SSR\n * This is not a public API.\n */\nexport function ServerRouter({ children, route }: { children: ReactNode; route: RouteProps }) {\n return createElement(\n Fragment,\n null,\n createElement(\n RouterContext.Provider,\n {\n value: {\n route,\n changeRoute: notAvailableInServer('changeRoute'),\n prefetchRoute: notAvailableInServer('prefetchRoute'),\n },\n },\n children\n )\n );\n}\n\nexport type LinkProps = ClassicLinkProps & {\n href: string;\n // pending?: ReactNode;\n // notPending?: ReactNode;\n\n // unstable_prefetchOnEnter?: boolean;\n // unstable_prefetchOnView?: boolean;\n} & Omit, 'href'>;\n\nexport const Link = forwardRef(ExpoRouterLink) as unknown as LinkComponent;\n\nLink.resolveHref = resolveHref;\n\nfunction ExpoRouterLink(\n {\n href,\n replace,\n push,\n // TODO: This does not prevent default on the anchor tag.\n relativeToDirectory,\n asChild,\n rel,\n target,\n download,\n // pending,\n // notPending,\n // unstable_prefetchOnEnter,\n // unstable_prefetchOnView,\n children,\n ...props\n }: LinkProps,\n ref: ForwardedRef\n) {\n // Mutate the style prop to add the className on web.\n const style = useInteropClassName(props);\n\n // If not passing asChild, we need to forward the props to the anchor tag using React Native Web's `hrefAttrs`.\n const hrefAttrs = useHrefAttrs({ asChild, rel, target, download });\n\n const resolvedHref = useMemo(() => {\n if (href == null) {\n throw new Error('Link: href is required');\n }\n return resolveHref(href);\n }, [href]);\n\n const router = use(RouterContext);\n const changeRoute = router\n ? router.changeRoute\n : () => {\n throw new Error('Missing Router');\n };\n const prefetchRoute = router\n ? router.prefetchRoute\n : () => {\n throw new Error('Missing Router');\n };\n // TODO: Implement support for pending states in the future.\n const [, startTransition] = useTransition();\n // const elementRef = useRef();\n\n // useEffect(() => {\n // if (unstable_prefetchOnView && process.env.EXPO_OS === 'web' && ref.current) {\n // const observer = new IntersectionObserver(\n // (entries) => {\n // entries.forEach((entry) => {\n // if (entry.isIntersecting) {\n // const url = new URL(resolvedHref, getHref());\n // if (router && url.href !== getHref()) {\n // const route = parseRoute(url);\n // router.prefetchRoute(route);\n // }\n // }\n // });\n // },\n // { threshold: 0.1 }\n // );\n\n // observer.observe(ref.current);\n\n // return () => {\n // observer.disconnect();\n // };\n // }\n // return () => {};\n // }, [unstable_prefetchOnView, router, resolvedHref]);\n\n const onClick = (event: MouseEvent) => {\n event.preventDefault();\n const url = new URL(resolvedHref, getHref());\n // TODO: Use in-memory route for native platforms.\n // if (url.href !== getHref()) {\n const route = parseRoute(url);\n prefetchRoute(route);\n startTransition(() => {\n getHistory().pushState(\n {\n ...getHistory().state,\n expo_new_path: url.pathname !== window.location.pathname,\n },\n '',\n url\n );\n changeRoute(route);\n });\n // }\n props.onClick?.(event);\n };\n // const onMouseEnter = unstable_prefetchOnEnter\n // ? (event: MouseEvent) => {\n // const url = new URL(to, getHref());\n // if (url.href !== getHref()) {\n // const route = parseRoute(url);\n // prefetchRoute(route);\n // }\n // props.onMouseEnter?.(event);\n // }\n // : props.onMouseEnter;\n\n const Element = asChild ? ReactSlot : Text;\n\n const ele = createElement(\n // @ts-expect-error\n Element,\n {\n ...hrefAttrs,\n ...props,\n style,\n href: resolvedHref,\n onPress: onClick,\n // onMouseEnter,\n ref,\n },\n children\n );\n // if (isPending && pending !== undefined) {\n // return createElement(Fragment, null, ele, pending);\n // }\n // if (!isPending && notPending !== undefined) {\n // return createElement(Fragment, null, ele, notPending);\n // }\n return ele;\n}\n"]} \ No newline at end of file +{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/rsc/router/client.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Copyright © 2024 650 Industries.\n * Copyright © 2024 2023 Daishi Kato\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * https://github.com/dai-shi/waku/blob/3d1cc7d714b67b142c847e879c30f0724fc457a7/packages/waku/src/router/client.ts#L1\n */\n\n'use client';\n\nimport { Slot as ReactSlot } from '@radix-ui/react-slot';\nimport {\n startTransition,\n useCallback,\n use,\n useEffect,\n useRef,\n useTransition,\n createElement,\n createContext,\n useState,\n Fragment,\n forwardRef,\n useMemo,\n} from 'react';\nimport type {\n ComponentProps,\n FunctionComponent,\n ReactNode,\n MutableRefObject,\n AnchorHTMLAttributes,\n MouseEvent,\n ForwardedRef,\n} from 'react';\nimport { Text } from 'react-native';\n\nimport { PARAM_KEY_SKIP, getComponentIds, getInputString } from './common.js';\nimport type { RouteProps } from './common.js';\nimport { prefetchRSC, Root, Slot, useRefetch } from './host.js';\nimport type { NavigationOptions } from '../../global-state/routing.js';\nimport type { ImperativeRouter as ClassicExpoRouterType } from '../../imperative-api';\nimport type { LinkProps as ClassicLinkProps, LinkComponent } from '../../link/Link.js';\nimport { resolveHref } from '../../link/href';\nimport { useInteropClassName, useHrefAttrs } from '../../link/useLinkHooks';\nimport type { Href } from '../../types.js';\n\nconst normalizeRoutePath = (path: string) => {\n for (const suffix of ['/', '/index.html']) {\n if (path.endsWith(suffix)) {\n return path.slice(0, -suffix.length) || '/';\n }\n }\n return path;\n};\n\nconst parseRoute = (url: URL): RouteProps => {\n if ((globalThis as any).__EXPO_ROUTER_404__) {\n return { path: '/404', query: '', hash: '' };\n }\n const { pathname, searchParams, hash } = url;\n if (searchParams.has(PARAM_KEY_SKIP)) {\n console.warn(`The search param \"${PARAM_KEY_SKIP}\" is reserved`);\n }\n return {\n path: normalizeRoutePath(pathname),\n query: searchParams.toString(),\n hash,\n };\n};\nconst getHref = () =>\n process.env.EXPO_OS === 'web'\n ? window.location.href\n : // TODO: This is hardcoded on native to simplify the initial PR.\n 'http://localhost:8081/';\n\ntype ChangeRoute = (\n route: RouteProps,\n options?: {\n checkCache?: boolean;\n skipRefetch?: boolean;\n }\n) => void;\n\ntype PrefetchRoute = (route: RouteProps) => void;\n\nconst equalRouteProps = (a: RouteProps, b: RouteProps) => {\n if (a.path !== b.path) {\n return false;\n }\n if (a.query !== b.query) {\n return false;\n }\n return true;\n};\n\nconst RouterContext = createContext<{\n route: RouteProps;\n changeRoute: ChangeRoute;\n prefetchRoute: PrefetchRoute;\n} | null>(null);\n\nconst InnerRouter = ({ routerData }: { routerData: RouterData }) => {\n const refetch = useRefetch();\n\n const initialRouteRef = useRef(null);\n if (!initialRouteRef.current) {\n initialRouteRef.current = parseRoute(new URL(getHref()));\n }\n const [route, setRoute] = useState(() => ({\n // This is the first initialization of the route, and it has\n // to ignore the hash, because on server side there is none.\n // Otherwise there will be a hydration error.\n // The client side route, including the hash, will be updated in the effect below.\n ...initialRouteRef.current!,\n hash: '',\n }));\n\n // Update the route post-load to include the current hash.\n useEffect(() => {\n const initialRoute = initialRouteRef.current!;\n setRoute((prev) => {\n if (\n prev.path === initialRoute.path &&\n prev.query === initialRoute.query &&\n prev.hash === initialRoute.hash\n ) {\n return prev;\n }\n return initialRoute;\n });\n }, []);\n\n // TODO: Mint IDs from the active RouteNode's `contextKey` (via expo-router's\n // RouterStore) so they're group-aware and canonical. Today they're URL-derived\n // and omit `(group)` segments — the server's iterating resolver bridges this via\n // optional-group regex matching, but the asymmetry should go away if/when the\n // canonical-ID convention needs to include groups (e.g. signed skip tokens).\n const { layouts, page } = getComponentIds(route.path);\n const componentIds = [...layouts, page];\n\n // const refetchRoute = () => {\n // const loc = parseRoute(new URL(getHref()));\n // const input = getInputString(loc.path);\n // refetch(input, loc.query);\n // refetch(input, JSON.stringify({ query: route.query }));\n // };\n // globalThis.__EXPO_REFETCH_ROUTE_NO_CACHE__ = refetchRoute;\n\n const [cached, setCached] = useState>(() => {\n return Object.fromEntries(componentIds.map((id) => [id, route]));\n });\n const cachedRef = useRef(cached);\n useEffect(() => {\n cachedRef.current = cached;\n }, [cached]);\n\n const changeRoute: ChangeRoute = useCallback(\n (route, options) => {\n const { checkCache, skipRefetch } = options || {};\n startTransition(() => {\n setRoute(route);\n });\n const { layouts, page } = getComponentIds(route.path);\n const componentIds = [...layouts, page];\n if (\n checkCache &&\n componentIds.every((id) => {\n const cachedLoc = cachedRef.current[id];\n return cachedLoc && equalRouteProps(cachedLoc, route);\n })\n ) {\n return; // everything is cached\n }\n const shouldSkip = routerData[0];\n const skip = getSkipList(shouldSkip, componentIds, route, cachedRef.current);\n if (componentIds.every((id) => skip.includes(id))) {\n return; // everything is skipped\n }\n const input = getInputString(route.path);\n if (!skipRefetch) {\n refetch(input, JSON.stringify({ query: route.query, skip }));\n }\n startTransition(() => {\n setCached((prev) => ({\n ...prev,\n ...Object.fromEntries(\n componentIds.flatMap((id) => (skip.includes(id) ? [] : [[id, route]]))\n ),\n }));\n });\n },\n [refetch, routerData]\n );\n\n const prefetchRoute: PrefetchRoute = useCallback(\n (route) => {\n const { layouts, page } = getComponentIds(route.path);\n const componentIds = [...layouts, page];\n const shouldSkip = routerData[0];\n const skip = getSkipList(shouldSkip, componentIds, route, cachedRef.current);\n if (componentIds.every((id) => skip.includes(id))) {\n return; // everything is cached\n }\n const input = getInputString(route.path);\n prefetchRSC(input, JSON.stringify({ query: route.query, skip }));\n (globalThis as any).__EXPO_ROUTER_PREFETCH__?.(route.path);\n },\n [routerData]\n );\n\n useEffect(() => {\n const callback = () => {\n const route = parseRoute(new URL(getHref()));\n changeRoute(route, { checkCache: true });\n };\n if (window.addEventListener) {\n window.addEventListener('popstate', callback);\n return () => {\n window.removeEventListener('popstate', callback);\n };\n }\n return () => {};\n }, [changeRoute]);\n\n useEffect(() => {\n const callback = (pathname: string, searchParamsString: string) => {\n const url = new URL(getHref());\n url.pathname = pathname;\n url.search = searchParamsString;\n url.hash = '';\n getHistory().pushState(\n {\n ...getHistory().state,\n expo_new_path: url.pathname !== window.location.pathname,\n },\n '',\n url\n );\n changeRoute(parseRoute(url), { skipRefetch: true });\n };\n // eslint-disable-next-line no-multi-assign\n const listeners = (routerData[1] ||= new Set());\n listeners.add(callback);\n return () => {\n listeners.delete(callback);\n };\n }, [changeRoute, routerData]);\n\n useEffect(() => {\n const { hash } = window.location;\n const { state } = getHistory();\n const element = hash && document.getElementById(hash.slice(1));\n if (window.scrollTo) {\n window.scrollTo({\n left: 0,\n top: element ? element.getBoundingClientRect().top + window.scrollY : 0,\n behavior: state?.expo_new_path ? 'instant' : 'auto',\n });\n } else {\n // TODO: Native\n // console.log('window.scrollTo is not available');\n }\n });\n\n const children = componentIds.reduceRight(\n (acc: ReactNode, id) =>\n createElement(RouterSlot, { route, routerData, cachedRef, id, fallback: acc }, acc),\n null\n );\n\n return createElement(\n RouterContext.Provider,\n { value: { route, changeRoute, prefetchRoute } },\n children\n );\n};\n\nfunction getHistory() {\n if (process.env.EXPO_OS === 'web') {\n return window.history;\n }\n // Native shim\n return {\n pushState: () => {},\n replaceState: () => {},\n back: () => {},\n forward: () => {},\n state: {},\n };\n}\n\nexport function useRouter_UNSTABLE(): ClassicExpoRouterType &\n RouteProps & {\n forward: () => void;\n prefetch: (href: Href) => void;\n } {\n const router = use(RouterContext);\n if (!router) {\n throw new Error('Missing Router');\n }\n const { route, changeRoute, prefetchRoute } = router;\n const push: ClassicExpoRouterType['push'] = useCallback(\n (href: Href, options?: NavigationOptions) => {\n if (options) {\n // TODO(Bacon): Implement options\n console.warn(\n 'options prop of router.push() is not supported in React Server Components yet'\n );\n }\n\n const url = new URL(resolveHref(href), getHref());\n getHistory().pushState(\n {\n ...getHistory().state,\n expo_new_path: url.pathname !== window.location.pathname,\n },\n '',\n url\n );\n changeRoute(parseRoute(url));\n },\n [changeRoute]\n );\n const replace: ClassicExpoRouterType['replace'] = useCallback(\n (href: Href, options?: NavigationOptions) => {\n if (options) {\n // TODO(Bacon): Implement options\n console.warn(\n 'options prop of router.replace() is not supported in React Server Components yet'\n );\n }\n\n const url = new URL(resolveHref(href), getHref());\n getHistory().replaceState(getHistory().state, '', url);\n changeRoute(parseRoute(url));\n },\n [changeRoute]\n );\n const reload = useCallback(() => {\n const url = new URL(getHref());\n changeRoute(parseRoute(url));\n }, [changeRoute]);\n const back = useCallback(() => {\n // FIXME is this correct?\n getHistory().back();\n }, []);\n const forward = useCallback(() => {\n // FIXME is this correct?\n getHistory().forward();\n }, []);\n const prefetch = useCallback(\n (href: Href) => {\n const url = new URL(resolveHref(href), getHref());\n prefetchRoute(parseRoute(url));\n },\n [prefetchRoute]\n );\n return {\n ...route,\n canDismiss() {\n throw new Error('router.canDismiss() is not supported in React Server Components yet');\n },\n canGoBack() {\n throw new Error('router.canGoBack() is not supported in React Server Components yet');\n },\n dismiss() {\n throw new Error('router.dismiss() is not supported in React Server Components yet');\n },\n dismissTo() {\n throw new Error('router.dismissTo() is not supported in React Server Components yet');\n },\n dismissAll() {\n throw new Error('router.dismissAll() is not supported in React Server Components yet');\n },\n setParams() {\n throw new Error('router.setParams() is not supported in React Server Components yet');\n },\n\n // TODO: The behavior here is not the same as before.\n navigate: push,\n push,\n replace,\n reload,\n back,\n forward,\n prefetch,\n };\n}\n\nconst RouterSlot = ({\n route,\n routerData,\n cachedRef,\n id,\n fallback,\n children,\n}: {\n route: RouteProps;\n routerData: RouterData;\n cachedRef: MutableRefObject>;\n id: string;\n fallback?: ReactNode;\n children?: ReactNode;\n}) => {\n // const unstable_shouldRenderPrev = (_err: unknown) => {\n // const shouldSkip = routerData[0];\n // const skip = getSkipList(shouldSkip, [id], route, cachedRef.current);\n // return skip.length > 0;\n // };\n return createElement(Slot, { id, fallback }, children);\n};\n\nconst getSkipList = (\n shouldSkip: ShouldSkip | undefined,\n componentIds: readonly string[],\n route: RouteProps,\n cached: Record\n): string[] => {\n const shouldSkipObj = Object.fromEntries(shouldSkip || []);\n return componentIds.filter((id) => {\n const prevProps = cached[id];\n if (!prevProps) {\n return false;\n }\n const shouldCheck = shouldSkipObj[id];\n if (!shouldCheck) {\n return false;\n }\n if (shouldCheck[0] && route.path !== prevProps.path) {\n return false;\n }\n if (shouldCheck[0] && route.query !== prevProps.query) {\n return false;\n }\n return true;\n });\n};\n\n// TODO revisit shouldSkip API\ntype ShouldSkip = (readonly [\n string,\n readonly [\n boolean, // if we compare path\n string[], // searchParams keys to compare\n ],\n])[];\n\n// Note: The router data must be a stable mutable object (array).\ntype RouterData = [\n shouldSkip?: ShouldSkip,\n locationListeners?: Set<(path: string, query: string) => void>,\n];\n\nconst DEFAULT_ROUTER_DATA: RouterData = [];\n\nexport function Router({ routerData = DEFAULT_ROUTER_DATA }) {\n const route = parseRoute(new URL(getHref()));\n const initialInput = getInputString(route.path);\n const initialParams = JSON.stringify({ query: route.query });\n const unstable_onFetchData = () => {};\n return createElement(\n Root as FunctionComponent, 'children'>>,\n { initialInput, initialParams, unstable_onFetchData },\n createElement(InnerRouter, { routerData })\n );\n}\n\nconst notAvailableInServer = (name: string) => () => {\n throw new Error(`${name} is not in the server`);\n};\n/**\n * ServerRouter for SSR\n * This is not a public API.\n */\nexport function ServerRouter({ children, route }: { children: ReactNode; route: RouteProps }) {\n return createElement(\n Fragment,\n null,\n createElement(\n RouterContext.Provider,\n {\n value: {\n route,\n changeRoute: notAvailableInServer('changeRoute'),\n prefetchRoute: notAvailableInServer('prefetchRoute'),\n },\n },\n children\n )\n );\n}\n\nexport type LinkProps = ClassicLinkProps & {\n href: string;\n // pending?: ReactNode;\n // notPending?: ReactNode;\n\n // unstable_prefetchOnEnter?: boolean;\n // unstable_prefetchOnView?: boolean;\n} & Omit, 'href'>;\n\nexport const Link = forwardRef(ExpoRouterLink) as unknown as LinkComponent;\n\nLink.resolveHref = resolveHref;\n\nfunction ExpoRouterLink(\n {\n href,\n replace,\n push,\n // TODO: This does not prevent default on the anchor tag.\n relativeToDirectory,\n asChild,\n rel,\n target,\n download,\n // pending,\n // notPending,\n // unstable_prefetchOnEnter,\n // unstable_prefetchOnView,\n children,\n ...props\n }: LinkProps,\n ref: ForwardedRef\n) {\n // Mutate the style prop to add the className on web.\n const style = useInteropClassName(props);\n\n // If not passing asChild, we need to forward the props to the anchor tag using React Native Web's `hrefAttrs`.\n const hrefAttrs = useHrefAttrs({ asChild, rel, target, download });\n\n const resolvedHref = useMemo(() => {\n if (href == null) {\n throw new Error('Link: href is required');\n }\n return resolveHref(href);\n }, [href]);\n\n const router = use(RouterContext);\n const changeRoute = router\n ? router.changeRoute\n : () => {\n throw new Error('Missing Router');\n };\n const prefetchRoute = router\n ? router.prefetchRoute\n : () => {\n throw new Error('Missing Router');\n };\n // TODO: Implement support for pending states in the future.\n const [, startTransition] = useTransition();\n // const elementRef = useRef();\n\n // useEffect(() => {\n // if (unstable_prefetchOnView && process.env.EXPO_OS === 'web' && ref.current) {\n // const observer = new IntersectionObserver(\n // (entries) => {\n // entries.forEach((entry) => {\n // if (entry.isIntersecting) {\n // const url = new URL(resolvedHref, getHref());\n // if (router && url.href !== getHref()) {\n // const route = parseRoute(url);\n // router.prefetchRoute(route);\n // }\n // }\n // });\n // },\n // { threshold: 0.1 }\n // );\n\n // observer.observe(ref.current);\n\n // return () => {\n // observer.disconnect();\n // };\n // }\n // return () => {};\n // }, [unstable_prefetchOnView, router, resolvedHref]);\n\n const onClick = (event: MouseEvent) => {\n event.preventDefault();\n const url = new URL(resolvedHref, getHref());\n // TODO: Use in-memory route for native platforms.\n // if (url.href !== getHref()) {\n const route = parseRoute(url);\n prefetchRoute(route);\n startTransition(() => {\n getHistory().pushState(\n {\n ...getHistory().state,\n expo_new_path: url.pathname !== window.location.pathname,\n },\n '',\n url\n );\n changeRoute(route);\n });\n // }\n props.onClick?.(event);\n };\n // const onMouseEnter = unstable_prefetchOnEnter\n // ? (event: MouseEvent) => {\n // const url = new URL(to, getHref());\n // if (url.href !== getHref()) {\n // const route = parseRoute(url);\n // prefetchRoute(route);\n // }\n // props.onMouseEnter?.(event);\n // }\n // : props.onMouseEnter;\n\n const Element = asChild ? ReactSlot : Text;\n\n const ele = createElement(\n // @ts-expect-error\n Element,\n {\n ...hrefAttrs,\n ...props,\n style,\n href: resolvedHref,\n onPress: onClick,\n // onMouseEnter,\n ref,\n },\n children\n );\n // if (isPending && pending !== undefined) {\n // return createElement(Fragment, null, ele, pending);\n // }\n // if (!isPending && notPending !== undefined) {\n // return createElement(Fragment, null, ele, notPending);\n // }\n return ele;\n}\n"]} \ No newline at end of file diff --git a/packages/expo-router/build/rsc/router/common.d.ts b/packages/expo-router/build/rsc/router/common.d.ts index fd91a83e2ad546..02468c95b71849 100644 --- a/packages/expo-router/build/rsc/router/common.d.ts +++ b/packages/expo-router/build/rsc/router/common.d.ts @@ -10,7 +10,7 @@ export type RouteProps = { query: string; hash: string; }; -export declare function getComponentIds(path: string): readonly string[]; +export { getComponentIds, mintComponentId, type ComponentIdKind, type ComponentIds, } from './idMinting'; export declare function getInputString(path: string): string; export declare function parseInputString(input: string): string; export declare const PARAM_KEY_SKIP = "expo_router_skip"; diff --git a/packages/expo-router/build/rsc/router/common.d.ts.map b/packages/expo-router/build/rsc/router/common.d.ts.map index 5a67581f594b02..4c064ea8ceb32b 100644 --- a/packages/expo-router/build/rsc/router/common.d.ts.map +++ b/packages/expo-router/build/rsc/router/common.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../../src/rsc/router/common.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAS/D;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKnD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,eAAO,MAAM,cAAc,qBAAqB,CAAC;AAGjD,eAAO,MAAM,cAAc,iBAAiB,CAAC;AAG7C,eAAO,MAAM,WAAW,cAAc,CAAC;AAGvC,MAAM,MAAM,UAAU,GAAG,CAAC,SAAS;IACjC,WAAW,EAAE,MAAM;IACnB,UAAU,EAAE,SAAS;QACnB,IAAI,CAAC,EAAE,OAAO;QACd,IAAI,CAAC,EAAE,MAAM,EAAE;KAChB;CACF,CAAC,EAAE,CAAC"} \ No newline at end of file +{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../../src/rsc/router/common.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,OAAO,EACL,eAAe,EACf,eAAe,EACf,KAAK,eAAe,EACpB,KAAK,YAAY,GAClB,MAAM,aAAa,CAAC;AAErB,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKnD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED,eAAO,MAAM,cAAc,qBAAqB,CAAC;AAGjD,eAAO,MAAM,cAAc,iBAAiB,CAAC;AAG7C,eAAO,MAAM,WAAW,cAAc,CAAC;AAGvC,MAAM,MAAM,UAAU,GAAG,CAAC,SAAS;IACjC,WAAW,EAAE,MAAM;IACnB,UAAU,EAAE,SAAS;QACnB,IAAI,CAAC,EAAE,OAAO;QACd,IAAI,CAAC,EAAE,MAAM,EAAE;KAChB;CACF,CAAC,EAAE,CAAC"} \ No newline at end of file diff --git a/packages/expo-router/build/rsc/router/common.js b/packages/expo-router/build/rsc/router/common.js index 26cd7d16cba746..470f6f5ab09db9 100644 --- a/packages/expo-router/build/rsc/router/common.js +++ b/packages/expo-router/build/rsc/router/common.js @@ -7,20 +7,12 @@ * LICENSE file in the root directory of this source tree. */ Object.defineProperty(exports, "__esModule", { value: true }); -exports.LOCATION_ID = exports.SHOULD_SKIP_ID = exports.PARAM_KEY_SKIP = void 0; -exports.getComponentIds = getComponentIds; +exports.LOCATION_ID = exports.SHOULD_SKIP_ID = exports.PARAM_KEY_SKIP = exports.mintComponentId = exports.getComponentIds = void 0; exports.getInputString = getInputString; exports.parseInputString = parseInputString; -function getComponentIds(path) { - const pathItems = path.split('/').filter(Boolean); - const idSet = new Set(); - for (let index = 0; index <= pathItems.length; ++index) { - const id = [...pathItems.slice(0, index), 'layout'].join('/'); - idSet.add(id); - } - idSet.add([...pathItems, 'page'].join('/')); - return Array.from(idSet); -} +var idMinting_1 = require("./idMinting"); +Object.defineProperty(exports, "getComponentIds", { enumerable: true, get: function () { return idMinting_1.getComponentIds; } }); +Object.defineProperty(exports, "mintComponentId", { enumerable: true, get: function () { return idMinting_1.mintComponentId; } }); function getInputString(path) { if (!path.startsWith('/')) { throw new Error('Path should start with `/`'); diff --git a/packages/expo-router/build/rsc/router/common.js.map b/packages/expo-router/build/rsc/router/common.js.map index 44dbaae8c5d2c0..8fa98e2fc5ef0d 100644 --- a/packages/expo-router/build/rsc/router/common.js.map +++ b/packages/expo-router/build/rsc/router/common.js.map @@ -1 +1 @@ -{"version":3,"file":"common.js","sourceRoot":"","sources":["../../../src/rsc/router/common.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;;AAQH,0CASC;AAED,wCAKC;AAED,4CAEC;AApBD,SAAgB,eAAe,CAAC,IAAY;IAC1C,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAClD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,IAAI,SAAS,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC;QACvD,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC9D,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,CAAC;IACD,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,SAAS,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5C,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC3B,CAAC;AAED,SAAgB,cAAc,CAAC,IAAY;IACzC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;IAChD,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACvB,CAAC;AAED,SAAgB,gBAAgB,CAAC,KAAa;IAC5C,OAAO,GAAG,GAAG,KAAK,CAAC;AACrB,CAAC;AAEY,QAAA,cAAc,GAAG,kBAAkB,CAAC;AAEjD,oEAAoE;AACvD,QAAA,cAAc,GAAG,cAAc,CAAC;AAE7C,qEAAqE;AACxD,QAAA,WAAW,GAAG,WAAW,CAAC","sourcesContent":["/**\n * Copyright © 2024 650 Industries.\n * Copyright © 2024 2023 Daishi Kato\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nexport type RouteProps = {\n path: string;\n query: string;\n hash: string;\n};\n\nexport function getComponentIds(path: string): readonly string[] {\n const pathItems = path.split('/').filter(Boolean);\n const idSet = new Set();\n for (let index = 0; index <= pathItems.length; ++index) {\n const id = [...pathItems.slice(0, index), 'layout'].join('/');\n idSet.add(id);\n }\n idSet.add([...pathItems, 'page'].join('/'));\n return Array.from(idSet);\n}\n\nexport function getInputString(path: string): string {\n if (!path.startsWith('/')) {\n throw new Error('Path should start with `/`');\n }\n return path.slice(1);\n}\n\nexport function parseInputString(input: string): string {\n return '/' + input;\n}\n\nexport const PARAM_KEY_SKIP = 'expo_router_skip';\n\n// It starts with \"/\" to avoid conflicing with normal component ids.\nexport const SHOULD_SKIP_ID = '/SHOULD_SKIP';\n\n// It starts with \"/\" to avoid conflicting with normal component ids.\nexport const LOCATION_ID = '/LOCATION';\n\n// TODO revisit shouldSkip API\nexport type ShouldSkip = (readonly [\n componentId: string,\n components: readonly [\n path?: boolean, // if we compare path\n keys?: string[], // searchParams keys to compare\n ],\n])[];\n"]} \ No newline at end of file +{"version":3,"file":"common.js","sourceRoot":"","sources":["../../../src/rsc/router/common.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;;AAeH,wCAKC;AAED,4CAEC;AAhBD,yCAKqB;AAJnB,4GAAA,eAAe,OAAA;AACf,4GAAA,eAAe,OAAA;AAKjB,SAAgB,cAAc,CAAC,IAAY;IACzC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;IAChD,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACvB,CAAC;AAED,SAAgB,gBAAgB,CAAC,KAAa;IAC5C,OAAO,GAAG,GAAG,KAAK,CAAC;AACrB,CAAC;AAEY,QAAA,cAAc,GAAG,kBAAkB,CAAC;AAEjD,oEAAoE;AACvD,QAAA,cAAc,GAAG,cAAc,CAAC;AAE7C,qEAAqE;AACxD,QAAA,WAAW,GAAG,WAAW,CAAC","sourcesContent":["/**\n * Copyright © 2024 650 Industries.\n * Copyright © 2024 2023 Daishi Kato\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nexport type RouteProps = {\n path: string;\n query: string;\n hash: string;\n};\n\nexport {\n getComponentIds,\n mintComponentId,\n type ComponentIdKind,\n type ComponentIds,\n} from './idMinting';\n\nexport function getInputString(path: string): string {\n if (!path.startsWith('/')) {\n throw new Error('Path should start with `/`');\n }\n return path.slice(1);\n}\n\nexport function parseInputString(input: string): string {\n return '/' + input;\n}\n\nexport const PARAM_KEY_SKIP = 'expo_router_skip';\n\n// It starts with \"/\" to avoid conflicing with normal component ids.\nexport const SHOULD_SKIP_ID = '/SHOULD_SKIP';\n\n// It starts with \"/\" to avoid conflicting with normal component ids.\nexport const LOCATION_ID = '/LOCATION';\n\n// TODO revisit shouldSkip API\nexport type ShouldSkip = (readonly [\n componentId: string,\n components: readonly [\n path?: boolean, // if we compare path\n keys?: string[], // searchParams keys to compare\n ],\n])[];\n"]} \ No newline at end of file diff --git a/packages/expo-router/build/rsc/router/idMinting.d.ts b/packages/expo-router/build/rsc/router/idMinting.d.ts new file mode 100644 index 00000000000000..872eccde8660e8 --- /dev/null +++ b/packages/expo-router/build/rsc/router/idMinting.d.ts @@ -0,0 +1,31 @@ +/** + * Component-ID encoding for RSC routing. + * + * Component IDs identify a slot in the rendered React tree (a layout or a page). + * Both client and server mint the same IDs from the same inputs so they can agree + * about caching, skip lists, and component resolution. + * + * Convention: take the URL pathname segments, then suffix with `'layout'` or + * `'page'`. `mintComponentId('/posts/[id]', 'page') === 'posts/[id]/page'`. + */ +export type ComponentIdKind = 'page' | 'layout'; +/** + * Component IDs expected to render for a given URL pathname. The terminal + * `page` is separated from `layouts` so consumers don't have to rely on a + * positional convention (last element is the page) to tell them apart. + */ +export type ComponentIds = { + /** Layout IDs from root to deepest, in render order. */ + readonly layouts: readonly string[]; + /** The terminal page ID for this pathname. */ + readonly page: string; +}; +/** Encode a single component ID from a path and the slot kind. */ +export declare function mintComponentId(path: string, kind: ComponentIdKind): string; +/** + * Enumerate the component IDs expected to render for a given URL pathname: + * one layout ID per path-prefix (root layout first, deepest last) plus the + * terminal page ID. + */ +export declare function getComponentIds(path: string): ComponentIds; +//# sourceMappingURL=idMinting.d.ts.map \ No newline at end of file diff --git a/packages/expo-router/build/rsc/router/idMinting.d.ts.map b/packages/expo-router/build/rsc/router/idMinting.d.ts.map new file mode 100644 index 00000000000000..8c8818ead371a8 --- /dev/null +++ b/packages/expo-router/build/rsc/router/idMinting.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"idMinting.d.ts","sourceRoot":"","sources":["../../../src/rsc/router/idMinting.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEhD;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,wDAAwD;IACxD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,8CAA8C;IAC9C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,kEAAkE;AAClE,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,MAAM,CAE3E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAO1D"} \ No newline at end of file diff --git a/packages/expo-router/build/rsc/router/idMinting.js b/packages/expo-router/build/rsc/router/idMinting.js new file mode 100644 index 00000000000000..9d639dc6e1b13a --- /dev/null +++ b/packages/expo-router/build/rsc/router/idMinting.js @@ -0,0 +1,32 @@ +"use strict"; +/** + * Component-ID encoding for RSC routing. + * + * Component IDs identify a slot in the rendered React tree (a layout or a page). + * Both client and server mint the same IDs from the same inputs so they can agree + * about caching, skip lists, and component resolution. + * + * Convention: take the URL pathname segments, then suffix with `'layout'` or + * `'page'`. `mintComponentId('/posts/[id]', 'page') === 'posts/[id]/page'`. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.mintComponentId = mintComponentId; +exports.getComponentIds = getComponentIds; +/** Encode a single component ID from a path and the slot kind. */ +function mintComponentId(path, kind) { + return [...path.split('/').filter(Boolean), kind].join('/'); +} +/** + * Enumerate the component IDs expected to render for a given URL pathname: + * one layout ID per path-prefix (root layout first, deepest last) plus the + * terminal page ID. + */ +function getComponentIds(path) { + const segments = path.split('/').filter(Boolean); + const layouts = []; + for (let i = 0; i <= segments.length; i++) { + layouts.push(mintComponentId(segments.slice(0, i).join('/'), 'layout')); + } + return { layouts, page: mintComponentId(segments.join('/'), 'page') }; +} +//# sourceMappingURL=idMinting.js.map \ No newline at end of file diff --git a/packages/expo-router/build/rsc/router/idMinting.js.map b/packages/expo-router/build/rsc/router/idMinting.js.map new file mode 100644 index 00000000000000..a1a1b61f14199d --- /dev/null +++ b/packages/expo-router/build/rsc/router/idMinting.js.map @@ -0,0 +1 @@ +{"version":3,"file":"idMinting.js","sourceRoot":"","sources":["../../../src/rsc/router/idMinting.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;AAiBH,0CAEC;AAOD,0CAOC;AAjBD,kEAAkE;AAClE,SAAgB,eAAe,CAAC,IAAY,EAAE,IAAqB;IACjE,OAAO,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC9D,CAAC;AAED;;;;GAIG;AACH,SAAgB,eAAe,CAAC,IAAY;IAC1C,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACjD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1C,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,EAAE,CAAC;AACxE,CAAC","sourcesContent":["/**\n * Component-ID encoding for RSC routing.\n *\n * Component IDs identify a slot in the rendered React tree (a layout or a page).\n * Both client and server mint the same IDs from the same inputs so they can agree\n * about caching, skip lists, and component resolution.\n *\n * Convention: take the URL pathname segments, then suffix with `'layout'` or\n * `'page'`. `mintComponentId('/posts/[id]', 'page') === 'posts/[id]/page'`.\n */\n\nexport type ComponentIdKind = 'page' | 'layout';\n\n/**\n * Component IDs expected to render for a given URL pathname. The terminal\n * `page` is separated from `layouts` so consumers don't have to rely on a\n * positional convention (last element is the page) to tell them apart.\n */\nexport type ComponentIds = {\n /** Layout IDs from root to deepest, in render order. */\n readonly layouts: readonly string[];\n /** The terminal page ID for this pathname. */\n readonly page: string;\n};\n\n/** Encode a single component ID from a path and the slot kind. */\nexport function mintComponentId(path: string, kind: ComponentIdKind): string {\n return [...path.split('/').filter(Boolean), kind].join('/');\n}\n\n/**\n * Enumerate the component IDs expected to render for a given URL pathname:\n * one layout ID per path-prefix (root layout first, deepest last) plus the\n * terminal page ID.\n */\nexport function getComponentIds(path: string): ComponentIds {\n const segments = path.split('/').filter(Boolean);\n const layouts: string[] = [];\n for (let i = 0; i <= segments.length; i++) {\n layouts.push(mintComponentId(segments.slice(0, i).join('/'), 'layout'));\n }\n return { layouts, page: mintComponentId(segments.join('/'), 'page') };\n}\n"]} \ No newline at end of file diff --git a/packages/expo-router/src/internal/routing.ts b/packages/expo-router/src/internal/routing.ts index b5c05f1b6a50a4..01802f2d122e0c 100644 --- a/packages/expo-router/src/internal/routing.ts +++ b/packages/expo-router/src/internal/routing.ts @@ -10,6 +10,7 @@ export { export { getContextKey, isTypedRoute, + matchDynamicName, matchGroupName, removeSupportedExtensions, stripGroupSegmentsFromPath, diff --git a/packages/expo-router/src/internal/rsc.ts b/packages/expo-router/src/internal/rsc.ts index b2037b38febb60..8669cf8923f4ec 100644 --- a/packages/expo-router/src/internal/rsc.ts +++ b/packages/expo-router/src/internal/rsc.ts @@ -3,6 +3,9 @@ export { getComponentIds, getInputString, parseInputString, + mintComponentId, + type ComponentIdKind, + type ComponentIds, PARAM_KEY_SKIP, SHOULD_SKIP_ID, LOCATION_ID, diff --git a/packages/expo-router/src/rsc/router/client.ts b/packages/expo-router/src/rsc/router/client.ts index 12f757bea0cd12..12acd41350f28c 100644 --- a/packages/expo-router/src/rsc/router/client.ts +++ b/packages/expo-router/src/rsc/router/client.ts @@ -132,7 +132,13 @@ const InnerRouter = ({ routerData }: { routerData: RouterData }) => { }); }, []); - const componentIds = getComponentIds(route.path); + // TODO: Mint IDs from the active RouteNode's `contextKey` (via expo-router's + // RouterStore) so they're group-aware and canonical. Today they're URL-derived + // and omit `(group)` segments — the server's iterating resolver bridges this via + // optional-group regex matching, but the asymmetry should go away if/when the + // canonical-ID convention needs to include groups (e.g. signed skip tokens). + const { layouts, page } = getComponentIds(route.path); + const componentIds = [...layouts, page]; // const refetchRoute = () => { // const loc = parseRoute(new URL(getHref())); @@ -156,7 +162,8 @@ const InnerRouter = ({ routerData }: { routerData: RouterData }) => { startTransition(() => { setRoute(route); }); - const componentIds = getComponentIds(route.path); + const { layouts, page } = getComponentIds(route.path); + const componentIds = [...layouts, page]; if ( checkCache && componentIds.every((id) => { @@ -189,7 +196,8 @@ const InnerRouter = ({ routerData }: { routerData: RouterData }) => { const prefetchRoute: PrefetchRoute = useCallback( (route) => { - const componentIds = getComponentIds(route.path); + const { layouts, page } = getComponentIds(route.path); + const componentIds = [...layouts, page]; const shouldSkip = routerData[0]; const skip = getSkipList(shouldSkip, componentIds, route, cachedRef.current); if (componentIds.every((id) => skip.includes(id))) { diff --git a/packages/expo-router/src/rsc/router/common.ts b/packages/expo-router/src/rsc/router/common.ts index 1b3a0be05b78ff..27f78d36f032dd 100644 --- a/packages/expo-router/src/rsc/router/common.ts +++ b/packages/expo-router/src/rsc/router/common.ts @@ -12,16 +12,12 @@ export type RouteProps = { hash: string; }; -export function getComponentIds(path: string): readonly string[] { - const pathItems = path.split('/').filter(Boolean); - const idSet = new Set(); - for (let index = 0; index <= pathItems.length; ++index) { - const id = [...pathItems.slice(0, index), 'layout'].join('/'); - idSet.add(id); - } - idSet.add([...pathItems, 'page'].join('/')); - return Array.from(idSet); -} +export { + getComponentIds, + mintComponentId, + type ComponentIdKind, + type ComponentIds, +} from './idMinting'; export function getInputString(path: string): string { if (!path.startsWith('/')) { diff --git a/packages/expo-router/src/rsc/router/idMinting.ts b/packages/expo-router/src/rsc/router/idMinting.ts new file mode 100644 index 00000000000000..e7b1657402ab8c --- /dev/null +++ b/packages/expo-router/src/rsc/router/idMinting.ts @@ -0,0 +1,43 @@ +/** + * Component-ID encoding for RSC routing. + * + * Component IDs identify a slot in the rendered React tree (a layout or a page). + * Both client and server mint the same IDs from the same inputs so they can agree + * about caching, skip lists, and component resolution. + * + * Convention: take the URL pathname segments, then suffix with `'layout'` or + * `'page'`. `mintComponentId('/posts/[id]', 'page') === 'posts/[id]/page'`. + */ + +export type ComponentIdKind = 'page' | 'layout'; + +/** + * Component IDs expected to render for a given URL pathname. The terminal + * `page` is separated from `layouts` so consumers don't have to rely on a + * positional convention (last element is the page) to tell them apart. + */ +export type ComponentIds = { + /** Layout IDs from root to deepest, in render order. */ + readonly layouts: readonly string[]; + /** The terminal page ID for this pathname. */ + readonly page: string; +}; + +/** Encode a single component ID from a path and the slot kind. */ +export function mintComponentId(path: string, kind: ComponentIdKind): string { + return [...path.split('/').filter(Boolean), kind].join('/'); +} + +/** + * Enumerate the component IDs expected to render for a given URL pathname: + * one layout ID per path-prefix (root layout first, deepest last) plus the + * terminal page ID. + */ +export function getComponentIds(path: string): ComponentIds { + const segments = path.split('/').filter(Boolean); + const layouts: string[] = []; + for (let i = 0; i <= segments.length; i++) { + layouts.push(mintComponentId(segments.slice(0, i).join('/'), 'layout')); + } + return { layouts, page: mintComponentId(segments.join('/'), 'page') }; +} diff --git a/packages/expo-ui/CHANGELOG.md b/packages/expo-ui/CHANGELOG.md index 1c6b3f64e6ca71..bde0daa62d961e 100644 --- a/packages/expo-ui/CHANGELOG.md +++ b/packages/expo-ui/CHANGELOG.md @@ -6,6 +6,7 @@ ### 🎉 New features +- [iOS] Added `withAnimation(animation, body)` in `@expo/ui/swift-ui`, mirroring SwiftUI's [`withAnimation(_:_:)`](https://developer.apple.com/documentation/swiftui/withanimation(_:_:)). ([#45893](https://github.com/expo/expo/pull/45893) by [@nishan](https://github.com/intergalacticspacehighway)) - [jetpack-compose] Added `Snackbar` component. ([#45667](https://github.com/expo/expo/pull/45667) by [@nishan](https://github.com/intergalacticspacehighway)) - [android] Added `LoadingIndicator` and `ContainedLoadingIndicator` components. ([#41169](https://github.com/expo/expo/pull/41169) by [@suveshmoza](https://github.com/suveshmoza)) diff --git a/packages/expo-ui/build/swift-ui/index.d.ts b/packages/expo-ui/build/swift-ui/index.d.ts index 034f2d7534b942..14e95afa52a953 100644 --- a/packages/expo-ui/build/swift-ui/index.d.ts +++ b/packages/expo-ui/build/swift-ui/index.d.ts @@ -36,6 +36,7 @@ export * from './Stepper'; export * from './SwipeActions'; export * from './Text'; export { useNativeState } from '../State/useNativeState'; +export { withAnimation, type WithAnimationCompletionCriteria } from './withAnimation'; export * from './SyncToggle'; export * from './TabView'; export * from './Toggle'; diff --git a/packages/expo-ui/build/swift-ui/index.d.ts.map b/packages/expo-ui/build/swift-ui/index.d.ts.map index 2274f8e3b2e1b1..6d31dde6d3e364 100644 --- a/packages/expo-ui/build/swift-ui/index.d.ts.map +++ b/packages/expo-ui/build/swift-ui/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/swift-ui/index.tsx"],"names":[],"mappings":"AAAA,OAAO,mBAAmB,CAAC;AAE3B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC;AAC9B,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC;AAClC,cAAc,QAAQ,CAAC;AACvB,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC;AACvB,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,kBAAkB,CAAC;AACjC,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,QAAQ,CAAC;AACvB,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,OAAO,EACL,SAAS,EACT,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAC;AACrB,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,QAAQ,CAAC;AACvB,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,uBAAuB,EAAE,MAAM,SAAS,CAAC"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/swift-ui/index.tsx"],"names":[],"mappings":"AAAA,OAAO,mBAAmB,CAAC;AAE3B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,eAAe,CAAC;AAC9B,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC;AAClC,cAAc,QAAQ,CAAC;AACvB,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC;AACvB,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,kBAAkB,CAAC;AACjC,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,QAAQ,CAAC;AACvB,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,KAAK,+BAA+B,EAAE,MAAM,iBAAiB,CAAC;AACtF,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,OAAO,EACL,SAAS,EACT,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAC;AACrB,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,QAAQ,CAAC;AACvB,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,uBAAuB,EAAE,MAAM,SAAS,CAAC"} \ No newline at end of file diff --git a/packages/expo-ui/build/swift-ui/withAnimation.d.ts b/packages/expo-ui/build/swift-ui/withAnimation.d.ts new file mode 100644 index 00000000000000..542d814855f1c0 --- /dev/null +++ b/packages/expo-ui/build/swift-ui/withAnimation.d.ts @@ -0,0 +1,26 @@ +import type { ChainableAnimationType } from './modifiers/animation/types'; +/** + * @see Official [SwiftUI documentation](https://developer.apple.com/documentation/swiftui/animationcompletioncriteria). + */ +export type WithAnimationCompletionCriteria = 'logicallyComplete' | 'removed'; +/** + * Mirrors SwiftUI's [`withAnimation(_:_:)`](https://developer.apple.com/documentation/swiftui/withanimation(_:_:)). + * The body must be a worklet so the mutations run synchronously on the + * UI thread inside the animation transaction. + * + * Performs `body` inside a SwiftUI animation transaction. Any + * `useNativeState` values mutated by the worklet animate to their new value + * using `animation`. + * + * @param animation Animation to apply, built with the `Animation` factory + * from `@expo/ui/swift-ui/modifiers`. Pass `null` to run `body` without an + * animation. + * @param body Worklet that mutates one or more `useNativeState` values. + * @param completion Optional worklet invoked on the main thread when the + * animation finishes. Requires iOS 17 / tvOS 17; on earlier versions the + * animation still runs but the callback is silently skipped. + * @param completionCriteria Controls when `completion` fires. Defaults to + * `'logicallyComplete'`. + */ +export declare function withAnimation(animation: ChainableAnimationType | null, body: () => void, completion?: () => void, completionCriteria?: WithAnimationCompletionCriteria): void; +//# sourceMappingURL=withAnimation.d.ts.map \ No newline at end of file diff --git a/packages/expo-ui/build/swift-ui/withAnimation.d.ts.map b/packages/expo-ui/build/swift-ui/withAnimation.d.ts.map new file mode 100644 index 00000000000000..d9e194974d980c --- /dev/null +++ b/packages/expo-ui/build/swift-ui/withAnimation.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"withAnimation.d.ts","sourceRoot":"","sources":["../../src/swift-ui/withAnimation.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAmB,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAI3F;;GAEG;AACH,MAAM,MAAM,+BAA+B,GAAG,mBAAmB,GAAG,SAAS,CAAC;AAE9E;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,sBAAsB,GAAG,IAAI,EACxC,IAAI,EAAE,MAAM,IAAI,EAChB,UAAU,CAAC,EAAE,MAAM,IAAI,EACvB,kBAAkB,CAAC,EAAE,+BAA+B,GACnD,IAAI,CAiCN"} \ No newline at end of file diff --git a/packages/expo-ui/ios/ExpoUIModule.swift b/packages/expo-ui/ios/ExpoUIModule.swift index 87cbf96cd04f71..10e3f48e5a08a6 100644 --- a/packages/expo-ui/ios/ExpoUIModule.swift +++ b/packages/expo-ui/ios/ExpoUIModule.swift @@ -1,6 +1,7 @@ // Copyright 2025-present 650 Industries. All rights reserved. import ExpoModulesCore +import SwiftUI public final class ExpoUIModule: Module { public func definition() -> ModuleDefinition { @@ -38,6 +39,33 @@ public final class ExpoUIModule: Module { RefreshableManager.shared.completeRefresh(id: id) } + AsyncFunction("withAnimation") { ( + animation: AnimationConfig?, + body: WorkletCallback, + completion: WorkletCallback?, + completionCriteria: AnimationCompletionCriteriaType? + ) in + let swiftUIAnimation = animation?.toSwiftUIAnimation() + + if let completion { + if #available(iOS 17.0, tvOS 17.0, *) { + let criteria: SwiftUI.AnimationCompletionCriteria = + completionCriteria == .removed ? .removed : .logicallyComplete + withAnimation(swiftUIAnimation, completionCriteria: criteria) { + body.invoke() + } completion: { + completion.invoke() + } + return + } + } + + withAnimation(swiftUIAnimation) { + body.invoke() + } + } + .runOnQueue(.main) + // MARK: - Expo UI Views with AsyncFunctions ExpoUIView(SecureFieldView.self) { diff --git a/packages/expo-ui/ios/Modifiers/AnimationConfig.swift b/packages/expo-ui/ios/Modifiers/AnimationConfig.swift new file mode 100644 index 00000000000000..6eed60c9d6529a --- /dev/null +++ b/packages/expo-ui/ios/Modifiers/AnimationConfig.swift @@ -0,0 +1,109 @@ +// Copyright 2025-present 650 Industries. All rights reserved. + +import ExpoModulesCore +import SwiftUI + +internal enum AnimationType: String, Enumerable { + case easeInOut + case easeIn + case easeOut + case linear + case spring + case interpolatingSpring + case `default` +} + +internal enum AnimationCompletionCriteriaType: String, Enumerable { + case logicallyComplete + case removed +} + +/** + Describes a SwiftUI `Animation` value over the JS bridge. Consumed by both + the `animation(_:value:)` view modifier and the `withAnimation(_:_:)` + function so they accept the same JS shape. + */ +internal struct AnimationConfig: Record { + // Shared fallbacks used when the JS side omits a parameter. Mirrors + // SwiftUI's own defaults so each animation variant resolves the same way. + private static let defaultDuration: Double = 0.5 + private static let defaultResponse: Double = 0.5 + private static let defaultDampingFraction: Double = 0.825 + private static let defaultBlendDuration: Double = 0.0 + private static let defaultBounce: Double = 0.0 + + @Field var type: AnimationType = .default + @Field var duration: Double? + @Field var response: Double? + @Field var dampingFraction: Double? + @Field var blendDuration: Double? + @Field var bounce: Double? + @Field var mass: Double = 1.0 + @Field var stiffness: Double? + @Field var damping: Double? + @Field var initialVelocity: Double = 0.0 + @Field var delay: Double? + @Field var repeatCount: Int? + @Field var autoreverses: Bool = true + + func toSwiftUIAnimation() -> Animation { + var animation: Animation + + switch type { + case .easeIn: + animation = duration.map { Animation.easeIn(duration: $0) } ?? .easeIn + case .easeOut: + animation = duration.map { Animation.easeOut(duration: $0) } ?? .easeOut + case .linear: + animation = duration.map { Animation.linear(duration: $0) } ?? .linear + case .easeInOut: + animation = duration.map { Animation.easeInOut(duration: $0) } ?? .easeInOut + case .spring: + if response != nil || dampingFraction != nil { + animation = .spring( + response: response ?? Self.defaultResponse, + dampingFraction: dampingFraction ?? Self.defaultDampingFraction, + blendDuration: blendDuration ?? Self.defaultBlendDuration + ) + } else if duration != nil || bounce != nil { + animation = .spring( + duration: duration ?? Self.defaultDuration, + bounce: bounce ?? Self.defaultBounce, + blendDuration: blendDuration ?? Self.defaultBlendDuration + ) + } else if let blendDuration { + animation = .spring(blendDuration: blendDuration) + } else { + animation = .spring + } + case .interpolatingSpring: + if duration != nil || bounce != nil { + animation = .interpolatingSpring( + duration: duration ?? Self.defaultDuration, + bounce: bounce ?? Self.defaultBounce, + initialVelocity: initialVelocity + ) + } else if let stiffness, let damping { + animation = .interpolatingSpring( + mass: mass, + stiffness: stiffness, + damping: damping, + initialVelocity: initialVelocity + ) + } else { + animation = .interpolatingSpring + } + default: + animation = .default + } + + if let delay { + animation = animation.delay(delay) + } + if let repeatCount { + animation = animation.repeatCount(repeatCount, autoreverses: autoreverses) + } + + return animation + } +} diff --git a/packages/expo-ui/ios/Modifiers/ViewModifierRegistry.swift b/packages/expo-ui/ios/Modifiers/ViewModifierRegistry.swift index 8ae822c470846b..769c7e4f1f82f5 100644 --- a/packages/expo-ui/ios/Modifiers/ViewModifierRegistry.swift +++ b/packages/expo-ui/ios/Modifiers/ViewModifierRegistry.swift @@ -621,38 +621,12 @@ internal struct AnyViewModifier: ViewModifier { } } -internal enum AnimationType: String, Enumerable { - case easeInOut - case easeIn - case easeOut - case linear - case spring - case interpolatingSpring - case `default` -} - -internal struct AnimationConfig: Record { - @Field var type: AnimationType = .default - @Field var duration: Double? - @Field var response: Double? - @Field var dampingFraction: Double? - @Field var blendDuration: Double? - @Field var bounce: Double? - @Field var mass: Double? - @Field var stiffness: Double? - @Field var damping: Double? - @Field var initialVelocity: Double? - @Field var delay: Double? - @Field var repeatCount: Int? - @Field var autoreverses: Bool? -} - internal struct AnimationModifier: ViewModifier, Record { @Field var animation: AnimationConfig @Field var animatedValue: Either? func body(content: Content) -> some View { - let animationValue = parseAnimation(animation) + let animationValue = animation.toSwiftUIAnimation() if let value: Bool = animatedValue?.get() { content.animation(animationValue, value: value) } else if let value: Double = animatedValue?.get() { @@ -661,91 +635,6 @@ internal struct AnimationModifier: ViewModifier, Record { content } } - - private func parseAnimation(_ config: AnimationConfig) -> Animation { - let type = config.type - - var animation: Animation - - switch type { - case .easeIn: - if let duration = config.duration { - animation = .easeIn(duration: duration) - } else { - animation = .easeIn - } - - case .easeOut: - if let duration = config.duration { - animation = .easeOut(duration: duration) - } else { - animation = .easeOut - } - - case .linear: - if let duration = config.duration { - animation = .linear(duration: duration) - } else { - animation = .linear - } - - case .easeInOut: - if let duration = config.duration { - animation = .easeInOut(duration: duration) - } else { - animation = .easeInOut - } - - case .spring: - let duration = config.duration - let bounce = config.bounce - let response = config.response - let dampingFraction = config.dampingFraction - let blendDuration = config.blendDuration - - if response != nil || dampingFraction != nil { - // default values are 0.5, 0.825, 0.0 - animation = .spring(response: response ?? 0.5, dampingFraction: dampingFraction ?? 0.825, blendDuration: blendDuration ?? 0.0) - } else if duration != nil || bounce != nil { - // default values are 0.5, 0.0, 0.0 - animation = .spring(duration: duration ?? 0.5, bounce: bounce ?? 0.0, blendDuration: blendDuration ?? 0.0) - } else if let blendDuration = blendDuration { - animation = .spring(blendDuration: blendDuration) - } else { - animation = .spring - } - - case .interpolatingSpring: - let duration = config.duration - let bounce = config.bounce - let mass = config.mass - let stiffness = config.stiffness - let damping = config.damping - let initialVelocity = config.initialVelocity - - if duration != nil || bounce != nil { - animation = .interpolatingSpring(duration: duration ?? 0.5, bounce: bounce ?? 0.0, initialVelocity: initialVelocity ?? 0.0) - } else if let stiffness, let damping { - animation = .interpolatingSpring(mass: mass ?? 1.0, stiffness: stiffness, damping: damping, initialVelocity: initialVelocity ?? 0.0) - } else { - animation = .interpolatingSpring - } - - default: - animation = .default - } - - if let delay = config.delay { - animation = animation.delay(delay) - } - - if let repeatCount = config.repeatCount { - let autoreverses = config.autoreverses ?? false - animation = animation.repeatCount(repeatCount, autoreverses: autoreverses) - } - - return animation - } } internal enum ScrollContentBackgroundTypes: String, Enumerable { diff --git a/packages/expo-ui/src/swift-ui/index.tsx b/packages/expo-ui/src/swift-ui/index.tsx index e8f0fb4639f20d..c1c239d2d6dc4c 100644 --- a/packages/expo-ui/src/swift-ui/index.tsx +++ b/packages/expo-ui/src/swift-ui/index.tsx @@ -37,6 +37,7 @@ export * from './Stepper'; export * from './SwipeActions'; export * from './Text'; export { useNativeState } from '../State/useNativeState'; +export { withAnimation, type WithAnimationCompletionCriteria } from './withAnimation'; export * from './SyncToggle'; export * from './TabView'; export * from './Toggle'; diff --git a/packages/expo-ui/src/swift-ui/withAnimation.ts b/packages/expo-ui/src/swift-ui/withAnimation.ts new file mode 100644 index 00000000000000..fb3cbd2927f81c --- /dev/null +++ b/packages/expo-ui/src/swift-ui/withAnimation.ts @@ -0,0 +1,71 @@ +import { requireNativeModule } from 'expo'; + +import { worklets } from '../State/optionalWorklets'; +import { VALUE_SYMBOL } from './modifiers/animation/constants'; +import type { AnimationObject, ChainableAnimationType } from './modifiers/animation/types'; + +const ExpoUI = requireNativeModule('ExpoUI'); + +/** + * @see Official [SwiftUI documentation](https://developer.apple.com/documentation/swiftui/animationcompletioncriteria). + */ +export type WithAnimationCompletionCriteria = 'logicallyComplete' | 'removed'; + +/** + * Mirrors SwiftUI's [`withAnimation(_:_:)`](https://developer.apple.com/documentation/swiftui/withanimation(_:_:)). + * The body must be a worklet so the mutations run synchronously on the + * UI thread inside the animation transaction. + * + * Performs `body` inside a SwiftUI animation transaction. Any + * `useNativeState` values mutated by the worklet animate to their new value + * using `animation`. + * + * @param animation Animation to apply, built with the `Animation` factory + * from `@expo/ui/swift-ui/modifiers`. Pass `null` to run `body` without an + * animation. + * @param body Worklet that mutates one or more `useNativeState` values. + * @param completion Optional worklet invoked on the main thread when the + * animation finishes. Requires iOS 17 / tvOS 17; on earlier versions the + * animation still runs but the callback is silently skipped. + * @param completionCriteria Controls when `completion` fires. Defaults to + * `'logicallyComplete'`. + */ +export function withAnimation( + animation: ChainableAnimationType | null, + body: () => void, + completion?: () => void, + completionCriteria?: WithAnimationCompletionCriteria +): void { + if (!worklets) { + throw new Error( + "withAnimation needs the 'react-native-worklets' package, which couldn't be loaded. " + + 'Install react-native-worklets and rebuild the native app, then call withAnimation again.' + ); + } + + if (!worklets.isWorkletFunction(body)) { + throw new Error( + 'withAnimation body must be a worklet. Worklets run synchronously on the UI thread ' + + "so state changes are captured by SwiftUI's animation transaction. " + + "Add the 'worklet' directive as the first statement: " + + "() => { 'worklet'; state.value = next; }." + ); + } + + let completionCallback: object | null = null; + if (completion) { + if (!worklets.isWorkletFunction(completion)) { + throw new Error( + "withAnimation completion must be a worklet. Add the 'worklet' directive as the " + + 'first statement in your completion callback.' + ); + } + completionCallback = new ExpoUI.WorkletCallback(worklets.createSerializable(completion)); + } + + const animationObject: AnimationObject | null = + animation == null ? null : animation[VALUE_SYMBOL](); + + const bodyCallback = new ExpoUI.WorkletCallback(worklets.createSerializable(body)); + ExpoUI.withAnimation(animationObject, bodyCallback, completionCallback, completionCriteria); +} diff --git a/tools/src/EASUpdate.ts b/tools/src/EASUpdate.ts index 5921aade10e48f..83fd25e44796aa 100644 --- a/tools/src/EASUpdate.ts +++ b/tools/src/EASUpdate.ts @@ -1,7 +1,8 @@ +import spawnAsync from '@expo/spawn-async'; import process from 'process'; +import { EXPO_DIR } from './Constants'; import * as EASCLI from './EASCLI'; -import * as ExpoCLI from './ExpoCLI'; import * as Log from './Log'; type Options = { @@ -32,7 +33,14 @@ export async function setAuthAndPublishProjectWithEasCliAsync( if (username && password) { Log.collapsed('Logging in...'); - await ExpoCLI.runExpoCliAsync('login', ['-u', username, '-p', password]); + // Pass the password via stdin instead of argv to keep it out of the process table. + const child = spawnAsync('npx', ['expo', 'login', '-u', username, '-p', '-'], { + cwd: EXPO_DIR, + stdio: ['pipe', 'inherit', 'inherit'], + env: { ...process.env, EXPO_NO_DOCTOR: 'true' }, + }); + child.child.stdin!.end(`${password}\n`); + await child; } else { Log.collapsed('Expo username and password not specified. Using currently logged-in account.'); }