跳到主要内容

react-typescript-custom

2024年04月29日
柏拉文
越努力,越幸运

项目架构

website # 模板目录结构
├── .husky
│ └── commit-msg
│ └── pre-commit
├── .vscode
│ └── settings.json
├── src
│ └── assets
│ └── pages
│ └── styles
│ └── App.tsx
│ └── index.tsx
│ └── type-env.d.ts
│ └── type-resource.d.ts
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .lintstagedrc.js
├── .prettierignore
├── .prettierrc.js
├── .stylelintignore
├── .stylelintrc.js
├── commitlint.config.js
├── index.html
├── package.json
├── tsconfig.json
├── vite.config.ts
├── ...

步骤一、Vite


1. 安装依赖

yarn add vite @vitejs/plugin-react -D 

2. 构建入口文件、初始页面

根目录/index.html: 入口文件

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite-React 模板</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./src/index.tsx"></script>
</body>
</html>

根目录/src/index.ts: react 入口文件

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './styles/index';

ReactDOM.createRoot(document.getElementById('app')!).render(<App />);

根目录/src/App.tsx: react 初始页面

import React, {useState} from 'react';
import logo from '@/assets/svg/logo.svg';

function App() {
const [title, setTitle] = useState<string>('Vite-React 模板');
const name: string = 'st';
const age: number = 200;
return (
<div>
<h3>{title}</h3>
<img src={logo} alt="" />
</div>
);
}

export default App;

3. 支持 scss、less 等文件

Vite 天然支持 scssless 等文件,只需要安装响应依赖即可

yarn add scss less -D

步骤二、VsCode


1. 配置 .vscode

.vscode/settings.json文件

{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.fixAll.stylelint": true
},
"eslint.options": {
"extensions": [".ts", ".tsx"]
},
"eslint.validate": ["typescript", "typescriptreact"],
"stylelint.validate": ["css", "scss"]
}

2. 配置 .editorconfig 文件

[*.{ts,tsx}]
indent_style = space
indent_size = 4
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 200

步骤三、TypeScript


1. 安装依赖

yarn add typescript @types/node @types/react @types/react-dom -D

2. 配置 TypeScript 命令行

 "tsc": "tsc --noEmit",

3. 配置 tsconfig.json 文件

{
"compilerOptions": {
"target": "ESNext",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"types": ["vite/client", "node"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react",
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx"],
"exclude": ["node_modules"]
}

4. 配置 xx.d.ts 类型声明文件

type-env.d.ts

/// <reference types="vite/client" />

type-resource.d.ts

declare module '*.css';
declare module '*.scss';
declare module '*.png';
declare module '*.svg' {
export function ReactComponent(
props: React.SVGProps<SVGSVGElement>,
): React.ReactElement;
const url: string;
export default url;
}

步骤四、Eslint 校验


1. 安装依赖

yarn add eslint eslint-config-airbnb eslint-config-prettier eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-prettier eslint-plugin-react eslint-plugin-react-hooks @typescript-eslint/eslint-plugin @typescript-eslint/parser -D

2. 配置 Esling 命令行

"eslint-fix": "eslint src --ext .jsx,.js,.ts,.tsx --fix",

3. 配置 .eslintrc.js 文件

module.exports = {
root: true,
env: {
browser: true,
node: true,
es6: true,
commonjs: true,
},
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
extends: [
'eslint:recommended',
'airbnb',
'prettier',
'plugin:react/recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:react/jsx-runtime',
],
plugins: [
'react',
'react-hooks',
'import',
'prettier',
'@typescript-eslint',
],
rules: {
semi: 2,
'no-unused-vars': 0,
'import/no-unresolved': [
2,
{
ignore: ['^@/'], // @ 是设置的路径别名
},
],
'import/extensions': [
'error',
{
ignorePackages: true,
pattern: {
js: 'always',
jsx: 'never',
ts: 'never',
tsx: 'never',
scss: 'never',
vue: 'always',
png: 'always',
jpg: 'always',
svg: 'always',
},
},
],
'react/jsx-filename-extension': [1, {extensions: ['.js', '.tsx']}],
'@typescript-eslint/no-unused-vars': 0,
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/triple-slash-reference': 'off',
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
},
settings: {
react: {
version: 'detect',
},
'import/resolver': {
node: {
extensions: [
'.js',
'.ts',
'.jsx',
'.tsx',
'.json',
'.scss',
'.less',
],
},
},
'import/extensions': [
'.js',
'.ts',
'.jsx',
'.tsx',
'.json',
'.scss',
'.less',
],
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx'],
},
},
};

4. 配置 .eslintignore 文件

dist/
public/
node_modules/

步骤五、Prettier 配置


1. 安装依赖

yarn add prettier 

2. 配置 Prettier 命令行

"prettier": "prettier --write src/**/*.{ts,tsx}",

3. 配置 .prettierrc.js 文件

module.exports = {
arrowParens: 'always',
bracketSpacing: false,
eslintIntegration: true,
insertPragma: false,
jsxBracketSameLine: true,
jsxSingleQuote: false,
printWidth: 80,
requirePragma: false,
semi: true,
singleQuote: true,
trailingComma: 'all',
tabWidth: 4,
useTabs: false,
};

4. 配置 .prettierignore 文件

dist/
public/
node_modules/

步骤六、Stylelint 校验


1. 安装依赖

yarn add stylelint stylelint-config-prettier stylelint-config-recess-order stylelint-config-standard-scss stylelint-scss -D

2. 配置 .stylelintrc.js 文件

module.exports = {
root: true,
plugins: ['stylelint-order'],
extends: ['stylelint-config-standard', 'stylelint-config-prettier'],
rules: {
'selector-pseudo-class-no-unknown': [
true,
{
ignorePseudoClasses: ['global'],
},
],
'selector-pseudo-element-no-unknown': [
true,
{
ignorePseudoElements: ['v-deep'],
},
],
'at-rule-no-unknown': [
true,
{
ignoreAtRules: [
'tailwind',
'apply',
'variants',
'responsive',
'screen',
'function',
'if',
'each',
'include',
'mixin',
],
},
],
'no-empty-source': null,
'named-grid-areas-no-invalid': null,
'unicode-bom': 'never',
'no-descending-specificity': null,
'font-family-no-missing-generic-family-keyword': null,
'declaration-colon-space-after': 'always-single-line',
'declaration-colon-space-before': 'never',
// 'declaration-block-trailing-semicolon': 'always',
'rule-empty-line-before': [
'always',
{
ignore: ['after-comment', 'first-nested'],
},
],
'unit-no-unknown': [
true,
{
ignoreUnits: ['rpx'],
},
],
'order/order': [
[
'dollar-variables',
'custom-properties',
'at-rules',
'declarations',
{
type: 'at-rule',
name: 'supports',
},
{
type: 'at-rule',
name: 'media',
},
'rules',
],
{
severity: 'warning',
},
],
// 按照指定顺序排列
'order/properties-order': [
'position',
'content',
'top',
'right',
'bottom',
'left',
'z-index',
'display',
'float',
'width',
'height',
'max-width',
'max-height',
'min-width',
'min-height',
'padding',
'padding-top',
'padding-right',
'padding-bottom',
'padding-left',
'margin',
'margin-top',
'margin-right',
'margin-bottom',
'margin-left',
'margin-collapse',
'margin-top-collapse',
'margin-right-collapse',
'margin-bottom-collapse',
'margin-left-collapse',
'overflow',
'overflow-x',
'overflow-y',
'clip',
'clear',
'font',
'font-family',
'font-size',
'font-smoothing',
'osx-font-smoothing',
'font-style',
'font-weight',
'hyphens',
'src',
'line-height',
'letter-spacing',
'word-spacing',
'color',
'text-align',
'text-decoration',
'text-indent',
'text-overflow',
'text-rendering',
'text-size-adjust',
'text-shadow',
'text-transform',
'word-break',
'word-wrap',
'white-space',
'vertical-align',
'list-style',
'list-style-type',
'list-style-position',
'list-style-image',
'pointer-events',
'cursor',
'background',
'background-attachment',
'background-color',
'background-image',
'background-position',
'background-repeat',
'background-size',
'border',
'border-collapse',
'border-top',
'border-right',
'border-bottom',
'border-left',
'border-color',
'border-image',
'border-top-color',
'border-right-color',
'border-bottom-color',
'border-left-color',
'border-spacing',
'border-style',
'border-top-style',
'border-right-style',
'border-bottom-style',
'border-left-style',
'border-width',
'border-top-width',
'border-right-width',
'border-bottom-width',
'border-left-width',
'border-radius',
'border-top-right-radius',
'border-bottom-right-radius',
'border-bottom-left-radius',
'border-top-left-radius',
'border-radius-topright',
'border-radius-bottomright',
'border-radius-bottomleft',
'border-radius-topleft',
'quotes',
'outline',
'outline-offset',
'opacity',
'filter',
'visibility',
'size',
'zoom',
'transform',
'box-align',
'box-flex',
'box-orient',
'box-pack',
'box-shadow',
'box-sizing',
'table-layout',
'animation',
'animation-delay',
'animation-duration',
'animation-iteration-count',
'animation-name',
'animation-play-state',
'animation-timing-function',
'animation-fill-mode',
'transition',
'transition-delay',
'transition-duration',
'transition-property',
'transition-timing-function',
'background-clip',
'backface-visibility',
'resize',
'appearance',
'user-select',
'interpolation-mode',
'direction',
'marks',
'page',
'set-link-source',
'unicode-bidi',
'speak',
],
},
ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'],
};

3. 配置 .stylelintignore 文件

*.js
*.ts
*.png
*.jpg
*.webp
*.ttf
*.woff
dist/
public/
node_modules/

4. 配置 stylelint 校验命令行

"stylelint-fix": "stylelint src/**/*.scss --fix"

步骤七、Git Commit 校验


Git Commit 校验一共有三个操作点:

  • Husky 可以用于实现各种git Hook。项目中主要用到 pre-commit 这个 hook,在执行 commit 之前,运行一些自定义操作
  • LintStaged 用于对 git 暂存区中的文件执行代码检测
  • Commitlint 用于检查提交信息,提交规范配置,规范校验配置流程

1. 安装依赖

yarn add @commitlint/cli @commitlint/config-conventional husky lint-staged -D

2. 编辑命令行

工作如下

  • 新版 husky (v6.x)需要配置命令行: "postinstall": "husky install"
  • commit 规范校验命令行: "commitlint": "commitlint --config commitlint.config.js -e -V"

完整配置

"scripts": {
"start": "vite",
"tsc": "tsc --noEmit",
"build": "tsc && vite build",
"lint-staged": "lint-staged",
"postinstall": "husky install",
"prettier": "prettier --write src/**/*.{ts,tsx}",
"stylelint-fix": "stylelint src/**/*.scss --fix",
"eslint-fix": "eslint src --ext .jsx,.js,.ts,.tsx --fix",
"commitlint": "commitlint --config commitlint.config.js -e -V"
},

3. 执行命令行,生成脚本

  1. 执行yarn postinstall , 启动husky
  2. 执行npx husky add .husky/pre-commit "npm run lint-staged" 添加pre-commit 钩子
  3. 执行npx husky add .husky/commit-msg "npm run commitlint" 添加commit-msg 钩子

4. 配置 LintStaged 文件

  1. 创建.lintstagedrc.js

  2. 编辑文件如下:

    • yarn prettier: 执行已配置好的prettier命令,用于提交时格式化代码

    • yarn eslint-fix: 执行已配置好的eslint命令,用于校验暂存区的代码。注意: 此时不会校验TypeScript代码中的类型

    • (fileName) => generateTSConfig(fileName, 'tsc'): 用于手动校验处于暂存区的TypeScript代码中的类型是否正确

    • 配置代码如下:

      const fs = require('fs');
      const generateTSConfig = (fileName, type) => {
      const tsconfig = JSON.parse(fs.readFileSync('tsconfig.json', 'utf8'));
      const include = ['src/**/*.d.ts', ...fileName].filter(
      (file) => !file.includes('TabContent'),
      );
      tsconfig.include = include;
      fs.writeFileSync('tsconfig.lint.json', JSON.stringify(tsconfig));
      return `${type} --noEmit --project tsconfig.lint.json`;
      };

      module.exports = {
      'src/**/*.{ts,tsx}': ['yarn prettier', 'yarn eslint-fix',(fileName) => generateTSConfig(fileName, 'tsc')],
      'src/**/*.{css,less,scss}': ['yarn stylelint-fix'],
      };

5. 配置 commitlint.config.js 文件

module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
// type枚举
2,
'always',
[
'build', // 编译相关的修改,例如发布版本、对项目构建或者依赖的改动
'feat', // 新功能
'fix', // 修补bug
'docs', // 文档修改
'style', // 代码格式修改, 注意不是 css 修改
'refactor', // 重构
'perf', // 优化相关,比如提升性能、体验
'test', // 测试用例修改
'revert', // 代码回滚
'ci', // 持续集成修改
'config', // 配置修改
'chore', // 其他改动
],
],
'type-empty': [2, 'never'], // never: type不能为空; always: type必须为空
'type-case': [0, 'always', 'lower-case'], // type必须小写,upper-case大写,camel-case小驼峰,kebab-case短横线,pascal-case大驼峰,等等
'scope-empty': [0],
'scope-case': [0],
'subject-empty': [2, 'never'], // subject不能为空
'subject-case': [0],
'subject-full-stop': [0, 'never', '.'], // subject以.为结束标记
'header-max-length': [2, 'always', 72], // header最长72
'body-leading-blank': [0], // body换行
'footer-leading-blank': [0, 'always'], // footer以空行开头
},
};

根据配置,项目提交时Commit的规范为:

  • "feat(修改的文件): 修改信息": 新增功能规范