最近項(xiàng)目中要使用 React 來開發(fā) web 應(yīng)用,這里記錄一下搭建 React web 腳手架的過程,方便后期自查。
一、CRA 創(chuàng)建官方腳手架
npx create-react-app my-app
create-react-app 腳手架中將 webpack + babel 封裝成了一個(gè)叫做 react-scripts 的庫,用來隱藏配置,開發(fā)人員可以快速上手。
但是如果要自定義 webpack 的配置,就必須使用 eject 將配置彈出,會(huì)導(dǎo)致我們關(guān)注到一些無關(guān)的配置,體驗(yàn)并不好,同時(shí)無法跟隨 react-scripts 的官方更新,所以我們通常會(huì)借助 craco 來優(yōu)化這個(gè)問題。
二、安裝 craco 方便自定義 webpack 配置
craco 全稱 Create React App Configuration Override,取首字母即組成了工具名稱。
craco 是為了無 eject 、可配置式的去修改 CRA 默認(rèn)提供的工程配置,這樣既能享受 CRA 帶來的便利和后續(xù)升級(jí),也能自己去自定義打包配置完成項(xiàng)目需要,一舉兩得。
1、安裝 @craco/craco
npm i @craco/craco -D
2、修改 package.json 中的 script 標(biāo)簽。
將 start/build/test 三個(gè)命令修改為 craco 方式:
"scripts": {
- "start": "react-scripts start",
- "build": "react-scripts build",
- "test": "react-scripts test",
+ "start": "craco start",
+ "build": "craco build",
+ "test": "craco test",
}
3、在項(xiàng)目根目錄中創(chuàng)建 craco.config.js
下面是支持了 路徑@別名配置、postcss 集成 的配置示例:
const path = require("path")
const resolve = pathUrl => path.join(__dirname, pathUrl)
const pxToViewport = require("postcss-px-to-viewport")
const vw = pxToViewport({
viewportWidth: 375
})
const WebpackBar = require("webpackbar")
module.exports = {
webpack: {
alias: {
"@": resolve("src/"),
"@common": resolve("src/common")
},
plugins: [new WebpackBar({ profile: true })]
},
style: {
postcss: {
mode: "extends",
loaderOptions: {
postcssOptions: {
ident: "postcss",
plugins: [vw]
}
}
}
}
}
craco 的基礎(chǔ)配置已經(jīng)完成,craco.config.js 配置文件結(jié)構(gòu),可以在 craco 官方的文檔中詳細(xì)查詢:Configuration Overview
關(guān)于 react-app-rewrited 的替代品 craco 及最佳實(shí)踐 可參考
三、支持修改環(huán)境變量 cross-env
為了支持添加不同網(wǎng)絡(luò)域名的環(huán)境變量,需要在 node 運(yùn)行時(shí)注入一些自定義的環(huán)境變量。
由于不同平臺(tái)注入環(huán)境變量有所差異,為了兼容 Windows 和 Linux 等不同平臺(tái),可以使用 cross-env 這個(gè)跨平臺(tái)設(shè)置和使用環(huán)境變量的腳本。
1、安裝 cross-env
npm install --save-dev cross-env
2、注入自定義變量
{
"scripts": {
"build": "cross-env REACT_APP_ENV=sit craco start"
}
}
3、使用自定義變量
process.env.REACT_APP_ENV
四、支持 sass 樣式編寫
在 React 腳手架中已經(jīng)有了 sass 的配置(并沒有配置 less,如果想用 less,需要另外配置),因此只需要安裝 sass 的依賴包,就可以直接使用 sass了:
1、安裝 sass
npm i sass -D
2、css modules 解決樣式污染問題
React 腳手架已集成 CSS Modules ,可直接使用
步驟:
- 改樣式文件名。從
xx.scss -> xx.module.scss(React腳手架中的約定,與普通 CSS 作區(qū)分) - 引入使用:
- 組件中導(dǎo)入該樣式文件(注意語法)
import styles from './index.module.scss'
- 通過
styles對(duì)象訪問對(duì)象中的樣式名來設(shè)置樣式
<div className={styles.css類名}></div>
css類名是 index.module.scss 中定義的類名。
3、css modules 最佳實(shí)踐
- 每個(gè)組件的根節(jié)點(diǎn)使用 CSSModules 形式的類名( 根元素的類名: root )
- 其他所有的子節(jié)點(diǎn),都使用普通的 CSS 類名 :global
component.tsx
import styles from './index.module.scss'
const 組件 = () => {
return (
{/* (1) 根節(jié)點(diǎn)使用 CSSModules 形式的類名( 根元素的類名: `root` )*/}
<div className={styles.root}>
{/* (2) 所有子節(jié)點(diǎn),都使用普通的 CSS 類名*/}
<h1 className="title">
<span className="text">登錄</span>
<span>登錄</span>
</h1>
<form className="login-form"></form>
</div>
)
}
index.module.scss
.root {
display: 'block';
position: 'absolute';
// 此處,使用 global 包裹其他子節(jié)點(diǎn)的類名。此時(shí),這些類名就不會(huì)被處理,在 JSX 中使用時(shí),就可以用字符串形式的類名
// 如果不加 :global ,所有類名就必須添加 styles.title 才可以
:global {
.title {
.text {
}
span {
}
}
.login-form { ... }
}
}
參考:https://juejin.cn/post/7031556713329197093
五、eslint、prettier 配置
由于我們項(xiàng)目使用的 TS,所以我們需要配置的環(huán)境為:react + ts + hooks 的工程配置。
1、安裝 eslint 相關(guān)依賴
通過 CRA 腳手架創(chuàng)建的 TypeScript 項(xiàng)目,會(huì)默認(rèn)安裝一個(gè) eslint-config-react-app 的 eslint 配置庫,這個(gè)庫自帶了如下依賴:
-
@typescript-eslint/parser:將 TypeScript 轉(zhuǎn)換為 ESTree,使 eslint 可以識(shí)別 -
@typescript-eslint/eslint-plugin:TypeScript eslint 內(nèi)置的規(guī)則列表,可以直接繼承過來 -
eslint-plugin-react:校驗(yàn) React -
eslint-plugin-react-hooks:根據(jù) Hooks API 校驗(yàn) Hooks 的使用 -
eslint-plugin-jsx-a11y:提供 jsx 元素可訪問性校驗(yàn) -
eslint-plugin-import:此插件主要為了校驗(yàn) import/export 語法,防止錯(cuò)誤拼寫文件路徑以及導(dǎo)出名稱的問題
基本幫我們把 React 需要的 eslint 相關(guān)依賴都預(yù)先安裝好了,我們只需要再安裝 eslint 支持庫就可以:
npm install -save-dev eslint
這里要注意你項(xiàng)目中 eslint-config-react-app 需要依賴的 eslint 版本要求,然后安裝指定的版本。
2、安裝 prettier 相關(guān)依賴
我們可以借助 ESlint 來提高我們編碼的質(zhì)量,但是卻無法保證統(tǒng)一代碼風(fēng)格。一個(gè)統(tǒng)一的代碼風(fēng)格對(duì)于團(tuán)隊(duì)來說是很有價(jià)值的,所以為了達(dá)到目的,我們可以選擇使用 Prettier 在保存和提交代碼的時(shí)候,將代碼修改成統(tǒng)一的風(fēng)格。這樣做同時(shí)也降低了 Code Review 的成本。它不會(huì)代替 ESlint,所以需要和 ESlint 搭配使用。
prettier 主要需要安裝以下三個(gè)依賴:
npm install --save-dev prettier eslint-config-prettier eslint-plugin-prettier
-
prettier:格式化規(guī)則核心庫 -
eslint-config-prettier:禁用 ESLint 所有和 Prettier 產(chǎn)生沖突的規(guī)則 -
eslint-plugin-prettier:把 Prettier 應(yīng)用到 ESlint,配合 rules "prettier/prettier": "error" 實(shí)現(xiàn) ESlint 提醒
3、配置 eslint
在項(xiàng)目根目錄創(chuàng)建 .eslintrc.js 配置文件:
module.exports = {
env: {
node: true,
browser: true,
es2021: true,
jest: true
},
extends: [ // 繼承已有規(guī)則,繼承按順序進(jìn)行覆蓋
"eslint:recommended", // eslint 推薦的規(guī)則
"plugin:react/recommended", // @eslint-plugin-react 的推薦規(guī)則
"plugin:@typescript-eslint/recommended", // @typescript-eslint/eslint-plugin的推薦規(guī)則
"plugin:prettier/recommended" // eslint-plugin-prettier 的推薦規(guī)則
],
parser: "@typescript-eslint/parser", // 指定解析器
parserOptions: {
ecmaVersion: "latest", // 允許解析那個(gè)版本的特性
sourceType: "module", // 允許使用 import
ecmafeatures: {
jsx: true // 允許對(duì)JSX進(jìn)行解析
}
},
plugins: ["react", "react-hooks", "@typescript-eslint", "prettier"],
settings: {
react: {
version: "detect" // 告訴eslint-plugin-react 自動(dòng)檢測 React的版本
}
},
// 自定義規(guī)則
rules: {
// ///////////////////////// error
// 禁止不必要的分號(hào)
// "no-extra-semi": 1,
// 禁止出現(xiàn)令人困惑的多行表達(dá)式
"no-unexpected-multiline": 2,
// 禁止在return、throw、continue 和 break語句之后出現(xiàn)不可達(dá)代碼
/*
function foo() {
return true;
console.log("done");//錯(cuò)誤
}
*/
"no-unreachable": 2,
// 強(qiáng)制 typeof 表達(dá)式與有效的字符串進(jìn)行比較
// typeof foo === "undefimed" 錯(cuò)誤
"valid-typeof": 2,
// 啟用嚴(yán)格模式
strict: 2,
// 不允許改變用const聲明的變量
"no-const-assign": 2,
// 禁止對(duì)全局變量賦值
"no-global-assign": 2,
// 禁止重復(fù)導(dǎo)入模塊
"no-duplicate-imports": 2,
// ///////////////////////// warning
// 對(duì)于不符合 prettier 規(guī)則,eslint只提示警告
"prettier/prettier": 1,
// 不允許使用var
"no-var": 1,
// 禁止定義沒有被使用的變量
"no-unused-vars": 0,
"@typescript-eslint/no-unused-vars": 0,
// 要求或禁止使用分號(hào)而不是 ASI(這個(gè)才是控制行尾部分號(hào)的,)
semi: [1, "never"],
// 禁止分號(hào)之前出現(xiàn)空格
"semi-spacing": [1, { before: false, after: true }],
// 禁止在字符串和注釋之外不規(guī)則的空白
"no-irregular-whitespace": 1,
// 強(qiáng)制使用一致的換行風(fēng)格
// "linebreak-style": [1, "unix"],
// 使用雙引號(hào) 允許es6的``
quotes: [1, "double", { allowTemplateLiterals: true }],
// 強(qiáng)制在代碼塊中開括號(hào)前和閉括號(hào)后有空格
"block-spacing": [1, "always"],
// 在代碼塊之前強(qiáng)制使用空格
"space-before-blocks": 1,
// 要求操作符周圍有空格
"space-infix-ops": 1,
// 一元操作符必須要有空格
"space-unary-ops": 1,
// 強(qiáng)制在注釋中 // 或 /* 使用一致的空格
"spaced-comment": [1, "always", { exceptions: ["-"] }],
// 強(qiáng)制關(guān)鍵字周圍空格的一致性
"keyword-spacing": [1, { before: true, after: true }],
// 強(qiáng)制在箭頭函數(shù)中 "xxx() => {}"
"arrow-spacing": [1, { before: true, after: true }],
// 在冒號(hào)后要加上空格
"key-spacing": [1, { beforeColon: false }],
// 如果一個(gè)變量不會(huì)被重新賦值,最好使用const進(jìn)行聲明。
"prefer-const": 1,
// 強(qiáng)制類型后面要有一個(gè)","
// "flowtype/delimiter-dangle": [1, "only-multiline"],
// 在 : 后強(qiáng)制加空格
// "flowtype/space-after-type-colon": [1, "always"],
// 在 | & 符號(hào)中,強(qiáng)制加空格
// "flowtype/union-intersection-spacing": [1, "always"],
// ///////////////////////// off
// 盡可能使用`===`
eqeqeq: 0,
// 禁止不必要的布爾轉(zhuǎn)換
"no-extra-boolean-cast": 0,
"no-useless-computed-key": 0,
// 禁止不必要的括號(hào) (a * b) + c;
"no-extra-parens": 0,
// 允許使用行內(nèi)樣式
"react-native/no-inline-styles": 0,
// 禁止空格和 tab 的混合縮進(jìn)
"no-mixed-spaces-and-tabs": 0,
"react/jsx-filename-extension": 0,
// react配置
// 強(qiáng)制組件方法順序
"react/sort-comp": [2],
// 結(jié)束標(biāo)簽,組件省略閉合標(biāo)簽,html不省略閉合標(biāo)簽
"react/self-closing-comp": [
2,
{
component: true,
html: false
}
],
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-var-requires": 0,
"@typescript-eslint/ban-types": [
"error",
{
extendDefaults: true,
types: {
"{}": false
}
}
],
// 檢查 Hook 的規(guī)則,不允許在if for里面使用
"react-hooks/rules-of-hooks": [2],
// 檢查 effect 的依賴
"react-hooks/exhaustive-deps": [2],
// suppress errors for missing 'import React' in files
"react/react-in-jsx-scope": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"no-empty-function": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-empty-function": "off",
"@angular-eslint/no-empty-lifecycle-method": "off"
}
}
4、配置 prettier
根目錄創(chuàng)建 .prettierrc.js 配置文件:
module.exports = {
// 字符串是否使用單引號(hào),默認(rèn)為false,使用雙引號(hào)
singleQuote: false,
// 在jsx中把'>' 是否單獨(dú)放一行
jsxBracketSameLine: true,
// 根據(jù)顯示樣式?jīng)Q定 html 要不要折行
htmlWhitespaceSensitivity: "css",
// 換行符使用 crlf/lf/auto
endOfLine: "auto",
// 一個(gè)tab代表幾個(gè)空格數(shù),默認(rèn)為80
tabWidth: 4,
// 是否使用tab進(jìn)行縮進(jìn),默認(rèn)為false,表示用空格進(jìn)行縮減
useTabs: false,
// 換行字符串閾值
printWidth: 80,
// 句末加分號(hào),默認(rèn)為true
semi: false,
// 是否使用尾逗號(hào),有三個(gè)可選值"<none|es5|all>"
trailingComma: "none",
// 對(duì)象大括號(hào)直接是否有空格,默認(rèn)為true,效果:{ foo: bar }
bracketSpacing: true,
// (x) => {} 是否要有小括號(hào)
arrowParens: "avoid",
// 是否要注釋來決定是否格式化代碼
requirePragma: false,
// 是否要換行
proseWrap: "preserve",
};
5、配置 VS Code 編輯器 (如果需要保存時(shí)自動(dòng)格式化可以配置)
- 在 VS Code 商店中尋找并安裝插件 ESlint,Prettier


- 在項(xiàng)目根目錄創(chuàng)建
.vscode文件夾,然后創(chuàng)建settings.json文件,填充如下內(nèi)容:
{
"editor.defaultFormatter": "esbenp.prettier-vscode", // 默認(rèn)的格式化插件prettier
"editor.formatOnType": true, // 輸完一行后自動(dòng)格式化
"editor.formatOnSave": true, // 保存時(shí)格式化
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true // 保存時(shí)使用 eslint fix 命令進(jìn)行格式化對(duì)應(yīng)文件
},
"eslint.run": "onSave",
"eslint.validate": [
"javascript",
"javascriptreact",
"typescriptreact",
"html",
"vue"
]
}
這樣當(dāng)我們?cè)诒4嫖募臅r(shí)候,就會(huì)自動(dòng)優(yōu)化文件格式了。
如果團(tuán)隊(duì)合作,VSCode 配置文件可以上傳到 git 倉庫,這樣大家都能共享一份配置,有助于代碼格式的統(tǒng)一。