一、搭建环境及运行第一个项目
1.按照安装教程中使用Homebrew一步一步的安装RN的开发环境及运行环境。
2.按照教程创建第一个RN项目
3.使用命令行运行
react-native run-ios
4.加载资源.....等了很久,无任何报错。。。一脸懵逼。
5.使用Xcode打开iOS文件夹下的工程文件。
6.报错信息如下:
React/RCTbundleURLProvider.h not found
未找到文件。7.检查RN版本
react-native --version
0.47.2版本过高,boost下载未成功。
8.使用命令:
react-native init MyApp --version 0.44.3
创建指定版本的App9.编译程序不报错。
10.使用Xcode查看RN项目结构:
11.继续输入命令:
react-native run-ios
运行成功。12.运行结果
13.找到
index.ios.js
文件使用sublime打开14.编辑内容后继续使用命令:
react-native run-ios
运行结果如下:
第一个项目
Hello World
了已经。
二、思考第一个问题:react-native init AwesomeProject 这个命令做了什么,是怎样创建 RN 模板项目的?
实际上,在按照教程安装环境后,会在/usr/local/bin/
加上react-native
脚本,实际是个node.js脚本,也就是github上的react-native-cli/index.js
,在命令行全局调用react-native
就会调用这个脚本。这个文件的注释也可以看到,这只是一个转接层,所有命令都会转接到local-cli
上,但很奇怪react-native init
创建工程的逻辑部分在这个转接层react-native-cli/index.js
,部分在 local-cli/init/init.js
,其他命令则全部转接到 local-cli
上。
看看执行 react-native init AwesomeProject
的流程:
- 安装
react-native
依赖:在AwesomeProject
目录执行npm install react-native
,安装react-native
所有依赖的node
模块。这是init
命令第一个做的事情,代码在react-native-cli/index.js -> run()
,复制项目模板:安装依赖后init
命令随即转接到local-cli
,通过local-cli/generator
初始化项目,复制项目模板,模板文件在local-cli/templates
里。 - 链接
native
代码源文件:项目模板复制后需要把刚才安装的node_module/react-native
里的源文件链接到natvie
工程上,不同平台有不同逻辑,都在local-cli/link
里处理native
工程的链接。iOS 处理逻辑在local-cli/link/ios/
。
这一步骤处理后,AwesomeProject.xcodeproj
所需要的模块都链接完成,可以直接运行,可以看到工程Libraries
里所有模块都是从AwesomeProject/node_modules/react-native/
里链接过来的。
react-native
模块依赖了 500 多个npm
模块,这在前端界也算是正常,这些模块小部分是 RN 源码依赖的 JS 模块,大部分是用于前端构建,包括 JS 编译/打包/语法检测/http服务中间层等。
RN 模板项目创建过程大致就是这样。
项目 JS 源码在哪里,如何跑起来的?
在生成的AwesomeProject
模板项目里,iOS 端所依赖的所有模块和源码直接可以在工程里看到。但 JS 端的源码在项目里只看到业务实现代码index.ios.js
,XCode 项目跑起来后,index.ios.js
就执行生效了,RN 核心 JS 代码在哪里,有哪些,怎么跑起来的,都是个黑盒,接下来拆解下,看看 JS 代码是怎样运行起来的。
两种模式
RN 在 iOS 上对 JS 脚本的处理分两种模式:
- 本地
Server
模式。在本地自建一个Server
,客户端通过请求的方式获取 JS 代码。对于在模拟器跑debug
版,会使用这种方式,用于接入 chrome 调试和脚本实时更新。 - 本地静态
bundle
模式。编译时就把所有相关 JS 文件打包编译到 APP 里,运行时直接本地读取。对于所有release
版,或无法连接本地Server
的iPhone
真机上的debug
版,会使用这种方式。
本地 Server 模式在下一节 chrome
调试再描述,这里先看看本地静态 bundle
模式。
本地静态 bundle
在本地静态Bundle
模式中,最终所有 JS 代码都会打包成一个文件,客户端最终只需读取一个打包后的 JS 文件执行。这里从依赖分散的 JS 源文件,到最终可执行的单个 JS,有一个编译和打包 JS 的处理过程。这套处理过程的启动是在主工程AwesomeProject.xcodeproj Build Phases
里执行了一个脚本node_modules/react-native/packager/react-native-xcode.sh
,最终它在 Release
版或真机上执行了这样一条打包命令:
react-native bundle --entry-file index.ios.js --platform ios --dev true --reset-cache --bundle-output main.bundle --assets-dest assets
这个命令最终会输出一个 main.bundle
文件,实际是个 JS 文件,包含了 RN 所有核心代码和我们项目的业务代码(这里只有index.ios.js
)。
这个打包命令包含非常多处理,流程很长,算是整个 RN 部署工具的核心,主要实现在 react-native/packager
里,在这个生成静态 bundle
的流程里,主要做的事情是:
- 编译/解析依赖
现代前端工程中,编译几乎已经是必须的了,这里编译主要做两件事:ES6
-> 通用JS
,JSX -> JS
。
RN 源码以及业务代码都是以 ES6 的语法去写,像import xxx
这种写法在不支持ES6
语法的JS
引擎上是无法运行的,需要编译成require('xxx')
。此外像JSX
这种在 JS 代码里嵌入XML
标签的语法糖也需要编译成普通JS
语法才能在JS
引擎上运行,所以需要一个编译的过程。此外需要把 JS 文件的依赖也解析出来,因为这涉及到对 JS 代码的解析,把require('xxx')
语句解析出来,所以这部分也是在编译过程中处理。
这里统一用 Babel 这个库去做所有编译的工作。它的官网也说得很清楚它做了什么工作,除了编译,后续会提到的SourceMap
也是用它生成,由packager/src/JSTransformer
去封装编译解析后的数据。
解析依赖是在packager/src/JSTransformer/worker/extract-dependencies.js
,这里用 babel解析出当前文件中require
的内容后组装返回。编译是在packager/src/JSTransformer/worker/worker.js
里。 - 管理依赖、打包压缩
上述解析依赖仅提取了当前 JS 文件依赖的文件名,并没有做依赖文件查找/读取/拼装/更新等工作,这个工作在packager/src/node-haste
里做,把一个个 JS 文件封装成一个个Module
,根据上述解析出来的依赖信息,去读取依赖文件,并递归检测依赖,直到所有依赖都加载完毕。
这里面还有层层处理,最终所有依赖模块会封装成一个packager/src/Bundler
,提供给 cli 命令行调用,打包压缩是小意思,在local-cli/bundle.js
里处理了。 - 请求执行
在本地静态bundle
模式下,RN 最终会统一执行上述生成的main.bundle
,所有 JS 代码都在这里面,由RCTBundleURLProvider.m
处理执行,整个 RN 应用就跑起来了。
main.bundle
里是合并后的 JS 代码,如果想要看这个 JS 文件合并之前是包括哪些 JS 源文件,可以在上述模块组装的过程中去打出每个模块的信息,例如在packager/src/Bundler/Bundle.js
的addModule()
方法里加上console.log(moduleTransport.sourcePath)
就能看到所有依赖的 JS 文件路径。另外通过下述SourceMap
能更方便地看到。
代码流程
从 cli 命令 – 编译文件 – 解析依赖 – 组装数据 – 写入文件,这个过程在代码中实现流程很长,这里就不列出来了,大致涉及的几个文件的作用列以下:
local-cli/bundle/ - cli命令入口,传参,获取组装好的 Bundle压缩/写入文件
packager/src/Bundler/Bundle.js - 保存 bundle相关的所有模块信息/依赖/源码
packager/src/Bundler/index.js - 组装 Bundle 对象packager/src/JSTransformer - babel 转接,编译 JS,解析依赖
packager/src/node-haste - 管理依赖 cache,把 JS 源文件模块封装成 Module 对象
packager/src/Resolver - JS 模块组装打包成一个文件并不只是直接把 JS 源码拼一起,还需要重新封装模块,处理引用逻辑
第二部分文字引自bang's blog。