深入淺出 Create React App
本文差點難產而死。因為總結的過程中,多次懷疑本文是對官方文件的直接翻譯和簡單諾列;同時官方文件很全面,全範圍的介紹無疑加深了寫作的心智負擔。但在最終的梳理中,發現走出了一條與眾不同的路,於是堅持分享出來。
希望本文除了能帶領我們再次瞭解 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 應用程式清單 支援 (注意:從[email protected]及更高版本開始支援 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 打包入口檔案,通常連結整個業務程式碼;後者是頁面模板,是打包後整個靜態頁面的總入口。
這裡對以下兩個檔案的出現進行簡要的意義概括。
_
- 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 技術棧了