RN技术探索 ——React-Native APP中如何做好原生的依赖管理?
前言
使用React-Native
做跨端APP开发被越来越多的开发所接受,我们的团队在业务中也做了相应的实践。为了帮助大家更好的使用手机的原生特性,我们团队为React-Native
开发者提供了地图、埋点、路由、长链接、蓝牙、相机扫码等功能组件。在开发组件期间我们遇到了一些问题:
前端技术栈开发不了解如何使用
gradle
和CocoaPods
来管理原生依赖,通常都需要客户端技术栈的同学辅助。我们的组件需要添加一些原生依赖,必须要添加到原生依赖的配置文件中,组件开发者需要帮助更新这些配置,APP太多添加更新就会成问题。
所有的这些都是因为使用React-Native
管理原生的依赖困难导致的,为了解决这些问题我们先来了解一下React-Native
的整个依赖结构。
React-Native是如何工作的?
大部分的React-Native
开发者都会使用React-Native
的cli来初始化工程,cli使用起来很简单只需要输入工程名称即可,cli会自动拉取最新版本的React-Native
模板,你也可以通过设置拉取自定义的模板。为了展示方便,下面我会新建一个名叫testcli的React-Native APP。
执行后我们会得到下图这样的结构,不同版本的模板会不同,图中是拉取了React-Native
0.60.5版本(为了方便大家学习这里选择最新版本)的模板,模板文件就在React-Native
仓库的template目录下,部分是隐藏文件需要打开隐藏文件显示才会有。
整个APP主要分三部分:
Android目录 Android工程相关文件构成
iOS目录 保存了iOS工程相关的目录
JS 其余的文件大部分是对JS相关组件以及配置,比如node_modules 存储了npm 组件的相关文件,index.js是APP的整个入口文件,babel.config.js 与metrol.config.js 则是和打包相关的配置,package.json则是npm依赖管理配置文件。
React-Native
的包管理是依赖于npm来管理的,相信前端同学对npm一定不会陌生。最新版的React-Native
的cli拉取模板后会自动运行npm install来安装依赖组件,npm会将所有的依赖,包括依赖的依赖,安装到node_modules里,React-Native
0.60.5版本默认的依赖如下图所示。
React-Native
给出的iOS依赖解决方案是怎样的?
首先我们来看看iOS的目录结构。
分别简单介绍一下各个文件以及目录的作用。
xcodeproj 是iOS工程管理文件,用来管理当前工程。
xcodeworkspace xcode的工作区文件,一个工作区内会存在多个工程。
podfile iOS的依赖管理配置文件,iOS是通过
CocoaPods
做依赖管理,工作原理类似于gempods 用于存储
CocoaPods
安装后产生的依赖文件。testcli 当前的工程包含的文件。
最新的React-Native
版本现在已经给iOS工程配置了podfile文件,使iOS工程的依赖管理简单了很多,较老的版本需要自己手动去添加,才能使用CocoaPods
做依赖管理。打开podfile文件我们看React-Native
已经添加了React-Native
能运行的最小依赖。简单举个例子pod 'React', :path => '../node_modules/react-native/'
是指React这个组件的配置文件的路径是../node_modules/react-native/
目录下React.podspec文件。这样经过CocoaPods
安装后,iOS工程就可以依赖到React定义的原生文件了。
一般情况下让前端开发者去操作CocoaPods
或者gradle
,需要一定的学习时间,React-Native
很贴心的为大家添加了一个link的功能,关于link相关的代码React-Native
在 0.59.0版本以后将整个的cli代码,从React-Native 仓库分离了出来,安置在了新的仓库react-native-community/cli。开发者只需要简单使用link命令,就可以直接将package.json中配置的依赖组件的添加到podfile中。
如果想要新添加一个组件,例如添加一个地图组件到testcli工程中
开发者需要在package.json文件的dependencies字段中添加react-native-maps的依赖
通过 npm 安装依赖
运行
react-native link
命令将依赖同步到原生依赖配置中(iOS的podfile文件)在iOS目录下运行
pod install
安装依赖。
以上就是React-Native
的整个包管理操作的过程,将依赖结构总结为一张图如下:
我们做了什么?
从上文我们了解到,React-Native
开发者想要添加一条组件的原生依赖,是需要自己去操作podfile或者build.gradle的,对于客户端技术栈的开发者来说也许没有什么问题,但是对于前端技术栈的开发者来说简直就是灾难...想要掌握一个新的技术栈的包管理工具是需要时间的,并且要对依赖包也要有一些了解。为了方便起见还是用工具实现更方便一些,为了方便起见我们下面所有的流程都会以iOS平台来举例。
目标确认
首先明确我们要做些什么?我们的目标是方便React-Native
开发者添加原生依赖,为了实现这个目标我们需要通过生产一系列的工具帮助React-Native
开发者简单添加原生依赖,我们将整个工具实现拆解为三部分:
写一个cli程序,实现给原生的包管理配置文件添加依赖,即使用工具给podfile添加依赖。
将程序添加到整个rn app 依赖安装环节中,即在整个依赖安装执行写好的cli程序。
定义组件的依赖以及配置存储位置,即cli程序的输入来源进行设定。
cli程序实现
cli程序代码实现很简单,React-Native
本身就是建立在node体系下,所以我们我参考了React-Native
的link指令的实现,用nodejs实现了整个功能,原理上是通过全文检索和正确的正则表达式将需要加入或者修改的依赖写入文件中,由于工作中很少写正则,整个程序的过程中大部分时间都花在了完善正则表达式和调试修复bug上,这里就不详细展开讲解代码的实现过程了。
如何执行cli程序
完成了cli程序,只相当于完成一半,还需要考虑如何整个依赖安装执行写好的cli程序的时机问题。经过尝试我们总结了三种方式:
第一种方式就是完成依赖安装以后,通过设置命令行,逐一调用添加依赖。这样会简单很多,但是和手动添加差不多,只不过不用再去修改podfile了,只需要通过命令行操作就可以完成。带来的问题是需要知道依赖是什么,对于非客户端背景的开发不太友好。github上有一个叫react-native-pod的
React-Native
组件提供的的解决方案就是这样的。第二种方式是将组件的依赖提前写入组件的配置当中,然后再通过命令行工具,添加组件的依赖。这种方案的的优点是组件使用者不需要关注组件的依赖是哪些,不足就是用户需要自己操作,当接入多个组件的时候有可能会忘记或者漏掉操作。
第三种方式是将依赖添加的操作添加到固有的依赖的流程中,所有的操作能自动完成,用户不需要过多操作,不再去关心依赖安装整个过程做了什么,只需要固有流程即可完成。这种方案对于用户的影响是最小的,用户用过了后可能没有任何感知,缺点是自动化程度越高完成难度会越高。
显然第三种才是让用户使用更舒服的选择,当然也是最有难度的选择。既然想让用户无感知那么我们就来实现第三种方式好了。我们在仔细研究了React-Native
的cli工具的源码,并且查找了其他开源项目后,我找到具体的实现方案,我们再看看整个安装依赖的流程:
首先是执行
npm install
通过 npm 安装package.json 中的依赖。其次是通过 react-native 的cli工具的 link 命令,将安装好的node组件,为原生添加依赖。
最后需要执行
pod install
来安装原生的依赖。
三个流程中,流程2中react-native link
命令和我们要做的工作实质上相似的,我们只要在这个环节中加入我们自己的逻辑,但又不能侵入React-Native
原生代码。我阅读了link命令的源码后发现整个指令分为4个任务:
link逻辑执行前的钩子函数(
prelink
)执行link逻辑执行
link逻辑执行后的钩子函数(
postlink
)执行将assets资源添加到原生应用中
有了这两个钩子函数,我们就可以完成很多事情了,prelink
可以检查podfile文件是不是存在,如果不存在我们可以新建一个podfile文件(用最新版本的React-Native
模板新建的工程已经都建有podfile文件了,为了兼容老版本做检查更安全),在postlink
中则可以添加执行添加、修改依赖的逻辑。
cli程序输入设定
确定了执行入口,最后我们来定义cli程序的输入,每一个 React-Native
组件都有一个package.json来定义组件的信息,这些信息中部分是npm 需要的关键字,开发者也可以自己定义关键字段为我们所用,我将iOS的依赖写在了package.json文件中的pods字段下,只要去读取组件的package.json的配置就可以完成依赖的添加与修改,定义的格式如下:
"AFNetworking": {
"pod": "AFNetworking", // pod名称
"git": "git@github.com:AFNetworking/AFNetworking.git", // git仓库地址
"tag": "2.7.0", // tag版本号
"inhibit_warnings": true
}
}
啥?还不知道怎么设置postlink
这个钩子函数?这个点的确需要注意,必须在需要依赖的组件中添加设置才能顺利运行,还是在package.json中可以通过设置rnpm字段来设置React-Native
的cli相关的配置。用户只要在commands字段设置对应的指令即可。
"commands": {
"prelink": {run prelink commands in this case run cli check pod},
"postlink": {run postlink commands in this case run cli command add and modify pod}
}
}
one more things
现在看起来是都串起来了,我们回顾一下整个过程,用户想要安装React-Native APP的时候,需要先执行 npm install
,再执行react-native link
,最后再执行 pod install
。虽然把组件的原生依赖都安装成功了,但是执行起来还是不够方便。能不能只执行一条命令就可以呢?当然是可以了,我们只要在React-Native APP 的package.json文件中给npm install
添加postinstall
指令就可以啦:
"postinstall": "react-native link & cd ios & pod install"
}
整理一下整个依赖架构调整如下图所示:
未来展望
本文虽然没有对Android的优化做详细介绍,但是大体的思路是一致的,只不过原生包管理工具从CocoaPods
换成了gradle
。整体解决方案在流程上没有给用户增加任何的负担,整个都是无感知的,对于组件的开发者来说只要在package.json中添加依赖相关的配置即可,其余和以前的开发并无差异。关于ReactNative最小支持版本问题,我们最初是在0.50.2 版本上提供整套解决方案,仔细查了一下 rnpm 最早的支持是在2016年8月的0.31.0版本,link功能没有查到最早的支持版本,但是只会比 rnpm 更早,所以各位可以放心使用,如果使用了过老的版本,也可以联系我们一起探讨交流一下解决方案。
当然现在的方案上还是有一些问题需要解决:
不能在执行unlink的时候去掉依赖,由于部分依赖是重叠的,所以一直都没有实现。
只能自动添加
dependencies
字段下的依赖,这个与react-native link
命令获取依赖列表有关系。组件需要依赖cli工具软件,更新不够智能。
cli工具软件做更新时版本比较规则现在比较简单,只是数字的对比,如果能加入正则配置可能会更好。这一点可以借鉴一下比较成熟的包管理工具的实现
这些问题现在未来也会尝试去解决,目标也都是为了协助开发者使用React-Native
更容易。但是不可以否认,即便React-Native
是一个跨端框架,但是由于需要使用硬件平台特性,以及React-Native
的渲染机制需要依赖原生能力,开发React-Native
依旧无法离开原生的知识栈。知识栈更加丰富才是提升跨端开发效率最有效的方式,大家一起努力吧。
作者简介
丁超,喜好古典乐的coder,意甲fans,节能主义的崇尚者,虔诚的信仰着科学教,以脱离低级趣味为目标的人。
欢迎打赏