├── .gitattributes
├── .github
├── ISSUE_TEMPLATE
│ ├── bug.yml
│ └── config.yml
├── PULL_REQUEST_TEMPLATE
└── workflows
│ ├── docs.yml
│ ├── test-e2e.yml
│ └── test.yml
├── .gitignore
├── .vscode
└── settings.json
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── README.md
├── apps
├── sandbox
│ ├── app.config.js
│ ├── app_105
│ │ ├── README.md
│ │ ├── [...bacon].tsx
│ │ ├── [user].tsx
│ │ ├── _layout.tsx
│ │ └── other.tsx
│ ├── app_15
│ │ ├── README.md
│ │ └── [foo]
│ │ │ ├── [bar]
│ │ │ ├── _layout.js
│ │ │ └── index.js
│ │ │ └── index.js
│ ├── app_221
│ │ ├── README.md
│ │ ├── _layout.js
│ │ ├── index.js
│ │ ├── permissions.js
│ │ ├── protected.js
│ │ └── two.js
│ ├── app_232
│ │ ├── [root]
│ │ │ ├── _layout.tsx
│ │ │ ├── index.tsx
│ │ │ ├── item
│ │ │ │ └── [itemId].tsx
│ │ │ └── list
│ │ │ │ └── [listId].tsx
│ │ ├── _layout.tsx
│ │ └── index.tsx
│ ├── app_277
│ │ ├── README.md
│ │ ├── [...blog].tsx
│ │ ├── _layout.tsx
│ │ └── index.tsx
│ ├── app_dynamicUsePathname
│ │ ├── [...dynamic].tsx
│ │ ├── exact.tsx
│ │ └── index.tsx
│ ├── app_initialRouteName
│ │ ├── _layout.tsx
│ │ ├── index.tsx
│ │ └── other.tsx
│ ├── app_link
│ │ ├── beta.tsx
│ │ └── index.tsx
│ ├── babel.config.js
│ ├── etc
│ │ ├── auth
│ │ │ ├── TokenResponseContext.tsx
│ │ │ ├── createAuthSessionContextProvider.ts
│ │ │ ├── google.tsx
│ │ │ ├── useSecureAuthState.ts
│ │ │ ├── useStorageState.ts
│ │ │ └── utils.ts
│ │ ├── data.tsx
│ │ └── urlBar.tsx
│ ├── icon.png
│ ├── index.js
│ ├── metro.config.js
│ ├── package.json
│ ├── public
│ │ ├── .well-known
│ │ │ └── apple-app-site-association
│ │ ├── hello.json
│ │ └── snack.png
│ ├── splash.png
│ ├── styles.css
│ ├── tsconfig.json
│ └── wdyr.js
└── tester
│ ├── app.config.js
│ ├── babel.config.js
│ ├── custom-html
│ ├── +html.js
│ └── index.js
│ ├── global-css
│ └── index.js
│ ├── html-hooks
│ ├── +html.js
│ ├── index.js
│ └── test.js
│ ├── index.js
│ ├── metro.config.js
│ ├── package.json
│ ├── relative-fetch
│ └── index.tsx
│ ├── splash.png
│ ├── static-head
│ ├── _layout.js
│ ├── about.js
│ └── index.js
│ ├── static-params
│ └── [post].tsx
│ ├── styles.css
│ └── tsconfig.json
├── docs
├── .gitignore
├── README.md
├── babel.config.js
├── docs
│ └── migration
│ │ ├── _category_.json
│ │ └── react-navigation
│ │ ├── _category_.json
│ │ ├── custom-navigators.md
│ │ ├── drawer-navigator.md
│ │ ├── drawer.md
│ │ ├── link.md
│ │ ├── native-stack.md
│ │ ├── navigating.md
│ │ ├── navigation-container.md
│ │ ├── navigation-state.md
│ │ ├── params.md
│ │ ├── screen-tracking.md
│ │ ├── screen.md
│ │ ├── stack.md
│ │ ├── themes.md
│ │ ├── use-link-to.md
│ │ ├── use-navigation.md
│ │ └── use-route.md
├── docusaurus.config.js
├── package.json
├── sidebars.js
├── src
│ ├── components
│ │ ├── HomepageFeatures
│ │ │ ├── index.tsx
│ │ │ └── styles.module.css
│ │ └── redirect.tsx
│ ├── css
│ │ └── custom.css
│ ├── pages
│ │ ├── docs
│ │ │ ├── faq.tsx
│ │ │ ├── features
│ │ │ │ ├── dynamic-routes.tsx
│ │ │ │ ├── errors.tsx
│ │ │ │ ├── head.tsx
│ │ │ │ ├── layout-routes.tsx
│ │ │ │ ├── layouts.tsx
│ │ │ │ ├── linking.tsx
│ │ │ │ ├── routes.tsx
│ │ │ │ ├── routing.tsx
│ │ │ │ ├── shared-routes.tsx
│ │ │ │ ├── splash.tsx
│ │ │ │ └── unmatched.tsx
│ │ │ ├── guides
│ │ │ │ ├── auth.tsx
│ │ │ │ ├── header-buttons.tsx
│ │ │ │ ├── headers.tsx
│ │ │ │ ├── hosting.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ ├── modals.tsx
│ │ │ │ ├── nesting-navigators.tsx
│ │ │ │ ├── platform-specific-code.tsx
│ │ │ │ ├── root-layout.tsx
│ │ │ │ ├── tabs.tsx
│ │ │ │ └── universal-links.tsx
│ │ │ ├── index.tsx
│ │ │ ├── intro.tsx
│ │ │ ├── lab
│ │ │ │ ├── bundle-splitting.tsx
│ │ │ │ ├── handoff.tsx
│ │ │ │ ├── jest.tsx
│ │ │ │ ├── root-html.tsx
│ │ │ │ ├── runtime-location.tsx
│ │ │ │ ├── static-rendering.tsx
│ │ │ │ ├── styles.tsx
│ │ │ │ └── typescript.tsx
│ │ │ └── troubleshooting.tsx
│ │ ├── index.module.css
│ │ └── index.tsx
│ └── theme
│ │ └── NotFound.js
├── static
│ ├── .nojekyll
│ ├── demo
│ │ ├── modal.mp4
│ │ └── routing.mp4
│ ├── handoff
│ │ ├── iphone-handoff.png
│ │ └── macos-handoff.png
│ └── img
│ │ ├── directory.png
│ │ ├── docusaurus.png
│ │ ├── error-boundary.png
│ │ ├── favicon.ico
│ │ ├── logo.dark.svg
│ │ ├── logo.light.svg
│ │ ├── logo.svg
│ │ ├── og-image.png
│ │ ├── undraw_docusaurus_mountain.svg
│ │ ├── undraw_docusaurus_react.svg
│ │ ├── undraw_docusaurus_tree.svg
│ │ └── unmatched.png
├── tsconfig.json
└── yarn.lock
├── lerna.json
├── package.json
├── packages
├── expo-head
│ ├── .eslintrc.js
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── android
│ │ ├── build.gradle
│ │ └── src
│ │ │ └── main
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java
│ │ │ └── expo
│ │ │ └── modules.head
│ │ │ ├── ExpoHeadModule.kt
│ │ │ └── ExpoHeadView.kt
│ ├── app.plugin.js
│ ├── expo-module.config.json
│ ├── ios
│ │ ├── ExpoHead.podspec
│ │ ├── ExpoHeadAppDelegateSubscriber.swift
│ │ └── ExpoHeadModule.swift
│ ├── package.json
│ ├── src
│ │ ├── ExpoHead.android.tsx
│ │ ├── ExpoHead.ios.tsx
│ │ ├── ExpoHead.tsx
│ │ ├── ExpoHeadModule.native.ts
│ │ ├── ExpoHeadModule.ts
│ │ ├── index.ts
│ │ └── url.tsx
│ └── tsconfig.json
├── expo-metro-runtime
│ ├── .eslintrc.js
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── README.md
│ ├── assets
│ │ ├── alert-triangle.png
│ │ ├── chevron-left.png
│ │ ├── chevron-right.png
│ │ ├── close.png
│ │ └── loader.png
│ ├── async-require.d.ts
│ ├── async-require.js
│ ├── babel.config.js
│ ├── error-overlay.d.ts
│ ├── error-overlay.js
│ ├── jest.config.js
│ ├── package.json
│ ├── src
│ │ ├── HMRClient.native.ts
│ │ ├── HMRClient.ts
│ │ ├── LoadingView.native.ts
│ │ ├── LoadingView.ts
│ │ ├── async-require
│ │ │ ├── __tests__
│ │ │ │ ├── buildAsyncRequire.test.ts
│ │ │ │ ├── buildUrlForBundle.test.native.ts
│ │ │ │ ├── buildUrlForBundle.test.web.ts
│ │ │ │ ├── fetchAsync.test.web.ts
│ │ │ │ ├── loadBundle.test.ios.ts
│ │ │ │ └── loadBundle.test.web.ts
│ │ │ ├── buildAsyncRequire.ts
│ │ │ ├── buildUrlForBundle.native.ts
│ │ │ ├── buildUrlForBundle.ts
│ │ │ ├── fetchAsync.native.ts
│ │ │ ├── fetchAsync.ts
│ │ │ ├── fetchThenEval.ts
│ │ │ ├── fetchThenEval.web.ts
│ │ │ ├── index.ts
│ │ │ └── loadBundle.ts
│ │ ├── effects.native.ts
│ │ ├── effects.ts
│ │ ├── error-overlay
│ │ │ ├── Data
│ │ │ │ ├── LogBoxData.tsx
│ │ │ │ ├── LogBoxLog.ts
│ │ │ │ ├── LogBoxSymbolication.tsx
│ │ │ │ ├── LogContext.tsx
│ │ │ │ └── parseLogBoxLog.tsx
│ │ │ ├── ErrorOverlay.tsx
│ │ │ ├── LogBox.ts
│ │ │ ├── LogBox.web.ts
│ │ │ ├── UI
│ │ │ │ ├── AnsiHighlight.tsx
│ │ │ │ ├── LogBoxButton.tsx
│ │ │ │ ├── LogBoxMessage.tsx
│ │ │ │ ├── LogBoxStyle.ts
│ │ │ │ └── constants.ts
│ │ │ ├── formatProjectFilePath.ts
│ │ │ ├── index.tsx
│ │ │ ├── modules
│ │ │ │ ├── ExceptionsManager
│ │ │ │ │ ├── index.native.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── NativeLogBox
│ │ │ │ │ ├── index.native.ts
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── openFileInEditor
│ │ │ │ │ ├── index.native.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── parseErrorStack
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── parseHermesStack.ts
│ │ │ │ ├── stringifySafe
│ │ │ │ │ └── index.ts
│ │ │ │ └── symbolicateStackTrace
│ │ │ │ │ ├── index.native.ts
│ │ │ │ │ └── index.ts
│ │ │ ├── overlay
│ │ │ │ ├── LogBoxInspectorCodeFrame.tsx
│ │ │ │ ├── LogBoxInspectorFooter.tsx
│ │ │ │ ├── LogBoxInspectorHeader.tsx
│ │ │ │ ├── LogBoxInspectorMessageHeader.tsx
│ │ │ │ ├── LogBoxInspectorSection.tsx
│ │ │ │ ├── LogBoxInspectorSourceMapStatus.tsx
│ │ │ │ ├── LogBoxInspectorStackFrame.tsx
│ │ │ │ └── LogBoxInspectorStackFrames.tsx
│ │ │ ├── toast
│ │ │ │ ├── ErrorToast.tsx
│ │ │ │ ├── ErrorToastContainer.tsx
│ │ │ │ ├── ErrorToastContainer.web.tsx
│ │ │ │ └── ErrorToastMessage.tsx
│ │ │ └── useRejectionHandler.ts
│ │ ├── getDevServer.native.ts
│ │ ├── getDevServer.ts
│ │ ├── index.ts
│ │ ├── location
│ │ │ ├── Location.native.ts
│ │ │ ├── Location.ts
│ │ │ ├── install.native.ts
│ │ │ └── install.ts
│ │ ├── messageSocket.ts
│ │ ├── setupFastRefresh.ts
│ │ ├── setupHMR.ts
│ │ ├── symbolicate.ts
│ │ └── ts-declarations.d.ts
│ ├── symbolicate
│ │ ├── index.d.ts
│ │ └── index.js
│ └── tsconfig.json
└── expo-router
│ ├── .eslintrc.js
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── _app.tsx
│ ├── _ctx.android.tsx
│ ├── _ctx.ios.tsx
│ ├── _ctx.tsx
│ ├── _ctx.web.tsx
│ ├── _entry.tsx
│ ├── _error.js
│ ├── _html-ctx.tsx
│ ├── app.plugin.js
│ ├── assets
│ ├── error.png
│ ├── file.png
│ ├── forward.png
│ └── pkg.png
│ ├── babel.config.js
│ ├── babel.js
│ ├── drawer.ts
│ ├── e2e
│ ├── __tests__
│ │ ├── __snapshots__
│ │ │ └── export.test.ts.snap
│ │ ├── export.test.ts
│ │ └── utils.ts
│ └── jest.config.js
│ ├── entry.js
│ ├── head.ts
│ ├── html.ts
│ ├── index.d.ts
│ ├── node
│ ├── getExpoConstantsManifest.js
│ └── render.js
│ ├── package.json
│ ├── plugin
│ ├── build
│ │ ├── index.d.ts
│ │ └── index.js
│ ├── options.json
│ ├── src
│ │ └── index.ts
│ └── tsconfig.json
│ ├── src
│ ├── ExpoRoot.tsx
│ ├── LocationProvider.tsx
│ ├── Route.tsx
│ ├── __tests__
│ │ ├── LocationProvider.test.node.ts
│ │ ├── Route.test.node.ts
│ │ ├── getId.test.tsx
│ │ ├── getReactNavigationConfig.test.node.ts
│ │ ├── getRoutes.test.node.ts
│ │ ├── globalState.test.node.tsx
│ │ ├── hooks.test.node.tsx
│ │ ├── initialRouteName.test.tsx
│ │ ├── loadStaticParamsAsync.test.node.ts
│ │ ├── matchers.test.node.ts
│ │ ├── navigation.test.tsx
│ │ ├── smoke.test.tsx
│ │ └── useNavigation.test.node.ts
│ ├── exports.ts
│ ├── fork
│ │ ├── NavigationContainer.native.tsx
│ │ ├── NavigationContainer.tsx
│ │ ├── __tests__
│ │ │ ├── __snapshots__
│ │ │ │ └── extractPathFromURL.test.ios.ts.snap
│ │ │ ├── extractPathFromURL.test.ios.ts
│ │ │ ├── getPathFromState-upstream.test.node.ts
│ │ │ ├── getStateFromPath-upstream.test.node.ts
│ │ │ └── getStateFromPath.test.node.ts
│ │ ├── expo
│ │ │ ├── createRoot.native.tsx
│ │ │ ├── createRoot.tsx
│ │ │ └── registerRootComponent.tsx
│ │ ├── extractPathFromURL.ts
│ │ ├── findFocusedRoute.tsx
│ │ ├── getPathFromState.ts
│ │ ├── getStateFromPath.ts
│ │ ├── useLinking.native.ts
│ │ ├── useLinking.ts
│ │ └── validatePathConfig.ts
│ ├── getDevServer
│ │ ├── index.native.ts
│ │ └── index.ts
│ ├── getLinkingConfig.ts
│ ├── getReactNavigationConfig.ts
│ ├── getRoutes.ts
│ ├── global-state
│ │ ├── router-store.tsx
│ │ ├── routing.ts
│ │ └── sort-routes.ts
│ ├── hooks.ts
│ ├── imperative-api.ts
│ ├── import-mode
│ │ ├── index.android.ts
│ │ ├── index.ios.ts
│ │ ├── index.ts
│ │ └── index.web.ts
│ ├── index.tsx
│ ├── layouts
│ │ ├── Drawer.tsx
│ │ ├── Stack.tsx
│ │ ├── Tabs.tsx
│ │ └── withLayoutContext.tsx
│ ├── link
│ │ ├── Link.tsx
│ │ ├── __tests__
│ │ │ └── href.test.node.ts
│ │ ├── href.ts
│ │ ├── linking.ts
│ │ ├── path.ts
│ │ ├── useLinkToPathProps.tsx
│ │ └── useLoadedNavigation.ts
│ ├── loadStaticParamsAsync.ts
│ ├── matchers.tsx
│ ├── onboard
│ │ ├── Tutorial.tsx
│ │ ├── __tests__
│ │ │ └── createEntryFile.test.node.ts
│ │ └── createEntryFile.ts
│ ├── primitives.tsx
│ ├── renderRootComponent.tsx
│ ├── static
│ │ ├── getRootComponent.ts
│ │ ├── html.tsx
│ │ └── renderStaticContent.tsx
│ ├── testing-library
│ │ ├── context-stubs.ts
│ │ ├── expect.ts
│ │ ├── index.tsx
│ │ ├── mocks.ts
│ │ └── require-context-ponyfill.ts
│ ├── ts-declarations.d.ts
│ ├── types.ts
│ ├── useDeprecated.ts
│ ├── useFocusEffect.tsx
│ ├── useNavigation.ts
│ ├── useScreens.tsx
│ ├── utils
│ │ ├── __tests__
│ │ │ ├── mockState.test.node.ts
│ │ │ └── url.test.node.ts
│ │ ├── mockState.ts
│ │ └── url.ts
│ └── views
│ │ ├── EmptyRoute.tsx
│ │ ├── ErrorBoundary.tsx
│ │ ├── Navigator.tsx
│ │ ├── Screen.tsx
│ │ ├── Sitemap.tsx
│ │ ├── Splash.tsx
│ │ ├── SuspenseFallback.tsx
│ │ ├── Toast.tsx
│ │ ├── Try.tsx
│ │ └── Unmatched.tsx
│ ├── stack.ts
│ ├── tabs.ts
│ ├── testing-library.ts
│ ├── tsconfig.json
│ └── types
│ ├── expect.d.ts
│ ├── global.d.ts
│ ├── index.d.ts
│ ├── jest.d.ts
│ ├── metro-require.d.ts
│ └── react-native-web.d.ts
├── scripts
├── changelog-draft.js
└── publish.js
├── ts-declarations
├── index.d.ts
├── metro-babel-transformer
│ └── index.d.ts
├── metro-react-native-babel-transformer
│ └── index.d.ts
├── metro-source-map
│ └── index.d.ts
└── react-native-web
│ └── index.d.ts
├── tsconfig.base.json
└── yarn.lock
/.gitattributes:
--------------------------------------------------------------------------------
1 | packages/*/build/** -diff linguist-generated
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug.yml:
--------------------------------------------------------------------------------
1 | name: "\U0001F41B Bug Report (MOVED TO expo/expo)"
2 | description: "Expo Router has moved repos and issues should no longer be opened here."
3 | body:
4 | - type: markdown
5 | attributes:
6 | value: The source code for Expo Router has moved upstream to the [expo/expo](https://github.com/expo/expo/tree/main/packages/expo-router) repo, please open new issues here [Expo Router Bug Report](https://github.com/expo/expo/issues/new?template=bug_report_router.yml). Learn more about the [`expo-router` source migration](https://github.com/expo/router/discussions/820).
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Open an Expo Router issue
4 | url: https://github.com/expo/expo/issues/new?template=bug_report_router.yml
5 | about: Open issues for Expo Router in the expo/expo repo.
6 | - name: Expo Developers Discord
7 | url: https://chat.expo.dev/
8 | about: Join other developers in discussions regarding Expo.
9 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE:
--------------------------------------------------------------------------------
1 | # Motivation
2 |
3 |
6 |
7 | # Execution
8 |
9 |
12 |
13 | # Test Plan
14 |
15 |
18 |
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | name: GitHub Pages
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | paths:
8 | - ".github/workflows/docs.yml"
9 | - "docs/**"
10 | pull_request:
11 |
12 | jobs:
13 | deploy:
14 | runs-on: ubuntu-latest
15 | permissions:
16 | contents: write
17 | concurrency:
18 | group: ${{ github.workflow }}-${{ github.ref }}
19 | defaults:
20 | run:
21 | working-directory: docs
22 | steps:
23 | - name: 🏗 Setup repository
24 | uses: actions/checkout@v3
25 |
26 | - name: 🏗 Setup Node
27 | uses: actions/setup-node@v3
28 | with:
29 | node-version: "18"
30 | cache: yarn
31 | cache-dependency-path: docs/yarn.lock
32 |
33 | - name: 📦 Install dependencies
34 | run: yarn install --frozen-lockfile
35 |
36 | - name: 👷 Build docs
37 | run: yarn build
38 |
39 | - name: 🚀 Deploy docs
40 | uses: peaceiris/actions-gh-pages@v3
41 | if: ${{ github.ref == 'refs/heads/main' }}
42 | with:
43 | github_token: ${{ github.token }}
44 | publish_dir: ./docs/build
45 | # The following lines assign commit authorship to the official
46 | # GH-Actions bot for deploys to `gh-pages` branch:
47 | # https://github.com/actions/checkout/issues/13#issuecomment-724415212
48 | # The GH actions bot is used by default if you didn't specify the two fields.
49 | # You can swap them out with your own user credentials.
50 | user_name: evanbacon
51 | user_email: baconbrix@gmail.com
52 |
--------------------------------------------------------------------------------
/.github/workflows/test-e2e.yml:
--------------------------------------------------------------------------------
1 | name: E2E Test Packages
2 | on:
3 | push:
4 | branches: [main]
5 | pull_request:
6 | types: [opened, synchronize]
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | strategy:
12 | matrix:
13 | node: ["18"]
14 | name: Build with Node ${{ matrix.node }}
15 | steps:
16 | - name: 🏗 Setup repository
17 | uses: actions/checkout@v3
18 | with:
19 | fetch-depth: 1
20 |
21 | - name: 🏗 Setup Node
22 | uses: actions/setup-node@v3
23 | with:
24 | node-version: ${{ matrix.node }}
25 |
26 | - name: 📦 Install dependencies
27 | run: yarn install --frozen-lockfile --check-files
28 |
29 | - name: 👷 Build packages
30 | run: yarn lerna run prepare --stream
31 |
32 | # - name: 🚨 Lint Docs app
33 | # run: yarn lint --max-warnings 0
34 |
35 | - name: ♻️ Store build artifacts
36 | uses: actions/cache@v3
37 | with:
38 | path: "*"
39 | key: v3-${{ github.sha }}-${{ matrix.node }}
40 |
41 | test:
42 | runs-on: ubuntu-latest
43 | needs: build
44 | strategy:
45 | fail-fast: false
46 | matrix:
47 | node: ["18"]
48 | package: [expo-router]
49 | name: Test ${{ matrix.package }} on Node ${{ matrix.node }}
50 | steps:
51 | - name: ♻️ Restore build artifacts
52 | uses: actions/cache@v3
53 | with:
54 | path: "*"
55 | key: v3-${{ github.sha }}-${{ matrix.node }}
56 |
57 | - name: 🏗 Setup Node
58 | uses: actions/setup-node@v3
59 | with:
60 | node-version: ${{ matrix.node }}
61 |
62 | - name: 🧪 Test ${{ matrix.package }}
63 | run: yarn test:e2e
64 | working-directory: packages/${{ matrix.package }}
65 | env:
66 | EXPO_DEBUG: true
67 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test Packages
2 | on:
3 | push:
4 | branches: [main]
5 | pull_request:
6 | types: [opened, synchronize]
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | strategy:
12 | matrix:
13 | node: ["16"]
14 | name: Build with Node ${{ matrix.node }}
15 | steps:
16 | - name: 🏗 Setup repository
17 | uses: actions/checkout@v3
18 | with:
19 | fetch-depth: 1
20 |
21 | - name: 🏗 Setup Node
22 | uses: actions/setup-node@v3
23 | with:
24 | node-version: ${{ matrix.node }}
25 |
26 | - name: 📦 Install dependencies
27 | run: yarn install --frozen-lockfile --check-files
28 |
29 | - name: 👷 Build packages
30 | run: yarn lerna run prepare --stream
31 |
32 | # - name: 🚨 Lint Docs app
33 | # run: yarn lint --max-warnings 0
34 |
35 | - name: ♻️ Store build artifacts
36 | uses: actions/cache@v3
37 | with:
38 | path: "*"
39 | key: v3-${{ github.sha }}-${{ matrix.node }}
40 |
41 | test:
42 | runs-on: ubuntu-latest
43 | needs: build
44 | strategy:
45 | fail-fast: false
46 | matrix:
47 | node: ["16"]
48 | package: [expo-router, expo-metro-runtime]
49 | name: Test ${{ matrix.package }} on Node ${{ matrix.node }}
50 | steps:
51 | - name: ♻️ Restore build artifacts
52 | uses: actions/cache@v3
53 | with:
54 | path: "*"
55 | key: v3-${{ github.sha }}-${{ matrix.node }}
56 |
57 | - name: 🏗 Setup Node
58 | uses: actions/setup-node@v3
59 | with:
60 | node-version: ${{ matrix.node }}
61 |
62 | - name: 💅 Lint ${{ matrix.package }}
63 | run: yarn lint --max-warnings=0
64 | working-directory: packages/${{ matrix.package }}
65 |
66 | - name: 🧪 Test ${{ matrix.package }}
67 | run: yarn test
68 | working-directory: packages/${{ matrix.package }}
69 | env:
70 | EXPO_DEBUG: true
71 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Expo
2 | .expo
3 | __generated__
4 | web-build
5 |
6 | # macOS
7 | .DS_Store
8 |
9 | # Node
10 | node_modules
11 | npm-debug.log
12 | yarn-error.log
13 |
14 | # Ruby
15 | .direnv
16 |
17 | # Emacs
18 | *~
19 |
20 | # Vim
21 | .*.swp
22 | .*.swo
23 | .*.swn
24 | .*.swm
25 |
26 | # Sublime Text
27 | *.sublime-project
28 | *.sublime-workspace
29 |
30 | # Xcode
31 | *.pbxuser
32 | !default.pbxuser
33 | *.xccheckout
34 | *.xcscmblueprint
35 | xcuserdata
36 |
37 | # Android Studio
38 | *.iml
39 | .gradle
40 | .idea/libraries
41 | .idea/workspace.xml
42 | .idea/gradle.xml
43 | .idea/misc.xml
44 | .idea/modules.xml
45 | .idea/vcs.xml
46 |
47 | # Eclipse
48 | .project
49 | .settings
50 |
51 | # VSCode
52 | .history/
53 |
54 | # Ignore build folders
55 |
56 | packages/*/build/**
57 | packages/@*/*/build/**
58 |
59 |
60 | # App native folders
61 |
62 | apps/*/dist/*
63 | apps/*/ios/*
64 | apps/*/android/*
65 | apps/*/web-build/*
66 | lerna-debug.log
67 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "explorer.fileNesting.enabled": true,
3 | "explorer.fileNesting.patterns": {
4 | "package.json": "package-lock.json, yarn.lock",
5 | "*.js": "${capture}.js.map, ${capture}.d.ts, ${capture}.d.ts.map",
6 | "*.ts": "$(capture).test.ts, $(capture).test.tsx, $(capture).test.node.ts, $(capture).test.node.tsx, $(capture).test.native.ts, $(capture).test.native.tsx, $(capture).test.ios.ts, $(capture).test.ios.tsx, $(capture).test.web.ts, $(capture).test.web.tsx, $(capture).test.android.ts, $(capture).test.android.tsx, ${capture}.native.tsx, ${capture}.ios.tsx, ${capture}.android.tsx, ${capture}.web.tsx, ${capture}.native.ts, ${capture}.ios.ts, ${capture}.android.ts, ${capture}.web.ts, ${capture}.native.js, ${capture}.ios.js, ${capture}.android.js, ${capture}.web.js, ${capture}.native.jsx, ${capture}.ios.jsx, ${capture}.android.jsx, ${capture}.web.jsx",
7 | "*.tsx": "$(capture).test.ts, $(capture).test.tsx, $(capture).test.node.ts, $(capture).test.node.tsx, $(capture).test.native.ts, $(capture).test.native.tsx, $(capture).test.ios.ts, $(capture).test.ios.tsx, $(capture).test.web.ts, $(capture).test.web.tsx, $(capture).test.android.ts, $(capture).test.android.tsx, ${capture}.native.tsx, ${capture}.ios.tsx, ${capture}.android.tsx, ${capture}.web.tsx, ${capture}.native.ts, ${capture}.ios.ts, ${capture}.android.ts, ${capture}.web.ts, ${capture}.native.js, ${capture}.ios.js, ${capture}.android.js, ${capture}.web.js, ${capture}.native.jsx, ${capture}.ios.jsx, ${capture}.android.jsx, ${capture}.web.jsx"
8 | }
9 | }
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | ## Code of Conduct
4 |
5 | Expo has adopted the [Contributor Covenant](https://www.contributor-covenant.org/) as its Code of Conduct, and we expect project participants to adhere to it. Please read [the full text](https://github.com/expo/router/blob/main/CODE_OF_CONDUCT.md) so that you can understand what actions will and will not be tolerated.
6 |
7 | ## Source
8 |
9 | If you want to work against the latest branch for contributions, you can use `apps/sandbox`.
10 |
11 | > `apps/sandbox` is a basic playground for developing the Expo Router package, please don't open PRs specifically to improve the tester.
12 |
13 | - Run `yarn` in the root of the repo to install dependencies.
14 | - Run `yarn start` in the root to compile the `packages/`
15 | - Change directory to `apps/sandbox` and run `yarn start` to start the demo app.
16 | - Modify the contents of `apps/sandbox/app/` to use the router.
17 |
18 | ## Docs
19 |
20 | - Run `yarn` in `docs/` to install dependencies.
21 | - Run `yarn start` in `docs/` to start the docs site.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Expo Router
2 |
3 | > [!warning]
4 | > The Expo Router repo **has moved** upstream to [**expo/expo**](https://github.com/expo/expo/).
5 |
6 | - [View the source code](https://github.com/expo/expo/tree/main/packages/expo-router).
7 | - [Read the docs](https://docs.expo.dev/router/introduction/).
8 | - [Report an issue](https://github.com/expo/expo/issues/new?assignees=&labels=needs+validation%2CRouter&projects=&template=bug_report_router.yml).
9 |
10 | This repo will remain in maintenance-mode until Expo Router v3 is released.
11 |
12 | ---
13 |
14 | > [Stable version 2 is out now](https://blog.expo.dev/introducing-expo-router-v2-3850fd5c3ca1)!
15 |
16 | Repo for the new File-based router for React Native apps. Please open a discussion if you have any questions or feedback.
17 |
18 | ## Running
19 |
20 | The easiest way to try **Expo Router** is by creating a new project:
21 |
22 | ```
23 | npx create-expo-app@latest -e with-router
24 | ```
25 |
26 | See the [setup guide for more](https://docs.expo.dev/router/installation/).
27 |
28 | ## Examples
29 |
30 | - [Layouts showcase](https://github.com/EvanBacon/expo-router-layouts-example) - Demo of bottom sheet, drawer, material tabs, top tabs, bottom tabs, js stack, and collapsing header layouts.
31 | - [Basic Twitter layout clone](https://github.com/EvanBacon/expo-router-twitter) - Similar layout to Twitter. Uses shared routes, and implements universal links.
32 | - [Test app](/apps/sandbox) - Test app, in this repository.
33 | - [Expo Router Guest List App](https://github.com/hola-soy-milk/upleveled-react-native-expo) - Simple guest list app with fetching from external API
34 |
35 | ## Contributing
36 |
37 | - [Contributing](/CONTRIBUTING.md) - Read before contributing.
38 |
--------------------------------------------------------------------------------
/apps/sandbox/app.config.js:
--------------------------------------------------------------------------------
1 | process.env.EXPO_TUNNEL_SUBDOMAIN = "bacon-router-sandbox";
2 |
3 | const origin =
4 | process.env.NODE_ENV === "development"
5 | ? `https://${process.env.EXPO_TUNNEL_SUBDOMAIN}.ngrok.io`
6 | : "https://smart-symbiote.netlify.app";
7 |
8 | /** @type {import('expo/config').ExpoConfig} */
9 | module.exports = {
10 | name: "Everywhere",
11 | slug: "expo-router-sandbox",
12 | icon: "./icon.png",
13 | scheme: "sandbox",
14 | splash: {
15 | image: "./icon.png",
16 | resizeMode: "contain",
17 | backgroundColor: "#ffffff",
18 | },
19 | web: {
20 | bundler: "metro",
21 | },
22 | plugins: [
23 | "expo-head",
24 | [
25 | "expo-router",
26 | {
27 | asyncRoutes: "development",
28 | headOrigin: origin,
29 | origin,
30 | },
31 | ],
32 | ],
33 | android: {
34 | package: "app.expo.router.sandbox",
35 | },
36 | ios: {
37 | bundleIdentifier: "app.expo.router.sandbox",
38 | associatedDomains: [
39 | `applinks:${process.env.EXPO_TUNNEL_SUBDOMAIN}.ngrok.io`,
40 | `webcredentials:${process.env.EXPO_TUNNEL_SUBDOMAIN}.ngrok.io`,
41 | `activitycontinuation:${process.env.EXPO_TUNNEL_SUBDOMAIN}.ngrok.io`,
42 | ],
43 | infoPlist: {
44 | CoreSpotlightContinuation: true,
45 | },
46 | },
47 | };
48 |
--------------------------------------------------------------------------------
/apps/sandbox/app_105/README.md:
--------------------------------------------------------------------------------
1 | Tests the automatic `getId` feature proposed in [#97](https://github.com/expo/router/discussions/97) and implemented in [#105](https://github.com/expo/router/pull/105).
2 |
--------------------------------------------------------------------------------
/apps/sandbox/app_105/[...bacon].tsx:
--------------------------------------------------------------------------------
1 | import { Link, useSearchParams } from "expo-router";
2 | import { StyleSheet, Text, View } from "react-native";
3 |
4 | export default function Page() {
5 | const params = useSearchParams();
6 | console.log("params", params);
7 | return (
8 |
9 |
10 | User: {params?.bacon}
11 |
17 | Go to same user
18 |
19 |
25 | Go to posts
26 |
27 |
34 | Go to posts (replace)
35 |
36 |
41 | Go to "other"
42 |
43 |
44 |
45 | );
46 | }
47 |
48 | const styles = StyleSheet.create({
49 | container: {
50 | flex: 1,
51 | alignItems: "center",
52 | padding: 24,
53 | },
54 | main: {
55 | flex: 1,
56 | justifyContent: "center",
57 | maxWidth: 960,
58 | marginHorizontal: "auto",
59 | },
60 | title: {
61 | fontSize: 64,
62 | fontWeight: "bold",
63 | },
64 | subtitle: {
65 | fontSize: 36,
66 | color: "#38434D",
67 | },
68 | });
69 |
--------------------------------------------------------------------------------
/apps/sandbox/app_105/[user].tsx:
--------------------------------------------------------------------------------
1 | import { Link, useSearchParams } from "expo-router";
2 | import { StyleSheet, Text, View } from "react-native";
3 |
4 | export default function Page() {
5 | const params = useSearchParams();
6 | return (
7 |
8 |
9 | User: {params?.user}
10 |
16 | Go to multi-level user
17 |
18 |
24 | Go to same user
25 |
26 |
32 | Go to posts
33 |
34 |
41 | Go to posts (replace)
42 |
43 |
48 | Go to "other"
49 |
50 |
51 |
52 | );
53 | }
54 |
55 | const styles = StyleSheet.create({
56 | container: {
57 | flex: 1,
58 | alignItems: "center",
59 | padding: 24,
60 | },
61 | main: {
62 | flex: 1,
63 | justifyContent: "center",
64 | maxWidth: 960,
65 | marginHorizontal: "auto",
66 | },
67 | title: {
68 | fontSize: 64,
69 | fontWeight: "bold",
70 | },
71 | subtitle: {
72 | fontSize: 36,
73 | color: "#38434D",
74 | },
75 | });
76 |
--------------------------------------------------------------------------------
/apps/sandbox/app_105/_layout.tsx:
--------------------------------------------------------------------------------
1 | import { Stack } from "expo-router";
2 |
3 | export default function Layout() {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/apps/sandbox/app_105/other.tsx:
--------------------------------------------------------------------------------
1 | import { Link, useSearchParams } from "expo-router";
2 | import { StyleSheet, Text, View } from "react-native";
3 |
4 | export default function Page() {
5 | const params = useSearchParams();
6 | return (
7 |
8 |
9 | User: {params.user}
10 |
15 | Go to same
16 |
17 |
23 | Go to posts
24 |
25 |
26 |
27 | );
28 | }
29 |
30 | const styles = StyleSheet.create({
31 | container: {
32 | flex: 1,
33 | alignItems: "center",
34 | padding: 24,
35 | },
36 | main: {
37 | flex: 1,
38 | justifyContent: "center",
39 | maxWidth: 960,
40 | marginHorizontal: "auto",
41 | },
42 | title: {
43 | fontSize: 64,
44 | fontWeight: "bold",
45 | },
46 | subtitle: {
47 | fontSize: 36,
48 | color: "#38434D",
49 | },
50 | });
51 |
--------------------------------------------------------------------------------
/apps/sandbox/app_15/README.md:
--------------------------------------------------------------------------------
1 | A nested route should get all of the nested params from the URL.
2 |
--------------------------------------------------------------------------------
/apps/sandbox/app_15/[foo]/[bar]/_layout.js:
--------------------------------------------------------------------------------
1 | import { Slot, useSearchParams } from "expo-router";
2 | import { Text } from "react-native";
3 |
4 | export default function Page() {
5 | const params = useSearchParams();
6 | return (
7 | <>
8 | [foo]/[bar].js: {JSON.stringify(params)}
9 |
10 | >
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/apps/sandbox/app_15/[foo]/[bar]/index.js:
--------------------------------------------------------------------------------
1 | import { useSearchParams } from "expo-router";
2 | import { Text } from "react-native";
3 |
4 | export default function Page() {
5 | const params = useSearchParams();
6 | return [foo]/[bar]/index.js: {JSON.stringify(params)};
7 | }
8 |
--------------------------------------------------------------------------------
/apps/sandbox/app_15/[foo]/index.js:
--------------------------------------------------------------------------------
1 | import { useSearchParams } from "expo-router";
2 | import { Text } from "react-native";
3 |
4 | export default function Page() {
5 | const params = useSearchParams();
6 | return [foo]/index.js: {JSON.stringify(params)};
7 | }
8 |
--------------------------------------------------------------------------------
/apps/sandbox/app_221/README.md:
--------------------------------------------------------------------------------
1 | Modified repro of [#221](https://github.com/expo/router/issues/221).
2 |
3 | The following cases should be possible:
4 |
5 | - `/` =(push)=> `/two` =(push)=> `/protected` =(**replace**)=> `/permissions`
6 |
7 | - Can go back to `/two` => can go back to `/`
8 | - Can **replace** to `/protected` => can go back to `/two`
9 |
10 | - Reload from `/permissions` goes to `/` (initial route name set to `index`).
11 |
12 | ## Issue
13 |
14 | We were setting the exact results of `getStateFromPath` as the new navigation state. This would clear the history as `getStateFromPath` is agnostic to the current state.
15 |
16 | ## Solution
17 |
18 | We now check if the `replace` action is going from one screen to a sibling screen in the same navigator. If so, we use more refined logic to handle `replace` actions.
19 |
20 | `replace` actions are no longer applied to routing across navigators. In the future we might want to have a changed action that replaces the lowest common denominator of the current screen and the destination screen, then navigates.
21 |
--------------------------------------------------------------------------------
/apps/sandbox/app_221/_layout.js:
--------------------------------------------------------------------------------
1 | import { Stack } from "expo-router";
2 |
3 | export const unstable_settings = {
4 | initialRouteName: "index",
5 | };
6 |
7 | export default function Layout() {
8 | return ;
9 | }
10 |
--------------------------------------------------------------------------------
/apps/sandbox/app_221/index.js:
--------------------------------------------------------------------------------
1 | import { Button } from "react-native";
2 | import { useRouter } from "expo-router";
3 |
4 | export default function Page() {
5 | const router = useRouter();
6 | return