React-Native 服务启动过程

启动React-Native 服务

执行 react-native init AwsomeProject 会初始化一个名为AwsomeProject的RN Demo,编译运行就会自动启动一个服务。
工程根目录下面会有
package.json ,node_modules, ios,android 几个文件夹以及 index.ios.js,index.android.js 程序主文件。

package.json 是node的依赖配置文件,node_modules 是 node的所有依赖文件,这些都是开发时候使用的,最后运行到真机的时候,会从主文件开始,把所有依赖的JS文件,根据依赖配置打包成一个bundle文件,拷贝到App的文件夹下去。

先看 ReactNative 服务启动的过程

image.png
  • HMR (hot module replace) 模块热更新服务
  • Haste Map(node-haste)用来转换 JS 到 Module

Package.json 里可以看到服务的入口文件。

"scripts": {
    "start": "node node_modules/react-native/local-cli/cli.js start",
    "test": "jest --no-cache"
  }

cli

image.png

ReactNative 代码生成 bundle 有两种方式,一个是 cli start 命令启动server 实时转换。 一个是 react-native bundle 命令生成。

Server.js

react-native 把所有的命令注册在了 commands.js 里, server.js 是cli start的真身,也就是服务的起点。

commands.js

module.exports = {
  name: 'start',
  func: server,
  description: 'starts the webserver',
  options: [{
    command: '--port [number]',
    default: 8081,
    parse: (val) => Number(val),
  }, {
    command: '--host [string]',
    default: '',
  }
  ...

同时我们也找到了服务启动后控制台前两条输出的代码

 console.log(formatBanner(
    'Running packager on port ' + args.port + '.\n\n' +
    'Keep this packager running while developing on any JS projects. ' +
    'Feel free to close this tab and run your own packager instance if you ' +
    'prefer.\n\n' +
    'https://github.com/facebook/react-native', {
      marginLeft: 1,
      marginRight: 1,
      paddingBottom: 1,
    })
  );
  
console.log(
    'Looking for JS files in\n  ',
    chalk.dim(args.projectRoots.join('\n   ')),
    '\n'
  );

Server.js 的作用只是收集参数,做事情的是 runServer.js

runServer.js

runServer 使用 node 的connect 创建了服务,同时注册了一些中间件和webScoket

loadRawBodyMiddleware
compress
DevToolsMiddleware
openStackFrameInEditorMiddleware
copyToClipBoardMiddleware
statusPageMiddleware
systraceProfileMiddleware
heapCaptureMiddleware
cpuProfilerMiddleware
jscProfilerMiddleware
indexPageMiddleware
packagerServer
logger
errorHandler

中间件对各自的请求做了不同的处理。

以systraceProfileMiddleware 举例,其中有这么一段代码

if (error.code === 127) {
        var response = '\n** Failed executing `' + cmd + '` **\n\n' +
          'Google trace-viewer is required to visualize the data, ' +
          'You can install it with `brew install trace2html`\n\n' +
          'NOTE: Your profile data was kept at:\n' + dumpName;
        console.log(response);
        res.end(response);
      } else {
        console.error(error);
        res.end('Unknown error: ' + error.message);
      }

摇一摇模拟器,点击start systrace,然后stop,弹出的信息和代码中的是对应的,说明systraceProfileMiddleware 就是处理模拟器的systrace调试信息的中间件

image.png
image.png

再比如说 getDevToolsMiddleware 开启了debugger-ui 服务

image.png

最后一个中间件是 packagerServer,它负责转换RN代码到js,它才是最核心的内容。这里只是把参数传递给了ReactPackager ,干活的还在另一个文件ReactPackager.js里。

function getPackagerServer(args, config) {
  const transformModulePath =
    args.transformer ? path.resolve(args.transformer) :
    typeof config.getTransformModulePath === 'function' ? config.getTransformModulePath() :
    undefined;

  return ReactPackager.createServer({
    assetExts: defaultAssetExts.concat(args.assetExts),
    blacklistRE: config.getBlacklistRE(),
    cacheVersion: '3',
    extraNodeModules: config.extraNodeModules,
    getTransformOptions: config.getTransformOptions,
    platforms: defaultPlatforms.concat(args.platforms),
    projectRoots: args.projectRoots,
    resetCache: args.resetCache,
    transformModulePath: transformModulePath,
    verbose: args.verbose,
    watch: !args.nonPersistent,
  });
}

关于 connect 可以参照

http://blog.fens.me/nodejs-connect/?utm_source=tuicool&utm_medium=referral

ReactPackager.js

本文件只代理了一下 server ,并使用 server 提供了一些方法。

 var Server = require('./src/Server');
  return new Server(options);
  
  暴露的方法
  ...
  exports.buildBundle
  
  exports.getOrderedDependencyPaths
  

Server/index.js

从 Babel 开始分析 ReactNative 代码转换逻辑

babel-cli 的原文
https://babeljs.io/docs/usage/cli/

阮一峰翻译
http://www.ruanyifeng.com/blog/2016/01/babel.html

从Package.json 看到 ReactNative 转换用到的转码规则有以下这些

    "babel-plugin-transform-class-properties": "*",
    "babel-polyfill": "*",
    "babel-preset-es2015": "*",
    "babel-preset-react": "*",
    "babel-preset-react-native": "1.9.0",

按照babel文档里面的步骤新建一个转换RN代码的工程

1.创建工程目录

image.png

工程名称为babel-test
src 源码文件夹
lib 转换后的文件夹

  1. package.json内容
{
  "name": "babel-test",
  "version": "1.0.0",
  "devDependencies": {
    "react": "15.4.2",
    "react-native": "^0.40.0",
    "babel-cli": "^6.24.0",
    "babel-register": "^6.24.0",
    "babel-plugin-transform-class-properties": "*",
    "babel-polyfill": "*",
    "babel-preset-es2015": "*",
    "babel-preset-react": "*",
    "babel-preset-react-native": "1.9.0"
  },
  "scripts": {
    "build": "babel src -d lib"//制作 npm build 命令
  }
}

  1. 执行npm install,安装依赖

4.编写 src/script.js 源码为ReactNative 代码

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View
} from 'react-native';
import ConferenceList from './conference'
class User extends Component {
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>
          Welcome to React Native!
        </Text>
        <Text style={styles.instructions}>
          To get started, edit index.android.js
        </Text>
        <Text style={styles.instructions}>
          Double tap R on your keyboard to reload,{'\n'}
          Shake or press menu button for dev menu
        </Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});

module.exports = User

  • 在目录下创建 .babelrc
{
  "presets": ["react-native"]
}

执行转换命令 npm build

image.png

转换后的代码为

var _createClass=function(){function defineProperties(target,props){for(var i=0;i<props.length;i++){var descriptor=props[i];descriptor.enumerable=descriptor.enumerable||false;descriptor.configurable=true;if("value"in descriptor)descriptor.writable=true;Object.defineProperty(target,descriptor.key,descriptor);}}return function(Constructor,protoProps,staticProps){if(protoProps)defineProperties(Constructor.prototype,protoProps);if(staticProps)defineProperties(Constructor,staticProps);return Constructor;};}();





var _react=require('react');var _react2=_interopRequireDefault(_react);
var _reactNative=require('react-native');





var _conference=require('./conference');var _conference2=_interopRequireDefault(_conference);function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj};}function _classCallCheck(instance,Constructor){if(!(instance instanceof Constructor)){throw new TypeError("Cannot call a class as a function");}}function _possibleConstructorReturn(self,call){if(!self){throw new ReferenceError("this hasn't been initialised - super() hasn't been called");}return call&&(typeof call==="object"||typeof call==="function")?call:self;}function _inherits(subClass,superClass){if(typeof superClass!=="function"&&superClass!==null){throw new TypeError("Super expression must either be null or a function, not "+typeof superClass);}subClass.prototype=Object.create(superClass&&superClass.prototype,{constructor:{value:subClass,enumerable:false,writable:true,configurable:true}});if(superClass)Object.setPrototypeOf?Object.setPrototypeOf(subClass,superClass):subClass.__proto__=superClass;}var
User=function(_Component){_inherits(User,_Component);function User(){_classCallCheck(this,User);return _possibleConstructorReturn(this,(User.__proto__||Object.getPrototypeOf(User)).apply(this,arguments));}_createClass(User,[{key:'render',value:function render()
{
return(
_react2.default.createElement(_reactNative.View,{style:styles.container},
_react2.default.createElement(_reactNative.Text,{style:styles.welcome},'Welcome to React Native!'),


_react2.default.createElement(_reactNative.Text,{style:styles.instructions},'To get started, edit index.android.js'),


_react2.default.createElement(_reactNative.Text,{style:styles.instructions},'Double tap R on your keyboard to reload,',
'\n','Shake or press menu button for dev menu')));




}}]);return User;}(_react.Component);


var styles=_reactNative.StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',
backgroundColor:'#F5FCFF'},

welcome:{
fontSize:20,
textAlign:'center',
margin:10},

instructions:{
textAlign:'center',
color:'#333333',
marginBottom:5}});



module.exports=User;

下面是执行 react-native bundle 命令转换后的代码。RN转换后的bundle 里require的内容都转换成了数字,为了方便查看,我保留了转换前的路径。


__d(/* AwsomeProject/user.js */function(global, require, module, exports) {





var _react=require("react/react.js" /* react */);var _react2=babelHelpers.interopRequireDefault(_react);
var _reactNative=require("react-native/Libraries/react-native/react-native.js" /* react-native */);




var _conference=require("AwsomeProject/conference/index.js" /* ./conference */);var _conference2=babelHelpers.interopRequireDefault(_conference);var
User=function(_Component){babelHelpers.inherits(User,_Component);function User(){babelHelpers.classCallCheck(this,User);return babelHelpers.possibleConstructorReturn(this,(User.__proto__||Object.getPrototypeOf(User)).apply(this,arguments));}babelHelpers.createClass(User,[{key:'render',value:function render()
{
return(
_react2.default.createElement(_reactNative.View,{style:styles.container},
_react2.default.createElement(_reactNative.Text,{style:styles.welcome},'Welcome to React Native!'),


_react2.default.createElement(_reactNative.Text,{style:styles.instructions},'To get started, edit index.android.js'),


_react2.default.createElement(_reactNative.Text,{style:styles.instructions},'Double tap R on your keyboard to reload,',
'\n','Shake or press menu button for dev menu')));




}}]);return User;}(_react.Component);


var styles=_reactNative.StyleSheet.create({
container:{
flex:1,
justifyContent:'center',
alignItems:'center',
backgroundColor:'#F5FCFF'},

welcome:{
fontSize:20,
textAlign:'center',
margin:10},

instructions:{
textAlign:'center',
color:'#333333',
marginBottom:5}});



module.exports=User;
}, "AwsomeProject/user.js", null, "AwsomeProject/user.js");
__d(/* AwsomeProject/conference/index.js */function(global, require, module, exports) {var _List=require("AwsomeProject/conference/List.js" /* ./List */);var _List2=babelHelpers.interopRequireDefault(_List);

module.exports=_List2.default;
}, "AwsomeProject/conference/index.js", null, "AwsomeProject/conference/index.js");
__d(/* AwsomeProject/conference/List.js */function(global, require, module, exports) {Object.defineProperty(exports,"__esModule",{value:true});var _react=require("react/react.js" /* react */);var _react2=babelHelpers.interopRequireDefault(_react);
var _reactNative=require("react-native/Libraries/react-native/react-native.js" /* react-native */);


这样就能看出两个文件的差异是react-native bundle 命令多了以下内容

__d(/* AwsomeProject/user.js */function(global, require, module, exports) {
    ...code
}, "AwsomeProject/user.js", null, "AwsomeProject/user.js");

说明 react-native bundle 对于一个模块的转换的过程是

1.定义 __d 方法,模块化组件
2.内部代码调用 babel 进行转换
3.以index.ios.js/index.android.js 为入口,找到所有的依赖,全部转换成普通JS。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容