本文差點難產而死。因為總結的過程中,多次懷疑本文是對官方文檔的直接翻譯和簡單羅列;同時官方文檔很全面,全范圍的介紹無疑加深了寫作的心智負擔。但在最終的梳理中,發現走出了一條與眾不同的路,于是堅持分享出來。
希望本文除了能帶領我們再次了解 Create React App(后文簡稱 CRA) 外,還能提供一種不同的知識組織結構和技術視角,加深我們對整個 React 技術生態的理解。
本文可能是多篇博客的綜合體,整理和寫作時間 15h+,仔細閱讀時間 30min+,請慢用。
本文面向的讀者是:
- 前端開發初學者或 React 初學者;
- 使用過 CRA 搭建 React 項目但想拓展相關知識面的人;
- 希望通過一篇文章快速復習 CRA 的人;
- 英文初學者,想要通過一篇中文技術文章來讓自己接下來讀英文文檔不再困難的人;
- 以及就想點進來支持一下的人。
其次,本文在對官方文檔進行一定的重新編排下,加上了如下創新點以完善整體的閱讀學習體驗:
- 添加了實戰 1:使用單個 HTML 文件構建 React App;
- 添加了實戰 2:使用 Webpack 手動構建 React App;
- 添加了實戰 3:使用 CRA 一站式構建 React App;
- 添加了實戰 4:使用 Source Map Explorer 分析打包文件;
- 添加了實戰 5:在已有的 React 項目中引入/升級 CRA;
- 添加了實戰 6:使用 React App Rewired 注入新配置;
- 添加了:對 CRA 未來版本的簡單展望;
- 添加了:一個 Dan 十年回顧文章的導讀。
最終,本文不涉及源碼的解讀,想要閱讀源碼的同學可以移步官方源碼倉庫,整體設計思路并不是很難,具體實現原理可以細細品嚼;且本文對與 CRA 不直接相關的技術點會略略而過,歡迎從點到面主動學習更多。以下是官方源碼倉庫以及官方文檔地址:
- Github 地址:https://github.com/facebook/create-react-app
- 官方文檔地址:https://create-react-app.dev

初始化 React App 的多種方式
常見的初始化 React App 的方式有:
- 不使用構建工具構建 React App;
- 使用 Webpack 手動構建 React App;
- 使用 Create React App 一站式構建 React App;
- 在在線沙箱平臺直接構建 React App(一般用于 Demo 預演,本文不涉及)。
下面我們分別進行介紹與實戰練習。
實戰 1:使用單個 HTML 文件構建 React App
React 本身專注于構建用戶界面,并不依賴于某個構建工具,因此我們可以用傳統的方式引入 React 并書寫第一個“Hello World!” App。這種方式是快速嘗試 React 的好方法,但并不適用于正式開發。
以下 HTML 代碼段是一種實現方式,使用了可選的 Babel 編譯和 JSX 語法,基于非構建工具的更多初始化頁面的方法(如不使用 JSX 等)可以自行探索。
<html>
<head>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <!-- 不需要用于生產環境 -->
</head>
<body>
<div id="root"></div>
<script type="text/babel">
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
)
</script>
</body>
</html>
實戰 2:使用 Webpack 手動構建 React App
構建工具有很多種,目前最為主流的構建工具當屬 Webpack。如何使用 Webpack 逐步構建 React App?
果不其然,為了證明 CRA 的便捷性而引出的本節 Webpack 實戰,耗費了一小時多的時間進行了親自踩坑,搜索了較多的博文都由于發布時間性而不能和最新的版本進行融合,最終根據 Github 中 react-webpack-babel 庫的 package.json 文件里的相關信息才得到實現。
# 創建一個項目并進入該項目
$ mkdir react-webpack-steper & cd react-webpack-steper
# 使用默認選項直接生成一個初始化的 package.json
$ npm init -y
# 安裝 React 基礎包
$ sudo npm install --save react react-dom
# 安裝 Webpack 相關工具 - 打包、本地啟動支持、本地異步請求模擬以及熱更新等
$ sudo npm install --save webpack webpack-cli webpack-dev-server
# 安裝 Babel 相關工具 - 提供 ES6+ 新功能支持
$ sudo npm install --save-dev @babel/cli @babel-core @babel/preset-env @babel/preset-react
$ sudo npm install --save-dev babel-loader babel-plugin-module-resolver html-webpack-plugin
# 新建打包、編譯配置文件并準備編寫
$ touch webpack.config.js
$ touch .babelrc
# 新建 React 文件
$ mkdir src
$ touch src/index.js
$ touch src/index.html
其中,webpack.config.js 源碼如下:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: path.resolve(__dirname, './src/index.js'),
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
},
]
},
resolve: {
extensions: ['*', '.js', '.jsx']
},
output: {
path: path.resolve(__dirname, './build'),
filename: 'bundle.js',
publicPath: '/'
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, './src/index.html')
})
]
}
.babelrc 源碼如下:
{
"presets": ["@babel/preset-env", "@babel/preset-react"],
"plugins": [
["module-resolver", {
"root": ["./src"]
}]
]
}
src/index.html 源碼如下:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Hello World!</title>
</head>
<body>
<div id="root">Loading...</div>
</body>
</html>
src/index.js 源碼如下:
import React from 'react'
import ReactDOM from 'react-dom'
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
)
此時,一個基于 Webpack 手動搭建的簡易型“Hello World”App 開發完成,可以通過如下命令本地運行。
$ webpack-dev-server --mode development --open --hot
更多自定義內容如添加 devServer 支持、添加多頁應用支持...等各種各樣新技術棧的支持,也可以引申實戰。
因此,我們需要 CRA
可見,不使用構建工具編寫不切實際,使用構建工具手動搭建 React App 又很繁瑣。因此我們需要一個能初始化一個可直接運行項目的工具,并提供各種簡易的插件,Create React App 應運而生。
CRA 適用于中小型 React 項目。
CRA 的設計哲學
- 一種依賴關系。盡管 CRA 使用了 Webpack,Babel,ESLint 等各種出色的項目,但我們只需要 CRA 一種依賴;從 CRA 生成項目的 package.json 中也可以看到并沒有 Webpack、Babel 的痕跡。
- 無需配置。我們無需進行任何額外配置便可以直接運行代碼,專注業務開發;同時 CRA 還提供了開發和生產版本的合理配置。
- 非鎖定配置。只需要運行一個命令, 所有的配置項和構建依賴項都將直接“彈出”到項目中,交由我們來修改。
CRA 包含了什么?
CRA 將具有構建現代單頁 React 應用所需的一切:
- React,JSX,ES6,TypeScript 和 Flow 語法支持;
- ES6+ 標準的支持,例如對象傳播運算符;
- CSS 自動添加前綴的支持,因此我們不需要 -webkit- 或其他前綴;
- 快速的交互式單元測試運行程序,內置對覆蓋率報告的支持;
- 實時開發服務器,警告常見錯誤;
- 一個構建腳本,用于將 JS,CSS 和圖像與哈希和源映射捆綁在一起進行生產;
- 滿足所有漸進式 Web 應用程序標準的 ServiceWorker 和 Web 應用程序清單 支持 (注意:從react-scripts@2.0.0及更高版本開始支持 ServiceWorker);
- 單一依賴項即可輕松更新上述工具。
CRA 的使用場景
Create React App 非常適合:
- 在方便且功能豐富的開發環境中學習 React;
- 快速啟動新的單頁 React 應用程序;
- 快速使用 React 為庫和組件創建示例。
如果我們想在沒有數百個傳遞構建工具依賴的情況下嘗試 React,請考慮使用單個 HTML 文件構建或使用在線沙箱平臺構建。
- 如果需要將 React 代碼與服務器端模板框架(如 Rails,Django 或 Symfony)集成,或者如果不構建單頁應用,請考慮使用更靈活的 nwb 或 Neutrino。特別是對于 Rails,可以使用 Rails Webpacker。對于 Symfony,請嘗試 Symfony's Webpack Encore;
- 如果需要發布 React 組件,nwb 以及 Neutrino 的 react-components preset 也可以這樣做;
- 如果要使用 React 和 Node.js 進行服務器渲染,請查看 Next.js 或 Razzle。 Create React App 與后端無關,僅生成靜態HTML / JS / CSS包;
- 如果網站大部分是靜態的(例如,投資組合或博客),請考慮改用 Gatsby。與 Create React App 不同,它在構建時會將網站預先渲染為 HTML;
- 最后,如果需要更多自定義設置,請查看 Neutrino 及其 React preset。
CRA 支持的瀏覽器
一些支持的瀏覽器規則如下:
- 默認情況下,生成的項目支持所有現代瀏覽器。 對 Internet Explorer 9、10 和 11 的支持需要 polyfill。 對于一組支持舊版瀏覽器的 polyfill,請使用 react-app-polyfill;
- 默認情況下,生成的項目在 package.json 文件中包含一個 browserslist 配置,以針對基于全球使用情況(> 0.2%)的廣泛瀏覽器(用于生產構建)和用于開發的現代瀏覽器。 這提供了良好的開發體驗,尤其是在使用異步/等待等語言功能時,但仍與生產中的許多瀏覽器保持高度兼容性;
- browserslist 配置控制輸出的 JavaScript,以使注入的代碼與指定的瀏覽器兼容。 通過運行構建腳本來創建生產構建時,將使用生產列表,而在運行啟動腳本時,將使用開發列表。 可以使用 https://browserl.ist 查看配置的瀏覽器列表支持的瀏覽器;
- 請注意,這不會自動包括 polyfills,仍然需要根據所支持的瀏覽器來添加語言功能(見上文);
- 在編輯 browserslist 配置時,我們的更改可能不會立即被獲取。 這是由于 babel-loader 中的一個未檢測到 package.json 中更改的問題。 一種快速的解決方案是刪除 node_modules/.cache 文件夾,然后重試。
這里的重點是 BrowsersList,一個“在不同的前端工具之間共用目標瀏覽器和 node 版本的配置工具”。簡而言之,就是 Babel 等轉移工具通過我們設置的 BrowsersList 中想要支持的瀏覽器版本來決定哪些語法需要被編譯。
CRA 支持的 ES 標準
一些支持的 ES 標準規則如下:
- 該項目支持最新 JavaScript 標準的超集。 除ES6語法功能外,它還支持:
- Exponentiation Operator (ES2016);
- Async/await (ES2017);
- Object Rest/Spread Properties (ES2018);
- Dynamic import() (stage 4 proposal);
- Class Fields and Static Properties (part of stage 3 proposal);
- JSX, Flow and TypeScript;
- Learn more about different proposal stages。
- 盡管 Facebook 建議謹慎使用實驗性功能,但 Facebook 會在產品代碼中大量使用這些功能,因此,如果將來任何這些建議發生更改,Facebook 都打算提供 codemod;
- 請注意,默認情況下,該項目不包含任何 polyfill;
- 如果您使用任何其他需要運行時支持的ES6 +功能(例如Array.from()或Symbol),請確保手動包括適當的polyfill,或者您要定位的瀏覽器已支持它們。
CRA 的兩個核心庫
Create React App 有兩個核心庫,如下:
- create-react-app 是全局命令,用于創建初始化的 React 項目;
- react-scripts 是所生成的項目中的開發依賴項,包括運行項目、測試項目、打包項目等多種命令。由于 CRA 的一種依賴性原則,react-scripts 便開放了所有內部其它依賴的使用方式。
實戰 3:使用 CRA 構建 React App
到這里,我們終于需要通過命令行來安裝和使用 CRA,來構建我們的第三個“Hello World”App。
全局安裝 CRA
為保證每一個新項目都能使用到 CRA 最新最全的功能,請確保 CRA 為最新版本。
# 再已安裝 CRA 的情況下,可以先卸載 CRA
$ npm uninstall -g create-react-app
# 正式安裝 CRA
$ npm install -g create-react-app
初始化 CRA 項目
根據我們的 npm 版本,選擇相應命令來安裝最新版的 CRA 并初始化第一個項目。同時檢查自己的 node 版本,需要在本地開發計算機上安裝 Node 8.16.0 或 Node 10.16.0 或更高版本(但服務器上不需要)。 我們可以使用nvm(macOS / Linux)或 nvm-windows 在不同項目之間切換Node版本。
# 查看自己的 npm 版本
$ npm --version
# 第一種新建項目方式——npm 5.2+ 時,以下命令會安裝最新版 CRA
$ npx create-react-app my-app
# 第一種新建項目方式——npm 版本小于等于 5.1 時
$ create-react-app my-app
# 第二種新建項目方式
# npm 6+ 開始支持 npm init <initializer>
$ npm init react-app my-app
# 第三種新建項目方式
$ yarn create react-app my-app
項目的文件結構
通過命令行的構建,我們初始化了第一個 CRA 項目,其中幫我們生成的項目目錄結構如下(只有 src 下的文件才會被 Webpack 處理,只有 public 下的文件才能被 public/index.html 使用):
my-app
├── .git # 隱藏文件夾,會初始化第一個 Commit 記錄
├── README.md
├── node_modules
├── package.json # 依賴配置文件
├── .gitignore
├── [floder_name] # 根目錄下可以建立其他文件夾,但不會被用在生產環境中
├── public # 只有 public 下的文件才能被 public/index.html 使用
│ ├── favicon.ico
│ ├── index.html # public/index.html 頁面模板
│ └── manifest.json
└── src # 只有 src 下的文件才會被 Webpack 處理
├── App.css
├── App.js
├── App.test.js
├── [floder_name] # 可以建立其他文件夾,以被 Webpack 成功導入
├── index.css
├── index.js # JavaScript 打包入口文件
├── logo.svg
└── serviceWorker.js
關于 package.json、index.js 和 public/index.html 文件夾,我們通過“實戰 2”已經有所了解。前者是 JavaScript 打包入口文件,通常鏈接整個業務代碼;后者是頁面模板,是打包后整個靜態頁面的總入口。
這里對以下兩個文件的出現進行簡要的意義概括。<br />_
- src/serviceWorker.js:提供漸進式 Web 應用的核心功能,不論網絡狀況如何都能立即加載,并且在不需要網絡請求(離線時)的情況下也能展示 UI ;
- public/manifest.json:是漸進式 Web 應用將自身添加至桌面的功能依賴文件,也可以對圖標、名稱等信息進行配置。
運行 CRA 項目
CRA 默認提供了運行、測試、打包、部署以及彈出項目的命令。其中的一些貼士:
- npm start 內置熱更新機制,代碼改動時頁面自動刷新;
- npm test 以交互方式運行測試觀察程序,默認情況下運行與自上次提交以來更改的文件相關的測試;
- npm run build 將要生產的應用程序生成到生成文件夾。它在生產模式下正確捆綁了React,并優化了構建以獲得最佳性能。生成文件被壓縮,并且文件名包含哈希;
- npm run eject 將內置的各種 Webpack 配置彈出到項目中,讓我們可以進行自定義。同時此操作不可逆,意味著我們承擔了彈出配置后的風險。通常不推薦彈出,可以通過 React App Rewired 庫進行配置注入。
# ---- 運行 ----
$ npm start
$ open http://localhost:3000
# ---- 測試 ----
$ npm test
# ---- 打包 ----
$ npm run builds
# ---- 彈出配置 ----
$ npm run eject
搭建 CRA 生態
根據官方文檔的思路,我們還能從更多角度拓展 CRA 的使用邊界,下面進行概要介紹。
- 為開發環境添加額外功能:包括“配置編輯器風格”、“開發隔離組件”、“分析打包文件”和“添加 HTTPS 支持”;
- 添加樣式與靜態資源支持:包括“添加樣式表文件”、“添加 CSS Modules 支持”、“添加 Sass 支持”、“添加 PostCSS 支持”、“添加圖片文字和字體支持”、“添加 GraphQL 支持”、“使用 public 文件夾”、“進行代碼拆分”;
- 添加業務驅動支持:包括安裝各種依賴項如“BootStrap”、“Flow”、“TypeScript”、“Delay”、“Router”,以及“導出組件”、“使用全局變量”、“配置環境變量”、“制作漸進式 Web 應用”和“創建生產環境”;
- 添加測試支持:包括“運行測試”和“調試測試”;
- 添加后端集成支持:包括“在開發環境中代理 API 請求”、“使用 AJAX 請求獲取數據”、“集成后端 API”和“使用 Title & Meta 標簽”;
- 部署進階:包括“靜態服務器”、“Azure”、“Firebase”、“Github Pages”等平臺的部署等。
這里無法深入展開,每一個點都可以是一個新的實戰,當我們需要某個功能時便可以查閱相關文檔來主動探索。其中“分析打包文件”的解讀見“實戰 4”。
實戰 4:使用 Source Map Explorer 分析打包文件
# 安裝文件分析工具 source-map-explorer
$ sudo npm install --save source-map-explorer
# 打包項目
$ npm run build
# 將如下命令放入 package.json 中并生成快捷方式 npm run analyze
# $ source-map-explorer 'build/static/js/*.js'
# 注意此命令直接在命令行輸入會提示找不到相關命令
$ npm run analyze
對于一個剛被 CRA 生成的 React App 來說,分析的結果如下,包大總計 129.38k。

實戰 5:在已有的 React 項目中引入/升級 CRA
回到剛才“實戰 2”建立的 react-webpack-steper 項目中,當我們已經編寫了一部分業務時,能否直接在當前項目中無痛引入 CRA?
解決思路便是:在大多數情況下,更改 package.json 中的 react-scripts 版本并刪除不必要文依賴配置,接著在此文件夾中運行 npm install 就足夠了,但最好參考更改日志以了解潛在的重大更改。CRA 致力于將重大更改保持在最低限度,以便可以輕松升級 React 腳本。
# 卸載 CRA 本身已經提供的依賴
$ sudo npm uninstall --save webpack webpack-cli webpack-dev-server
$ sudo npm uninstall --save-dev @babel/cli @babel-core @babel/preset-env @babel/preset-react
$ sudo npm uninstall --save-dev babel-loader babel-plugin-module-resolver html-webpack-plugin
# 刪除 CRA 不需要使用的文件
$ rm webpack.config.js .babelrc
# 刪除 node_modules
$ rm -rf node_modules
# 手動安裝 React Script
$ sudo npm install --save react-scripts@latest
# 由于 CRA 默認規則,將 src/index.html 移至 public/index.html
$ mkdir public
$ mv src/index.html public
# 在 package.json 中添加 React Script 啟動命令
$ vim package.json
package.json 中添加/覆蓋如下指令。
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
再次執行即可。由于每個人的具體配置不一定一致,可根據自身所遇問題進行搜索。升級原理類似。
# 當沒有 BrowsersList 時,CRA 會進行詢問并幫助我們生成
$ npm start
實戰 6:使用 React App Rewired 注入新配置
CRA 官方并不推薦使用 npm run eject 彈出配置,這會增加更多的 Webpack 維護工作。對于實在想改的 Webpack 配置來說,我們可以使用 React App Rewired 庫進行配置注入,這里來做個小例子。
此工具可以在不 'eject' 也不創建額外 react-scripts 的情況下修改 create-react-app 內置的 webpack 配置,然后你將擁有 create-react-app 的一切特性,且可以根據你的需要去配置 webpack 的 plugins, loaders 等。
繼續使用 react-webpack-steper 項目,我們的簡易目標是增加 devServer 本地代理。
第一步:安裝依賴并進行基礎配置
# 安裝依賴
$ sudo npm install --save-dev react-app-rewired customize-cra
# 根目錄建立 config-overrides.js
$ touch config-overrides.js
# 修改 package.json
$ vim package.json
# 運行項目
$ npm start
其中,config-overrides.js 的初始代碼為:
/* config-overrides.js */
module.exports = function override(config, env) {
//do stuff with the webpack config...
return config;
}
package.json 的修改思路為:
/* package.json */
"scripts": {
- "start": "react-scripts start",
+ "start": "react-app-rewired start",
- "build": "react-scripts build",
+ "build": "react-app-rewired build",
- "test": "react-scripts test --env=jsdom",
+ "test": "react-app-rewired test --env=jsdom",
"eject": "react-scripts eject"
}
第二步:編寫配置,進行代理
# 新增配置文件
$ mkdir config
$ touch config/proxy.js
# 修改 config-overrides.js
$ vim config-overrides.js
其中,config/proxy.js 源碼是:
module.exports = {
'/api/**': {
target: 'http://110.114.120.120:8080',
secure: false,
changeOrigin: false,
},
}
config-overrides.js 修改為:
const { overrideDevServer } = require('customize-cra')
const proxy = require('./config/proxy')
module.exports = {
devServer: overrideDevServer((config) => {
config.proxy = proxy
return config
}),
}
此時,本地的所有 api 開頭的接口請求都會被轉發到 http://110.114.120.120:8080 的模擬后端 IP 上。
對 CRA 未來版本的簡單展望
截止目前(2020-01-10),CRA 的最新版本是 v3.3.0,我們可以從 Github 的 MileStone 中看到未來可能會改善的功能,其中整理并如下所述。
- v3.x:添加多入口文件支持(不只是一個 index.js 入口);使用 worker-loader 添加對 WebWorker 的支持;更早地檢查 Node 的版本;添加對子資源完整性 SRI 支持;生產環境中預加載腳本和鏈接...
- v4.0:支持 Webpack 5.0(Webpack 目前最新版 v4.41.5,v5 也推出了一年多內測版);在 tsconfig.json 和 jsconfig.json 里新增對 baseUrl 和 paths 的支持(方便寫 @ 絕對路徑等);支持 Jest 配置中設置browser 為 true(根據環境提供正確的 Node 或 Browser 模塊)...
- v100.0:提供構建過程中的監視模式;適用于 Hooks 的熱加載...
讓我們一起持續關注。
結語
回顧文章,我們從初始化 React App 的多種方式,引出 CRA 的必要性再對其進行較為充分的解釋,最后配上 6 個角度來從一些角度對 CRA 的使用方式進行了實戰,最后回歸到 CRA 的版本展望之中。
感謝你的閱讀,如果你有什么更多的疑惑,CRA 的官方文檔 + 開源倉庫一定會滿足你的一切。

最后,一起拜讀一下 CRA 和 Redux 作者、React 的核心貢獻者 Dan Abramov 發布的這篇“我的十年回顧”文章。
現在我們可以開始正式深入地學習 React 技術棧了。