启动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 服务启动的过程
- 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
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调试信息的中间件
再比如说 getDevToolsMiddleware 开启了debugger-ui 服务
最后一个中间件是 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.创建工程目录
工程名称为babel-test
src 源码文件夹
lib 转换后的文件夹
- 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 命令
}
}
- 执行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
转换后的代码为
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。