React-Native学习笔记之第一个RN项目

一、搭建环境及运行第一个项目

1.按照安装教程中使用Homebrew一步一步的安装RN的开发环境及运行环境。
2.按照教程创建第一个RN项目

Snip20170822_1.png

3.使用命令行运行react-native run-ios
4.加载资源.....等了很久,无任何报错。。。一脸懵逼。
5.使用Xcode打开iOS文件夹下的工程文件。
6.报错信息如下:
Snip20170822_7.png

React/RCTbundleURLProvider.h not found未找到文件。
7.检查RN版本 react-native --version 0.47.2
版本过高,boost下载未成功。
8.使用命令:react-native init MyApp --version 0.44.3创建指定版本的App
9.编译程序不报错。
10.使用Xcode查看RN项目结构:
Snip20170822_4.png

11.继续输入命令:react-native run-ios运行成功。
12.运行结果
Snip20170822_3.png

13.找到index.ios.js文件使用sublime打开
Snip20170822_8.png

14.编辑内容后继续使用命令:react-native run-ios运行结果如下:

Snip20170822_6.png

Snip20170822_2.png

第一个项目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 的流程里,主要做的事情是:

  1. 编译/解析依赖
    现代前端工程中,编译几乎已经是必须的了,这里编译主要做两件事: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
    里。
  2. 管理依赖、打包压缩
    上述解析依赖仅提取了当前 JS 文件依赖的文件名,并没有做依赖文件查找/读取/拼装/更新等工作,这个工作在 packager/src/node-haste里做,把一个个 JS 文件封装成一个个Module,根据上述解析出来的依赖信息,去读取依赖文件,并递归检测依赖,直到所有依赖都加载完毕。
    这里面还有层层处理,最终所有依赖模块会封装成一个 packager/src/Bundler,提供给 cli 命令行调用,打包压缩是小意思,在local-cli/bundle.js里处理了。
  3. 请求执行
    在本地静态 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。

最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容