likes
comments
collection
share

细嚼慢咽 Typescript + React17 +Eslint + Git hook 工作流

作者站长头像
站长
· 阅读数 26

ts-loader 和 babel 混用的时代

那时候 create-react-app 还没有官方的 ts 版本。 eslint 和 tslint 还没合并在一起。 大部分项目都在用 ts-loader + eslint + tslint + webpack + babel 混用的工作流。

缺点:

  1. 每次修改了一点代码,都会将 ts 代码传递给 typescript 转换为 js ,然后再将这份 js 代码传递给 Babel 转换为低版本 js 代码,不但慢,而且最终转译出来的代码可能比较冗余。
  2. 需要配置两个编译器,并且每次做了一点更改,都会经过两次转译,代码重新编译会非常慢。 优点: typescript 会做两个动作转译和类型检查。

ts-loader + fork-ts-checker-webpack-plugin

因为 typescript 会同时做转译和代码检查,那可不可以只做转译或者代码检查其中一件事情呢? 可以通过 ts-loader 配置关闭掉类型检查

transpileOnly: true,

然后再使用 fork-ts-checker-webpack-plugin来做类型检查 ,开辟一个单独的进程去执行类型检查的任务,这样就不会影响 webpack 重新编译的速度。

// webpack.config.js

const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: 'ts-loader',
        exclude: /node_modules/,
        options: {
          // disable type checker - we will use it in fork plugin
          transpileOnly: true
        }
      }
    ]
  },
  plugins: [new ForkTsCheckerWebpackPlugin()]
};

后来还出现过 awesome-typescript-loader 来解决一些问题,不过这插件有各种各样的小问题,已经成为历史了,大家有空的自己了解。

缺点:

  1. 每次修改了一点代码,都会将 ts 代码传递给 typescript 转换为 js ,然后再将这份 js 代码传递给 Babel 转换为低版本 js 代码,不但慢,而且最终转译出来的代码可能比较冗余。
  2. 当时需要转译和类型检查等时候速度还是非常慢。 优点:
  3. 不需要类型检查,只编译等时候,速度比较快

@babel/preset-typescript 横空出世。

具体文档和文章在这里。

  • iamturns.com/typescript-…
  • babeljs.io/docs/en/bab… 你可以理解成 @babel/preset-typescript 和 typescript 的区别就是。
  • typescript 既做代码转换,也做类型检查)。
  • @babel/preset-typescript 只做语法转换,不做类型检查。 还有在语法转换区别有以下四点:
  1. Namespace语法不推荐(改用标准的 ES6 module(import/export)。
  2. 不支持 x 语法转换类型,(改用x as newtype)。
  3. const 枚举
  4. 历史遗留风格的 import/export 语法。比如:import foo = require(...) 和 export = foo。
  • 第1条和第4条语法新项目基本没人用了。
  • 第2条缺陷改一下语法就好了,这个语法会直接提示语法报错,很好改,问题不大。
  • 第3条 2021 年 02 月亲测可用。 优点:
  1. 更快的编译速度。
  2. babel 可以根据目标浏览器情况转换部分语法。
  3. 配置简单许多。 缺点: 如果单单只用 @babel/preset-typescript,当我们在执行 npm run build 的时候,肯定需要把 ts 文件的代码输出成 js 代码,这时候不管代码是否正确,代码都会被打包出来,不能及时发现代码的问题。

我大概在三年前写过一个 babel + tsx 最简单的 demo (只转译,没做类型检查,会出现上述问题) github.com/Faithree/we…

下面再介绍一种方式。

  • @babel/preset-typescript 做语法转换
  • typescript 做语法检查。 package.json
"scripts": {
    "check-types": "tsc --watch"
}

tsconfig.json 设置 noEmit 不生成文件,只做类型检查

{
    "compilerOptions": {
        "noEmit": true,
    },
    "include": [
        "src"
    ]
}

缺点:

  1. 你要起两个线程,一个是 webpack 的线程,另一个是 typescript 执行代码类型检查的线程,可能会出现报错不同步,还要搭配支持 vscode 编辑器可能才有比较好的体验。
  2. 防止 build 转译代码的时候出现类型错误。如果你改了代码,没运行 check-types 命令,代码也能转译成功,但是可能代码是有类型问题的。

最佳实践 @babel/preset-typescript+ fork-ts-checker-webpack-plugin

这种是在上面的基础上,把 typescript 替换成 fork-ts-checker-webpack-plugin。

// webpack.config.js

const path = require('path');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const HtmlWebPackPlugin = require('html-webpack-plugin');
const ESLintPlugin = require('eslint-webpack-plugin');
module.exports = {
  mode: 'development',
  entry: './src/index',
  output: {
    path: path.resolve(__dirname, 'dist'),
    // filename: '[name]-[hash].js'
    filename: 'bundle.js',
  },
  resolve: {
    extensions: ['.ts', '.tsx', '.js', '.json'],
  },
  devServer: {
    publicPath: '/',
    host: '127.0.0.1',
    port: 3000,
    stats: {
      colors: true,
    },
  },
  module: {
    rules: [
      {
        test: /\.(ts|js)x?$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
      },
    ],
  },
  plugins: [
    new ForkTsCheckerWebpackPlugin({
      formatter: 'codeframe',
      async: true,
    }),
    new HtmlWebPackPlugin({
      template: 'public/index.html',
      filename: 'index.html',
      inject: true,
    }),
  ],
};

优点:

  1. @babel/preset-typescript 做语法转换,fork-ts-checker-webpack-plugin 做语法检查, 集成在了 webpack 中,只用一个命令就可以启动。
  2. 编译速度快。
  3. 代码转译的时候也能发现类型错误。
  4. 丰富的 babel 插件,根据浏览器版本,输出指定 js 代码。

React 17

react 17 源代码不需要再引入 React 了,所以在 babel 和 ts 配置上有一些不同。

yarn add @babel/preset-react -D

babel.config.js


const presets = [
  ...
  ['@babel/preset-typescript'],
  ["@babel/preset-react", {
    "runtime": "automatic"
  }]
];

tsconfig.json

{
  ...
  "jsx": "react-jsx"
}

eslint 和 tslint ?

eslint 其实也经历了很多的变化,从最开始 eslint-loader 到现在的 eslint-webpack-plugin,从 tslint + eslint 混用,到现在 eslint 的统一也经历了非常多年。

tslint -> eslint 主要是下面这三个库来做兼容。

  • @typescript-eslint/eslint-plugin
  • @typescript-eslint/parser
  • @typescript-eslint/typescript-estree

详情可以看看 github.com/typescript-…

如何使用

eslint 具体的规则配置,从来没用过的,我的建议是没必要太纠结具体配置,先用上再说,我推荐 @umijs/fabric,也可以自行选择 airBnb等其他代码书写规则。

yarn add  eslint-webpack-plugin -D
  new ESLintPlugin({
    extensions: ['js', 'mjs', 'jsx', 'ts', 'tsx'],
    cache: true,
  }),

eslint-webpack-plugin 详细配置可参考文档 github.com/webpack-con…

.eslint.js

module.exports = {
  parserOptions: {
    tsconfigRootDir: __dirname,
    project: './tsconfig.json',
    createDefaultProgram: true,
  },
  extends: [require.resolve('@umijs/fabric/dist/eslint')],
  rules: {
    'no-console': 0,
    'no-new': 0,
    'no-case-declarations': 0,
    'react/jsx-tag-spacing': 0,
    'react/no-danger': 0,
    'react-hooks/exhaustive-deps': 'warn',
    'no-param-reassign': 0,
    'react/jsx-uses-react': 0,
    'react/react-in-jsx-scope': 0,
    'no-undef':2,
    quotes: ['error', 'single'], // 使用单引号
    semi: ['error', 'always'], // 结束添加分号
  },
};

具体 demo 我放到了这里 github.com/Faithree/we…

你可以运行下面这三个命令,会发现他们的报错信息都一致。

  • yarn start
  • yarn build
  • yarn lint

git hook

yarn add yorkie lint-staged -D

package.json

  "gitHooks": {
    "pre-commit": "lint-staged"
  },
  "lint-staged": {
    "*.{js,jsx}": [
      "eslint"
    ],
    "*.ts?(x)": [
      "eslint"
    ]
  },

.prettierrc.js

因为上面 用了 umi 的eslint 规范,这里我推荐直接用他们提供的 prettierrc

yarn add @umijs/fabric -D
const fabric = require('@umijs/fabric');
module.exports = {
  ...fabric.prettier,
};

参考