零Node基礎看懂React-Native腳手架工具

做過RN開發的同學肯定對react-native-cli命令行工具不陌生,官方文檔一開始在搭建開發環境的章節就會介紹到利用這個工具快速創建一個新項目。剛開始接觸RN開發的你,一定會對這個命令行工具感到好奇,它是如何快速的創建一個RN項目的呢,又是如何完成一系列的工程目錄結構創建,配置信息添加,各種依賴安裝的呢,本文就帶你來一探究竟。

react-native-cli是Facebook開源項目ReactNative自帶的一個腳手架工具,可以很方便幫助開發者快速的從0開始創建一個完整的RN項目。react-native-cli其實是一個node項目,你可能沒有接觸過node開發,就像作者一樣,是從原生轉RN開發的,別擔心,沒有node基礎,一樣可以看懂,下面我們就正式開始吧。


這裡簡單描述一下本地搭建React-Native開發環境的主要流程,按照RN官網的文檔描述主要有如下步驟:

•安裝必須的軟件

本文重點是RN腳手架,這裡簡要概況,具體安裝方法見React-Native官方文檔

–Homebrew:Mac系統的包管理器,用於安裝NodeJS和一些其他必需的工具軟件。

–Node.js:使用Homebrew來安裝Node.js

–Yarn:是Facebook提供的替代npm的工具,非必須安裝,可跳過

–Xcode:iOS開發工具,提供了iOS開發環境,運行iOS端需要該開發環境

–WebStorm:RN開發工具,用來編寫React Native應用,推薦使用,另外Nuclide、VSCode、Sublime Text也可以


•安裝react-native-cli命令行工具(RN腳手架)

<code>  npm install -g react-native-cli/<code>

npm 常用的安裝命令,用來安裝node包,react-native-cli是一個node包,

-g是全局安裝,根據需要,這裡選擇全局安裝


•快速創建RN應用

–創建RN工程

<code>react-native init MyRNProject/<code>

init:初始工程,快速創建RN工程

MyRNProject: 項目名稱

–啟動工程(運行iOS項目,查看效果)

<code>   react-native run-ios/<code>

如果以上步驟都安裝正確,這裡應該能啟動iOS模擬器,並運行RN工程了。整個流程看起來是不是很簡單。之所以看起來很方便、簡單,是因為react-native-cli命令行工具替我們完成了一系列的創建、配置、初始化、安裝依賴的工作。本文的重點就是帶大家探究一下,react-native-cli究竟替我們做了哪些工作,又是如何完成的。


01 概覽

其實整個react-native-cli命令行工具包括兩部分

•react-native-cli包內部分:主要完成React-Native引擎的下載安裝

•React-Native內的node_modules/@react-native-community/cli:主要負責RN工程的創建,初始化,依賴安裝等

具體這兩部分是如何工作的呢,不要著急,下面我們一步一步的拆解並跟蹤整個工程創建過程,看完你就明白了。


02 react-native-cli部分


1)找到真身

首先,來看react-native-cli部分,當安裝完成後,我們就可以全局使用react-native命令了,例如:

<code>~ % react-native --version
> react-native-cli: 2.0.1
> react-native: n/a - not inside a React Native project directory/<code>

可以看到,當執行react-native --version時,會輸出react-native-cli的版本號,(這裡是2.0.1)還有react-native的版本號(由於這裡不是在RN項目根目錄下執行的,所以這裡提示當前目錄不是RN項目的目錄)。

那這個react-native命令究竟是個啥呢?通過which命令,可以查看一個命令的安裝路徑。

<code>~ % which react-native> /usr/local/bin/react-native/<code>

我們打開這個目錄看看:


零Node基礎看懂React-Native腳手架工具

原來是一個替身,讓他現行吧,右鍵顯示原身:


零Node基礎看懂React-Native腳手架工具


如圖所示,最終指向的是react-native-cli模塊中的index.js文件。可以看出react-native-cli其實就是一個node.js項目,運行在node上。

看到這裡可能有些同學有點擔心了,“我不懂node呀,是個node小白,進行不下去了”。別擔心,筆者對node的理解也只是停留在使用node模塊層面,也沒有過node模塊的開發經驗。

廢話不多說,雖然沒開發過node項目,但是RN項目還是有些經驗的,不都是JS的項目嘛,結合以往RN項目的開發經驗,讓我們大膽的用webstorm打開這個node工程看看。


2)剖析真身(react-native-cli部分)

打開工程,我們發現,這個node工程還是比較簡單的:


零Node基礎看懂React-Native腳手架工具


除了node_modules文件夾,只有一個代碼文件:index.js,這個文件既是入口文件又是全部功能實現邏輯。下面我們來分析一下這個index.js文件。


01 配置調試參數

分析代碼最好的方式就是調試、跟蹤代碼執行的每一個步驟。筆者使用的是webstorm,將react-native-cli文件夾作為node工程打開後,添加調試配置。添加->選擇Node.js模板->配置Configuration標籤下的參數,具體如下:


零Node基礎看懂React-Native腳手架工具


•Node interpreter: Project //node(usr/local/bin/node) 默認會選擇,不需要修改

•Working directory : ~/Documents/workspace/Node/ 運行時的所在的目錄,會在當前目錄下創能你的新RN工程

•Application parameters:init TestRNProject 命令行參數,即shell命令react-native init TestRNProject 中的init TestRNProject部分

點擊‘OK’,保存後,在index.js文件中打上斷點,點擊debug就可以一步一步跟蹤調試了。


02 代碼跟蹤


可以看到,index.js文件首先是引入了一堆工具類(node的工具類)

<code>var fs = require('fs');
var path = require('path');
var exec = require('child_process').exec;
var execSync = require('child_process').execSync;
var chalk = require('chalk');
var prompt = require('prompt');
var semver = require('semver');/<code>

fs-文件讀寫等處理工具模塊、path-文件路徑處理模塊、child_process-子進程模塊。具體每個模塊的作用,可自行搜索資料瞭解。

接著出現了這麼一行:

<code>var options = require('minimist')(process.argv.slice(2));/<code>

後續調試得知該行的作用是讀取命令行的輸入參數,例如react-native init TestRNProject命令,會把’init’ ’myProject’作為兩個參數讀入options

實際調試中可以看到,options最終得到的是一個對象:

<code> {  "_": [    "init",    "TestRNProject"  ]}/<code>

接著是兩個工具方法,用來獲取目標文件的路徑

<code>var CLI_MODULE_PATH = function() {
...

var REACT_NATIVE_PACKAGE_JSON_PATH = function() {
.../<code>

然後是cli的定義和賦值及運行(react-native-cli 的核心邏輯部分)

<code>var cli;
var cliPath = CLI_MODULE_PATH();
if (fs.existsSync(cliPath)) {
cli = require(cliPath);
}


var commands = options._;
if (cli) {
cli.run();
}/<code>

面這一段實際上是判斷當前路徑是否是一個RN的工程根目錄,如果是,則調用RN工程中的 cli工具進行初始化和運行。咱們當前是創建一個全新的RN工程,所以這段代碼暫時不用關注。

接下來是:

<code>if (cli) {
cli.run();
} else {
if (options._.length === 0 && (options.h || options.help)) {
console.log([
。。。//本文作者注:一些幫助信息的輸出,這裡省略
].join('\\n'));
process.exit(0);
}


if (commands.length === 0) {
console.error(
'You did not pass any commands, run `react-native --help` to see a list of all available commands.'
);
process.exit(1);
}


switch (commands[0]) {
case 'init':
if (!commands[1]) {
console.error(
'Usage: react-native init <projectname> [--verbose]'
);
process.exit(1);
} else {
init(commands[1], options); //本文作者注:關鍵部分,完成初始化創建工作
}
break;
default:
console.error(
'Command `%s` unrecognized. ' +
'Make sure that you have run `npm install` and that you are inside a react-native project.',
commands[0]
);
process.exit(1);
break;
}
}/<projectname>/<code>

上面這段代碼的else部分就是創建RN工程的核心部分了:

•可以看到首先是進行了一些參數校驗處理,校驗不通過直接退出進程;

•然後判斷第一個參數是否是’init’(通過一個switch匹配),最終實際上會調用init方法( init(commands[1], options);)

下面是init方法的實現:

<code>function init(name, options) {
validateProjectName(name);


if (fs.existsSync(name)) {
createAfterConfirmation(name, options);
} else {
createProject(name, options);
}
}/<code>

可以看到,init方法中實際進行了一些工程名稱校驗、文件是否存在校驗後,最終調用了createProject方法,並將參數傳了過去

這裡name:'TestRNProject2' options:{"_":["init","TestRNProject"]}


createProject方法:

<code>function createProject(name, options) {
var root = path.resolve(name); //本文作者注:這裡是獲取新RN項目的根目錄的路徑
var projectName = path.basename(root); //本文作者注:項目名稱:TestRNProject


console.log(
'This will walk you through creating a new React Native project in',
root

);


//本文作者注:判斷項目根目錄是否存現,不存在就創建該目錄
if (!fs.existsSync(root)) {
fs.mkdirSync(root);
}


var packageJson = {
name: projectName,
version: '0.0.1',
private: true,
/> start: 'node node_modules/react-native/local-cli/cli.js start'
}
};
//本文作者注:將配置信息寫入新項目根目錄下的package.json文件
fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify(packageJson));

process.chdir(root); //本文作者注:設置當前工作目錄到新RN工程,這裡是~/Documents/workspace/Node/TestRNProject


run(root, projectName, options); //本文作者注:root同上,projectName:TestRNProject, options同上
}/<code>

這一步,主要是在新RN工程根目錄下寫入了一個配置文件package.json:


零Node基礎看懂React-Native腳手架工具


然後進入run方法:

<code>function run(root, projectName, options) {
// E.g. '0.38' or '/path/to/archive.tgz'
const rnPackage = options.version;
。。。
//===============本文作者注:上面這一段主要是對yarn的版本校驗和誘導安裝,可以跳過

try {
/*
本文作者注:調用創建進程方法,同步執行 npm install --save --save-exact react-nativ 命令
這一步就是安裝react-nativ的node包
*/
execSync(installCommand, {stdio: 'inherit'}); //installCommand:npm install --save --save-exact react-native
} catch (err) {
console.error(err);
console.error('Command `' + installCommand + '` failed.');
process.exit(1);
}
checkNodeVersion();
cli = require(CLI_MODULE_PATH());
cli.init(root, projectName);
}/<code>

注意 當在webstorm下調試時,執行到execSync(installCommand, {stdio: 'inherit'});這一步時,如果是debug模式下,會出現無法執行,至於為什麼,先暫不討論。

不過分析代碼可以看到,正常流程下,運行完這一段安裝命令後,會執行

<code>cli = require(CLI_MODULE_PATH()); cli.init(root, projectName);/<code> 

分析CLI_MODULE_PATH()方法的返回結果可以看到,這裡實際上是調用了新RN工程下的’node_modules/react-native/cli.js’的init方法。這裡是相當於調用了新的node工程的代碼,當前打開的工程中無法繼續跟蹤源碼。

綜上,我們直接註釋掉cli.init(root, projectName);這行代碼,非調試模式下,直接運行。由運行結果可見,在調用cli.init方法之前,最終實際是在新RN工程目錄下安裝了react-native的nodemodule(同時包括相關依賴包):


零Node基礎看懂React-Native腳手架工具


執行結果如上圖所示,可以看到新RN項目的根目錄下新增了一個node_modules文件夾,這裡面放的就是react-native引擎和相關依賴包。

看到這裡,離我們的目標工程似乎還差了不少東西,沒錯,iOS、Android的原生工程,還有一些App.js入口文件等等都還沒有。不急,別忘了,上面我們註釋掉的那行代碼:cli.init(root, projectName);

如之前所述,最後的這段代碼:

<code>cli = require(CLI_MODULE_PATH());cli.init(root, projectName);/<code>

實際上是調用了新RN工程下的node_modules/react-native/cli.js的init方法,但是當前工程又無法繼續調試跟蹤進去,怎麼辦呢?先來看一下react-native的工程吧。


03 React-Native local-cli

首先,我們用webstorm打開react-native引擎項目:進入我們新生成的RN工程下的node_modules文件夾下的react-native,用webstorm打開該目錄。

受前面的啟發,我們是不是也可以把這個react-native看做一個node工程呢,答案是肯定的。

打開工程後我們發現,根目錄下面存在一個cli.js的文件,這正是之前react-native-cli工程我們最後跟蹤到的那個引用文件。不錯,視乎很順利,但是別高興的太早,再仔細看看,我們發現,之前react-native-cli在調用react-native中的cli時是調用了init方法,並傳入了兩個參數。但是這裡的cli.js文件只是定義了cli並導出了,我們該如何調用呢?直接運行,顯然是不行的(把這個cli作為入口文件直接運行的話,不會調用主體方法,無法執行後續的創建任務)


1)添加入口文件

關鍵的地方來了,總結一下截止到目前我們的源碼深入和調試的經過(例如react-native-cli這個node工程的目錄結構和我們的調試過程),結合以往的開發經驗,我們是不是可以大膽的猜想或者說推測一下:如果我們給這個React-Native local-cli的node項目也加一個入口文件呢?

說幹就幹,下面我們創建一個index.js的文件,作為React-Native local-cli的node項目的入口文件。index.js文件內容是什麼呢,很顯然,我們又想起了之前註釋掉的那行代碼,我們把之前那行調用的代碼放在這裡,是不是整個流程一下就通了,豁然開朗,完美!所以最終我們加入的index.js就是這樣:

<code>#!/usr/bin/env node
//本文作者注:開頭部分,為啥這樣寫,直接從react-nativ-cli工程的index.js拷貝過來的(其實是指定運行環境,這裡是shell-node)
'use strict';


var cli;
cli = require('./cli'); //本文作者注:對應之前的 ‘cli = require(CLI_MODULE_PATH());’
cli.init('/Users/dingxin/Documents/workspace/Node/TestRNProject', 'TestRNProject'); //本文作者注:對應之前的‘cli.init(root, projectName);’/<code>
零Node基礎看懂React-Native腳手架工具


2)WebStorm調試配置:

好了,有了之前的調試經驗,下面的調試配置就比較簡單了:

•Node interpreter: Project //node(usr/local/bin/node) 默認會選擇,不需要修改

•Working directory: ~/Documents/workspace/Node/TestRNProject //目標RN工程的根目錄

•JavaScript file: node_modules/react-native/index.js //node啟動的入口文件,(相對於當前工程目錄的相對路徑)


零Node基礎看懂React-Native腳手架工具


為什麼這裡我們不能像react-native-cli工程調試配置的時候一樣通過Application parameters 參數來傳入參數呢?當然也可以,我們可以在index.js文件中通過var options = require('minimist')(process.argv.slice(2));來讀取參數輸入參數,然後傳給cli.init調用。這裡我們僅是為了測試驗證問題,簡單起見,我們直接寫死就好了,正常開發中肯定是要用這種傳參的形式。


3)運行、調試

一切準備就緒,我們的猜想是不是正確呢,終究要實際運行驗證一下。我們直接點運行,可以看到:


零Node基礎看懂React-Native腳手架工具


成功了,看日誌輸出應該是成功了。這時候查看剛才的新RN工程目錄:


零Node基礎看懂React-Native腳手架工具


對比之前的目錄,明顯多了許多文件、文件夾,實際上這就是react-native腳手架替我們創建的新RN工程的最終樣子。

等等,這樣就結束了嗎,好像少點什麼。當然,本文的重點是探究RN腳手架究竟做了什麼,光看到結果顯然不是我們想要的。下面我們就一步一步深入看看。


4)工程目錄、文件創建

再次對比剛才生成的工程目錄,和上一步對比,明顯可以看到多了許多文件、文件夾(圖中項目根目錄下紅框部分)。這些文件(夾)是如何創建的呢?讓我們來看看代碼。

首先打開react-native/cli.js,找到cli的定義:var cli = require('@react-native-community/cli');,這裡是cli實際上定義在@react-native-community下的模塊。繼續找到定義,最終實際上是當前工程目錄(RN引擎包)下的:node_modules/@react-native-community/cli/build/index.js。打開這個文件,搜索init,可以看到,實際上是這個方法:

注:這裡我們的當前工程目錄(RN引擎包):~/Documents/workspace/Node/TestRNProject/node_modules/react-native

<code>Object.defineProperty(exports, "init", {

enumerable: true,

get: function () {

return _initCompat.default;

}

});

exports.bin = void 0;/<code>

繼續跳轉定義,可以找到_initCompat的實際實現,node_modules/@react-native-community/cli/build/commands/init/initCompat.js 文件下的:

<code>async function initCompat(projectDir, argsOrName) {

const args = Array.isArray(argsOrName) ? argsOrName // argsOrName was e.g. ['AwesomeApp', '--verbose']

: [argsOrName].concat(_process().default.argv.slice(4)); // argsOrName was e.g. 'AwesomeApp'

// args array is e.g. ['AwesomeApp', '--verbose', '--template', 'navigation']



if (!args || args.length === 0) {

_cliTools().logger.error('react-native init requires a project name.');



return;

}



const newProjectName = args[0];

const options = (0, _minimist().default)(args);



_cliTools().logger.info(`Setting up new React Native app in ${projectDir}`);



await generateProject(projectDir, newProjectName, options);

}/<code>

可以看到,這裡首先進行了一系列的校驗,然後會調用generateProject方法。看來這個generateProject應該就是整個操作的核心。那麼這個generateProject究竟都幹了啥呢?

<code>async function generateProject(destinationRoot, newProjectName, options) {

const pkgJson = require('react-native/package.json');



const reactVersion = pkgJson.peerDependencies.react;


//筆者注:第一步,是根據RN包內置的模板工程,創建用戶的新RN工程

await (0, _templates.createProjectFromTemplate)(destinationRoot, newProjectName, options.template);



_cliTools().logger.info('Adding required dependencies');



//筆者注:第二步,安裝依賴的react庫(node Module)

await PackageManager.install([`react@${reactVersion}`], {

root: destinationRoot

});



_cliTools().logger.info('Adding required dev dependencies');



//筆者注:這裡是安裝開發環境下依賴的輔助工具庫,例如下面的babel、eslint等等

await PackageManager.installDev(['@babel/core', '@babel/runtime', '@react-native-community/eslint-config', 'eslint', 'jest', 'babel-jest', 'metro-react-native-babel-preset', `react-test-renderer@${reactVersion}`], {

root: destinationRoot

});

addJestToPackageJson(destinationRoot);



if (_process().default.platform === 'darwin') {

//筆者注:第三步,判斷如果是mac環境,自動安裝iOS工程的依賴庫(通過cocopods安裝)


_cliTools().logger.info('Installing required CocoaPods dependencies');



await (0, _installPods.default)({

projectName: newProjectName

});

}



//至此,安裝完成,打印幫助信息。

(0, _printRunInstructions.default)(destinationRoot, newProjectName);

}/<code>

先來看第一步,可以看到,這裡調用了_templates.createProjectFromTemplate方法,從方法名來看,應該是通過一個工程模板去創建一個新的RN工程。繼續追蹤,可以看到其實現方法:node_modules/@react-native-community/cli/build/tools/generator/copyProjectTemplateAndReplace.js 下的copyProjectTemplateAndReplace。添加斷點,可以看到該方法的入參分別為:

•srcPath: 源路徑(模板工程) /Users/dingxin/Documents/workspace/Node/TestRNProject/node_modules/react-native/template

•destPath: 模板路徑(目標新工程) /Users/dingxin/Documents/workspace/Node/TestRNProject

•newProjectName: 新工程名稱 TestRNProject

•options: {}

源碼如下:

<code>function copyProjectTemplateAndReplace(srcPath, destPath, newProjectName, options = {}) {



//筆者注:首先是一些參數校驗,這裡有刪減,具體實現參考源碼


if (!srcPath) {

throw new Error('Need a path to copy from');

}





這裡是一個遞歸循環,源模板工程目錄下依次遞歸執行處理(文件夾創建、文件複製修改等)

(0, _walk.default)(srcPath).forEach(absoluteSrcFilePath => {

// 'react-native upgrade'

if (options.upgrade) {

// Don't upgrade these files

const fileName = _path().default.basename(absoluteSrcFilePath); // This also includes __tests__/index.*.js

。。。

//筆者注:這裡省略了一些校驗邏輯,具體實現參考源碼

}



const relativeFilePath = translateFilePath(_path().default.relative(srcPath, absoluteSrcFilePath)).replace(/HelloWorld/g, newProjectName).replace(/helloworld/g, newProjectName.toLowerCase()); // Templates may contain files that we don't want to copy.

。。。

//筆者注:這裡省略了一些校驗邏輯,具體實現參考源碼



let contentChangedCallback = null;



if (options.upgrade && !options.force) {

contentChangedCallback = (_destPath, contentChanged) => upgradeFileContentChangedCallback(absoluteSrcFilePath, relativeFilePath, contentChanged);

}



//筆者注:下面是文件(夾)複製修改的最終實現方法

(0, _copyAndReplace.default)(absoluteSrcFilePath, _path().default.resolve(destPath, relativeFilePath), {

'Hello App Display Name': options.displayName || newProjectName,

HelloWorld: newProjectName,

helloworld: newProjectName.toLowerCase()

}, contentChangedCallback);

});

}/<code>

可以看到,該方法的核心是,對模板工程目錄下所有文件(文件夾)遞歸調用了_copyAndReplace.defaul方法。(node_modules/@react-native-community/cli/build/tools/copyAndReplace.js),該方法包括三個部分,下面分別介紹:

<code>function copyAndReplace(srcPath, destPath, replacements, contentChangedCallback) {



if (_fs().default.lstatSync(srcPath).isDirectory()) {

if (!_fs().default.existsSync(destPath)) {

_fs().default.mkdirSync(destPath);

} // Not recursive

return;

}

...

}/<code>

_copyAndReplace第一部分:這段代碼實際上是判斷是否是文件夾,如果是,則直接創建新文件夾。

<code>function copyAndReplace(srcPath, destPath, replacements, contentChangedCallback) {
。。。
const extension = _path().default.extname(srcPath);

if (binaryExtensions.indexOf(extension) !== -1) {
// Binary file 二進制文件
let shouldOverwrite = 'overwrite';
if (contentChangedCallback) {
const newContentBuffer = _fs().default.readFileSync(srcPath);
。。。
//筆者注:這裡是對二進制文件是否需要重寫的校驗,具體實現參考源碼
}


if (shouldOverwrite === 'overwrite') {
copyBinaryFile(srcPath, destPath, err => {
if (err) {
throw err;
}
});
}
} else {
。。。/<code>

_copyAndReplace第二部分:這一段,是對二進制文件的直接複製處理

<code>function copyAndReplace(srcPath, destPath, replacements, contentChangedCallback) {
。。。
} else {
// Text file 文本文件
const srcPermissions = _fs().default.statSync(srcPath).mode;



//筆者注:讀入文本文件,保存在變量content中
let content = _fs().default.readFileSync(srcPath, 'utf8');

/*筆者注:下面的forEach是對讀入的文本文件進行查找替換文本,主要是把模板工程中的HelloWorld替換成我們的目標工程名字
調試可看到當前replacements參數:
{
'Hello App Display Name': 'MyRNProject',
HelloWorld: 'MyRNProject',
helloworld: 'myrnproject'
}
*/
Object.keys(replacements).forEach(regex => {
content = content.replace(new RegExp(regex, 'g'), replacements[regex]);
});
let shouldOverwrite = 'overwrite';

。。。


//筆者注:寫入文本文件
if (shouldOverwrite === 'overwrite') {
_fs().default.writeFileSync(destPath, content, {
encoding: 'utf8',
mode: srcPermissions
});
}
//筆者注:文本文件處理結束
}

//筆者注:copyAndReplace方法結束
}/<code>

_copyAndReplace第三部分:這裡文本文件的處理是重點,其實現方法是先把文本文件讀成一個字符串,然後查找替換字符串中的模板名稱,替換為我們設置在模板工程的新名稱。

至此,工程目錄結構、文件(文件夾)的創建工作完成。


5)node依賴包:node modules安裝

即generateProject方法的第二步,這裡比較簡單,直接上源碼:

<code>async function generateProject(destinationRoot, newProjectName, options) {
。。。


//筆者注:第二步,安裝依賴的react庫(node Module)
await PackageManager.install([`react@${reactVersion}`], {
root: destinationRoot
});


_cliTools().logger.info('Adding required dev dependencies');


//筆者注:這裡是安裝開發環境下依賴的輔助工具庫,例如下面的babel、eslint等等
await PackageManager.installDev(['@babel/core', '@babel/runtime', '@react-native-community/eslint-config', 'eslint', 'jest', 'babel-jest', 'metro-react-native-babel-preset', `react-test-renderer@${reactVersion}`], {
root: destinationRoot
});
addJestToPackageJson(destinationRoot);


。。。
}/<code>

6)iOS依賴包:pods安裝

即generateProject方法的第三步,這裡同樣比較簡單,直接上源碼:

<code>async function generateProject(destinationRoot, newProjectName, options) {
。。。
if (_process().default.platform === 'darwin') {
//筆者注:這裡是判斷如果是mac環境,自動安裝iOS工程的依賴庫(通過cocopods安裝)
_cliTools().logger.info('Installing required CocoaPods dependencies');


await (0, _installPods.default)({
projectName: newProjectName
});
}


//筆者注:至此,安裝完成,打印幫助信息。
(0, _printRunInstructions.default)(destinationRoot, newProjectName);
}/<code>

這裡是調用了node_modules/@react-native-community/cli/build/tools/installPods.js模塊,installPods是對Cocopods的調用封裝,包括cocopods的安裝狀態,版本校驗等。具體實現不是本文重點,感興趣的可以自行查看源碼。

7)React-Native Local-Cli回顧

可以看到,第二部分(React-Native Local-Cli部分)的創建過程實際包括瞭如下幾個步驟:

•創建工程結構:通過_templates.createProjectFromTemplate方法根據RN包內置的模板工程,創建用戶的新RN工程結構

•安裝node依賴包

–安裝react庫

–安裝開發依賴庫:babel、eslint等

•安裝iOS工程依賴庫:通過_installPods.default安裝iOS工程的依賴pods,最終會生成iOS的workspace


總結

至此,我們終於搞明白了整個腳手架的原理和執行過程,實際包括了兩大部分,每個部分的具體步驟總結如下:

1.react-native-cli部分

–1.1 下載安裝react-native-cli Node包(命令行工具) npm install -g react-native-cli

–1.2 執行react-native init MyRNProject (react-native-cli包內的init命令),創建工程根目錄,下載安裝react-native 的node module。

2.react-native local-cli 部分

–2.1 創建工程結構(從模板template拷貝修改)

–2.2 安裝node依賴包(公共依賴、dev依賴等)

–2.3 安裝iOS依賴(pods依賴)


寫在最後

本文帶領大家以node.js小白的身份從頭到尾捋了一遍react-native腳手架的執行流程和原理,文中介紹的一些調試技巧和方法希望對大家有所幫助或啟發。特別是在面對一些新技術或者不熟悉的領域時,我們一定要多思考,充分利用已有的一些知識、經驗,聯想、推測和大膽假設,一步一步驗證嘗試,最終肯定會越來越接近事實的真相。


分享到:


相關文章: