這篇文章不是聊React這門技術自己,而是關於如何維護好一個React項目。javascript
文本可能會涉及一些Webpack的基礎知識,若是你還不太瞭解Webpack的用法的話,能夠從我以前的一篇文章《Webpack 速成》入門,深刻淺出,童叟無欺。css
編程領域中的「腳手架(Scaffolding)」指的是可以快速搭建項目「骨架」的一類工具。例如大多數的React項目都有src目錄,public目錄,webpack配置文件,babel配置等等,而src目錄中又一般包含components目錄,reducers目錄等等。每次在新建項目時,你不得不手動建立這些固定的文件目錄,繁瑣而累贅。腳手架的做用就是幫助你完成這些重複性的工做,包括一鍵生成主要的目錄結構、安裝依賴等等。Yeoman就是著名的腳手架工具。html
當你進入一個公司參與React項目時,你要作的可能只是開發指定的組件,執行命令啓動項目查看運行和調試,最後發佈打包上線。你可能不會去思考爲何目錄結構是這個樣子,那麼多配置文件是幹什麼用的(我曾經也是這個樣子)。今天我選擇了兩個React項目的腳手架工具 create-react-app(如下簡稱CRA)和react-starter-kit(如下簡稱RSK),根據它們的說明文檔以及一些我的的經驗,來逐個解析不一樣文件的做用。前端
這些知識並不只僅適用於React項目,文件的背後表明的是工具,工具的背後表明的實際上是要解決的問題。不一樣的公司不一樣團隊使用的工具可能會不一樣,未來也會有新的技術或者框架出現,但這些解決問題的思路一樣可以複用。或者當你須要立項一個React項目而又不想依賴腳手架時,它們會是一份好的教科書。java
由於CRA是Facebook官方推出的腳手架工具,因此咱們以CRA爲主線索展開,它的User Guide文檔最爲(特)豐(別)富(長),本文的大部份內容也都參(翻)考(譯)自這份文檔,若是也理解不恰當之處還多多指教。其中也會穿插react-starter-kit的相關內容。node
這個系列的文章會分爲上下兩個部分。在這上篇中,講解的是一些常規項目搭建的基礎配置,而在下篇的計劃中,則會講解高級配置,涉及開發環境的後端功能以及測試和部署。react
最後一句廢話想強調的是,任何腳手架生成的項目結構都僅供參考。實際的組織方式和使用工具都要依據實際狀況而定。webpack
首先讓咱們從最基本的目錄文件夾開始git
CRA中有兩個很是重要的目錄有兩個,src
和public
:github
src
: 該目錄中存放的是你的腳本和樣式源文件,全部你須要通過Webpack打包的或者編譯的文件都必須並且只能放在這個文件夾裏(反過來講Webpack也只會去這個文件夾裏找須要打包的文件)。例如TypeScript、LESS、SASS、Stylus源碼等等,也多是你須要在組件中引用的圖片、SVG資源等等(以後會談到在組件中引用樣式和圖片的用法)。總之src
目錄顧名思義(src = source),存放的是程序源碼。固然,src
目錄之下子目錄的命名和組織就沒有那麼講究了。若是你開發的是redux項目,天然會有components、reducers、actions等文件夾,甚至在components中分別爲container component和stateless component創建文件夾都沒問題。
最後,src
目錄的入口是src/index.js
,不妨能夠看看index.js
的內容
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
ReactDOM.render(<App />, document.getElementById('root')); registerServiceWorker();複製代碼
很簡單,它將入口組件渲染至頁面上,而且註冊service worker。
public
: public
一般用於存放用戶可以訪問的資源,例如打包後的腳本、圖片、HTML文件。但事實上並不只限於此,從RSK項目中咱們能夠看到public文件夾中還有robots.txt
、humans.txt
、crossdomain.xml
、favicon.ico
等等雖然public中存放的不是組件,public目錄一樣存在入口,即/index.html
,也便是用戶在域名根路徑下訪問到的頁面。在CRA中規定,只有public
文件夾內的資源才能被index.html
使用。而html引用靜態資源的方式也比較特別,並不是是經過相對路徑或者絕對路徑的,而是經過全局變量引用。這個話題咱們放在後面資源使用環節再說。
public
文件夾有時候也被命名爲assets
甚至resources
,這都沒有關係。若是更加規矩一點,你能夠在public
中創建子文件夾dist
用於存儲發佈上線的腳本和樣式(dist其實就是distribute的縮寫,也意味着發佈的意思),或者創建build
文件夾用於存儲開發中構建後的腳本
src
和public
是最重要的兩個文件夾。CRA中的文件夾只有這兩個。咱們不妨再能夠看看RSK中的文件夾還有哪些:
docs
: 用於存放(markdown格式的)開發相關文檔tools
: 用於存放「工具腳本」的文件夾。「工具腳本」便是那些用於完成指定工做的腳本,在文件夾裏你會看到例如build.js
、deploy.js
、copy.js
等等。即便不展現這些腳本的具體內容,經過文件名也很容易判斷這些腳本的做用,依次爲構建、部署、複製文件等。這一部分腳本也能夠經過npm
命令執行,稍後詳談。src/server
: 若是你的項目是以Javascript全棧的形式開發的話,能夠將服務端代碼也放入src
中test
: 用於存放測試相關腳本愈來愈多的工具被髮明來用於輔助咱們的開發,但不一樣的工具配合不一樣的項目須要進行不一樣的配置。因此有各式各樣的配置文件可能存在於咱們的項目文件中。這些工具和配置文件你不必定都會用上,但至少你在過目以後不會再對它們陌生,或許在之後解決問題的過程當中可以派的上用場。
如下的配置文件摘自RSK腳手架中(若是你第一次看到腳手架爲你生成了這麼多歷來沒有看到過的文件你必定會感到懼怕,反正我是這麼以爲的。)
.editorconfig
: 告訴編輯器該項目的代碼規範。在團隊開發中可能涉及的一個問題是,不一樣的同窗可能使用的開發工具和開發習慣並不相同,有的使用WebStorm,有的使用Visual Studio Code。因此有可能在你的編輯器中習慣縮進使用的是2個空格,在他的編輯器中縮進使用的是4個空格。該配置文件就是用於存儲統一的樣式規範,告訴編輯器統一使用兩個空格,不容許空字符串結尾等等。具體請參考editorconfig.org.eslintrc.js
: 這個很好理解,eslint工具的配置文件。eslint是一款專業對js語法和格式進行檢測的工具,大部分的編輯器應該都進行了集成,或者看成插件進行安裝。該配置文件告訴eslint哪些文件能夠忽略,哪些規則能夠忽略,哪些文件適配哪些規則等等。具體請參考: eslint.org/docs/user-g….stylelintrc.js
: 同上,stylelint是對樣式文件進行語法規範檢測的工具,該配置文件則能夠對檢測規則進行細節配置。具體規則請參考: stylelint.io/user-guide/….flowconfig
: flow是Facebook推出一款用於對JavaScript語法進行類型檢測的開源工具(有TypeScript的意思)。該文件就是該工具的配置文件,具體能夠前往flow.org/en/docs/con….env
: 在啓動項目時不免會使用到環境變量,最著名的環境環境變量莫過於NODE_ENV
,例如告訴程序使用生產環境:NODE_ENV=production
。咱們都知道能夠在執行命令行時經過命令行參數的形式指定環境變量,例如NODE_ENV=production node app.js
,而後再從程序裏經過讀取命令行參數的方式間接讀取環境變量。而經過dotenv模塊,咱們能夠將環境變量都放入.env
環境中統一管理統一讀取。.travis.yml
: 持續集成工具travis-ci的配置文件,該工具github marketplace有售,更多配置能夠參考docs.travis-ci.com/user/custom…circle.yml
: 持續集成工具circleci的配置文件,該工具github marketplace有售,更多配置能夠參考circleci.com/docs/1.0/co…jest.config.js
: Facebook 的測試工具jest的配置文件,更多配置能夠參考facebook.github.io/jest/docs/e…jsdoc.config.json
: jsDoc是一款可以根據文件內函數註釋生成文檔的工具,該文件是該工具的配置文件,更多信息能夠參考usejsdoc.org/about-confi…Dockerfile
: Docker容器的配置文件(對不起Docker我實在不熟,沒有什麼好補充的)除此以外,還有一些你可能會用得上的一些文件,好比
CHANGELOG.md
: 版本更新的日誌CONTRIBUTEING.md
: 關於如何向該項目作出貢獻還在我入行的時候,前端開發流程是很簡單的,手動建立一個靜態頁面,而後引入你須要的腳本就能夠開始了。然而到了如今,不只引入腳本的方式發生了改變,包括調試過程,打包流程,發佈上線都變得複雜並且專業,而這一切都離不開NodeJS腳本。腳本帶來的好處是可複用、自動化以及批量化處理。
開發中須要使用腳本處理的環節很是的多,例如將less編譯爲css,將腳本編譯、壓縮、拼接,壓縮圖片等等。這些工做能夠交給Webpack或者Gulp或者Grunt去作。但這些第三方庫並非萬能的,它們的運做也依賴它們所處生態裏的插件。在這種複雜的依賴狀況下,出錯的狀況常容易發生,爲何不建議再使用Gulp或者Grunt了呢,詳見這篇文章:Why we should stop using Grunt & Gulp。正所謂流水的工具,鐵打的腳本
npm腳本都存放在package.json
文件裏的scripts
字段裏
npm命令有機會咱們能單獨拿出一篇文章來聊,但言歸正傳回到腳手架,CRA中只用到了四種npm命令,分別是
npm start
: 在開發模式下啓動app,默認使用使用3000端口,啓動後在瀏覽器中輸入http://localhost:3000就能訪問,若是應用發生了更改頁面會自動刷新npm test
: 運行應用的測試腳本npm run build
: 爲生產環境編譯而且打包應用程序,打包到build
文件夾中npm run eject
: 若是你稍有心的觀察CRA的目錄裏的文件,你會發現沒有.babel
文件,沒有webpack.config.js
相似文件。由於全部的這些瑣碎的配置腳手架都幫你搞定了。所有都在react-scripts
類庫中。因此你看到package.json
文件裏npm start
實際運行的是react-scripts start
。當你不知足於腳手架爲了你預設的配置時,你就可使用eject
命令將配置暴露出來(好比start
命令,還有webpack.config.dev.js
),這樣你就能夠徹底自定義這些配置。注意這個操做是不可逆的。當你運行完畢以後你會發現package.json
裏的scripts
的start
命令變爲node scripts/start.js
順表說一下爲何build
命令前須要加關鍵字run
,而start
和test
就不須要,由於npm start
是內置的預設命令,你能夠理解爲相似於宏的東西。若是你沒有在package.json
裏自定義start命令的話而又執行npm start
的話,它實際上執行的是node server.js
。更多的內置命令請參考docs.npmjs.com/misc/script…
這裏所說的格式化代碼並非指美化和格式化已經壓縮過的代碼以便於閱讀。而是在代碼的提交階段(commit)強制對代碼進行格式化。因此這裏用到了額外的三個庫
husky
: 便於以npm腳本的形式調用git hooks(hook指的是在某一個特定狀況下執行的代碼,好比React的各個預留出來的生命週期函數就算是hook)lint-staged
: 便於咱們對staged階段(準備提交階段)的文件執行npm腳本prettier
: 對代碼進行格式化核心類庫固然是prettier,爲何在開發時仍然須要對代碼格式化,prettier本身給出了幾個理由,好比強制對代碼進行格式化避免PR時產生沒必要要的語法問題,好比幫助還不熟悉的新同窗規範代碼,總之仍然是有必要的。
prettier解決了how的問題,可是還須要husky
和lint-staged
解決when的問題,也就是何時作格式化。在CRA中,格式化的工做時放在準備提交的階段(pre-commit),在實際項目中你還能夠放在預備push的階段。
husky解決的問題是將pre-commit的hook暴露出來。默認狀況下若是你想編寫pre-commit腳本,你須要編輯你項目的.git/hooks/pre-commit
文件,若是我沒有記錯的話應該是shell腳本,而且在執行以前記得賦予它們執行權限。
然而當你安裝完husky以後,你就能夠把pre-commit階段須要執行的腳本直接放在package.json
裏的scipts
裏的precommit
字段裏,好比:
"scripts": {
"precommit": "eslint"
},複製代碼
而lint-staged
解決的則是最後一千米的問題,即封裝在pre-commit階段須要執行的腳本,一樣是在package.json
配置,例如:
"dependencies": {
// ...
},
"lint-staged": {
"src/**/*.{js,jsx,json,css}": [
"prettier --single-quote --write",
"git add"
]
},
"scripts": {複製代碼
進入到組件化的時代,一切都是組件,就連html也能夠變身爲組件。在RSK腳手架中,你甚至會看到一個名爲Html.js
的組件(而後採用後端渲染)。咱們但願用組件解決一切問題,而不是把須要維護的代碼遺落在各個地方,甚至包括<head />
標籤裏的內容。<title />
、<meta />
就交給React Helmet解決吧。
開發組件和引用組件就不贅述了,全世界都同樣,相信你們也耳熟能詳了。
至於樣式,不管你是使用Less、Sass、仍是Stylus都同樣,只要在Webpack中使用對應的loader就能將其編譯爲css。須要注意的是組織樣式的方式。傳統項目中樣式和腳本是分離的,放在不一樣的文件夾中。可是在React項目中,咱們只有組件一個維度,組件同時包含樣式和腳本,都放在components
文件夾中。例如:
components/
|--Button.js
|--Button.less複製代碼
那麼在Button.js
中你能夠直接引用樣式
import React, { Component } from 'react';
import './Button.css';複製代碼
或者,你也能夠把全部的樣式都在樣式入口src/index.css
中引入,而後在組件入口src/index.js
中又統一引入樣式入口src/index.css
。
除了編譯樣式以外還有一些額外的工做須要進行,例如壓縮,例如爲某些樣式屬性添加瀏覽器前綴。在CRA中會使用Autoprefixer或者postcss進行處理,固然這一切都集成在react-scripts中。你也能夠獨立的使用npm腳本進行處理,監視樣式的變化,當樣式文件發生更改時自動的進行預處理和「後」處理,這個流程對腳本文件也一樣有效。
目前也有不少專門用於優化npm腳本執行的類庫,在上述流程中你也可以(或者說是必須)用上:
onchange
、watch
: 用於監視文件修改,而後執行特定的npm腳本,好比"scripts": {
...
"watch:css": "onchange 'src/scss/*.scss' -- npm run build:css",
"watch:js": "onchange 'src/js/*.js' -- npm run build:js",
}複製代碼
parallelshell
: 用於並行的執行多個npm腳本,好比"scripts": {
...
"watch:all": "parallelshell 'npm run serve' 'npm run watch:css' 'npm run watch:js'"
}複製代碼
相信你能領悟到這些代碼幹了什麼事情:P圖片與字體等資源也和樣式同樣都與要使用它們的組件放在同一層級,至少都應該屬於同一個components
文件夾中,在組件也是經過import
的關鍵字引入,例如
import React from 'react';
import logo from './logo.png'; // Tell Webpack this JS file uses this image
console.log(logo); // /logo.84287d09.png
function Header() {
// Import result is the URL of your image
return <img src={logo} alt="Logo" />; }複製代碼
爲了減小頁面的請求數,體積小於10000 bytes的圖片會返回data URI而不是實際的路徑。當項目須要(爲生產環境)進行構建時,Webpack會把大於10000 bytes的圖片資源拷貝到最終構建的文件夾中(在CRA中的目錄是/build/static/media
),而且根據內容hash值進行從新命名。因此不用擔憂資源發生修改以後由於瀏覽器的緩存而不會生效
爲何要採用import
的方式引用樣式和圖片,文檔中給出了三條理由:
public
文件夾中的資源並不是全部的資源都能在組件中引用,又或者有的第三方類庫並不支持與React集成,此時你就須要把資源放入public
文件夾中,而後在html中引用,好比:
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">複製代碼
那麼在構建時(npm run build
),Webpack會將%PUBLIC_URL%
替換爲實際的public
目錄的絕對路徑。
在js文件中也能夠經過訪問process.env.PUBLIC_URL
變量來得到public
文件夾的絕對路徑
render() {
// Note: this is an escape hatch and should be used sparingly!
// Normally we recommend using `import` for getting asset URLs
// as described in 「Adding Images and Fonts」 above this section.
return <img src={process.env.PUBLIC_URL + '/img/logo.png'} />; }複製代碼
這種訪問資源的方式有如下一些缺點(固然是相對於import
方式而言),請務必瞭解:
public
文件夾內的文件不會作任何處理,包括壓縮或者拼接之類的Etag
等緩存條件。詳情能夠參考個人這篇文章《設計一個無懈可擊的瀏覽器緩存方案:關於思路,細節,ServiceWorker,以及HTTP/2》可是在某些狀況下能夠考慮使用這種訪問資源的方式
CRA腳手架還容許你在process.env
上添加自定義的環境變量供全局訪問。
默認它會提供兩個環境變量供使用,一個是上一節用到的public
文件夾路徑PUBLIC_URL
。另外一個是你們更加熟悉的NODE_ENV
。後者是一個表明當前開發環境的變量,當你運行npm start
時它等於development
;當你運行npm test
時它的值是test
;當你運行npm run build
時,它的值是production
。你沒法手動的覆蓋它,它可以防止開發者不當心打包了一個開發版本部署到線上。NODE_ENV
也可以幫助你有針對性的調試代碼,好比你只但願非production
環境下停用分析腳本:
if (process.env.NODE_ENV !== 'production') {
analytics.disable();
}複製代碼
固然你也能夠添加本身的環境變量,添加方式有兩種,一種是經過命令行的方式好比在Windows系統下set REACT_APP_SECRET_CODE=abcdef&&npm start
。另外一種是經過.env
文件(也就是經過dotenv類庫),把你所須要的環境變量都寫在這個文件中:
REACT_APP_SECRET_CODE=abcdef複製代碼
須要注意的事情是,全部的自定義環境變量都須要以REACT_APP_SECRET_CODE
開頭(至於理由我沒有看懂:Any other variables except NODE_ENV will be ignored to avoid accidentally exposing a private key on the machine that could have the same name. 我不是很理解 exposing a private key 是什麼意思)。
一旦環境變量定義完畢以後,就能在文件中使用,好比在腳本中:
render() {
return (
<div> <small>You are running this application in <b>{process.env.NODE_ENV}</b> mode.</small> </div>
);
}複製代碼
好比在html中:
<title>%REACT_APP_WEBSITE_NAME%</title>複製代碼
最後,不一樣開發環境中的環境變量沒必要都放在.env
文件中,能夠劃分爲.env.development
, .env.test
, .env.production
等不一樣的文件存放,而且不一樣文件之間還存在優先級的關係,詳情能夠訪問dotenv的文檔
目前比較流行的IDE好比Visual Studio Code和WebStorm都支持編輯器內的代碼調試,可是可能須要配置編輯器的環境變量,或者增長配置文件,又或者給瀏覽器安裝插件。我我的使用編輯器進行調試的體驗並很差,並不是全部的場景都支持調試。同時由於腳手架和編輯器調試時都會啓動後端環境,這之中可能須要解決衝突的地方。
具體的配置信息能夠參考這兩款編輯器的官方文檔。
上篇完
本文也同時發佈在個人知乎專欄前端技術漫遊指南中,歡迎你們關注