基于 pnpm 搭建 React + TypeScript + Vite 项目

6,166 阅读10分钟

项目的创建和运行

项目包管理器使用 pnpm

pnpm 和 npm/yarn 一样,本质上都是包管理器,但是它在性能上有极大的提升:包安装速度快、高效利用磁盘空间、安全性高、支持 monorepo 等。

它的安装很方便,一般使用 npm 安装:

npm install pnpm

首先,基于 Vite 创建一个命名为 react-basicreact-typescript 模板项目:

pnpm create vite react-basic --template react-ts

创建成功后,进入项目,执行 install 命令安装依赖:

pnpm i

启动服务前,可以在 vite.config.ts 中修改开发服务器的配置

export default defineConfig({
  server: {
    host: 'localhost', 
    port: 3333, // 指定服务器端口
    open: true, // 启动开发服务器时,自动在浏览器打开应用
    strictPort: false, // 若端口被占用,尝试下一个可用端口
    https: false, // 不开启 https 服务
    cors: true, // 允许跨域
  },
})

最后,执行 dev 命令启动项目,就会自动在浏览器打开对应页面:

pnpm run dev

image.png

项目配置

Vite 配置

vite.config.js 文件用于管理 Vite 的打包配置。在这个文件中,可以配置插件、别名、构建选项、服务器设置等各种与 Vite 相关的选项。

以下是一些常见的选项及其说明:

import { defineConfig } from 'vite';
import * as path from 'path';
import react from '@vitejs/plugin-react';

export default defineConfig({
  base: './',
  publicDir: 'public',
  resolve: {
    // 路径别名
    alias: [
      { find: '@', replacement: path.resolve(__dirname, 'src') }, 
    ],
  },
  build: {
    target: 'modules', // 浏览器兼容目标
    outDir: 'dist', // 打包输出路径
    assetsDir: 'assets', // 静态资源存放路径
    cssCodeSplit: true, // 允许 css 代码拆分
    sourcemap: false, // 不生成 sourceMap 文件
    minify: 'terser', // 缩小文件体积
    terserOptions: {
      compress: {
        drop_console: true, // 取消 console
        drop_debugger: true, // 取消 debugger
      },
    },
  },
  server: {
    host: 'localhost', 
    port: 3333, // 指定服务器端口
    open: true, // 启动开发服务器时,自动在浏览器打开应用
    strictPort: false, // 若端口被占用,尝试下一个可用端口
    https: false, // 不开启 https 服务
    cors: true, // 允许跨域
    // 配置代理
    proxy: {
      '/api': {
        target: 'http://127.0.0.1:8000', // 接口地址
        changeOrigin: true, // 接口跨域
        secure: false, // 启用 https 服务时需要配置
      },
    },
  },
  plugins: [react()],
});

如果在配置中指定了 minify: terser,需要单独安装 terser 插件:

pnpm i terser -D

Typescript 配置

tsconfig.json 文件用于检查和编译 TypeScript 静态类型。

由于在 vite.config.ts 中配置了路径别名,为了防止 @ 找不到对应模块而报错,接下来需要在 tsconfig.json 中添加以下配置:

"compilerOptions": {
  // 路径映射配置
  "baseUrl": "./",
  "paths": {
    "@": [ "src" ],
    "@/*": [ "src/*" ],
  }
  // ...
},

代码规范配置

Eslint 配置

Eslint 用于检查代码质量。以下是个人常用的 .eslintrc.cjs 文件配置:

module.exports = {
  env: {
    es2020: true,
    node: true,
    browser: true,
  },
  extends: [
    'eslint:recommended', // Eslint
    'plugin:@typescript-eslint/recommended' // Typescript
  ],
  parser: '@typescript-eslint/parser', // 解析 TS 代码
  parserOptions: {
    parser: '@babel/eslint-parser', // 解析和检查使用 Babel 转译的 JS 代码
    sourceType: 'module',
    ecmaVersion: 'latest',
    ecmaFeatures: {
      jsx: true
    },
  },
  plugins: ['react-refresh', '@typescript-eslint'],
  rules: { 
    // 禁止使用分号
    'semi': 'off',
    // 强制使用单引号
    'quotes': ['error', 'single'],
    // 不强制在箭头函数体周围使用大括号
    'arrow-body-style': 'off',
    // 警告变量声明未按字母顺序排序
    'sort-vars': 'warn',
    // 警告对象键未按字母顺序排序
    'sort-keys': 'warn',
    // 警告使用 `console` 方法
    'no-console': 'off',
    // 允许使用未声明的变量
    'no-undef': 'off',
    // 关闭禁止重复声明变量的规则
    'no-redeclare': 'off',
    // 关闭禁止重赋值函数参数的规则
    'no-param-reassign': 'off',
    // 关闭要求函数明确返回类型的规则
    '@typescript-eslint/explicit-function-return-type': 'off',
    // 警告使用 `any` 类型
    '@typescript-eslint/no-explicit-any': 'warn',
    // 关闭禁止未处理的 Promise 的规则
    '@typescript-eslint/no-floating-promises': 'off',
    // 关闭禁止错误使用 Promise 的规则
    '@typescript-eslint/no-misused-promises': 'off',
    // 关闭禁止使用非 null 断言运算符的规则
    '@typescript-eslint/no-non-null-assertion': 'off',
    // 强制禁止重复声明类型
    '@typescript-eslint/no-redeclare': 'error',
    // 关闭禁止使用与外部变量同名的变量的规则
    '@typescript-eslint/no-shadow': 'off',
    // 关闭禁止默认导出的规则
    'import/no-default-export': 'off',
    // 关闭禁止使用命名导入作为默认成员的规则
    'import/no-named-as-default-member': 'off',
    // 无须在组件中显式 import React
    'react/jsx-uses-react': 'off',
    // 关闭禁止在 JSX 中使用未转义的实体的规则
    'react/no-unescaped-entities': 'off',
    // 关闭强制要求在 JSX 文件中使用 `<React>` 命名空间的规则
    'react/react-in-jsx-scope': 'off'
  },
};

另外,可以在根目录下新建 .eslintignore 文件,告诉 Eslint 在进行规则校验时需要跳过的文件和目录。

.idea
.local
.vscode

/bin
/dist
/docs
/node_modules
/public

*.md
*.sh
*.ttf
*.woff

配置完成后,运行命令,看看 Eslint 校验能否正常工作:

pnpm run lint

image.png

此时尝试添加一个声明但未使用的变量,就会发出警告。

image.png

Prettier 配置

Prettier 专注于代码格式化,对代码不做质量检查。在项目中安装 prettier

pnpm i prettier -D

以下是个人常用的 .prettierrc.cjs 文件配置:

module.exports = {
  tabWidth: 2, // 缩进长度
  useTabs: false, // 使用空格代替 Tab 缩进
  printWidth: 150, // 单行长度
  semi: false, // 句末不使用分号
  singleQuote: true, // 使用单引号
  quoteProps: 'as-needed', // 仅在必需时为对象的 key 添加引号
  jsxSingleQuote: true, // jsx 中使用单引号
  trailingComma: 'all', // 多行时尽可能打印尾随逗号
  bracketSpacing: true, // 在对象前后添加空格
  arrowParens: 'always', // 箭头函数单参数时包裹圆括号
  requirePragma: false, // 无需顶部注释即可格式化
  htmlWhitespaceSensitivity: 'ignore', // 对 HTML 全局空白不敏感
  embeddedLanguageFormatting: 'auto', // 对引用代码进行格式化
  endOfLine: 'auto', // 不检查结束行形式
};

另外,同样可以在根目录下新建 .prettierignore 文件,告诉 Prettier 在进行代码格式化时需要跳过的文件和目录。

# Ignore artifacts:
build
coverage

# Ignore all HTML files:
*.html

配置完成后,我们就可以通过命令行进行文档格式化。

1)使用 write 命令,此处使用 . 指定格式化内容为根目录下所有文件:

npx prettier --write .

image.png

2)使用 check 命令,检查指定范围内的文件是否都符合规则:

npx prettier --check .

image.png

如果运行的是本地 Prettier 插件,需要在 prettier 命令前加上 npx

Eslint 和 Prettier 的集成

由于 EslintPrettier 有部分重叠功能,为了避免冲突,需要安装下列依赖:

pnpm install eslint-plugin-prettier eslint-config-prettier -D

接下来,在 .eslintrc.cjs 中添加配置:

extends: [
  'plugin:prettier/recommended',
],

配置完成后,测试一下 EslintPrettier 是否可以正常工作。

image.png

在这个过程中,可能会遇到一些问题:

1)如果执行 lint 命令后出现了警告或报错信息,可以在 lint 命令加上 --fix,此时就会采用项目中的 Prettier 配置规则自动格式化文件,而不需要手动修改。

image.png image.png

2)如果新建的 EslintPrettier 配置文件使用了 .js 后缀,执行 lint 命令后会报错,因为 Vite 运行时使用 esbuild,它遵循 ESModule 语法规范,此时 .js 文件默认使用 ESModule 语法规范来解析,如果配置文件使用了 CommonJS 语法规范,就会出错。

此时,将配置文件的后缀改为 .cjs,明确声明使用的是 CommonJS 语法规范即可。

image.png

扩展:Eslint 相关插件的作用

  • eslint-plugin-vue 插件:针对 VueEslint 语法校验。

  • eslint-plugin-react 插件:针对 ReactEslint 语法校验。

  • eslint-config-prettier 插件:协调 EslintPrettier 的格式化规则,防止冲突,本质上是禁用与 Prettier 配置冲突的 Eslint 规则。

  • eslint-plugin-prettier 插件:将 Prettier 规则作为 Eslint 规则使用,如果代码不符合 Prettier 配置也会报错,此时可以通过 eslint --fix 进行自动格式化,从而解决问题。

  • @typescript-eslint/eslint-plugin 插件:针对 TypescriptEslint 语法校验。

  • @typescript-eslint/parser 插件:将 Typescript 语法解析成 AST

Husky 配置(可选)

Husky 是一个 Git 钩子管理工具,我们可以借助它,在某些 Git 操作对应的生命周期进行一些自动化操作,比如校验提交信息、检查代码质量等。首先,安装 husky 依赖:

pnpm install husky -D

配置 package.json 文件:

"scripts": {
  // ...
  "prepare": "husky install"
},

执行 prepare 命令,继续安装 husky

pnpm run prepare

在这个过程中,可能会遇到一些问题:

1)如果执行 prepare 命令后,出现 install command is deprecated 的提示信息,就将 husky install 替换成 husky,然后重新执行 prepare 命令。

2)如果出现以下报错,说明项目在本地还没有初始化 git 仓库,需要先执行 git init 操作。重新执行 prepare 命令,继续安装 Husky

image.png

配置完成后,查看项目结构,可以看到新增了 .husky 目录。

image.png

添加 pre-commit hook

pre-commit hook 通常对暂存区文件进行规范校验。安装 lint-staged 依赖:

pnpm i lint-staged -D

添加 package.json 配置:

"scripts": {
  // ...
  "lint": "eslint src --ext js,jsx,ts,tsx --fix", // 添加 --fix 在遇到问题时会尝试自动修复
  "lint:lint-staged": "lint-staged"
},
"lint-staged": {
  "*.{js,jsx,ts,tsx}": [
    "pnpm run lint", 
    "npx prettier --write"
  ],
  "*.{css,scss,less,styl,html}": [
    "npx prettier --write"
  ],
  "package.json": [
    "npx prettier --write"
  ],
  "{!(package)*.json,*.code-snippets,.!(browserslist)*rc}": [
    "npx prettier --write--parser json"
  ]
},
// ...

配置完成后,执行命令添加 pre-commit hook

npx husky add .husky/pre-commit "pnpm run lint:lint-staged"

image.png

现在可以使用 commit 命令,测试一下 lint-staged 配置是否生效。

image.png image.png

可以看到,暂存区文件经过 EslintPrettier 的双重校验后成功提交,说明 pre-commit hook 配置生效了。

添加 commit-msg hook

commit-msg hook 通常对 commit 信息进行规范校验。安装依赖:

pnpm install @commitlint/config-conventional @commitlint/cli -D

在项目根目录下新建 .commitlintrc.cjs 文件,可以直接引入 @commitlint/config-conventional 插件,也可以自定义配置,更推荐直接使用 Angular 规范集。

module.exports = {
  // 直接引入插件即可
  extends: ['@commitlint/config-conventional'],
  // 自定义配置
  rules: {
    'type-enum': [2, 'always', [
      'build', // 编译相关修改,例如发布版本、项目构建或者依赖的改动
      'feat', // 添加新功能
      'fix', // 修复Bug
      'update', // 更新某功能
      'refactor', // 重构
      'docs', // 文档更改
      'chore', // 构建过程或辅助工具的变动,如添加依赖等
      'style', // 不影响代码运行的变动
      'revert', // 回滚到上一个版本
      'perf', // 性能优化
      'test', // 单元测试、集成测试等
    ],
    ],
    'type-case': [0],
    'type-empty': [0],
    'scope-empty': [0],
    'scope-case': [0],
    'subject-full-stop': [0, 'never'],
    'subject-case': [0, 'never'],
    'header-max-length': [0, 'always', 74],
  },
};

配置完成后,执行命令添加 commit-msg hook

npx husky add .husky/commit-msg "npx --no -- commitlint --edit ${1}"

image.png

此时使用 commit 命令提交代码,出现以下结果就说明 commit-msg hook 配置生效了。

image.png

目录结构划分

根据个人习惯添加对应的目录和文件,个人常用目录结构及其说明如下:

assets:原始静态资源管理

components:全局通用组件管理

hooks:自定义 Hooks 管理

pages:页面逻辑管理

routes:路由管理

service:接口管理

store:状态管理

utils:公用工具管理

image.png

组件库使用 Antd5

pnpm i antd@latest @ant-design/icons

App.tsx 中引入 Layout 布局,效果如下。

image.png

可以看到,Layout 组件不能占满屏幕,这里需要重置一下部分元素的样式。

最简单的解决方法:在 assets 目录下定义一个 css 文件夹,存放全局使用的 css 文件,然后创建 reset.css 文件,配置如下内容:

/* margin、padding 重置 */
body, h1, h2, h3, ul, ol, li, p, dl, dt, dd {
  padding: 0;
  margin: 0;
}

/* a 元素重置 */
a {
  text-decoration: none;
  color: #333;
}

/* img 的 vertical-align 重置 */
img {
  vertical-align: top;
}

/* ul, ol, li 重置 */
ul, ol, li {
  list-style: none;
}

/* 对斜体元素重置 */
i, em {
  font-style: normal;
}

配置完成后,在 App.tsx 中引入 reset.css 并刷新一下界面,就可以看到 Layout 占满屏幕了。

最后

新手记录一下自己的学习过程,欢迎大家指出问题或提出建议。

关于项目路由的配置和集中管理,可以看这一篇: React 项目路由配置与跳转