├── .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