React 脚手架搭建过程记录

最近项目中要使用 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 运行时注入一些自定义的环境变量。

由于不同平台注入环境变量有所差异,为了兼容 WindowsLinux 等不同平台,可以使用 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
ESlint.png
Prettier.png
  • 在项目根目录创建 .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 仓库,这样大家都能共享一份配置,有助于代码格式的统一。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271