最近项目中要使用 React 来开发 web 应用,这里记录一下搭建 React web
脚手架的过程,方便后期自查。
一、CRA 创建官方脚手架
npx create-react-app my-app
create-react-app 脚手架中将 webpack + babel 封装成了一个叫做 react-scripts
的库,用来隐藏配置,开发人员可以快速上手。
但是如果要自定义 webpack 的配置,就必须使用 eject
将配置弹出,会导致我们关注到一些无关的配置,体验并不好,同时无法跟随 react-scripts
的官方更新,所以我们通常会借助 craco 来优化这个问题。
二、安装 craco
方便自定义 webpack 配置
craco 全称 Create React App Configuration Override,取首字母即组成了工具名称。
craco 是为了无 eject
、可配置式的去修改 CRA 默认提供的工程配置,这样既能享受 CRA 带来的便利和后续升级,也能自己去自定义打包配置完成项目需要,一举两得。
1、安装 @craco/craco
npm i @craco/craco -D
2、修改 package.json 中的 script 标签。
将 start/build/test
三个命令修改为 craco 方式:
"scripts": {
- "start": "react-scripts start",
- "build": "react-scripts build",
- "test": "react-scripts test",
+ "start": "craco start",
+ "build": "craco build",
+ "test": "craco test",
}
3、在项目根目录中创建 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 的基础配置已经完成,craco.config.js 配置文件结构,可以在 craco 官方的文档中详细查询:Configuration Overview
关于 react-app-rewrited 的替代品 craco 及最佳实践 可参考
三、支持修改环境变量 cross-env
为了支持添加不同网络域名的环境变量,需要在 node 运行时注入一些自定义的环境变量。
由于不同平台注入环境变量有所差异,为了兼容 Windows
和 Linux
等不同平台,可以使用 cross-env
这个跨平台设置和使用环境变量的脚本。
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 脚手架中已经有了 sass
的配置(并没有配置 less
,如果想用 less
,需要另外配置),因此只需要安装 sass
的依赖包,就可以直接使用 sass
了:
1、安装 sass
npm i sass -D
2、css modules 解决样式污染问题
React 脚手架已集成 CSS Modules ,可直接使用
步骤:
- 改样式文件名。从
xx.scss -> xx.module.scss
(React脚手架中的约定,与普通 CSS 作区分) - 引入使用:
- 组件中导入该样式文件(注意语法)
import styles from './index.module.scss'
- 通过
styles
对象访问对象中的样式名来设置样式
<div className={styles.css类名}></div>
css类名是 index.module.scss
中定义的类名。
3、css modules 最佳实践
- 每个组件的根节点使用 CSSModules 形式的类名( 根元素的类名: root )
- 其他所有的子节点,都使用普通的 CSS 类名 :global
component.tsx
import styles from './index.module.scss'
const 组件 = () => {
return (
{/* (1) 根节点使用 CSSModules 形式的类名( 根元素的类名: `root` )*/}
<div className={styles.root}>
{/* (2) 所有子节点,都使用普通的 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 包裹其他子节点的类名。此时,这些类名就不会被处理,在 JSX 中使用时,就可以用字符串形式的类名
// 如果不加 :global ,所有类名就必须添加 styles.title 才可以
:global {
.title {
.text {
}
span {
}
}
.login-form { ... }
}
}
参考:https://juejin.cn/post/7031556713329197093
五、eslint、prettier 配置
由于我们项目使用的 TS,所以我们需要配置的环境为:react + ts + hooks 的工程配置。
1、安装 eslint 相关依赖
通过 CRA 脚手架创建的 TypeScript 项目,会默认安装一个 eslint-config-react-app
的 eslint 配置库,这个库自带了如下依赖:
-
@typescript-eslint/parser
:将 TypeScript 转换为 ESTree,使 eslint 可以识别 -
@typescript-eslint/eslint-plugin
:TypeScript eslint 内置的规则列表,可以直接继承过来 -
eslint-plugin-react
:校验 React -
eslint-plugin-react-hooks
:根据 Hooks API 校验 Hooks 的使用 -
eslint-plugin-jsx-a11y
:提供 jsx 元素可访问性校验 -
eslint-plugin-import
:此插件主要为了校验 import/export 语法,防止错误拼写文件路径以及导出名称的问题
基本帮我们把 React 需要的 eslint 相关依赖都预先安装好了,我们只需要再安装 eslint 支持库就可以:
npm install -save-dev eslint
这里要注意你项目中 eslint-config-react-app
需要依赖的 eslint 版本要求,然后安装指定的版本。
2、安装 prettier 相关依赖
我们可以借助 ESlint 来提高我们编码的质量,但是却无法保证统一代码风格。一个统一的代码风格对于团队来说是很有价值的,所以为了达到目的,我们可以选择使用 Prettier
在保存和提交代码的时候,将代码修改成统一的风格。这样做同时也降低了 Code Review 的成本。它不会代替 ESlint,所以需要和 ESlint 搭配使用。
prettier 主要需要安装以下三个依赖:
npm install --save-dev prettier eslint-config-prettier eslint-plugin-prettier
-
prettier
:格式化规则核心库 -
eslint-config-prettier
:禁用 ESLint 所有和 Prettier 产生冲突的规则 -
eslint-plugin-prettier
:把 Prettier 应用到 ESlint,配合 rules "prettier/prettier": "error" 实现 ESlint 提醒
3、配置 eslint
在项目根目录创建 .eslintrc.js
配置文件:
module.exports = {
env: {
node: true,
browser: true,
es2021: true,
jest: true
},
extends: [ // 继承已有规则,继承按顺序进行覆盖
"eslint:recommended", // eslint 推荐的规则
"plugin:react/recommended", // @eslint-plugin-react 的推荐规则
"plugin:@typescript-eslint/recommended", // @typescript-eslint/eslint-plugin的推荐规则
"plugin:prettier/recommended" // eslint-plugin-prettier 的推荐规则
],
parser: "@typescript-eslint/parser", // 指定解析器
parserOptions: {
ecmaVersion: "latest", // 允许解析那个版本的特性
sourceType: "module", // 允许使用 import
ecmafeatures: {
jsx: true // 允许对JSX进行解析
}
},
plugins: ["react", "react-hooks", "@typescript-eslint", "prettier"],
settings: {
react: {
version: "detect" // 告诉eslint-plugin-react 自动检测 React的版本
}
},
// 自定义规则
rules: {
// ///////////////////////// error
// 禁止不必要的分号
// "no-extra-semi": 1,
// 禁止出现令人困惑的多行表达式
"no-unexpected-multiline": 2,
// 禁止在return、throw、continue 和 break语句之后出现不可达代码
/*
function foo() {
return true;
console.log("done");//错误
}
*/
"no-unreachable": 2,
// 强制 typeof 表达式与有效的字符串进行比较
// typeof foo === "undefimed" 错误
"valid-typeof": 2,
// 启用严格模式
strict: 2,
// 不允许改变用const声明的变量
"no-const-assign": 2,
// 禁止对全局变量赋值
"no-global-assign": 2,
// 禁止重复导入模块
"no-duplicate-imports": 2,
// ///////////////////////// warning
// 对于不符合 prettier 规则,eslint只提示警告
"prettier/prettier": 1,
// 不允许使用var
"no-var": 1,
// 禁止定义没有被使用的变量
"no-unused-vars": 0,
"@typescript-eslint/no-unused-vars": 0,
// 要求或禁止使用分号而不是 ASI(这个才是控制行尾部分号的,)
semi: [1, "never"],
// 禁止分号之前出现空格
"semi-spacing": [1, { before: false, after: true }],
// 禁止在字符串和注释之外不规则的空白
"no-irregular-whitespace": 1,
// 强制使用一致的换行风格
// "linebreak-style": [1, "unix"],
// 使用双引号 允许es6的``
quotes: [1, "double", { allowTemplateLiterals: true }],
// 强制在代码块中开括号前和闭括号后有空格
"block-spacing": [1, "always"],
// 在代码块之前强制使用空格
"space-before-blocks": 1,
// 要求操作符周围有空格
"space-infix-ops": 1,
// 一元操作符必须要有空格
"space-unary-ops": 1,
// 强制在注释中 // 或 /* 使用一致的空格
"spaced-comment": [1, "always", { exceptions: ["-"] }],
// 强制关键字周围空格的一致性
"keyword-spacing": [1, { before: true, after: true }],
// 强制在箭头函数中 "xxx() => {}"
"arrow-spacing": [1, { before: true, after: true }],
// 在冒号后要加上空格
"key-spacing": [1, { beforeColon: false }],
// 如果一个变量不会被重新赋值,最好使用const进行声明。
"prefer-const": 1,
// 强制类型后面要有一个","
// "flowtype/delimiter-dangle": [1, "only-multiline"],
// 在 : 后强制加空格
// "flowtype/space-after-type-colon": [1, "always"],
// 在 | & 符号中,强制加空格
// "flowtype/union-intersection-spacing": [1, "always"],
// ///////////////////////// off
// 尽可能使用`===`
eqeqeq: 0,
// 禁止不必要的布尔转换
"no-extra-boolean-cast": 0,
"no-useless-computed-key": 0,
// 禁止不必要的括号 (a * b) + c;
"no-extra-parens": 0,
// 允许使用行内样式
"react-native/no-inline-styles": 0,
// 禁止空格和 tab 的混合缩进
"no-mixed-spaces-and-tabs": 0,
"react/jsx-filename-extension": 0,
// react配置
// 强制组件方法顺序
"react/sort-comp": [2],
// 结束标签,组件省略闭合标签,html不省略闭合标签
"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 的规则,不允许在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
根目录创建 .prettierrc.js
配置文件:
module.exports = {
// 字符串是否使用单引号,默认为false,使用双引号
singleQuote: false,
// 在jsx中把'>' 是否单独放一行
jsxBracketSameLine: true,
// 根据显示样式决定 html 要不要折行
htmlWhitespaceSensitivity: "css",
// 换行符使用 crlf/lf/auto
endOfLine: "auto",
// 一个tab代表几个空格数,默认为80
tabWidth: 4,
// 是否使用tab进行缩进,默认为false,表示用空格进行缩减
useTabs: false,
// 换行字符串阈值
printWidth: 80,
// 句末加分号,默认为true
semi: false,
// 是否使用尾逗号,有三个可选值"<none|es5|all>"
trailingComma: "none",
// 对象大括号直接是否有空格,默认为true,效果:{ foo: bar }
bracketSpacing: true,
// (x) => {} 是否要有小括号
arrowParens: "avoid",
// 是否要注释来决定是否格式化代码
requirePragma: false,
// 是否要换行
proseWrap: "preserve",
};
5、配置 VS Code 编辑器 (如果需要保存时自动格式化可以配置)
- 在 VS Code 商店中寻找并安装插件 ESlint,Prettier
- 在项目根目录创建
.vscode
文件夹,然后创建settings.json
文件,填充如下内容:
{
"editor.defaultFormatter": "esbenp.prettier-vscode", // 默认的格式化插件prettier
"editor.formatOnType": true, // 输完一行后自动格式化
"editor.formatOnSave": true, // 保存时格式化
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true // 保存时使用 eslint fix 命令进行格式化对应文件
},
"eslint.run": "onSave",
"eslint.validate": [
"javascript",
"javascriptreact",
"typescriptreact",
"html",
"vue"
]
}
这样当我们在保存文件的时候,就会自动优化文件格式了。
如果团队合作,VSCode 配置文件可以上传到 git 仓库,这样大家都能共享一份配置,有助于代码格式的统一。