行动前的考虑

◆原有的React Native工程是在Windows下安装完Android开发环境后使用命令”react-native init”生成的,看到存储Android SDK所需的30G+的硬盘和Android模拟器运行所需的8G+内存预算,想在自己的便携机上搞一套相同环境负担实在太重;

◆看到Expo时被其简练的开发流程所吸引,而环境轻量太多了,也同样支持代码修改保存后自动刷新内容。设想利于Expo能随时随地用便携机开发APP,到构建发布时候再使用笨重的Android环境,很美好;

◆React Native中文官网警告Expo大量依赖于国外网络环境,只用于学习、演示、试验等,不建议国内使用。我的目的就是用于开发过程方便看效果,实践后发现仅仅在安装Expo环境和创建工程的时候才需要国外网络环境;

尝试基础的React Native项目

步骤总结如下:
◆安装Expo环境,使用“react-native init”命令创建个新的React Native工程;
◆进入React Native工程目录下创建可运行的Expo的工程:expo init demo ;
◆进入demo目录,为“app.json”文件增加如下packagerOpts配置内容,以指定使用自定义的metro配置文件;

1
2
3
4
5
6
7
8
9
10
11
12
{
"expo": {
"name": "demo",
"slug": "demo",
"privacy": "public",
"sdkVersion": "33.0.0",
"packagerOpts": {
"projectRoots": "",
"config": "metro.config.js"
},
... ...
}

◆新建“metro.config.js”文件,内容如下。其中的blacklistRE配置了一个正则,打包时会忽略掉正则匹配到的路径。因为在根目录下的node_modules中会存在与demo目录下node_modules中相同的库,例如react-native, react, prop-types,如果不忽略就会使得providesModule在解析时产生重名,从而导致jest-haste-map报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const path = require('path');
const blacklist = require('metro-config/src/defaults/blacklist');
const escapeRegexString = require('escape-regex-string');

module.exports = {
resolver: {
blacklistRE: blacklist([
new RegExp(
`^${escapeRegexString(path.resolve(__dirname, '..', 'node_modules'))}\\/.*$`,
),
]),
providesModuleNodeModules: [
'react-native',
'react'
],
extraNodeModules: {
'@babel/runtime': path.resolve(__dirname, 'node_modules/@babel/runtime'),
},
},
projectRoot: path.resolve(__dirname),
watchFolders: [
path.resolve(__dirname, '..'),
],
};

◆安装“metro.config.js”文件中用到的escape-regex-string,执行:yarn add escape-regex-string ;
◆修改App.js将原React Native的App作为组件引入,如下:

1
2
3
4
5
6
7
import App from '../App';

export default function Demo() {
return (
<App />
);
}

◆回到上一级的React Native工程目录,修改”metro.config.js”文件,内容如下,目的是忽略demo文件夹下的node_modules中的库;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const path = require('path');
const blacklist = require('metro-config/src/defaults/blacklist');
const escapeRegexString = require('escape-regex-string');

module.exports = {
resolver: {
blacklistRE: blacklist([
new RegExp(
`${escapeRegexString(path.resolve(__dirname, './demo', 'node_modules'))}`,
)
])
},
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: false,
},
}),
}
};

◆安装“metro.config.js”文件中用到的escape-regex-string,执行:yarn add escape-regex-string ;
◆以上即创建了支持Expo的最基础React Native项目的开发环境,直观上的修改是:
(1)增加了demo文件夹;
(2)增加了escape-regex-string的依赖;
(3)metro配置中增加了忽略文件夹;
◆项目需要安装新的npm依赖的步骤:
(1)React Native项目中安装依赖;
(2)Expo项目中安装依赖;
(3)Expo的metro配置中增加依赖对应的extraNodeModules项,如增加“react-navigation”后,修改为如下样子:

1
2
3
4
extraNodeModules: {
'@babel/runtime': path.resolve(__dirname, 'node_modules/@babel/runtime'),
'react-navigation': path.resolve(__dirname, 'node_modules/react-navigation'),
},

◆之后就可在demo目录下使用expo开发上一层目录的React Native工程了,运行“npm start”即可开始;
参考项目地址,查看git log可看到大致改造过程;

尝试真实的React Native项目

基础React Native项目尝试成功之后对于真实项目的改造也比较有信心了,按照总结的思路步骤对一个真实项目做尝试,遇到了如下几个坑:
◇Icons
(1)React Native通常使用的图标库是“react-native-vector-icons”,Expo使用的图标库是“@expo/vector-icons”,它对“react-native-vector-icons”增加了一层兼容之后的库,默认是包含在Expo中的;
(2)React Native引入图标的语法如:__import MaterialIcon from “react-native-vector-icons/MaterialIcons__,而Expo引入图标的语法如:__import { MaterialIcons } from ‘@expo/vector-icons’__;
(3)原项目中的__import MaterialIcon from “react-native-vector-icons/MaterialIcons”__语句在Expo运行环境下会被自动解析成__import MaterialIcon from “@expo/vector-icons/MaterialIcons__,这在Expo中会引起报错;
(4)按照Expo引入图标的语法,将原项目的__import MaterialIcon from “react-native-vector-icons/MaterialIcons”__语句改为__import {MaterialIcons} from “react-native-vector-icons”__才能通过编译,但这样就影响了原项目的代码内容和正常编译;
(5)目前未发现Expo环境中控制自动解析的metro配置项
◇SplashScreen
(1)原项目中使用了“react-native-splash-screen”实现启动页,虽然使用上一步中的方法引入h和指定了“react-native-splash-screen”,但在调用SplashScreen函数时还是会报如下找不到splash库的错误;

(2)“react-native-splash-screen”官方文档指出在Android和iOS环境下分别需要执行link和增加配置才可正常工作,而在Expo环境该如何配置暂时没有找到;
(3)Expo官方文档暂未找到关于如何link lib的说明;

结论

以上,得出结论:目前在保持原React Native代码正常的前提下,为工程增加Expo开发方式支持的想法无法实现。