AntDesignPro中台项目迁移Typescript,并接入Eslint+Prettier代码格式化

前言

在一家小公司实习了一段时间,接手的项目代码格式及规范化不可描述,为了保证项目的可维护性,决定接入Typescript,同时采用EslintPrettier进行代码规范化,为下一步的CodeReview

Typescript是强类型语言,接入Typescript一开始会有很大的痛点,但是过了一阵子就可以享受到Typescript带来的好处,bug减少了,代码易读了,也可维护了,好处网上一大把就不累赘了。

常用的代码格式化工具主要有ESlintTSLintStandardJS。TS官方已经决定弃用TSLint,全面拥抱ESLint。因此在技术选型方面将采用ESlint

Eslint的主要功能包含代码格式的校验,代码质量的校验,JS规范,如用===而不是==判断相等、用驼峰命名变量而不是用下划线。而 Prettier 是美丽的意思,只是代码格式的校验(并格式化代码),不会对代码质量进行校验,如单行代码长度、tab长度、空格、逗号表达式等问题。

一、安装相关依赖

1.1 安装 Typescript

推荐使用全局安装,可以在其他项目中也使用TS。

npm install -g typescript

1.2 安装声明文件

所需的 react, react-dom 的声明文件, 以及 加载TS的ts-loader

npm install --save-dev @types/react @types/react-dom ts-loader

1.3 配置 tsconfig.json

在使用Typescript时需要根据实际项目的需要进行相关规则的配置,具体配置根据项目而异、可参考官网,具体看这里TS官网。我的配置项如下所示:

{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "noUnusedParameters": true,
    "outDir": "build/dist",
    "baseUrl": ".",
    "strict": true,
    "noImplicitAny": true,
    "removeComments": true,
    "preserveConstEnums": true,
    "sourceMap": true,
    "forceConsistentCasingInFileNames": true,
    "strictPropertyInitialization": true,
    "experimentalDecorators": true,
    "noImplicitReturns": true,
    "moduleResolution": "node",
    "strictNullChecks": true,
    "esModuleInterop": true,
    "noUnusedLocals": true,
    "importHelpers": true,
    "noImplicitThis": false,
    "suppressImplicitAnyIndexErrors": false,
    "skipLibCheck": true,
    "noResolve": false,
    "module": "es2015",
    "allowJs": true,
    "target": "es5",
    "jsx": "react",
    "lib": [
      "es5",
      "es2015",
      "dom",
      "es7",
      "es2018"
    ],
    "paths": {
      "@/*": [
        "./src/*"
      ]
    }
  },
  "exclude": [
    "node_modules",
    "build",
    "scripts",
    "acceptance-tests",
    "webpack",
    "jest",
    "src/setupTests.ts",
    "tslint:latest",
    "tslint-config-prettier"
  ]
}

二、ESLint+Prettier代码规范

针对JS项目迁移到TS的项目,主要有两种选择ESLintTSLintTSLint仅针对TS代码,因此如果采用TSLint规范TS代码,JS代码需要采用其他工具。而ESLint不仅能规范js代码,通过配置解析器,也能规范TS代码。此外由于性能问题,TypeScript 官方决定全面采用ESLint。因此本项目采用ESLint配合Prettier规范化TSJS代码。

2.1 ESLint规范TS代码

2.1.1 安装ESLint依赖

npm i eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin --save-dev
  • eslint: ESLint的核心代码

  • @typescript-eslint/parser:ESLint的解析器,用于解析Typescript文件,从而检查和规范Typescript代码

  • @typescript-eslint/eslint-plugin:这是一个ESLint插件,包含了各类定义好的检测Typescript代码的规范

安装好依赖后,需要在项目根目录中的.eslintrc.js中配置,包括解析器、继承的代码规范、插件和环境:

module.exports = {
   parser:  '@typescript-eslint/parser', //定义ESLint的解析器
   extends: ['plugin:@typescript-eslint/recommended'],//定义文件继承的子规范
   plugins: ['@typescript-eslint'],//定义了该eslint文件所依赖的插件
   env:{                          //指定代码的运行环境
       browser: true,
       node: true,
   }                                
}

2.2 规范React代码

项目是以依赖于React的AntDesignPro为基础,因此需要安装规范React代码的Eslint插件。

2.2.1安装插件

npm i eslint-plugin-react --save-dev

2.2.2 配置React规范

然后修改.eslintrc.js的配置,如下所示:

module.exports = {
   parser:  '@typescript-eslint/parser',
   extends: [
    'plugin:react/recommended',
    'plugin:@typescript-eslint/recommended'
   ],//使用推荐的React代码检测规范
   plugins: ['@typescript-eslint'],
   env:{                          
       browser: true,
       node: true,
   },
   settings: {//自动发现React的版本,从而进行规范react代码
       "react": {
           "pragma": "React",
           "version": "detect"
       }
   },  
   parserOptions: {//指定ESLint可以解析JSX语法
       "ecmaVersion": 2019,
       "sourceType": 'module',
       "ecmaFeatures":{
           jsx:true
       }
   }
   rules: {
   //在Rules中可以自定义你的React代码编码规范。
   }
}

2.3接入Prettier

2.3.1 安装依赖

npm install prettier eslint-config-prettier eslint-plugin-prettier -g
  • prettier:prettier插件的核心代码

  • eslint-config-prettier:解决ESLint中的样式规范和prettier中样式规范的冲突,以prettier的样式规范为准,使ESLint中的样式规范自动失效

  • eslint-plugin-prettier:将prettier作为ESLint规范来使用

2.3.2 配置规则

在项目的根目录下创建.prettierrc.js文件并配置prettier代码检查规则

module.exports = {
    //最大长度80个字符
    printWidth: 80,
    //行末分号
    semi: false,
    //单引号
    singleQuote: true,
    //尽可能使用尾随逗号(包括函数参数)
    trailingComma: 'all',
    //在对象文字中打印括号之间的空格。
    bracketSpacing: true,
    // > 标签放在最后一行的末尾,而不是单独放在下一行
    jsxBracketSameLine: false,
    //箭头圆括号
    arrowParens: 'avoid',
    //在文件顶部插入一个特殊的 @format 标记,指定文件格式需要被格式化。
    insertPragma: false,
    //缩进
    tabWidth: 4,
    //使用tab还是空格
    useTabs: false,
}

2.3.3 结合Prettier配置Eslint

修改.eslintrc.js文件,引入prettier
最终配置为:

module.exports = {
    parser: '@typescript-eslint/parser',
    extends: [
        'plugin:react/recommended',
        'plugin:@typescript-eslint/recommended',
        'prettier/@typescript-eslint',
        'plugin:prettier/recommended',
    ],
    plugins: ['@typescript-eslint', 'react'],
    env: {
        browser: true,
        node: true,
        es6: true,
    },
    rules: {
        quotes: ['error', 'single'], //强制使用单引号
        semi: ['error', 'never'], // 要求或禁止使用分号而不是 ASI
        camelcase: 0, // 双峰驼命名格式
        eqeqeq: 2, //必须使用全等
        yoda: [2, 'never'], //禁止尤达条件
        strict: [2, 'never'], // 禁用严格模式,禁止在任何地方出现 'use strict'
        'no-extra-boolean-cast': 2, //禁止不必要的bool转换
        'no-lone-blocks': 2, //禁止不必要的嵌套块
        'no-plusplus': 0, //禁止使用++,--
        'no-proto': 2, //禁止使用__proto__属性
        'no-self-compare': 2, //不能比较自身
        'no-undef': 2, //不能有未定义的变量
        'no-unreachable': 2, //不能有无法执行的代码
        'no-unused-expressions': 2, //禁止无用的表达式
        'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
        'no-alert': 2, //禁止使用alert
        'no-caller': 1, //禁止使用arguments.caller或arguments.callee
        'no-inline-comments': 2, //禁止行内备注
        'no-func-assign': 2, //禁止重复的函数声明
        'no-eval': 2, //禁止使用eval,
        'no-empty': 2, //块语句中的内容不能为空
        'no-const-assign': 2, //禁止修改const声明的变量
        'no-var': 2, //禁止使用var
        'no-multiple-empty-lines': [1, { max: 2 }], //空行最多不能超过2行
        'no-extra-semi': 'error', // 禁止不必要的分号
        'array-bracket-spacing': [2, 'never'], //是否允许非空数组里面有多余的空格
        'linebreak-style': ['error', 'unix'], // 强制使用一致的换行风格
        'brace-style': [2, '1tbs', { allowSingleLine: true }], // if while function 后面的{必须与if在同一行,java风格。
        'comma-dangle': 0, // 数组和对象键值对最后一个逗号, never参数:不能带末尾的逗号, always参数:必须带末尾的逗号,
        'comma-spacing': [2, { before: false, after: true }], // 控制逗号前后的空格
        'computed-property-spacing': [2, 'never'], // 以方括号取对象属性时,[ 后面和 ] 前面是否需要空格, 可选参数 never, always
        'use-isnan': 2, //禁止比较时使用NaN,只能用isNaN()
        'default-case': 2, //switch语句最后必须有default
        'newline-after-var': 2, //变量声明后是否需要空一行
        'max-depth': [2, 4], //嵌套块深度最多四层
        'max-params': [2, 4], //函数最多只能有4个参数
        'no-else-return': 2, //如果if语句里面有return,后面不能跟else语句,禁止出现 if (cond) { return a } else { return b },应该写为 if (cond) { return a } return b
        'no-eq-null': 2, //禁止对null使用==或!=运算符
        'no-iterator': 2, //禁止使用__iterator__ 属性
        'no-mixed-spaces-and-tabs': [2, false], //禁止混用tab和空格
        'no-new-func': 1, //禁止使用new Function
        'no-new-object': 2, //禁止使用new Object()
        'no-self-compare': 2, //不能比较自身
        'no-unused-vars': [2, { vars: 'all', args: 'after-used' }], //不能有声明后未被使用的变量或参数
        'no-use-before-define': 0, //未定义前不能使用
        'valid-typeof': 2, //无效的类型判断
        'wrap-iife': [2, 'inside'], //立即执行函数表达式的小括号风格
        // 注释的斜线和星号后要加空格
        'spaced-comment': [
            2,
            'always',
            {
                block: {
                    exceptions: ['*'],
                    balanced: true,
                },
            },
        ],
        // new, delete, typeof, void, yield 等表达式前后必须有空格,-, +, --, ++, !, !! 等表达式前后不许有空格
        'space-unary-ops': [
            2,
            {
                words: true,
                nonwords: false,
            },
        ],
        'prefer-rest-params': 2, // 必须使用解构 ...args 来代替 arguments
        'consistent-this': [2, 'self', 'that'], // this 的别名规则,只允许 self 或 that
        curly: [2, 'multi-line', 'consistent'], // if 后必须包含 { ,单行 if 除外
        'for-direction': 2, // for 循环不得因方向错误造成死循环
        'getter-return': [2, { allowImplicit: true }], // getter 必须有返回值,允许返回 undefined
        'keyword-spacing': 2, // 关键字前后必须有空格
        // new关键字后类名应首字母大写
        'new-cap': [
            2,
            {
                capIsNew: false, // 允许大写开头的函数直接执行
            },
        ],
        'no-await-in-loop': 2, // 禁止将 await 写在循环里
        'no-class-assign': 2, // class定义的类名不得与其它变量重名
        'no-dupe-args': 2, // 函数参数禁止重名
        'no-duplicate-case': 2, // 禁止 switch 中出现相同的 case
        'no-duplicate-imports': 2, // 禁止重复 import
        'no-empty-function': 0, // 禁止空的 function,包含注释的情况下允许
        'no-empty-pattern': 2, // 禁止解构中出现空 {} 或 []
        'no-ex-assign': 2, // catch 定义的参数禁止赋值
        'no-extend-native': [2, { exceptions: ['Array', 'Object'] }], // 禁止扩展原生对象
        'no-extra-parens': [2, 'functions'], // 禁止额外的括号,仅针对函数体
        'no-floating-decimal': 2, // 不允许使用 2. 或 .5 来表示数字,需要用 2、2.0、0.5 的格式
        'no-func-assign': 2, // 禁止对函数声明重新赋值
        'no-implied-eval': 2, // 禁止在 setTimeout 和 setInterval 中传入字符串,因会触发隐式 eval
        'no-multi-assign': 2, // 禁止连等赋值
        '@typescript-eslint/explicit-function-return-type': [
            'off',
            {
                allowExpressions: true,
                allowTypedFunctionExpressions: true,
            },
        ],
        '@typescript-eslint/no-explicit-any': 0, // 特殊情况可将类型显示设置为any
        '@typescript-eslint/interface-name-prefix': 0, // 允许接口命名以I开头
        '@typescript-eslint/no-var-requires': 0, // antd中引用style需要用require
        '@typescript-eslint/no-use-before-define': 0, // mapStateToProps在之前就用到(typeof推断类型)
        '@typescript-eslint/camelcase': 0, // 驼峰命名格式
        '@typescript-eslint/no-empty-function': 0, // 给函数默认值可以为空
        'react/display-name': 0, // 一个莫名其妙的Bug
        'react/no-find-dom-node': 0,
        '@typescript-eslint/no-non-null-assertion': 0, // 允许用!断言不为空
    },
    settings: {
        //自动发现React的版本,从而进行规范react代码
        react: {
            pragma: 'React',
            version: 'detect',
        },
    },
    parserOptions: {
        //指定ESLint可以解析JSX语法
        ecmaVersion: 2019,
        sourceType: 'module',
        ecmaFeatures: {
            jsx: true,
        },
    },
}
  • prettier/@typescript-eslint:使得@typescript-eslint中的样式规范失效,遵循prettier中的样式规范。

  • plugin:prettier/recommended:使用prettier中的样式规范,且如果使得ESLint会检测prettier的格式问题,同样将格式问题以error的形式抛出。

2.4 在VSCode中集成ESLint配置

当在项目中有了如上配置,其他开发人员需要在自己的VSCode中进行ESLint和Prettier插件的安装配置。VScode的ESLint和Prettier会读取项目的配置文件,从而达到对代码的检查。踩坑如下:

  • 需要注意的是如果是通过工作机进行远程工作的,一定要记得远程的VScode安装插件才生效,本地安装并没用。
  • 同时在团队协作过程中,插件的版本有可能不同,如稳定版本和非稳定版本对于eslint规则的解析不同,因此团队直接尽可能安装相同版本的插件.

2.4.1 配置setting,保存自动校验

prettier和eslint可以在保存时自动检查并自动格式化一部分问题,在settings.json文件中修改其配置文件如下:

{
    "eslint.enable": true, //是否开启vscode的eslint
    "eslint.options": { //指定vscode的eslint所处理的文件的后缀
        "extensions": [
            ".js",
            ".vue",
            ".ts",
            ".tsx"
        ]
    },
    "editor.formatOnSave": true,
    "editor.codeActionsOnSave": {
        "source.fixAll.eslint": true
    },
    "eslint.validate": [ //确定校验准则
        "javascript",
        "javascriptreact",
        {
            "language": "html",
            "autoFix": true
        },
        {
            "language": "vue",
            "autoFix": true
        },
        {
            "language": "typescript",
            "autoFix": true
        },
        {
            "language": "typescriptreact",
            "autoFix": true
        }
    ]
}

2.5 husky规范工作流

在项目迁移和规范化的过程中,我们不可能一次性将所有已经存在的代码迁移到TS,因此在实际过程中我们是采用JS和TS混合开发,在实际做业务需求过程中将改动的文件迁移成TS,对尚未碰见的代码不做改动,保证项目的正常运行。同样,对Eslint的格式化也是主要集中在新开发的页面。在开发的过程中,为了保证团队所有成员都能严格执行Eslint规范,采用husky构建工作流,eslint将检查做了修改,存在stage阶段尚未commit阶段的代码,在commit前进行校验,校验无误即通过,否则不通过。

2.5.1 安装依赖

    npm install husky --save-dev

2.5.2 配置

在package.json的script中配置:

 "scripts": {
   "lint": "npm run lint:js && npm run lint:style && npm run lint:prettier",
    "lint-staged": "lint-staged",
    "lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx ",
    "lint:fix": "eslint --fix --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src && npm run lint:style",
    "lint:js": "eslint --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src",
    "lint:prettier": "check-prettier lint",
    "lint:style": "stylelint --fix \"src/**/*.less\" --syntax less",
}

接着需要在package.json的husky中配置如下:

 "husky": {
   "hooks": {
      "pre-commit": "npm run lint"
    }
},

在pre-commit这个hook也就是在提交之前进行lint的检测。

上述我们通过在husky的pre-comit这个hook中执行一个npm命令来做lint校验。其实一般情况,我们会定义一个lint-staged,来在提交前检测代码的规范。使用lint-staged来规范代码的方式如下,我们修改package.json文件为:

{
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "**/*.{js,jsx,ts,tsx}": [
      "eslint --fix",
      "prettier --write",
      "git add ."
    ]
  }
}

注意:这里有个需要注意,如果发现文件eslint还报错,居然还能提交成功,也就是husky没有生效,那么可以cd进入到.git/hooks文件夹,查看一下有没有pre-commit文件(不是pre-commit.sample文件),如果没有,那么就是git版本的原因,需要升级到2.13.0以上。

在本项目中采用cicd进行持续集成,因此也可以将eslint加入到ci中,在这里不在详细介绍。

总结

项目的Typescript迁移和Eslint+Prettier的代码格式化,目前已经上线几个月运行良好,至今项目已经迁移一半,基本无痛点,很爽。


AntDesignPro中台项目迁移Typescript,并接入Eslint+Prettier代码格式化_第1张图片
qrcode.png

你可能感兴趣的:(AntDesignPro中台项目迁移Typescript,并接入Eslint+Prettier代码格式化)