Skip to content

Commit

Permalink
[router] Support platform extensions for _layout and routes (#27408)
Browse files Browse the repository at this point in the history
# Why

Revised version #26047
Blocked by #27407 

# How

Rules:

* API routes cannot have platform extensions
* Layouts and routes can have extensions only if a non-extension version
exists

For example, you have have `/page.web.tsx` only if `/page.tsx` also
exists. This makes platform extensions an additive change and won't
break concepts like universal deep linking and typed routes

API routes do not support platform extensions as they are independent of
a platform.

# Implementation

`getRoutes` allows multiple files to be assigned to the same route, but
all files were given the same specificity.

This PR adds logic to calculate the specificity of a file and to select
the most specific file.

Specificity highest-to-lowest

`2`: Platform extension matches platform
`1`: `.native` platform extension
`0`: No platform extensions
`-1`: Invalid route 

If no platform is provided to `getRoutes` the current behaviour is
retained and files with platform extensions will be ignored.

# `getRoutes` checklist

These are the locations we use `getRoutes` and whether they should use
platform extensions or not.


**[renderStaticContent](https://github.com/expo/expo/blob/269625f5b70a95656212ad14ca7f3e82b45b9f61/packages/expo-router/src/static/renderStaticContent.tsx#L19)**

- Used in `getManifest()` ❌
- Used in `getBuildTimeServerManifestAsync` ❌ - I'm not 100% sure on
this one. Need to understand how to test this correctly

**[routes-manifest](

https://github.com/expo/expo/blob/269625f5b70a95656212ad14ca7f3e82b45b9f61/packages/expo-router/src/routes-manifest.ts#L4)**

- Used in `createRoutesManifest` ❌


**[router-store](https://github.com/expo/expo/blob/269625f5b70a95656212ad14ca7f3e82b45b9f61/packages/expo-router/src/global-state/router-store.tsx#L27)**

- Used in `RouterStore` ✅


**[typed-routes](https://github.com/expo/expo/blob/269625f5b70a95656212ad14ca7f3e82b45b9f61/packages/expo-router/src/typed-routes/generate.ts#L5)**

- Used in `getTypedRoutesDeclarationFile` ❌


There is also an `getExactRoutes` function, but this is only used for
testing purposes.

** 

# Test Plan

<!--
Please describe how you tested this change and how a reviewer could
reproduce your test, especially if this PR does not include automated
tests! If possible, please also provide terminal output and/or
screenshots demonstrating your test/reproduction.
-->

# Checklist

<!--
Please check the appropriate items below if they apply to your diff.
This is required for changes to Expo modules.
-->

- [ ] Documentation is up to date to reflect these changes (eg:
https://docs.expo.dev and README.md).
- [ ] Conforms with the [Documentation Writing Style
Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md)
- [ ] This diff will work correctly for `npx expo prebuild` & EAS Build
(eg: updated a module plugin).

---------

Co-authored-by: Evan Bacon <bacon@expo.io>
Co-authored-by: Expo Bot <34669131+expo-bot@users.noreply.github.com>
  • Loading branch information
3 people committed Apr 26, 2024
1 parent 67ad439 commit c46e44c
Show file tree
Hide file tree
Showing 37 changed files with 650 additions and 52 deletions.
1 change: 1 addition & 0 deletions packages/@expo/cli/CHANGELOG.md
Expand Up @@ -51,6 +51,7 @@ _This version does not introduce any user-facing changes._

- Fixed `TypeError: osascript(...) is not a function` when pressing "j" to open JS debugger. ([#28315](https://github.com/expo/expo/pull/28315) by [@kudo](https://github.com/kudo))
- Fix API routes in folders starting with `.` characters. ([#28366](https://github.com/expo/expo/pull/28366) by [@byCedric](https://github.com/byCedric))
- Allow platform extensions for layout and route files ([#27408](https://github.com/expo/expo/pull/27408) by [@marklawlor](https://github.com/marklawlor))

## 0.18.0 — 2024-04-18

Expand Down
Expand Up @@ -181,7 +181,9 @@ export class MetroBundlerDevServer extends BundlerDevServer {

async getExpoRouterRoutesManifestAsync({ appDir }: { appDir: string }) {
// getBuiltTimeServerManifest
const { exp } = getConfig(this.projectRoot);
const manifest = await fetchManifest(this.projectRoot, {
...exp.extra?.router?.platformRoutes,
asJson: true,
appDir,
});
Expand Down Expand Up @@ -219,10 +221,12 @@ export class MetroBundlerDevServer extends BundlerDevServer {
}
);

const { exp } = getConfig(this.projectRoot);

return {
serverManifest: await getBuildTimeServerManifestAsync(),
// Get routes from Expo Router.
manifest: await getManifest({ preserveApiRoutes: false }),
manifest: await getManifest({ preserveApiRoutes: false, ...exp.extra?.router }),
// Get route generating function
async renderAsync(path: string) {
return await getStaticContent(new URL(path, url));
Expand Down Expand Up @@ -574,6 +578,7 @@ export class MetroBundlerDevServer extends BundlerDevServer {
appDir,
routerRoot,
config,
...config.exp.extra?.router,
bundleApiRoute: (functionFilePath) => this.ssrImportApiRoute(functionFilePath),
getStaticPageAsync: (pathname) => {
return this.getStaticPageAsync(pathname);
Expand Down
Expand Up @@ -33,7 +33,7 @@ export function createRouteHandlerMiddleware(
functionFilePath: string
) => Promise<null | Record<string, Function> | Response>;
config: ProjectConfig;
}
} & import('expo-router/build/routes-manifest').Options
) {
if (!resolveFrom.silent(projectRoot, 'expo-router')) {
throw new CommandError(
Expand Down
Expand Up @@ -30,12 +30,15 @@ function getExpoRouteManifestBuilderAsync(projectRoot: string) {
// TODO: Simplify this now that we use Node.js directly, no need for the Metro bundler caching layer.
export async function fetchManifest<TRegex = string>(
projectRoot: string,
options: { asJson?: boolean; appDir: string }
options: {
asJson?: boolean;
appDir: string;
} & import('expo-router/build/routes-manifest').Options
): Promise<ExpoRouterServerManifestV1<TRegex> | null> {
const getManifest = getExpoRouteManifestBuilderAsync(projectRoot);
const paths = getRoutePaths(options.appDir);
// Get the serialized manifest
const jsonManifest = getManifest(paths);
const jsonManifest = getManifest(paths, options);

if (!jsonManifest) {
return null;
Expand Down
1 change: 1 addition & 0 deletions packages/expo-router/CHANGELOG.md
Expand Up @@ -31,6 +31,7 @@ _This version does not introduce any user-facing changes._
- Mark React client components with "use client" directives. ([#27300](https://github.com/expo/expo/pull/27300) by [@EvanBacon](https://github.com/EvanBacon))
- Add URL hash support ([#27105](https://github.com/expo/expo/pull/27105) by [@marklawlor](https://github.com/marklawlor))
- Type `Href` is no longer generic ([#27690](https://github.com/expo/expo/pull/27690) by [@marklawlor](https://github.com/marklawlor))
- Allow platform extensions for layout and route files ([#27408](https://github.com/expo/expo/pull/27408) by [@marklawlor](https://github.com/marklawlor))

### 🐛 Bug fixes

Expand Down
2 changes: 1 addition & 1 deletion packages/expo-router/_ctx.android.js
@@ -1,6 +1,6 @@
export const ctx = require.context(
process.env.EXPO_ROUTER_APP_ROOT,
true,
/^(?:\.\/)(?!(?:(?:(?:.*\+api)|(?:\+html)))\.[tj]sx?$).*\.[tj]sx?$/,
/^(?:\.\/)(?!(?:(?:(?:.*\+api)|(?:\+html)))\.[tj]sx?$).*(?:\.ios|\.web)\.[tj]sx?$/,
process.env.EXPO_ROUTER_IMPORT_MODE
);
2 changes: 1 addition & 1 deletion packages/expo-router/_ctx.ios.js
@@ -1,6 +1,6 @@
export const ctx = require.context(
process.env.EXPO_ROUTER_APP_ROOT,
true,
/^(?:\.\/)(?!(?:(?:(?:.*\+api)|(?:\+html)))\.[tj]sx?$).*\.[tj]sx?$/,
/^(?:\.\/)(?!(?:(?:(?:.*\+api)|(?:\+html)))\.[tj]sx?$).*(?:\.android|\.web)?\.[tj]sx?$/,
process.env.EXPO_ROUTER_IMPORT_MODE
);
2 changes: 1 addition & 1 deletion packages/expo-router/_ctx.web.js
@@ -1,6 +1,6 @@
export const ctx = require.context(
process.env.EXPO_ROUTER_APP_ROOT,
true,
/^(?:\.\/)(?!(?:(?:(?:.*\+api)|(?:\+html)))\.[tj]sx?$).*\.[tj]sx?$/,
/^(?:\.\/)(?!(?:(?:(?:.*\+api)|(?:\+html)))\.[tj]sx?$).*(?:\.android|\.ios|\.native)?\.[tj]sx?$/,
process.env.EXPO_ROUTER_IMPORT_MODE
);
2 changes: 2 additions & 0 deletions packages/expo-router/build/getRoutes.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/expo-router/build/getRoutes.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

60 changes: 51 additions & 9 deletions packages/expo-router/build/getRoutes.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/expo-router/build/getRoutes.js.map

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion packages/expo-router/build/global-state/router-store.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit c46e44c

Please sign in to comment.