expo-router 1.4.3 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_entry.tsx +7 -1
- package/assets/error.png +0 -0
- package/assets/file.png +0 -0
- package/assets/forward.png +0 -0
- package/assets/pkg.png +0 -0
- package/babel.js +55 -1
- package/build/getRoutes.d.ts.map +1 -1
- package/build/layouts/Tabs.d.ts +1 -1
- package/build/onboard/useTutorial.d.ts.map +1 -1
- package/build/useScreens.d.ts.map +1 -1
- package/build/views/EmptyRoute.d.ts +3 -0
- package/build/views/EmptyRoute.d.ts.map +1 -0
- package/build/views/SuspenseFallback.d.ts +6 -0
- package/build/views/SuspenseFallback.d.ts.map +1 -0
- package/build/views/Toast.d.ts +11 -0
- package/build/views/Toast.d.ts.map +1 -0
- package/entry.js +7 -3
- package/package.json +2 -2
- package/src/getRoutes.ts +11 -3
- package/src/onboard/useTutorial.tsx +12 -8
- package/src/useScreens.tsx +72 -19
- package/src/views/EmptyRoute.tsx +16 -0
- package/src/views/SuspenseFallback.tsx +12 -0
- package/src/views/Toast.tsx +99 -0
package/_entry.tsx
CHANGED
|
@@ -9,7 +9,13 @@ import { getNavigationConfig } from "./src/getLinkingConfig";
|
|
|
9
9
|
import { getRoutes } from "./src/getRoutes";
|
|
10
10
|
import { loadStaticParamsAsync } from "./src/loadStaticParamsAsync";
|
|
11
11
|
|
|
12
|
-
export const ctx = require.context(
|
|
12
|
+
export const ctx = require.context(
|
|
13
|
+
process.env.EXPO_ROUTER_APP_ROOT!,
|
|
14
|
+
true,
|
|
15
|
+
/.*/,
|
|
16
|
+
// @ts-expect-error
|
|
17
|
+
process.env.EXPO_ROUTER_IMPORT_MODE!
|
|
18
|
+
);
|
|
13
19
|
|
|
14
20
|
// Must be exported or Fast Refresh won't update the context >:[
|
|
15
21
|
export default function ExpoRouterRoot() {
|
package/assets/error.png
ADDED
|
Binary file
|
package/assets/file.png
CHANGED
|
Binary file
|
package/assets/forward.png
CHANGED
|
Binary file
|
package/assets/pkg.png
CHANGED
|
Binary file
|
package/babel.js
CHANGED
|
@@ -17,6 +17,46 @@ function getExpoAppManifest(projectRoot) {
|
|
|
17
17
|
return JSON.stringify(exp);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
let config;
|
|
21
|
+
|
|
22
|
+
function getConfigMemo(projectRoot) {
|
|
23
|
+
if (!config) {
|
|
24
|
+
config = getConfig(projectRoot);
|
|
25
|
+
}
|
|
26
|
+
return config;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getExpoRouterImportMode(projectRoot, platform) {
|
|
30
|
+
if (process.env.EXPO_ROUTER_IMPORT_MODE) {
|
|
31
|
+
return process.env.EXPO_ROUTER_IMPORT_MODE;
|
|
32
|
+
}
|
|
33
|
+
const env = process.env.NODE_ENV || process.env.BABEL_ENV;
|
|
34
|
+
|
|
35
|
+
const { exp } = getConfigMemo(projectRoot);
|
|
36
|
+
let mode = [env, true].includes(exp.extra?.router?.asyncRoutes)
|
|
37
|
+
? "lazy"
|
|
38
|
+
: "sync";
|
|
39
|
+
|
|
40
|
+
// TODO: Production bundle splitting
|
|
41
|
+
|
|
42
|
+
if (env === "production" && mode === "lazy") {
|
|
43
|
+
throw new Error(
|
|
44
|
+
"Async routes are not supported in production yet. Set `extra.router.asyncRoutes` to `development`, `false`, or `undefined`."
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// NOTE: This is a temporary workaround for static rendering on web.
|
|
49
|
+
if (platform === "web" && process.env.EXPO_USE_STATIC) {
|
|
50
|
+
mode = "sync";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Development
|
|
54
|
+
debug("Router import mode", mode);
|
|
55
|
+
|
|
56
|
+
process.env.EXPO_ROUTER_IMPORT_MODE = mode;
|
|
57
|
+
return mode;
|
|
58
|
+
}
|
|
59
|
+
|
|
20
60
|
function getExpoRouterAppRoot(projectRoot) {
|
|
21
61
|
// Bump to v2 to prevent the CLI from setting the variable anymore.
|
|
22
62
|
// TODO: Bump to v3 to revert back to the CLI setting the variable again, but with custom value
|
|
@@ -26,7 +66,7 @@ function getExpoRouterAppRoot(projectRoot) {
|
|
|
26
66
|
}
|
|
27
67
|
const routerEntry = resolveFrom.silent(projectRoot, "expo-router/entry");
|
|
28
68
|
|
|
29
|
-
const { exp } =
|
|
69
|
+
const { exp } = getConfigMemo(projectRoot);
|
|
30
70
|
const customSrc = exp.extra?.router?.unstable_src || "./app";
|
|
31
71
|
const isAbsolute = customSrc.startsWith("/");
|
|
32
72
|
// It doesn't matter if the app folder exists.
|
|
@@ -46,6 +86,7 @@ module.exports = function (api) {
|
|
|
46
86
|
const getRelPath = (state) =>
|
|
47
87
|
"./" + nodePath.relative(state.file.opts.root, state.filename);
|
|
48
88
|
|
|
89
|
+
const platform = api.caller((caller) => caller?.platform);
|
|
49
90
|
return {
|
|
50
91
|
name: "expo-router",
|
|
51
92
|
visitor: {
|
|
@@ -123,6 +164,19 @@ module.exports = function (api) {
|
|
|
123
164
|
return;
|
|
124
165
|
}
|
|
125
166
|
|
|
167
|
+
// Expose the app route import mode.
|
|
168
|
+
if (
|
|
169
|
+
t.isIdentifier(parent.node.property, {
|
|
170
|
+
name: "EXPO_ROUTER_IMPORT_MODE",
|
|
171
|
+
}) &&
|
|
172
|
+
!parent.parentPath.isAssignmentExpression()
|
|
173
|
+
) {
|
|
174
|
+
parent.replaceWith(
|
|
175
|
+
t.stringLiteral(getExpoRouterImportMode(projectRoot, platform))
|
|
176
|
+
);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
126
180
|
if (
|
|
127
181
|
!t.isIdentifier(parent.node.property, {
|
|
128
182
|
name: "EXPO_ROUTER_APP_ROOT",
|
package/build/getRoutes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getRoutes.d.ts","sourceRoot":"","sources":["../src/getRoutes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAS5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,MAAM,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,EAAE,YAAY,GAAG,WAAW,CAAC,GAAG;IACnE,yBAAyB;IACzB,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,KAAK,QAAQ,GAAG;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,8CAA8C;IAC9C,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;CACvB,CAAC;AAEF,KAAK,OAAO,GAAG;IACb,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,oEAAoE;AACpE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,QAAQ,CA+C5D;AAyBD,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,MAAM,GACX,iBAAiB,GAAG,IAAI,CAK1B;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC,SAAS,CAAC,CAMlE;
|
|
1
|
+
{"version":3,"file":"getRoutes.d.ts","sourceRoot":"","sources":["../src/getRoutes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAS5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,MAAM,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,EAAE,YAAY,GAAG,WAAW,CAAC,GAAG;IACnE,yBAAyB;IACzB,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,KAAK,QAAQ,GAAG;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,8CAA8C;IAC9C,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;CACvB,CAAC;AAEF,KAAK,OAAO,GAAG;IACb,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,oEAAoE;AACpE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,QAAQ,CA+C5D;AAyBD,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,MAAM,GACX,iBAAiB,GAAG,IAAI,CAK1B;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC,SAAS,CAAC,CAMlE;AAsND;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,QAiBxD;AAED,sEAAsE;AACtE,wBAAgB,SAAS,CACvB,aAAa,EAAE,cAAc,EAC7B,OAAO,CAAC,EAAE,OAAO,GAChB,SAAS,GAAG,IAAI,CAYlB;AAED,wBAAsB,cAAc,CAClC,aAAa,EAAE,cAAc,EAC7B,OAAO,CAAC,EAAE,OAAO,GAChB,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAY3B;AAUD,+CAA+C;AAC/C,wBAAgB,cAAc,CAC5B,aAAa,EAAE,cAAc,EAC7B,OAAO,CAAC,EAAE,OAAO,GAChB,SAAS,GAAG,IAAI,CAIlB;AAYD,wBAAsB,mBAAmB,CACvC,aAAa,EAAE,cAAc,EAC7B,OAAO,CAAC,EAAE,OAAO,GAChB,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAI3B;AA4CD;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,SAAS,GAChB,SAAS,GAAG,IAAI,CAgBlB"}
|
package/build/layouts/Tabs.d.ts
CHANGED
|
@@ -139,7 +139,7 @@ export declare const Tabs: React.ForwardRefExoticComponent<Omit<Omit<import("@re
|
|
|
139
139
|
show?: import("@react-navigation/bottom-tabs/lib/typescript/src/types").TabBarVisibilityAnimationConfig | undefined;
|
|
140
140
|
hide?: import("@react-navigation/bottom-tabs/lib/typescript/src/types").TabBarVisibilityAnimationConfig | undefined;
|
|
141
141
|
} | undefined;
|
|
142
|
-
tabBarStyle?: false | import("react-native").
|
|
142
|
+
tabBarStyle?: false | import("react-native").Animated.Value | import("react-native").RegisteredStyle<import("react-native").ViewStyle> | import("react-native").Animated.AnimatedInterpolation<string | number> | import("react-native").Animated.WithAnimatedObject<import("react-native").ViewStyle> | import("react-native").Animated.WithAnimatedArray<import("react-native").Falsy | import("react-native").ViewStyle | import("react-native").RegisteredStyle<import("react-native").ViewStyle> | import("react-native").RecursiveArray<import("react-native").Falsy | import("react-native").ViewStyle | import("react-native").RegisteredStyle<import("react-native").ViewStyle>> | readonly (import("react-native").Falsy | import("react-native").ViewStyle | import("react-native").RegisteredStyle<import("react-native").ViewStyle>)[]> | null | undefined;
|
|
143
143
|
tabBarBackground?: (() => React.ReactNode) | undefined;
|
|
144
144
|
lazy?: boolean | undefined;
|
|
145
145
|
header?: ((props: import("@react-navigation/bottom-tabs").BottomTabHeaderProps) => React.ReactNode) | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTutorial.d.ts","sourceRoot":"","sources":["../../src/onboard/useTutorial.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAa1C,yIAAyI;AACzI,wBAAgB,WAAW,CAAC,OAAO,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"useTutorial.d.ts","sourceRoot":"","sources":["../../src/onboard/useTutorial.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAa1C,yIAAyI;AACzI,wBAAgB,WAAW,CAAC,OAAO,EAAE,cAAc,OA4BlD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useScreens.d.ts","sourceRoot":"","sources":["../src/useScreens.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,
|
|
1
|
+
{"version":3,"file":"useScreens.d.ts","sourceRoot":"","sources":["../src/useScreens.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,OAAO,EAIL,SAAS,EAGV,MAAM,SAAS,CAAC;AAMjB,MAAM,MAAM,WAAW,CACrB,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,IACxD;IACF,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAE,CAAC;IACvC,OAAO,CAAC,EAAE,QAAQ,CAAC;IAGnB,SAAS,CAAC,EAAE,GAAG,CAAC;CACjB,CAAC;AA8DF;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,CAUxE;AA6BD,mFAAmF;AACnF,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,SAAS,+GA2E1D;AAED,oGAAoG;AACpG,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC;;sCA0B5C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmptyRoute.d.ts","sourceRoot":"","sources":["../../src/views/EmptyRoute.tsx"],"names":[],"mappings":";AAKA,wBAAgB,UAAU,gBAUzB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SuspenseFallback.d.ts","sourceRoot":"","sources":["../../src/views/SuspenseFallback.tsx"],"names":[],"mappings":";AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,wBAAgB,gBAAgB,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,eAM/D"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export declare const CODE_FONT: string;
|
|
3
|
+
export declare function ToastWrapper({ children }: {
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
}): JSX.Element;
|
|
6
|
+
export declare function Toast({ children, filename, warning, }: {
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
filename?: string;
|
|
9
|
+
warning?: boolean;
|
|
10
|
+
}): JSX.Element;
|
|
11
|
+
//# sourceMappingURL=Toast.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Toast.d.ts","sourceRoot":"","sources":["../../src/views/Toast.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,eAAO,MAAM,SAAS,QAIpB,CAAC;AAeH,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,eASvE;AAED,wBAAgB,KAAK,CAAC,EACpB,QAAQ,EACR,QAAQ,EACR,OAAO,GACR,EAAE;IACD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,eAyBA"}
|
package/entry.js
CHANGED
|
@@ -2,10 +2,14 @@ import "@expo/metro-runtime";
|
|
|
2
2
|
|
|
3
3
|
import { ExpoRoot } from "expo-router";
|
|
4
4
|
import Head from "expo-router/head";
|
|
5
|
+
import { renderRootComponent } from "expo-router/src/renderRootComponent";
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
const ctx = require.context(
|
|
8
|
+
process.env.EXPO_ROUTER_APP_ROOT,
|
|
9
|
+
true,
|
|
10
|
+
/.*/,
|
|
11
|
+
process.env.EXPO_ROUTER_IMPORT_MODE
|
|
12
|
+
);
|
|
9
13
|
|
|
10
14
|
// Must be exported or Fast Refresh won't update the context
|
|
11
15
|
export function App() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-router",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"main": "src/index.tsx",
|
|
5
5
|
"types": "build/index.d.ts",
|
|
6
6
|
"files": [
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
},
|
|
93
93
|
"dependencies": {
|
|
94
94
|
"@bacons/react-views": "^1.1.3",
|
|
95
|
-
"@expo/metro-runtime": "2.0.
|
|
95
|
+
"@expo/metro-runtime": "2.0.4",
|
|
96
96
|
"@radix-ui/react-slot": "^1.0.0",
|
|
97
97
|
"@react-navigation/bottom-tabs": "~6.5.7",
|
|
98
98
|
"@react-navigation/native": "~6.1.6",
|
package/src/getRoutes.ts
CHANGED
|
@@ -271,11 +271,19 @@ function contextModuleToFileNodes(
|
|
|
271
271
|
// In development, check if the file exports a default component
|
|
272
272
|
// this helps keep things snappy when creating files. In production we load all screens lazily.
|
|
273
273
|
try {
|
|
274
|
-
if (
|
|
275
|
-
|
|
274
|
+
if (process.env.NODE_ENV === "development") {
|
|
275
|
+
// If the user has set the `EXPO_ROUTER_IMPORT_MODE` to `sync` then we should
|
|
276
|
+
// filter the missing routes.
|
|
277
|
+
if (process.env.EXPO_ROUTER_IMPORT_MODE === "sync") {
|
|
278
|
+
if (!contextModule(key)?.default) {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
276
282
|
}
|
|
277
283
|
const node: FileNode = {
|
|
278
|
-
loadRoute
|
|
284
|
+
loadRoute() {
|
|
285
|
+
return contextModule(key);
|
|
286
|
+
},
|
|
279
287
|
normalizedName: getNameFromFilePath(key),
|
|
280
288
|
contextKey: key,
|
|
281
289
|
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useMemo, ComponentType } from "react";
|
|
2
2
|
|
|
3
3
|
import { RequireContext } from "../types";
|
|
4
4
|
|
|
5
5
|
function isFunctionOrReactComponent(
|
|
6
6
|
Component: any
|
|
7
|
-
): Component is
|
|
7
|
+
): Component is ComponentType {
|
|
8
8
|
return (
|
|
9
9
|
!!Component &&
|
|
10
10
|
(typeof Component === "function" ||
|
|
@@ -23,14 +23,18 @@ export function useTutorial(context: RequireContext) {
|
|
|
23
23
|
const keys = useMemo(() => context.keys(), [context]);
|
|
24
24
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
25
25
|
const hasAnyValidComponent = useMemo(() => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
if (process.env.EXPO_ROUTER_IMPORT_MODE === "sync") {
|
|
27
|
+
for (const key of keys) {
|
|
28
|
+
// NOTE(EvanBacon): This should only ever occur in development as it breaks lazily loading.
|
|
29
|
+
const component = context(key)?.default;
|
|
30
|
+
if (isFunctionOrReactComponent(component)) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
31
33
|
}
|
|
34
|
+
return false;
|
|
32
35
|
}
|
|
33
|
-
return
|
|
36
|
+
return !!context.keys().length;
|
|
37
|
+
// return false;
|
|
34
38
|
}, [keys]);
|
|
35
39
|
|
|
36
40
|
if (hasAnyValidComponent) {
|
package/src/useScreens.tsx
CHANGED
|
@@ -3,12 +3,15 @@ import React from "react";
|
|
|
3
3
|
import { LocationProvider } from "./LocationProvider";
|
|
4
4
|
import {
|
|
5
5
|
DynamicConvention,
|
|
6
|
+
LoadedRoute,
|
|
6
7
|
Route,
|
|
7
8
|
RouteNode,
|
|
8
9
|
sortRoutesWithInitial,
|
|
9
10
|
useRouteNode,
|
|
10
11
|
} from "./Route";
|
|
11
12
|
import { Screen } from "./primitives";
|
|
13
|
+
import { EmptyRoute } from "./views/EmptyRoute";
|
|
14
|
+
import { SuspenseFallback } from "./views/SuspenseFallback";
|
|
12
15
|
import { Try } from "./views/Try";
|
|
13
16
|
|
|
14
17
|
export type ScreenProps<
|
|
@@ -103,6 +106,29 @@ export function useSortedScreens(order: ScreenProps[]): React.ReactNode[] {
|
|
|
103
106
|
);
|
|
104
107
|
}
|
|
105
108
|
|
|
109
|
+
function fromImport({ ErrorBoundary, ...component }: LoadedRoute) {
|
|
110
|
+
if (ErrorBoundary) {
|
|
111
|
+
return {
|
|
112
|
+
default: React.forwardRef((props: any, ref: any) => {
|
|
113
|
+
const children = React.createElement(component.default, {
|
|
114
|
+
...props,
|
|
115
|
+
ref,
|
|
116
|
+
});
|
|
117
|
+
return <Try catch={ErrorBoundary}>{children}</Try>;
|
|
118
|
+
}),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return { default: component.default || EmptyRoute };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function fromLoadedRoute(res: LoadedRoute) {
|
|
125
|
+
if (!(res instanceof Promise)) {
|
|
126
|
+
return fromImport(res);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return res.then(fromImport);
|
|
130
|
+
}
|
|
131
|
+
|
|
106
132
|
// TODO: Maybe there's a more React-y way to do this?
|
|
107
133
|
// Without this store, the process enters a recursive loop.
|
|
108
134
|
const qualifiedStore = new WeakMap<RouteNode, React.ComponentType<any>>();
|
|
@@ -113,7 +139,48 @@ export function getQualifiedRouteComponent(value: RouteNode) {
|
|
|
113
139
|
return qualifiedStore.get(value)!;
|
|
114
140
|
}
|
|
115
141
|
|
|
116
|
-
|
|
142
|
+
let getLoadable: (props: any, ref: any) => JSX.Element;
|
|
143
|
+
|
|
144
|
+
// TODO: This ensures sync doesn't use React.lazy, but it's not ideal.
|
|
145
|
+
if (process.env.EXPO_ROUTER_IMPORT_MODE === "sync") {
|
|
146
|
+
const SyncComponent = React.forwardRef((props, ref) => {
|
|
147
|
+
const res = value.loadRoute();
|
|
148
|
+
const Component = fromImport(res).default;
|
|
149
|
+
return <Component {...props} ref={ref} />;
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
getLoadable = (props: any, ref: any) => (
|
|
153
|
+
<SyncComponent
|
|
154
|
+
{...{
|
|
155
|
+
...props,
|
|
156
|
+
ref,
|
|
157
|
+
// Expose the template segment path, e.g. `(home)`, `[foo]`, `index`
|
|
158
|
+
// the intention is to make it possible to deduce shared routes.
|
|
159
|
+
segment: value.route,
|
|
160
|
+
}}
|
|
161
|
+
/>
|
|
162
|
+
);
|
|
163
|
+
} else {
|
|
164
|
+
const AsyncComponent = React.lazy(async () => {
|
|
165
|
+
const res = value.loadRoute();
|
|
166
|
+
return fromLoadedRoute(res) as Promise<{
|
|
167
|
+
default: React.ComponentType<any>;
|
|
168
|
+
}>;
|
|
169
|
+
});
|
|
170
|
+
getLoadable = (props: any, ref: any) => (
|
|
171
|
+
<React.Suspense fallback={<SuspenseFallback route={value} />}>
|
|
172
|
+
<AsyncComponent
|
|
173
|
+
{...{
|
|
174
|
+
...props,
|
|
175
|
+
ref,
|
|
176
|
+
// Expose the template segment path, e.g. `(home)`, `[foo]`, `index`
|
|
177
|
+
// the intention is to make it possible to deduce shared routes.
|
|
178
|
+
segment: value.route,
|
|
179
|
+
}}
|
|
180
|
+
/>
|
|
181
|
+
</React.Suspense>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
117
184
|
|
|
118
185
|
const QualifiedRoute = React.forwardRef(
|
|
119
186
|
(
|
|
@@ -128,32 +195,18 @@ export function getQualifiedRouteComponent(value: RouteNode) {
|
|
|
128
195
|
}: any,
|
|
129
196
|
ref: any
|
|
130
197
|
) => {
|
|
131
|
-
const
|
|
132
|
-
...props,
|
|
133
|
-
ref,
|
|
134
|
-
// Expose the template segment path, e.g. `(home)`, `[foo]`, `index`
|
|
135
|
-
// the intention is to make it possible to deduce shared routes.
|
|
136
|
-
segment: value.route,
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
const errorBoundary = React.useMemo(() => {
|
|
140
|
-
if (ErrorBoundary) {
|
|
141
|
-
return <Try catch={ErrorBoundary}>{children}</Try>;
|
|
142
|
-
}
|
|
143
|
-
return children;
|
|
144
|
-
}, [ErrorBoundary, children]);
|
|
198
|
+
const loadable = getLoadable(props, ref);
|
|
145
199
|
|
|
146
200
|
return (
|
|
147
201
|
<LocationProvider>
|
|
148
|
-
<Route node={value}>{
|
|
202
|
+
<Route node={value}>{loadable}</Route>
|
|
149
203
|
</LocationProvider>
|
|
150
204
|
);
|
|
151
205
|
}
|
|
152
206
|
);
|
|
153
207
|
|
|
154
|
-
QualifiedRoute.displayName = `Route(${
|
|
155
|
-
|
|
156
|
-
})`;
|
|
208
|
+
QualifiedRoute.displayName = `Route(${value.route})`;
|
|
209
|
+
|
|
157
210
|
qualifiedStore.set(value, QualifiedRoute);
|
|
158
211
|
return QualifiedRoute;
|
|
159
212
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import { Toast, ToastWrapper } from "./Toast";
|
|
4
|
+
import { useRouteNode } from "../Route";
|
|
5
|
+
|
|
6
|
+
export function EmptyRoute() {
|
|
7
|
+
const route = useRouteNode();
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<ToastWrapper>
|
|
11
|
+
<Toast warning filename={route?.contextKey}>
|
|
12
|
+
Missing default export
|
|
13
|
+
</Toast>
|
|
14
|
+
</ToastWrapper>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import { Toast, ToastWrapper } from "./Toast";
|
|
4
|
+
import { RouteNode } from "../Route";
|
|
5
|
+
|
|
6
|
+
export function SuspenseFallback({ route }: { route: RouteNode }) {
|
|
7
|
+
return (
|
|
8
|
+
<ToastWrapper>
|
|
9
|
+
<Toast filename={route?.contextKey}>Bundling...</Toast>
|
|
10
|
+
</ToastWrapper>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Image, StyleSheet, Text, View } from "@bacons/react-views";
|
|
2
|
+
import { BottomTabBarHeightContext } from "@react-navigation/bottom-tabs";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { ActivityIndicator, Animated, Platform } from "react-native";
|
|
5
|
+
import { SafeAreaView } from "react-native-safe-area-context";
|
|
6
|
+
|
|
7
|
+
export const CODE_FONT = Platform.select({
|
|
8
|
+
default: "Courier",
|
|
9
|
+
ios: "Courier New",
|
|
10
|
+
android: "monospace",
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
function useFadeIn() {
|
|
14
|
+
// Returns a React Native Animated value for fading in
|
|
15
|
+
const [value] = React.useState(() => new Animated.Value(0));
|
|
16
|
+
React.useEffect(() => {
|
|
17
|
+
Animated.timing(value, {
|
|
18
|
+
toValue: 1,
|
|
19
|
+
duration: 200,
|
|
20
|
+
useNativeDriver: true,
|
|
21
|
+
}).start();
|
|
22
|
+
}, []);
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function ToastWrapper({ children }: { children: React.ReactNode }) {
|
|
27
|
+
const inTabBar = React.useContext(BottomTabBarHeightContext);
|
|
28
|
+
const Wrapper = inTabBar ? View : SafeAreaView;
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Wrapper collapsable={false} style={{ flex: 1 }}>
|
|
32
|
+
{children}
|
|
33
|
+
</Wrapper>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function Toast({
|
|
38
|
+
children,
|
|
39
|
+
filename,
|
|
40
|
+
warning,
|
|
41
|
+
}: {
|
|
42
|
+
children: React.ReactNode;
|
|
43
|
+
filename?: string;
|
|
44
|
+
warning?: boolean;
|
|
45
|
+
}) {
|
|
46
|
+
const filenamePretty = React.useMemo(() => {
|
|
47
|
+
if (!filename) return undefined;
|
|
48
|
+
return "app" + filename.replace(/^\./, "");
|
|
49
|
+
}, [filename]);
|
|
50
|
+
const value = useFadeIn();
|
|
51
|
+
return (
|
|
52
|
+
<View style={styles.container}>
|
|
53
|
+
<Animated.View style={[styles.toast, { opacity: value }]}>
|
|
54
|
+
{!warning && <ActivityIndicator color="white" />}
|
|
55
|
+
{warning && (
|
|
56
|
+
<Image
|
|
57
|
+
source={require("expo-router/assets/error.png")}
|
|
58
|
+
style={styles.icon}
|
|
59
|
+
/>
|
|
60
|
+
)}
|
|
61
|
+
<View style={{ marginLeft: 8 }}>
|
|
62
|
+
<Text style={styles.text}>{children}</Text>
|
|
63
|
+
{filenamePretty && (
|
|
64
|
+
<Text style={styles.filename}>{filenamePretty}</Text>
|
|
65
|
+
)}
|
|
66
|
+
</View>
|
|
67
|
+
</Animated.View>
|
|
68
|
+
</View>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const styles = StyleSheet.create({
|
|
73
|
+
container: {
|
|
74
|
+
backgroundColor: "transparent",
|
|
75
|
+
flex: 1,
|
|
76
|
+
},
|
|
77
|
+
icon: { width: 20, height: 20, resizeMode: "contain" },
|
|
78
|
+
toast: {
|
|
79
|
+
alignItems: "center",
|
|
80
|
+
borderWidth: 1,
|
|
81
|
+
borderColor: "rgba(255,255,255,0.2)",
|
|
82
|
+
flexDirection: "row",
|
|
83
|
+
position: "absolute",
|
|
84
|
+
bottom: 8,
|
|
85
|
+
left: 8,
|
|
86
|
+
paddingVertical: 8,
|
|
87
|
+
paddingHorizontal: 12,
|
|
88
|
+
borderRadius: 4,
|
|
89
|
+
backgroundColor: "black",
|
|
90
|
+
},
|
|
91
|
+
text: { color: "white", fontSize: 16 },
|
|
92
|
+
filename: {
|
|
93
|
+
fontFamily: CODE_FONT,
|
|
94
|
+
opacity: 0.8,
|
|
95
|
+
color: "white",
|
|
96
|
+
fontSize: 12,
|
|
97
|
+
},
|
|
98
|
+
code: { fontFamily: CODE_FONT },
|
|
99
|
+
});
|