国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

深度解析`create-react-app`源碼

waruqi / 1553人閱讀

摘要:這個選項看意思就知道了,默認(rèn)使用來安裝,運行,如果你沒有使用,你可能就需要這個配置了,指定使用。

2018-06-13 更新。昨天突然好奇在Google上搜了一波關(guān)于create-react-app 源碼的關(guān)鍵詞,發(fā)現(xiàn)掘金出現(xiàn)好幾篇仿文,就連我開頭前沿瞎幾把啰嗦的話都抄,我還能說什么是吧?以后博客還是首發(fā)在Github上,地址戳這里戳這里!!轉(zhuǎn)載求你們注明出處、改編求你們貼一下參考鏈接...

2018-01-26 更新。這兩天我邊讀邊思考我是不是真的懂了,我發(fā)現(xiàn)我有個重大的失誤,我弄錯了學(xué)習(xí)的順序,學(xué)習(xí)一個新的東西,我們應(yīng)該是先學(xué)會熟練的使用它,然后在去探究它的原理,我居然把第一步忽略了,這明顯是錯誤的,所以我今天在開頭新補充一節(jié)使用說明,同時對后面做一些修改和補充。

之前寫了幾篇關(guān)于搭建react環(huán)境的文,一直還沒有完善它,這次擼完這波源碼在重新完善之前的從零搭建完美的react開發(fā)打包測試環(huán)境,如果你對如何從零搭建一個react項目有興趣,或者是還沒有經(jīng)驗的小白,可以期待一下,作為我看完源碼的成果作品。

如果后續(xù)有更正或者更新的地方,會在頂部加以說明。

前言

這段時間公司的事情變得比較少,空下了很多時間,作為一個剛剛畢業(yè)初入職場的菜鳥級程序員,一點都不敢放松,秉持著我為人人的思想也想為開源社區(qū)做點小小的貢獻(xiàn),但是一直又沒有什么明確的目標(biāo),最近在努力的準(zhǔn)備吃透react,加上react的腳手架工具create-react-app已經(jīng)很成熟了,初始化一個react項目根本看不到它到底是怎么給我搭建的這個開發(fā)環(huán)境,又是怎么做到的,我還是想知道知道,所以就把他拖出來溜溜。

文中若有錯誤或者需要指正的地方,多多指教,共同進步。

使用說明

就像我開頭說的那樣,學(xué)習(xí)一個新的東西,應(yīng)該是先知道如何用,然后在來看他是怎么實現(xiàn)的。create-react-app到底是個什么東西,總結(jié)一句話來說,就是官方提供的快速搭建一個新的react項目的腳手架工具,類似于vuevue-cliangularangular-cli,至于為什么不叫react-cli是一個值得深思的問題...哈哈哈,有趣!

不說廢話了,貼個圖,直接看create-react-app的命令幫助。

概略說明

畢竟它已經(jīng)是一個很成熟的工具了,說明也很完善,重點對其中--scripts-version說一下,其他比較簡單,大概說一下,注意有一行Only is required,直譯一下,僅僅只有項目名稱是必須的,也就是說你在用create-react-app命令的時候,必須在其后跟上你的項目名稱,其實這里說的不準(zhǔn)確,像--version --info --help這三個選項是不需要帶項目名稱的,具體看下面:

create-react-app -V(or --version):這個選項可以多帶帶使用,打印版本信息,每個工具基本都有吧?

create-react-app --info:這個選項也可以多帶帶使用,打印當(dāng)前系統(tǒng)跟react相關(guān)的開發(fā)環(huán)境參數(shù),也就是操作系統(tǒng)是什么啊,Node版本啊之類的,可以自己試一試。

create-react-app -h(or --help):這個肯定是可以多帶帶使用的,不然怎么打印幫助信息,不然就沒有上面的截圖了。

也就是說除了上述三個參數(shù)選項是可以脫離必須參數(shù)項目名稱以外來多帶帶使用的,因為它們都跟你要初始化的react項目無關(guān),然后剩下的參數(shù)就是對要初始化的react項目進行配置的,也就是說三個參數(shù)是可以同時出現(xiàn)的,來看一下它們分別的作用:

create-react-app --verbose:看上圖,打印本地日志,其實他是npmyarn安裝外部依賴包可以加的選項,可以打印安裝有錯時的信息。

create-react-app --scripts-version:由于它本身把創(chuàng)建目錄初始化步驟和控制命令分離了,用來控制react項目的開發(fā)、打包和測試都放在了react-scripts里面,所以這里可以多帶帶來配置控制的選項,可能這樣你還不是很明白,我下面具體說。

create-react-app --use-npm:這個選項看意思就知道了,create-react-app默認(rèn)使用yarn來安裝,運行,如果你沒有使用yarn,你可能就需要這個配置了,指定使用npm

定制選項

關(guān)于--scripts-version我還要多說一點,其實在上述截圖中我們已經(jīng)可以看到,create-react-app本身已經(jīng)對其中選項進行了說明,一共有四種情況,我并沒有一一去試他,因為還挺麻煩的,以后如果用到了再來補,我先來大概推測一下他們的意思:

指定版本為0.8.2

npm發(fā)布自己的react-scripts

在自己的網(wǎng)站上設(shè)置一個.tgz的下載包

在自己的網(wǎng)站上設(shè)置一個.tar.gz的下載包

從上述看的出來create-react-app對于開發(fā)者還是很友好的,可以自己去定義很多東西,如果你不想這么去折騰,它也提供了標(biāo)準(zhǔn)的react-scripts供開發(fā)者使用,我一直也很好奇這個,之后我在來多帶帶說官方標(biāo)準(zhǔn)的react配置是怎么做的。

目錄分析

隨著它版本的迭代,源碼肯定是會發(fā)生變化的,我這里下載的是v1.1.0,大家可以自行在github上下載這個版本,找不到的戳鏈接。

主要說明

我們來看一下它的目錄結(jié)構(gòu)

├── .github
├── packages
├── tasks
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .travis.yml
├── .yarnrc
├── appveyor.cleanup-cache.txt
├── appveyor.yml
├── CHANGELOG-0.x.md
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── lerna.json
├── LICENSE
├── package.json
├── README.md
└── screencast.svg

咋一看好多啊,我的天啊,到底要怎么看,其實仔細(xì)一晃,好像很多一眼就能看出來是什么意思,大概說一下每個文件都是干嘛的,具體的我也不知道啊,往下看,一步一步來。

.github:這里面放著當(dāng)你在這個項目提issuepr時候的規(guī)范

packages:字面意思就是包們.....暫時不管,后面詳說 ----> 重點

tasks:字面意思就是任務(wù)們.....暫時不管,后面詳說 ----> 重點

.eslintignore: eslint檢查時忽略文件

.eslintrceslint檢查配置文件

.gitignoregit提交時忽略文件

.travis.ymltravis配置文件

.yarnrcyarn配置文件

appveyor.cleanup-cache.txt:里面有一行Edit this file to trigger a cache rebuild編輯此文件觸發(fā)緩存,具體干嘛的,暫時不議

appveyor.ymlappveyor配置文件

CHANGELOG-0.x.md:版本0.X開頭的變更說明文件

CHANGELOG.md:當(dāng)前版本變更說明文件

CODE_OF_CONDUCT.mdfacebook代碼行為準(zhǔn)則說明

CONTRIBUTING.md:項目的核心說明

lerna.jsonlerna配置文件

LICENSE:開源協(xié)議

package.json:項目配置文件

README.md:項目使用說明

screencast.svg:圖片...

看了這么多文件,是不是打退堂鼓了?哈哈哈哈,好了好了,進入正題,其實上述對于我們閱讀源碼有用的只有packagestaskspackage.json三個文件而已,而且本篇能用到的也就packagespackage.json,是不是想打我.....我也只是想告訴大家這些文件有什么用,它們都是有各自的作用的,如果還不了解,參考下面的參考鏈接。

參考鏈接

eslint相關(guān)的:eslint官網(wǎng)
travis相關(guān)的:travis官網(wǎng) travis入門
yarn相關(guān)的:yarn官網(wǎng)
appveyor相關(guān)的:appveyor官網(wǎng)
lerna相關(guān)的:lerna官網(wǎng)

工具自行了解,本文只說源碼相關(guān)的packagespackage.json

尋找入口

現(xiàn)在的前端項目大多數(shù)都有很多別的依賴,不在像以前那些原生javascript的工具庫,拿到源碼文件,就可以開始看了,像jQueryunderscore等等,一個兩個文件包含了它所有的內(nèi)容,雖然也有很框架會有umd規(guī)范的文件可以直接閱讀,像better-scroll等等,但是其實他在書寫源碼的時候還是拆分成了很多塊,最后在用打包工具整合在一起了。但是像create-react-app這樣的腳手架工具好像不能像之前那種方法來看了,必須找到整個程序的入口,在逐步突破,所以最開始的工具肯定是尋找入口。

開始關(guān)注

拿到一個項目我們應(yīng)該從哪個文件開始看起呢?只要是基于npm管理的,我都推薦從package.json文件開始看,人家是項目的介紹文件,你不看它看啥。

它里面理論上應(yīng)該是有名稱、版本等等一些說明性信息,但是都沒用,看幾個重要的配置。

"workspaces": [
    "packages/*"
],

關(guān)于workspaces一開始我在npm的說明文檔里面沒找到,雖然從字面意思我們也能猜到它的意思是實際工作的目錄是packages,后來我查了一下是yarn里面的東東,具體看這篇文章,用于在本地測試,具體不關(guān)注,只是從這里我們知道了真正的起作用的文件都在packages里面。

重點關(guān)注

從上述我們知道現(xiàn)在真正需要關(guān)注的內(nèi)容都在packages里面,我們來看看它里面都是有什么東東:

├── babel-preset-react-app    --> 暫不關(guān)注
├── create-react-app
├── eslint-config-react-app   --> 暫不關(guān)注
├── react-dev-utils           --> 暫不關(guān)注
├── react-error-overlay       --> 暫不關(guān)注
└── react-scripts             --> 核心啊,還是暫不關(guān)注

里面有六個文件夾,哇塞,又是6個多帶帶的項目,這要看到何年何月.....是不是有這種感觸,放寬心大膽的看,先想一下我們在安裝了create-react-app后在,在命令行輸入的是create-react-app的命令,所以我們大膽的推測關(guān)于這個命令應(yīng)該都是存在了create-react-app下,在這個目錄下同樣有package.json文件,現(xiàn)在我們把這6個文件拆分成6個項目來分析,上面也說了,看一個項目首先看package.json文件,找到其中的重點:

"bin": {
    "create-react-app": "./index.js"
}

找到重點了,package.json文件中的bin就是在命令行中可以運行的命令,也就是說我們在執(zhí)行create-react-app命令的時候,就是執(zhí)行create-react-app目錄下的index.js文件。

多說兩句

關(guān)于package.json中的bin選項,其實是基于node環(huán)境運行之后的內(nèi)容。舉個簡單的例子,在我們安裝create-react-app后,執(zhí)行create-react-app等價于執(zhí)行node index.js

create-react-app目錄解析

經(jīng)過以上一系列的查找,我們終于艱難的找到了create-react-app命令的中心入口,其他的都先不管,我們打開packages/create-react-app目錄,仔細(xì)一瞅,噢喲,只有四個文件,四個文件我們還搞不定嗎?除了package.jsonREADME.md就只剩兩個能看的文件了,我們來看看這兩個文件。

index.js

既然之前已經(jīng)看到packages/create-react-app/package.json中關(guān)于bin的設(shè)置,就是執(zhí)行index.js文件,我們就從index.js入手,開始瞅瞅源碼到底都有些蝦米。

除了一大串的注釋以外,代碼其實很少,全貼上來了:

var chalk = require("chalk");

var currentNodeVersion = process.versions.node; // 返回Node版本信息,如果有多個版本返回多個版本
var semver = currentNodeVersion.split("."); // 所有Node版本的集合
var major = semver[0]; // 取出第一個Node版本信息

// 如果當(dāng)前版本小于4就打印以下信息并終止進程
if (major < 4) {
  console.error(
    chalk.red(
      "You are running Node " +
        currentNodeVersion +
        ".
" +
        "Create React App requires Node 4 or higher. 
" +
        "Please update your version of Node."
    )
  );
  process.exit(1); // 終止進程
}

// 沒有小于4就引入以下文件繼續(xù)執(zhí)行
require("./createReactApp");

咋一眼看過去其實你就知道它大概是什么意思了....檢查Node.js的版本,小于4就不執(zhí)行了,我們分開來看一下,這里他用了一個庫chalk ,理解起來并不復(fù)雜,一行一行的解析。

chalk:這個對這段代碼的實際影響就是在命令行中,將輸出的信息變色。也就引出了這個庫的作用改變命令行中輸出信息的樣式。npm地址

其中有幾個Node自身的API

process.versions 返回一個對象,包含Node以及它的依賴信息

process.exit 結(jié)束Node進程,1是狀態(tài)碼,表示有異常沒有處理

在我們經(jīng)過index.js后,就來到了createReactApp.js,下面再繼續(xù)看。

createReactApp.js

當(dāng)我們本機上的Node版本大于4的時候就要繼續(xù)執(zhí)行這個文件了,打開這個文件,代碼還不少,大概700多行吧,我們慢慢拆解。

這里放個小技巧,在讀源碼的時候,可以在開一個寫代碼的窗口,跟著寫一遍,執(zhí)行過的代碼可以在源文件中先刪除,這樣700行代碼,當(dāng)你讀了200行的時候,源文件就只剩500行了,不僅有成就感繼續(xù)閱讀,也把不執(zhí)行的邏輯先刪除了,影響不到你讀其他地方。
const validateProjectName = require("validate-npm-package-name");
const chalk = require("chalk");
const commander = require("commander");
const fs = require("fs-extra");
const path = require("path");
const execSync = require("child_process").execSync;
const spawn = require("cross-spawn");
const semver = require("semver");
const dns = require("dns");
const tmp = require("tmp");
const unpack = require("tar-pack").unpack;
const url = require("url");
const hyperquest = require("hyperquest");
const envinfo = require("envinfo");

const packageJson = require("./package.json");

打開代碼一排依賴,懵逼....我不可能挨著去查一個個依賴是用來干嘛的吧?所以,我的建議就是先不管,用到的時候在回來看它是干嘛的,理解更加透徹一些,繼續(xù)往下看。

let projectName; // 定義了一個用來存儲項目名稱的變量

const program = new commander.Command(packageJson.name)
  .version(packageJson.version) // 輸入版本信息,使用`create-react-app -v`的時候就用打印版本信息
  .arguments("") // 使用`create-react-app ` 尖括號中的參數(shù)
  .usage(`${chalk.green("")} [options]`) //  使用`create-react-app`第一行打印的信息,也就是使用說明
  .action(name => {
    projectName = name; // 此處action函數(shù)的參數(shù)就是之前argument中的 初始化項目名稱 --> 此處影響后面
  })
  .option("--verbose", "print additional logs") // option配置`create-react-app -[option]`的選項,類似 --help -V
  .option("--info", "print environment debug info") // 打印本地相關(guān)開發(fā)環(huán)境,操作系統(tǒng),`Node`版本等等
  .option(
    "--scripts-version ",
    "use a non-standard version of react-scripts"
  ) // 這我之前就說過了,指定特殊的`react-scripts`
  .option("--use-npm") // 默認(rèn)使用`yarn`,指定使用`npm`
  .allowUnknownOption() // 這個我沒有在文檔上查到,直譯就是允許無效的option 大概意思就是我可以這樣`create-react-app  -la` 其實 -la 并沒有定義,但是我還是可以這么做而不會保存
  .on("--help", () => {
    // 此處省略了一些打印信息
  }) // on("--help") 用來定制打印幫助信息 當(dāng)使用`create-react-app -h(or --help)`的時候就會執(zhí)行其中的代碼,基本都是些打印信息
  .parse(process.argv); // 這個就是解析我們正常的`Node`進程,可以這么理解沒有這個東東,`commander`就不能接管`Node`

在上面的代碼中,我把無關(guān)緊要打印信息省略了,這段代碼算是這個文件的關(guān)鍵入口地此處他new了一個commander,這是個啥東東呢?這時我們就返回去看它的依賴,找到它是一個外部依賴,這時候怎么辦呢?不可能打開node_modules去里面找撒,很簡單,打開npm官網(wǎng)查一下這個外部依賴。

commander:概述一下,Node命令接口,也就是可以用它代管Node命令。npm地址

上述只是commander用法的一種實現(xiàn),沒有什么具體好說的,了解了commander就不難,這里的定義也就是我們在命令行中看到的那些東西,比如參數(shù),比如打印信息等等,我們繼續(xù)往下來。

// 判斷在命令行中執(zhí)行`create-react-app ` 有沒有name,如果沒有就繼續(xù)
if (typeof projectName === "undefined") {
  // 當(dāng)沒有傳name的時候,如果帶了 --info 的選項繼續(xù)執(zhí)行下列代碼,這里配置了--info時不會報錯
  if (program.info) {
    // 打印當(dāng)前環(huán)境信息和`react`、`react-dom`, `react-scripts`三個包的信息
    envinfo.print({
      packages: ["react", "react-dom", "react-scripts"],
      noNativeIDE: true,
      duplicates: true,
    });
    process.exit(0); // 正常退出進程
  }
  // 在沒有帶項目名稱又沒帶 --info 選項的時候就會打印一堆錯誤信息,像--version 和 --help 是commander自帶的選項,所以不用多帶帶配置
  console.error("Please specify the project directory:");
  console.log(
    `  ${chalk.cyan(program.name())} ${chalk.green("")}`
  );
  console.log();
  console.log("For example:");
  console.log(`  ${chalk.cyan(program.name())} ${chalk.green("my-react-app")}`);
  console.log();
  console.log(
    `Run ${chalk.cyan(`${program.name()} --help`)} to see all options.`
  );
  process.exit(1); // 拋出異常退出進程
}

還記得上面把create-react-app 中的項目名稱賦予了projectName 變量嗎?此處的作用就是看看用戶有沒有傳這個參數(shù),如果沒有就會報錯,并顯示一些幫助信息,這里用到了另外一個外部依賴envinfo

envinfo:可以打印當(dāng)前操作系統(tǒng)的環(huán)境和指定包的信息。 npm地址

到這里我還要吐槽一下segmentfault的編輯器...我同時打開視圖和編輯好卡...捂臉.png!

這里我之前省略了一個東西,還是拿出來說一下:

const hiddenProgram = new commander.Command()
  .option(
    "--internal-testing-template ",
    "(internal usage only, DO NOT RELY ON THIS) " +
      "use a non-standard application template"
  )
  .parse(process.argv);

create-react-app在初始化一個項目的時候,會生成一個標(biāo)準(zhǔn)的文件夾,這里有一個隱藏的選項--internal-testing-template,用來更改初始化目錄的模板,這里他已經(jīng)說了,供內(nèi)部使用,應(yīng)該是開發(fā)者們開發(fā)時候用的,所以不建議大家使用這個選項。

我們繼續(xù)往下看,有幾個提前定義的函數(shù),我們不管,直接找到第一個被執(zhí)行的函數(shù):

createApp(
  projectName,
  program.verbose,
  program.scriptsVersion,
  program.useNpm,
  hiddenProgram.internalTestingTemplate
);

一個createAPP函數(shù),接收了5個參數(shù)

projectName: 執(zhí)行create-react-app name的值,也就是初始化項目的名稱

program.verbose:這里在說一下commanderoption選項,如果加了這個選項這個值就是true,否則就是false,也就是說這里如果加了--verbose,那這個參數(shù)就是true,至于verbose是什么,我之前也說過了,在yarn或者npm安裝的時候打印本地信息,也就是如果安裝過程中出錯,我們可以找到額外的信息。

program.scriptsVersion:與上述同理,指定react-scripts版本

program.useNpm:以上述同理,指定是否使用npm,默認(rèn)使用yarn

hiddenProgram.internalTestingTemplate:這個東東,我之前給他省略了,我在前面已經(jīng)補充了,指定初始化的模板,人家說了內(nèi)部使用,大家可以忽略了,應(yīng)該是用于開發(fā)測試模板目錄的時候使用。

找到了第一個執(zhí)行的函數(shù)createApp,我們就來看看createApp函數(shù)到底做了什么?

createApp()
function createApp(name, verbose, version, useNpm, template) {
  const root = path.resolve(name); // 獲取當(dāng)前進程運行的位置,也就是文件目錄的絕對路徑
  const appName = path.basename(root); // 返回root路徑下最后一部分

  checkAppName(appName); // 執(zhí)行 checkAppName 函數(shù) 檢查文件名是否合法
  fs.ensureDirSync(name); // 此處 ensureDirSync 方法是外部依賴包 fs-extra 而不是 node本身的fs模塊,作用是確保當(dāng)前目錄下有指定文件名,沒有就創(chuàng)建
  // isSafeToCreateProjectIn 函數(shù) 判斷文件夾是否安全
  if (!isSafeToCreateProjectIn(root, name)) {
    process.exit(1); // 不合法結(jié)束進程
  }
  // 到這里打印成功創(chuàng)建了一個`react`項目在指定目錄下
  console.log(`Creating a new React app in ${chalk.green(root)}.`);
  console.log();
  // 定義package.json基礎(chǔ)內(nèi)容
  const packageJson = {
    name: appName,
    version: "0.1.0",
    private: true,
  };
  // 往我們創(chuàng)建的文件夾中寫入package.json文件
  fs.writeFileSync(
    path.join(root, "package.json"),
    JSON.stringify(packageJson, null, 2)
  );
  // 定義常量 useYarn 如果傳參有 --use-npm useYarn就是false,否則執(zhí)行 shouldUseYarn() 檢查yarn是否存在
  // 這一步就是之前說的他默認(rèn)使用`yarn`,但是可以指定使用`npm`,如果指定使用了`npm`,`useYarn`就是`false`,不然執(zhí)行 shouldUseYarn 函數(shù)
  // shouldUseYarn 用于檢測本機是否安裝了`yarn`
  const useYarn = useNpm ? false : shouldUseYarn();
  // 取得當(dāng)前node進程的目錄,之前還懂為什么要多帶帶取一次,之后也明白了,下一句代碼將會改變這個值,所以如果我后面要用這個值,后續(xù)其實取得值將不是這個
  // 所以這里的目的就是提前存好,免得我后續(xù)使用的時候不好去找,這個地方就是我執(zhí)行初始化項目的目錄,而不是初始化好的目錄,是初始化的上級目錄,有點繞..
  const originalDirectory = process.cwd();
  // 修改進程目錄為底下子進程目錄
  // 在這里就把進程目錄修改為了我們創(chuàng)建的目錄
  process.chdir(root);
  // 如果不使用yarn 并且checkThatNpmCanReadCwd()函數(shù) 這里之前說的不是很對,在重新說一次
  // checkThatNpmCanReadCwd 這個函數(shù)的作用是檢查進程目錄是否是我們創(chuàng)建的目錄,也就是說如果進程不在我們創(chuàng)建的目錄里面,后續(xù)再執(zhí)行`npm`安裝的時候就會出錯,所以提前檢查
  if (!useYarn && !checkThatNpmCanReadCwd()) {
    process.exit(1);
  }
  // 比較 node 版本,小于6的時候發(fā)出警告
  // 之前少說了一點,小于6的時候指定`react-scripts`標(biāo)準(zhǔn)版本為0.9.x,也就是標(biāo)準(zhǔn)的`react-scripts@1.0.0`以上的版本不支持`node`在6版本之下
  if (!semver.satisfies(process.version, ">=6.0.0")) {
    console.log(
      chalk.yellow(
        `You are using Node ${process.version} so the project will be bootstrapped with an old unsupported version of tools.

` +
          `Please update to Node 6 or higher for a better, fully supported experience.
`
      )
    );
    // Fall back to latest supported react-scripts on Node 4
    version = "react-scripts@0.9.x";
  }
  // 如果沒有使用yarn 也發(fā)出警告
  // 這里之前也沒有說全,還判斷了`npm`的版本是不是在3以上,如果沒有依然指定安裝`react-scripts@0.9.x`版本
  if (!useYarn) {
    const npmInfo = checkNpmVersion();
    if (!npmInfo.hasMinNpm) {
      if (npmInfo.npmVersion) {
        console.log(
          chalk.yellow(
            `You are using npm ${npmInfo.npmVersion} so the project will be boostrapped with an old unsupported version of tools.

` +
              `Please update to npm 3 or higher for a better, fully supported experience.
`
          )
        );
      }
      // Fall back to latest supported react-scripts for npm 3
      version = "react-scripts@0.9.x";
    }
  }
  // 傳入這些參數(shù)執(zhí)行run函數(shù)
  // 執(zhí)行完畢上述代碼以后,將執(zhí)行`run`函數(shù),但是我還是先把上述用到的函數(shù)全部說完,在來下一個核心函數(shù)`run`
  run(root, appName, version, verbose, originalDirectory, template, useYarn);
}

我這里先來總結(jié)一下這個函數(shù)都做了哪些事情,再來看看他用到的依賴有哪些,先說做了哪些事情,在我們的目錄下創(chuàng)建了一個項目目錄,并且校驗了這個目錄的名稱是否合法,這個目錄是否安全,然后往其中寫入了一個package.json的文件,并且判斷了當(dāng)前環(huán)境下應(yīng)該使用的react-scripts的版本,然后執(zhí)行了run函數(shù)。我們在來看看這個函數(shù)用了哪些外部依賴:

fs-extra:外部依賴,Node自帶文件模塊的外部擴展模塊 npm地址

semver:外部依賴,用于比較Node版本 npm地址

之后函數(shù)的函數(shù)依賴我都會進行詳細(xì)的解析,除了少部分特別簡單的函數(shù),然后我們來看看這個函數(shù)的函數(shù)依賴:

checkAppName():用于檢測文件名是否合法,

isSafeToCreateProjectIn():用于檢測文件夾是否安全

shouldUseYarn():用于檢測yarn在本機是否已經(jīng)安裝

checkThatNpmCanReadCwd():用于檢測npm是否在正確的目錄下執(zhí)行

checkNpmVersion():用于檢測npm在本機是否已經(jīng)安裝了

checkAppName()
function checkAppName(appName) {
  // 使用 validateProjectName 檢查包名是否合法返回結(jié)果,這個validateProjectName是外部依賴的引用,見下面說明
  const validationResult = validateProjectName(appName); 
  // 如果對象中有錯繼續(xù),這里就是外部依賴的具體用法
  if (!validationResult.validForNewPackages) {
    console.error(
      `Could not create a project called ${chalk.red(
        `"${appName}"`
      )} because of npm naming restrictions:`
    );
    printValidationResults(validationResult.errors);
    printValidationResults(validationResult.warnings);
    process.exit(1);
  }
  
  // 定義了三個開發(fā)依賴的名稱
  const dependencies = ["react", "react-dom", "react-scripts"].sort();
  // 如果項目使用了這三個名稱都會報錯,而且退出進程
  if (dependencies.indexOf(appName) >= 0) {
    console.error(
      chalk.red(
        `We cannot create a project called ${chalk.green(
          appName
        )} because a dependency with the same name exists.
` +
          `Due to the way npm works, the following names are not allowed:

`
      ) +
        chalk.cyan(dependencies.map(depName => `  ${depName}`).join("
")) +
        chalk.red("

Please choose a different project name.")
    );
    process.exit(1);
  }
}

它這個函數(shù)其實還蠻簡單的,用了一個外部依賴來校驗文件名是否符合npm包文件名的規(guī)范,然后定義了三個不能取得名字reactreact-domreact-scripts,外部依賴:

validate-npm-package-name:外部依賴,檢查包名是否合法。npm地址

其中的函數(shù)依賴:

printValidationResults():函數(shù)引用,這個函數(shù)就是我說的特別簡單的類型,里面就是把接收到的錯誤信息循環(huán)打印出來,沒什么好說的。

isSafeToCreateProjectIn()
function isSafeToCreateProjectIn(root, name) {
  // 定義了一堆文件名
  // 我今天早上仔細(xì)的看了一些,以下文件的來歷就是我們這些開發(fā)者在`create-react-app`中提的一些文件
  const validFiles = [
    ".DS_Store",
    "Thumbs.db",
    ".git",
    ".gitignore",
    ".idea",
    "README.md",
    "LICENSE",
    "web.iml",
    ".hg",
    ".hgignore",
    ".hgcheck",
    ".npmignore",
    "mkdocs.yml",
    "docs",
    ".travis.yml",
    ".gitlab-ci.yml",
    ".gitattributes",
  ];
  console.log();

  // 這里就是在我們創(chuàng)建好的項目文件夾下,除了上述文件以外不包含其他文件就會返回true
  const conflicts = fs
    .readdirSync(root)
    .filter(file => !validFiles.includes(file));
  if (conflicts.length < 1) {
    return true;
  }
  // 否則這個文件夾就是不安全的,并且挨著打印存在哪些不安全的文件
  console.log(
    `The directory ${chalk.green(name)} contains files that could conflict:`
  );
  console.log();
  for (const file of conflicts) {
    console.log(`  ${file}`);
  }
  console.log();
  console.log(
    "Either try using a new directory name, or remove the files listed above."
  );
  // 并且返回false
  return false;
}

他這個函數(shù)也算比較簡單,就是判斷創(chuàng)建的這個目錄是否包含除了上述validFiles里面的文件,至于這里面的文件是怎么來的,就是create-react-app在發(fā)展至今,開發(fā)者們提出來的。

shouldUseYarn()
function shouldUseYarn() {
  try {
    execSync("yarnpkg --version", { stdio: "ignore" });
    return true;
  } catch (e) {
    return false;
  }
}

就三行...其中execSync是由node自身模塊child_process引用而來,就是用來執(zhí)行命令的,這個函數(shù)就是執(zhí)行一下yarnpkg --version來判斷我們是否正確安裝了yarn,如果沒有正確安裝yarn的話,useYarn依然為false,不管指沒有指定--use-npm

execSync:引用自child_process.execSync,用于執(zhí)行需要執(zhí)行的子進程

checkThatNpmCanReadCwd()
function checkThatNpmCanReadCwd() {
  const cwd = process.cwd(); // 這里取到當(dāng)前的進程目錄
  let childOutput = null; // 定義一個變量來保存`npm`的信息
  try {
    // 相當(dāng)于執(zhí)行`npm config list`并將其輸出的信息組合成為一個字符串
    childOutput = spawn.sync("npm", ["config", "list"]).output.join("");
  } catch (err) {
    return true;
  }
  // 判斷是否是一個字符串
  if (typeof childOutput !== "string") {
    return true;
  }
  // 將整個字符串以換行符分隔
  const lines = childOutput.split("
");
  // 定義一個我們需要的信息的前綴
  const prefix = "; cwd = ";
  // 去整個lines里面的每個line查找有沒有這個前綴的一行
  const line = lines.find(line => line.indexOf(prefix) === 0);
  if (typeof line !== "string") {
    return true;
  }
  // 取出后面的信息,這個信息大家可以自行試一試,就是`npm`執(zhí)行的目錄
  const npmCWD = line.substring(prefix.length);
  // 判斷當(dāng)前目錄和執(zhí)行目錄是否是一致的
  if (npmCWD === cwd) {
    return true;
  }
  // 不一致就打印以下信息,大概意思就是`npm`進程沒有在正確的目錄下執(zhí)行
  console.error(
    chalk.red(
      `Could not start an npm process in the right directory.

` +
        `The current directory is: ${chalk.bold(cwd)}
` +
        `However, a newly started npm process runs in: ${chalk.bold(
          npmCWD
        )}

` +
        `This is probably caused by a misconfigured system terminal shell.`
    )
  );
  // 這里他對windows的情況作了一些多帶帶的判斷,沒有深究這些信息
  if (process.platform === "win32") {
    console.error(
      chalk.red(`On Windows, this can usually be fixed by running:

`) +
        `  ${chalk.cyan(
          "reg"
        )} delete "HKCUSoftwareMicrosoftCommand Processor" /v AutoRun /f
` +
        `  ${chalk.cyan(
          "reg"
        )} delete "HKLMSoftwareMicrosoftCommand Processor" /v AutoRun /f

` +
        chalk.red(`Try to run the above two lines in the terminal.
`) +
        chalk.red(
          `To learn more about this problem, read: https://blogs.msdn.microsoft.com/oldnewthing/20071121-00/?p=24433/`
        )
    );
  }
  return false;
}

這個函數(shù)我之前居然貼錯了,實在是不好意思。我之前沒有弄懂這個函數(shù)的意思,今天再來看的時候已經(jīng)豁然開朗了,它的意思上述代碼已經(jīng)解析了,其中用到了一個外部依賴:

cross-spawn:這個我之前說到了沒有?忘了,用來執(zhí)行node進程。npm地址

為什么用多帶帶用一個外部依賴,而不是用node自身的呢?來看一下cross-spawn它自己對自己的說明,Node跨平臺解決方案,解決在windows下各種問題。

checkNpmVersion()
function checkNpmVersion() {
  let hasMinNpm = false;
  let npmVersion = null;
  try {
    npmVersion = execSync("npm --version")
      .toString()
      .trim();
    hasMinNpm = semver.gte(npmVersion, "3.0.0");
  } catch (err) {
    // ignore
  }
  return {
    hasMinNpm: hasMinNpm,
    npmVersion: npmVersion,
  };
}

這個能說的也比較少,一眼看過去就知道什么意思了,返回一個對象,對象上面有兩個對對,一個是npm的版本號,一個是是否有最小npm版本的限制,其中一個外部依賴,一個Node自身的API我之前也都說過了,不說了。

看到到這里createApp()函數(shù)的依賴和執(zhí)行都結(jié)束了,接著執(zhí)行了run()函數(shù),我們繼續(xù)來看run()函數(shù)都是什么,我又想吐槽了,算了,忍住!!!

run()函數(shù)在createApp()函數(shù)的所有內(nèi)容執(zhí)行完畢后執(zhí)行,它接收7個參數(shù),先來看看。

root:我們創(chuàng)建的目錄的絕對路徑

appName:我們創(chuàng)建的目錄名稱

versionreact-scripts的版本

verbose:繼續(xù)傳入verbose,在createApp中沒有使用到

originalDirectory:原始目錄,這個之前說到了,到run函數(shù)中就有用了

tempalte:模板,這個參數(shù)之前也說過了,不對外使用

useYarn:是否使用yarn

具體的來看下面run()函數(shù)。

run()
function run(
  root,
  appName,
  version,
  verbose,
  originalDirectory,
  template,
  useYarn
) {
  // 這里對`react-scripts`做了大量的處理
  const packageToInstall = getInstallPackage(version, originalDirectory); // 獲取依賴包信息
  const allDependencies = ["react", "react-dom", packageToInstall]; // 所有的開發(fā)依賴包

  console.log("Installing packages. This might take a couple of minutes.");
  getPackageName(packageToInstall) // 獲取依賴包原始名稱并返回
    .then(packageName =>
      // 檢查是否離線模式,并返回結(jié)果和包名
      checkIfOnline(useYarn).then(isOnline => ({
        isOnline: isOnline,
        packageName: packageName,
      }))
    )
    .then(info => {
      // 接收到上述的包名和是否為離線模式
      const isOnline = info.isOnline;
      const packageName = info.packageName;
      console.log(
        `Installing ${chalk.cyan("react")}, ${chalk.cyan(
          "react-dom"
        )}, and ${chalk.cyan(packageName)}...`
      );
      console.log();
      // 安裝依賴
      return install(root, useYarn, allDependencies, verbose, isOnline).then(
        () => packageName
      );
    })
    .then(packageName => {
      // 檢查當(dāng)前`Node`版本是否支持包
      checkNodeVersion(packageName);
      // 檢查`package.json`的開發(fā)依賴是否正常
      setCaretRangeForRuntimeDeps(packageName);
      // `react-scripts`腳本的目錄
      const scriptsPath = path.resolve(
        process.cwd(),
        "node_modules",
        packageName,
        "scripts",
        "init.js"
      );
      // 引入`init`函數(shù)
      const init = require(scriptsPath);
      // 執(zhí)行目錄的拷貝
      init(root, appName, verbose, originalDirectory, template);
      // 當(dāng)`react-scripts`的版本為0.9.x發(fā)出警告
      if (version === "react-scripts@0.9.x") {
        console.log(
          chalk.yellow(
            `
Note: the project was boostrapped with an old unsupported version of tools.
` +
              `Please update to Node >=6 and npm >=3 to get supported tools in new projects.
`
          )
        );
      }
    })
    // 異常處理
    .catch(reason => {
      console.log();
      console.log("Aborting installation.");
      // 根據(jù)命令來判斷具體的錯誤
      if (reason.command) {
        console.log(`  ${chalk.cyan(reason.command)} has failed.`);
      } else {
        console.log(chalk.red("Unexpected error. Please report it as a bug:"));
        console.log(reason);
      }
      console.log();

      // 出現(xiàn)異常的時候?qū)h除目錄下的這些文件
      const knownGeneratedFiles = [
        "package.json",
        "npm-debug.log",
        "yarn-error.log",
        "yarn-debug.log",
        "node_modules",
      ];
      // 挨著刪除
      const currentFiles = fs.readdirSync(path.join(root));
      currentFiles.forEach(file => {
        knownGeneratedFiles.forEach(fileToMatch => {
          if (
            (fileToMatch.match(/.log/g) && file.indexOf(fileToMatch) === 0) ||
            file === fileToMatch
          ) {
            console.log(`Deleting generated file... ${chalk.cyan(file)}`);
            fs.removeSync(path.join(root, file));
          }
        });
      });
      // 判斷當(dāng)前目錄下是否還存在文件
      const remainingFiles = fs.readdirSync(path.join(root));
      if (!remainingFiles.length) {
        console.log(
          `Deleting ${chalk.cyan(`${appName} /`)} from ${chalk.cyan(
            path.resolve(root, "..")
          )}`
        );
        process.chdir(path.resolve(root, ".."));
        fs.removeSync(path.join(root));
      }
      console.log("Done.");
      process.exit(1);
    });
}

他這里對react-script做了很多處理,大概是由于react-script本身是有node版本的依賴的,而且在用create-react-app init 初始化一個項目的時候,是可以指定react-script的版本或者是外部自身定義的東東。

他在run()函數(shù)中的引用都是用Promise回調(diào)的方式來完成的,從我正式接觸Node開始就習(xí)慣用async/await,所以對Promise還真不熟,惡補了一番,下面我們來拆解其中的每一句和每一個函數(shù)的作用,先來看一下用到外部依賴還是之前那些不說了,來看看函數(shù)列表:

getInstallPackage():獲取要安裝的react-scripts版本或者開發(fā)者自己定義的react-scripts

getPackageName():獲取到正式的react-scripts的包名

checkIfOnline():檢查網(wǎng)絡(luò)連接是否正常

install():安裝開發(fā)依賴包

checkNodeVersion():檢查Node版本信息

setCaretRangeForRuntimeDeps():檢查發(fā)開依賴是否正確安裝,版本是否正確

init():將事先定義好的目錄文件拷貝到我的項目中

知道了個大概,我們在來逐一分析每個函數(shù)的作用:

getInstallPackage()
function getInstallPackage(version, originalDirectory) {
  let packageToInstall = "react-scripts"; // 定義常量 packageToInstall,默認(rèn)就是標(biāo)準(zhǔn)`react-scripts`包名
  const validSemver = semver.valid(version); // 校驗版本號是否合法
  if (validSemver) {
    packageToInstall += `@${validSemver}`; // 合法的話執(zhí)行,就安裝指定版本,在`npm install`安裝的時候指定版本為加上`@x.x.x`版本號,安裝指定版本的`react-scripts`
  } else if (version && version.match(/^file:/)) {
    // 不合法并且版本號參數(shù)帶有`file:`執(zhí)行以下代碼,作用是指定安裝包為我們自身定義的包
    packageToInstall = `file:${path.resolve(
      originalDirectory,
      version.match(/^file:(.*)?$/)[1]
    )}`;
  } else if (version) {
    // 不合法并且沒有`file:`開頭,默認(rèn)為在線的`tar.gz`文件
    // for tar.gz or alternative paths
    packageToInstall = version;
  }
  // 返回最終需要安裝的`react-scripts`的信息,或版本號或本地文件或線上`.tar.gz`資源
  return packageToInstall;
}

這個方法接收兩個參數(shù)version版本號,originalDirectory原始目錄,主要的作用是判斷react-scripts應(yīng)該安裝的信息,具體看每一行。

這里create-react-app本身提供了安裝react-scripts的三種機制,一開始初始化的項目是可以指定react-scripts的版本或者是自定義這個東西的,所以在這里他就提供了這幾種機制,其中用到的外部依賴只有一個semver,之前就說過了,不多說。

getPackageName()
function getPackageName(installPackage) {
  // 函數(shù)進來就根據(jù)上面的那個判斷`react-scripts`的信息來安裝這個包,用于返回正規(guī)的包名
  // 此處為線上`tar.gz`包的情況
  if (installPackage.match(/^.+.(tgz|tar.gz)$/)) {
    // 里面這段創(chuàng)建了一個臨時目錄,具體它是怎么設(shè)置了線上.tar.gz包我沒試就不亂說了
    return getTemporaryDirectory()
      .then(obj => {
        let stream;
        if (/^http/.test(installPackage)) {
          stream = hyperquest(installPackage);
        } else {
          stream = fs.createReadStream(installPackage);
        }
        return extractStream(stream, obj.tmpdir).then(() => obj);
      })
      .then(obj => {
        const packageName = require(path.join(obj.tmpdir, "package.json")).name;
        obj.cleanup();
        return packageName;
      })
      .catch(err => {
        console.log(
          `Could not extract the package name from the archive: ${err.message}`
        );
        const assumedProjectName = installPackage.match(
          /^.+/(.+?)(?:-d+.+)?.(tgz|tar.gz)$/
        )[1];
        console.log(
          `Based on the filename, assuming it is "${chalk.cyan(
            assumedProjectName
          )}"`
        );
        return Promise.resolve(assumedProjectName);
      });
  // 此處為信息中包含`git+`信息的情況
  } else if (installPackage.indexOf("git+") === 0) {
    return Promise.resolve(installPackage.match(/([^/]+).git(#.*)?$/)[1]);
  // 此處為只有版本信息的時候的情況
  } else if (installPackage.match(/.+@/)) {
    return Promise.resolve(
      installPackage.charAt(0) + installPackage.substr(1).split("@")[0]
    );
  // 此處為信息中包含`file:`開頭的情況
  } else if (installPackage.match(/^file:/)) {
    const installPackagePath = installPackage.match(/^file:(.*)?$/)[1];
    const installPackageJson = require(path.join(installPackagePath, "package.json"));
    return Promise.resolve(installPackageJson.name);
  }
  // 什么都沒有直接返回包名
  return Promise.resolve(installPackage);
}

他這個函數(shù)的目標(biāo)就是返回一個正常的依賴包名,比如我們什么都不帶就返回react-scripts,在比如我們是自己定義的包就返回my-react-scripts,繼續(xù)到了比較關(guān)鍵的函數(shù)了,接收一個installPackage參數(shù),從這函數(shù)開始就采用Promise回調(diào)的方式一直執(zhí)行到最后,我們來看看這個函數(shù)都做了什么,具體看上面每一行的注釋。

總結(jié)一句話,這個函數(shù)的作用就是返回正常的包名,不帶任何符號的,來看看它的外部依賴:

hyperquest:這個用于將http請求流媒體傳輸。npm地址

他本身還有函數(shù)依賴,這兩個函數(shù)依賴我都不多帶帶再說,函數(shù)的意思很好理解,至于為什么這么做我還沒想明白:

getTemporaryDirectory():不難,他本身是一個回調(diào)函數(shù),用來創(chuàng)建一個臨時目錄。

extractStream():主要用到node本身的一個流,這里我真沒懂為什么藥改用流的形式,就不發(fā)表意見了,在看其實我還是沒懂,要真正的明白是要去試一次,但是真的有點麻煩,不想去關(guān)注。

PS:其實這個函數(shù)很好理解就是返回正常的包名,但是里面的有些處理我都沒想通,以后理解深刻了在回溯一下。
checkIfOnline()
function checkIfOnline(useYarn) {
  if (!useYarn) {
    return Promise.resolve(true);
  }

  return new Promise(resolve => {
    dns.lookup("registry.yarnpkg.com", err => {
      let proxy;
      if (err != null && (proxy = getProxy())) {
        dns.lookup(url.parse(proxy).hostname, proxyErr => {
          resolve(proxyErr == null);
        });
      } else {
        resolve(err == null);
      }
    });
  });
}

這個函數(shù)本身接收一個是否使用yarn的參數(shù)來判斷是否進行后續(xù),如果使用的是npm就直接返回true了,為什么會有這個函數(shù)是由于yarn本身有個功能叫離線安裝,這個函數(shù)來判斷是否離線安裝,其中用到了外部依賴:

dns:用來檢測是否能夠請求到指定的地址。npm地址

install()
function install(root, useYarn, dependencies, verbose, isOnline) {
  // 封裝在一個回調(diào)函數(shù)中
  return new Promise((resolve, reject) => {
    let command; // 定義一個命令
    let args;  // 定義一個命令的參數(shù)
    // 如果使用yarn
    if (useYarn) {
      command = "yarnpkg";  // 命令名稱
      args = ["add", "--exact"]; // 命令參數(shù)的基礎(chǔ)
      if (!isOnline) {
        args.push("--offline");  // 此處接上面一個函數(shù)判斷是否是離線模式
      }
      [].push.apply(args, dependencies); // 組合參數(shù)和開發(fā)依賴 `react` `react-dom` `react-scripts`
      args.push("--cwd"); // 指定命令執(zhí)行目錄的地址
      args.push(root); // 地址的絕對路徑
      // 在使用離線模式時候會發(fā)出警告
      if (!isOnline) {
        console.log(chalk.yellow("You appear to be offline."));
        console.log(chalk.yellow("Falling back to the local Yarn cache."));
        console.log();
      }
    // 不使用yarn的情況使用npm
    } else {
      // 此處于上述一樣,命令的定義 參數(shù)的組合
      command = "npm";
      args = [
        "install",
        "--save",
        "--save-exact",
        "--loglevel",
        "error",
      ].concat(dependencies);
    }
    // 因為`yarn`和`npm`都可以帶這個參數(shù),所以就多帶帶拿出來了拼接到上面
    if (verbose) {
      args.push("--verbose");
    }
    // 這里就把命令組合起來執(zhí)行
    const child = spawn(command, args, { stdio: "inherit" });
    // 命令執(zhí)行完畢后關(guān)閉
    child.on("close", code => {
      // code 為0代表正常關(guān)閉,不為零就打印命令執(zhí)行錯誤的那條
      if (code !== 0) {
        reject({
          command: `${command} ${args.join(" ")}`,
        });
        return;
      }
      // 正常繼續(xù)往下執(zhí)行
      resolve();
    });
  });
}

又到了比較關(guān)鍵的地方了,仔細(xì)看每一行代碼注釋,此處函數(shù)的作用就是組合一個yarn或者npm的安裝命令,把這些模塊安裝到項目的文件夾中,其中用到的外部依賴cross-spawn前面有說了,就不說了。

其實執(zhí)行到這里,create-react-app已經(jīng)幫我們創(chuàng)建好了目錄,package.json并且安裝了所有的依賴,reactreact-domreact-scrpts,復(fù)雜的部分已經(jīng)結(jié)束,繼續(xù)往下走。

checkNodeVersion()
function checkNodeVersion(packageName) {
  // 找到`react-scripts`的`package.json`路徑
  const packageJsonPath = path.resolve(
    process.cwd(),
    "node_modules",
    packageName,
    "package.json"
  );
  // 引入`react-scripts`的`package.json`
  const packageJson = require(packageJsonPath);
  // 在`package.json`中定義了一個`engines`其中放著`Node`版本的信息,大家可以打開源碼`packages/react-scripts/package.json`查看
  if (!packageJson.engines || !packageJson.engines.node) {
    return;
  }
  // 比較進程的`Node`版本信息和最小支持的版本,如果比他小的話,會報錯然后退出進程
  if (!semver.satisfies(process.version, packageJson.engines.node)) {
    console.error(
      chalk.red(
        "You are running Node %s.
" +
          "Create React App requires Node %s or higher. 
" +
          "Please update your version of Node."
      ),
      process.version,
      packageJson.engines.node
    );
    process.exit(1);
  }
}

這個函數(shù)直譯一下,檢查Node版本,為什么要檢查了?之前我已經(jīng)說過了react-scrpts是需要依賴Node版本的,也就是說低版本的Node不支持,其實的外部依賴也是之前的幾個,沒什么好說的。

setCaretRangeForRuntimeDeps()
function setCaretRangeForRuntimeDeps(packageName) {
  const packagePath = path.join(process.cwd(), "package.json");  // 取出創(chuàng)建項目的目錄中的`package.json`路徑
  const packageJson = require(packagePath); // 引入`package.json`
  // 判斷其中`dependencies`是否存在,不存在代表我們的開發(fā)依賴沒有成功安裝
  if (typeof packageJson.dependencies === "undefined") {
    console.error(chalk.red("Missing dependencies in package.json"));
    process.exit(1);
  }
  // 拿出`react-scripts`或者是自定義的看看`package.json`中是否存在
  const packageVersion = packageJson.dependencies[packageName];
  if (typeof packageVersion === "undefined") {
    console.error(chalk.red(`Unable to find ${packageName} in package.json`));
    process.exit(1);
  }
  // 檢查`react` `react-dom` 的版本 
  makeCaretRange(packageJson.dependencies, "react");
  makeCaretRange(packageJson.dependencies, "react-dom");
  // 重新寫入文件`package.json`
  fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2));
}

這個函數(shù)我也不想說太多了,他的作用并沒有那么大,就是用來檢測我們之前安裝的依賴是否寫入了package.json里面,并且對依賴的版本做了檢測,其中一個函數(shù)依賴:

makeCaretRange():用來對依賴的版本做檢測

我沒有多帶帶對其中的子函數(shù)進行分析,是因為我覺得不難,而且對主線影響不大,我不想貼太多說不完。

到這里createReactApp.js里面的源碼都分析完了,咦!你可能會說你都沒說init()函數(shù),哈哈哈,看到這里說明你很認(rèn)真哦,init()函數(shù)是放在packages/react-scripts/script目錄下的,但是我還是要給他說了,因為它其實跟react-scripts包聯(lián)系不大,就是個copy他本身定義好的模板目錄結(jié)構(gòu)的函數(shù)。

init()

它本身接收5個參數(shù):

appPath:之前的root,項目的絕對路徑

appName:項目的名稱

verbose:這個參數(shù)我之前說過了,npm安裝時額外的信息

originalDirectory:原始目錄,命令執(zhí)行的目錄

template:其實其中只有一種類型的模板,這個選項的作用就是配置之前我說過的那個函數(shù),測試模板

// 當(dāng)前的包名,也就是這個命令的包
const ownPackageName = require(path.join(__dirname, "..", "package.json")).name;
// 當(dāng)前包的路徑
const ownPath = path.join(appPath, "node_modules", ownPackageName);
// 項目的`package.json`
const appPackage = require(path.join(appPath, "package.json"));
// 檢查項目中是否有`yarn.lock`來判斷是否使用`yarn`
const useYarn = fs.existsSync(path.join(appPath, "yarn.lock"));

appPackage.dependencies = appPackage.dependencies || {};

// 定義其中`scripts`的
appPackage.scripts = {
  start: "react-scripts start",
  build: "react-scripts build",
  test: "react-scripts test --env=jsdom",
  eject: "react-scripts eject",
};
// 重新寫入`package.json`
fs.writeFileSync(
  path.join(appPath, "package.json"),
  JSON.stringify(appPackage, null, 2)
);

// 判斷項目目錄是否有`README.md`,模板目錄中已經(jīng)定義了`README.md`防止沖突
const readmeExists = fs.existsSync(path.join(appPath, "README.md"));
if (readmeExists) {
  fs.renameSync(
    path.join(appPath, "README.md"),
    path.join(appPath, "README.old.md")
  );
}
// 是否有模板選項,默認(rèn)為當(dāng)前執(zhí)行命令包目錄下的`template`目錄,也就是`packages/react-scripts/tempalte`
const templatePath = template
  ? path.resolve(originalDirectory, template)
  : path.join(ownPath, "template");
if (fs.existsSync(templatePath)) {
  // 拷貝目錄到項目目錄
  fs.copySync(templatePath, appPath);
} else {
  console.error(
    `Could not locate supplied template: ${chalk.green(templatePath)}`
  );
  return;
}

這個函數(shù)我就不把代碼貼全了,里面的東西也蠻好理解,基本上就是對目錄結(jié)構(gòu)的修改和重名了那些,挑了一些來說,到這里,create-react-app從零到目錄依賴的安裝完畢的源碼已經(jīng)分析完畢,但是其實這只是個初始化目錄和依賴,其中控制環(huán)境的代碼都存在react-scripts中,所以其實離我想知道的關(guān)鍵的地方還有點遠(yuǎn),但是本篇已經(jīng)很長了,不打算現(xiàn)在說了,多多包涵。

希望本篇對大家有所幫助吧。

啰嗦兩句

本來這篇我是打算把create-react-app中所有的源碼的拿出來說一說,包括其中的webpack的配置啊,eslint的配置啊,babel的配置啊.....等等,但是實在是有點多,他自己本身把初始化的命令和控制react環(huán)境的命令分離成了packages/create-react-apppackages/react-script兩邊,這個篇幅才把packages/create-react-app說完,更復(fù)雜的packages/react-script在說一下這篇幅都不知道有多少了,所以我打算之后空了,在多帶帶寫一篇關(guān)于packages/react-script的源碼分析的文。

碼字不易,可能出現(xiàn)錯別字什么的,說的不清楚的,說錯的,歡迎指正,多多包涵!

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/92706.html

相關(guān)文章

  • 從零開始開發(fā)一個react腳手架(二)

    摘要:弄清之后,就去腳手架源代碼里面找。這樣更加靈活,而且復(fù)用性高,起新項目,如果差別不大,幾乎可以做到零配置,這樣開發(fā)者壓根就不需要關(guān)心業(yè)務(wù)之外的東西從零開始開發(fā)一個腳手架三 上一篇已經(jīng)初步整了個kkk-react,這一篇不寫代碼,粗略講解下create-react-app的部分源碼。 前沿:科普下看源碼的思路。以本人看過N多源碼的經(jīng)驗總結(jié),想要看這種腳手架或者npm包的源碼,第一步就是看...

    Y3G 評論0 收藏0
  • create-react-app 源碼學(xué)習(xí)(上)

    摘要:這里通過調(diào)用方法方法主要是通過來通過命令執(zhí)行下的方法。 原文地址Nealyang/personalBlog 前言 對于前端工程構(gòu)建,很多公司、BU 都有自己的一套構(gòu)建體系,比如我們正在使用的 def,或者 vue-cli 或者 create-react-app,由于筆者最近一直想搭建一個個人網(wǎng)站,秉持著呼吸不停,折騰不止的原則,編碼的過程中,還是不想太過于枯燥。在 coding 之前...

    MkkHou 評論0 收藏0
  • 通過create-react-app從零搭建react環(huán)境

    摘要:通過文件可以對圖標(biāo)名稱等信息進行配置。注意,注冊的只在生產(chǎn)環(huán)境中生效,并且該功能只有在下才能有效果該文件是過濾文件配置該文件是描述文件定義了項目所需要的各種模塊,以及項目的配置信息比如名稱版本許可證等元數(shù)據(jù)。 一、 快速開始: 全局安裝腳手架: $ npm install -g create-react-app 通過腳手架搭建項目: $ create-react-app 開始項目: ...

    Cympros 評論0 收藏0
  • 通過create-react-app從零搭建react環(huán)境

    摘要:通過文件可以對圖標(biāo)名稱等信息進行配置。注意,注冊的只在生產(chǎn)環(huán)境中生效,并且該功能只有在下才能有效果該文件是過濾文件配置該文件是描述文件定義了項目所需要的各種模塊,以及項目的配置信息比如名稱版本許可證等元數(shù)據(jù)。 一、 快速開始: 全局安裝腳手架: $ npm install -g create-react-app 通過腳手架搭建項目: $ create-react-app 開始項目: ...

    CoyPan 評論0 收藏0
  • TypeScript 、React、 Redux和Ant-Design的最佳實踐

    摘要:使用官方的的另外一種版本和一起使用自動配置了一個項目支持。需要的依賴都在文件中。帶靜態(tài)類型檢驗,現(xiàn)在的第三方包基本上源碼都是,方便查看調(diào)試。大型項目首選和結(jié)合,代碼調(diào)試維護起來極其方便。 showImg(https://segmentfault.com/img/bVbrTKz?w=1400&h=930); 阿特伍德定律,指的是any application that can be wr...

    wangbinke 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<