Skip to content

Commit c4d59ac

Browse files
author
徐远翔
committed
feat: add NotFound Screen
1 parent b9368d2 commit c4d59ac

File tree

10 files changed

+162
-82
lines changed

10 files changed

+162
-82
lines changed

README.md

+18-6
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,13 @@ umi 在 RN 中仅用来生成中间代码(临时文件),介于**编码**
1919

2020
[发布日志](/CHANGELOG.md)
2121

22-
示例工程:[UMIRNExample](https://github.com/xuyuanxiang/UMIRNExample#readme)
22+
示例:
2323

24-
请点一下 Star 给我一些鼓励吧。
24+
- [UMIRNExample](https://github.com/xuyuanxiang/UMIRNExample#readme):使用[React Native CLI](https://github.com/react-native-community/cli/blob/master/docs/commands.md#commands)的 RN 工程;
25+
- [UMIExpoExample](https://github.com/xuyuanxiang/UMIExpoExample#readme):使用[expo](https://expo.io/)的 RN 工程;
26+
- [UMIHaulExample](https://github.com/xuyuanxiang/UMIHaulExample#readme):使用[haul](https://github.com/callstack/haul)的 RN 工程。
27+
28+
**请点击 Star 给我一些鼓励吧。**
2529

2630
## 目录
2731

@@ -623,10 +627,18 @@ yarn react-native unlink && yarn react-native link
623627

624628
### Unable to Resolve Module in React Native App
625629

626-
[facebook/react-native#issue-1924](https://github.com/facebook/react-native/issues/1924)
630+
[facebook/react-native#issue-1924](https://github.com/facebook/react-native/issues/1924)
631+
632+
终极清缓存方案:
627633

628-
加上`--reset-cache`参数:`yarn react-native start --reset-cache`
634+
MacOS
629635

630-
如果不行:`rm -rf node_modules && yarn && yarn react-native start --reset-cache`
636+
```shell
637+
watchman watch-del-all && rm -fr $TMPDIR/react-* && rm -fr $TMPDIR/metro-* && rm -fr $TMPDIR/haste-map-* && rm -fr node_modules && yarn cache clean --force && yarn && yarn start --reset-cache
638+
```
631639

632-
对于 MacOS,如果使用 watchman 还需要:`watchman watch-del-all`
640+
Windows
641+
642+
```shell
643+
del %appdata%\Temp\react-* & del %appdata%\Temp\metro-* & del %appdata%\Temp\haste-map-* & cd android & gradlew clean & cd .. & del node_modules/ & yarn cache clean --force & yarn & yarn start --reset-cache
644+
```

packages/umi-preset-react-native/src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
export default function () {
22
return {
33
plugins: [
4+
require.resolve('./plugins/features/expo'),
5+
require.resolve('./plugins/features/haul'),
46
require.resolve('./plugins/features/reactNative'),
57
require.resolve('./plugins/generateFiles/react-native/exports'),
68
require.resolve('./plugins/generateFiles/react-native/polyfill'),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { IApi } from 'umi';
2+
import { existsSync } from 'fs';
3+
import { getUserLib } from '../../utils';
4+
5+
export default (api: IApi) => {
6+
const EXPO_PATH = getUserLib({
7+
api,
8+
target: 'expo/package.json',
9+
dir: true,
10+
});
11+
12+
// umi-preset-react-native 扩展配置
13+
api.describe({
14+
key: 'expo',
15+
config: {
16+
default: existsSync(EXPO_PATH),
17+
schema(joi) {
18+
return joi.boolean().optional();
19+
},
20+
},
21+
});
22+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { IApi } from 'umi';
2+
import { existsSync } from 'fs';
3+
import { getUserLib } from '../../utils';
4+
5+
export default (api: IApi) => {
6+
const HAUL_CLI_PATH = getUserLib({
7+
api,
8+
target: '@haul-bundler/cli/package.json',
9+
dir: true,
10+
});
11+
12+
// umi-preset-react-native 扩展配置
13+
api.describe({
14+
key: 'haul',
15+
config: {
16+
default: existsSync(HAUL_CLI_PATH),
17+
schema(joi) {
18+
return joi.boolean().optional();
19+
},
20+
},
21+
});
22+
};
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,41 @@
11
import { IApi } from 'umi';
2-
import { dirname, join } from 'path';
3-
import { existsSync, readFileSync } from 'fs';
2+
import { join } from 'path';
3+
import { readFileSync } from 'fs';
44
import { EOL } from 'os';
5-
import { assertExists } from '../../utils';
5+
import { assertExists, getUserLib } from '../../utils';
66

77
export default (api: IApi) => {
88
const {
9-
utils: { resolve, lodash, winPath },
10-
paths: { absNodeModulesPath = '', absSrcPath = '' },
9+
utils: { lodash, winPath },
10+
paths: { absSrcPath = '' },
1111
} = api;
1212

13-
/**
14-
* 优先读取用户目录下依赖的绝对路径
15-
* @param library 比如:'react-native'(目录) 或者 'react-router/esm/index.js'(文件)
16-
* @param defaults library找不到时的缺省值
17-
* @param dir true-返回目录绝对路径,false-返回文件绝对路径
18-
* @param basedir 用户目录查找起始路径
19-
*/
20-
function getUserLibDir(library: string, defaults: string, dir: boolean = false, basedir = absSrcPath): string {
21-
try {
22-
const path = resolve.sync(library, {
23-
basedir,
24-
});
25-
if (dir) {
26-
return dirname(path);
27-
} else {
28-
return path;
29-
}
30-
} catch (ignored) {}
31-
return defaults;
32-
}
13+
const REACT_NATIVE_PATH = getUserLib({
14+
api,
15+
target: 'react-native/package.json',
16+
dir: true,
17+
});
3318

34-
const REACT_NATIVE_PATH = getUserLibDir(
35-
join('react-native', 'package.json'),
36-
join(absNodeModulesPath, 'react-native'),
37-
true,
38-
);
39-
const EXPO_PATH = getUserLibDir(join('expo', 'package.json'), join(absNodeModulesPath, 'expo'));
40-
const HAUL_CLI_PATH = getUserLibDir(join('@haul-bundler', 'cli', 'package.json'), join(absNodeModulesPath, 'expo'));
4119
assertExists(REACT_NATIVE_PATH);
4220

43-
const isExpo = existsSync(EXPO_PATH);
44-
const isHaul = existsSync(HAUL_CLI_PATH);
45-
4621
const { version } = require(join(REACT_NATIVE_PATH, 'package.json'));
4722

4823
let appKey;
4924
try {
50-
if (!isExpo) {
51-
const appJson = JSON.parse(
52-
readFileSync(getUserLibDir('app.json', join(absSrcPath, 'app.json'), false, absSrcPath), 'utf8'),
53-
);
54-
appKey = appJson.name;
55-
}
25+
const appJson = JSON.parse(readFileSync(join(absSrcPath, 'app.json'), 'utf8'));
26+
appKey = appJson.name;
5627
} catch (ignored) {}
5728

5829
// umi-preset-react-native 扩展配置
5930
api.describe({
6031
key: 'reactNative',
6132
config: {
62-
default: { appKey, version, bundler: isExpo ? 'expo' : isHaul ? 'haul' : 'react-native-cli' },
33+
default: { appKey, version },
6334
schema(joi) {
6435
return joi
6536
.object({
6637
appKey: joi.string(), // moduleName app.json#name
6738
version: joi.string(), // RN 版本号
68-
bundler: joi.allow('expo', 'react-native-cli', 'haul'),
6939
})
7040
.optional();
7141
},
@@ -97,16 +67,13 @@ export default (api: IApi) => {
9767
},
9868
{
9969
name: 'react-router-native',
100-
path: getUserLibDir(
101-
'react-router-native',
102-
winPath(dirname(require.resolve('react-router-native/package.json'))),
103-
true,
104-
),
70+
path: winPath(getUserLib({ api, target: 'react-router-native/package.json', dir: true })),
10571
},
10672
]);
10773

10874
// 启动时检查
10975
api.onStart(() => {
76+
const isExpo = Boolean(api.config?.expo);
11077
if (!isExpo) {
11178
// 使用 haul 和 react-native-cli 时,appKey 一定不能为空,否则 RN 引用没法注册/部署/启动。
11279
if (!api.config?.reactNative?.appKey) {
@@ -136,11 +103,15 @@ export default (api: IApi) => {
136103
throw new TypeError('"history.type" 配置错误');
137104
}
138105

139-
if (api.config.dynamicImport && !api.config.dynamicImport.loading) {
140-
api.logger.error(
141-
`在 RN 环境中启用"dynamicImport"功能时,必须实现自定义的"loading"!${EOL}因为 umi 默认 loading 使用了 HTML 标签,在 RN 中运行会报错!${EOL}查看如何配置自定义 loading:https://umijs.org/config#dynamicimport`,
142-
);
143-
throw new TypeError('"dynamicImport.loading" 未配置');
106+
if (api.config.dynamicImport) {
107+
api.logger.error('在 RN 环境中暂不支持:"dynamicImport"功能。');
108+
throw new TypeError('在 RN 环境中暂不支持:"dynamicImport"功能。');
144109
}
110+
// if (api.config.dynamicImport && !api.config.dynamicImport.loading) {
111+
// api.logger.error(
112+
// `在 RN 环境中启用"dynamicImport"功能时,必须实现自定义的"loading"!${EOL}因为 umi 默认 loading 使用了 HTML 标签,在 RN 中运行会报错!${EOL}查看如何配置自定义 loading:https://umijs.org/config#dynamicimport`,
113+
// );
114+
// throw new TypeError('"dynamicImport.loading" 未配置');
115+
// }
145116
});
146117
};

packages/umi-preset-react-native/src/plugins/generateFiles/react-native/runtime.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,11 @@ export function render(clientRender: () => any, args: {hot?: boolean} = {}) {
1111

1212
export default (api: IApi) => {
1313
// expo 不需要使用 AppRegistry
14-
api.addRuntimePlugin(() =>
15-
api.config?.reactNative?.bundler !== 'expo' ? [join(api.paths.absTmpPath!, 'react-native', 'runtime.ts')] : [],
16-
);
14+
api.addRuntimePlugin(() => (api.config.expo ? [] : [join(api.paths.absTmpPath!, 'react-native', 'runtime.ts')]));
1715

1816
// expo 不需要使用 AppRegistry
1917
api.onGenerateFiles(() => {
20-
if (api.config?.reactNative?.bundler !== 'expo') {
18+
if (api.config.expo) {
2119
api.writeTmpFile({
2220
path: 'react-native/runtime.ts',
2321
content: api.utils.Mustache.render(runtimeTpl, {

packages/umi-preset-react-native/src/plugins/generators/rn.ts

-6
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,6 @@ export default (api: IApi) => {
1414
const unwatch = await generateFiles(api, watch);
1515
await generateConfigFiles(api);
1616

17-
if (watch) {
18-
api.logger.info(
19-
'You can open another terminal and type: `yarn react-native run-ios` or `yarn react-native run-android` to launch your application.',
20-
);
21-
}
22-
2317
process.on('exit', () => {
2418
unwatch();
2519
if (!watch) {

packages/umi-preset-react-native/src/templates/indexTpl.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@ export default `/**
22
* @file umi 生成临时文件
33
* @format
44
*/
5-
import '@@/umi';
5+
{{#isExpo}}
6+
import registerRootComponent from 'expo/build/launch/registerRootComponent';
7+
import RootElement from '@@/umi';
68
9+
registerRootComponent(() => RootElement);
10+
11+
{{/isExpo}}
12+
{{^isExpo}}
13+
export default from '@@/umi';
14+
15+
{{/isExpo}}
716
`;

packages/umi-preset-react-native/src/utils.ts

+43-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { existsSync, readdir, stat, writeFile } from 'fs';
2-
import { join } from 'path';
2+
import { dirname, join } from 'path';
33
import { EOL } from 'os';
44
import { IApi, IRoute } from 'umi';
55
import memoizerific from 'memoizerific';
@@ -317,14 +317,12 @@ export async function generateConfigFiles(api: IApi): Promise<void> {
317317
];
318318
const presets: (string | [string, any, string?])[] = [];
319319

320-
const bundler = api.config?.reactNative?.bundler;
321-
322-
if (bundler === 'react-native-cli') {
323-
presets.push('module:metro-react-native-babel-preset');
324-
} else if (bundler === 'haul') {
320+
if (api.config.haul) {
325321
presets.push('@haul-bundler/babel-preset-react-native');
326-
} else if (bundler === 'expo') {
322+
} else if (api.config.expo) {
327323
presets.push('babel-preset-expo');
324+
} else {
325+
presets.push('module:metro-react-native-babel-preset');
328326
}
329327

330328
const presetOpts = await api.applyPlugins({
@@ -384,29 +382,30 @@ export async function generateConfigFiles(api: IApi): Promise<void> {
384382
`metro.${process.env.UMI_ENV || 'local'}.config.js`,
385383
);
386384

385+
const isExpo = Boolean(api.config.expo);
387386
const tasks: Promise<any>[] = [
388387
asyncWriteTmpFile(
389388
api,
390389
join(cwd, 'babel.config.js'),
391390
Mustache.render(BABEL_CONFIG_TPL, {
392391
presets: JSON.stringify(lodash.uniqWith(babelConfig.presets, lodash.isEqual)),
393392
plugins: JSON.stringify(lodash.uniqWith(babelConfig.plugins, lodash.isEqual)),
394-
isExpo: api.config?.reactNative?.bundler === 'expo',
393+
isExpo,
395394
}),
396395
),
397396
asyncWriteTmpFile(
398397
api,
399398
join(cwd, 'metro.config.js'),
400-
api.utils.Mustache.render(METRO_CONFIG_TPL, {
399+
Mustache.render(METRO_CONFIG_TPL, {
401400
watchFolders: [paths.absTmpPath],
402401
userConfigFile: winPath(userConfigFile),
403402
useUserConfig: existsSync(userConfigFile),
404403
}),
405404
),
406-
asyncWriteTmpFile(api, join(cwd, 'index.js'), INDEX_TPL),
405+
asyncWriteTmpFile(api, join(cwd, 'index.js'), Mustache.render(INDEX_TPL, { isExpo })),
407406
];
408407

409-
if (api.config?.reactNative?.bundler === 'haul') {
408+
if (api.config.hual) {
410409
const routes = await api.getRoutes();
411410
tasks.push(
412411
asyncWriteTmpFile(
@@ -445,3 +444,36 @@ export async function generateFiles(api: IApi, watch?: boolean): Promise<() => v
445444

446445
return generateFiles({ api, watch });
447446
}
447+
448+
interface IGetUserLibDirOptions {
449+
api: IApi;
450+
/**
451+
* 比如:'react-native'(目录) 或者 'react-router/esm/index.js'(文件)
452+
*/
453+
target: string;
454+
/**
455+
* true-返回目录绝对路径,false-返回文件绝对路径
456+
*/
457+
dir?: boolean;
458+
/**
459+
* 用户目录查找起始路径
460+
*/
461+
basedir?: string;
462+
}
463+
464+
export function getUserLib(opts: IGetUserLibDirOptions): string {
465+
const { api, target, dir, basedir = api.paths.absSrcPath } = opts;
466+
let path: string | undefined;
467+
try {
468+
path = api.utils.resolve.sync(target, {
469+
basedir,
470+
});
471+
} catch (ignored) {
472+
path = target;
473+
}
474+
if (dir) {
475+
return dirname(path);
476+
} else {
477+
return path;
478+
}
479+
}

packages/umi-renderer-react-navigation/src/Navigation.tsx

+19-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { ComponentType, useEffect } from 'react';
2+
import { View, Text } from 'react-native';
23
import {
34
ApplyPluginsType,
45
Plugin,
@@ -105,7 +106,24 @@ export function Navigation(props: INavigationProps) {
105106
}, [history]);
106107

107108
const screens = flattenRoutes(routes);
108-
console.info('screens:', screens);
109+
110+
if (__DEV__) {
111+
if (!screens || screens.length === 0) {
112+
return (
113+
<Navigator initialRouteName="/" history={history}>
114+
<Screen
115+
name="/"
116+
component={() => (
117+
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
118+
<Text style={{ fontWeight: 'bold', fontSize: 17, color: '#f4333c' }}>404</Text>
119+
<Text style={{ fontSize: 14, color: '#f4333c' }}>请在 pages/ 目录下实现一个 index 页面。</Text>
120+
</View>
121+
)}
122+
/>
123+
</Navigator>
124+
);
125+
}
126+
}
109127

110128
return (
111129
<Navigator initialRouteName="/" history={history}>

0 commit comments

Comments
 (0)