使用Webpack等搭建一个适用于React项目的脚手架(3 - Eslint、Jest)

《使用Webpack等搭建一个适用于React项目的脚手架(2 - React Router、Redux、Sass)》 中记录了在项目中使用React-Router、Redux和Sass,以及引入图片。这篇文章主要记录在项目中使用Eslint检查代码风格以及使用Jest等进行单元测试。

使用Eslint

Eslint 是用来让代码风格保持一致以及减少错误发生的。

Eslint官网推荐React检查使用 eslint-plugin-react

这篇官方文档 中得知,需要使用 @typescript-eslint/parser 进行TypeScript检查。在@typescript-eslint/parser的Github仓库中找到 检查TypeScript的方法

本文按照上面提到的这些参考路径进行Eslint配置。(其实在安装好eslint之后,可以用 npx eslint --init 创建一个配置。执行 npx eslint --init 后会有一些提问,Eslint会根据回答创建配置文件并安装依赖。)

首先安装依赖:

npm i --save-dev eslint eslint-loader eslint-plugin-react
npm i --save-dev @typescript-eslint/parser @typescript-eslint/eslint-plugin
复制代码

创建配置文件: touch .eslintrc.js 。.eslintrc.js文件内容如下:

module.exports = {
  extends: [
    'eslint:recommended',
    'plugin:react/recommended',
  ],
  overrides: [
    {
      files: ['*.ts', '*.tsx'],
      extends: [
        'plugin:@typescript-eslint/eslint-recommended',
        'plugin:@typescript-eslint/recommended'
      ],
      parser: '@typescript-eslint/parser',
      plugins: [
        '@typescript-eslint/eslint-plugin',
      ],
      rules: {
        '@typescript-eslint/ban-ts-ignore': 'off',
        '@typescript-eslint/no-explicit-any': 'off',
        '@typescript-eslint/no-use-before-define': 'off'
      },
    }
  ],
  settings: {
    react: {
      version: 'detect',
    }
  },
  rules: {
    'react/jsx-no-undef': 'off'
  },
  env: {
    browser: true,
    es2020: true,
    node: true,
    commonjs: true,
  },
  globals: {
    React: true,
    ReduxConnect: true,
    Axios: true,
    UseEffect: true,
  },
  ignorePatterns: ['dist/'],
}
复制代码

eslint:recommended 表示启用 Eslint推荐规则plugin:react/recommended 启用React推荐规则。

overrides 中进行配置,只对.ts文件和.tsx文件进行TypeScript相关的处理,比如只对.ts、.tsx文件使用 @typescript-eslint/parser 处理器。

设置 react: { version: 'detect' } 表示自动设置检查的React版本是安装的React的版本。

env 中的配置表示支持browser、es2020、node、commonjs中预定义的全局变量。

globals 中配置之前的步骤中设置的全局变量,比如React。

ignorePatterns 配置忽略的文件,数组的每一个元素相当于.eslintignore中的一行,.eslintignore能覆盖ignorePatterns的配置。Eslint默认会忽略/node_modules/* 和 /bower_components/*文件目录,所以没在ignorePatterns中添加这两个目录。

根据需要关掉了几条规则:

1.关掉在React中不允许未声明变量的规则,因为我们全局设置了React变量,但是Eslint会报错 error 'React' is not defined react/jsx-no-undef ,所以把这个规则关掉。

'react/jsx-no-undef': 'off'
复制代码

2.关掉不能在声明前使用的规则,还是因为已经在Webpack中定义了全局变量React,但是Eslint无法进行判断,所以会报错 'React' was used before it was defined

'@typescript-eslint/no-use-before-define': 'off'
复制代码

3.关掉不能使用 // @ts-ignore 的规则:

'@typescript-eslint/ban-ts-ignore': 'off',
复制代码

4.关掉不能使用any定义类型的规则(当然尽量不要使用any定义类型):

'@typescript-eslint/no-explicit-any': 'off',
复制代码

package.json 中添加:

"scripts": {
  	...
    "lint": "eslint . --ext .js,.jsx,.ts,.tsx"
  },
复制代码

执行 npm run lint ,会看见检查的结果,根据提示调整代码。

在Webpack( config/webpack.dev.js )中配置如下,表示在对.js、.jsx、.ts、.tsx文件进行处理前先使用 eslint-loader 对文件进行Eslint检查。

{
      enforce: 'pre',
      test: /(\.js(x?))|(\.ts(x?))$/,
      exclude: /[\\/]node_modules[\\/]/,
      loader: 'eslint-loader',
    },
复制代码

执行 npm start 启动webpack-dev-server,当文件修改并保存之后会重新打包,在打包前会进行Eslint检查,可以根据检查结果进行代码调整。

使用Jest

Jest 是一个专注于简化的JavaScript测试框架。本文参考 Jest官方文档--使用Reactreact-testing-library--基础例子 进行了简单的DOM测试。React Redux测试和React Router测试等可以查看 react-testing-library--更多例子

安装依赖:

npm i --save-dev jest @testing-library/react
复制代码

根据 Jest官方文档--使用Babel 的内容,安装Jest的时候自动安装了 babel-jestbabel-jest 会根据babel配置自动转换文件,( 《使用Webpack等搭建一个适用于React项目的脚手架(1 - React、TypeScript)》中已经在.babelrc.js中进行了babel配置,所以这里不用再考虑如何处理tsx文件 )。

在项目根目录创建一个 __tests__ 文件夹。 __tests__ 文件夹下包含测试文件,创建了3个文件夹分别表示测试组件、测试页面、测试函数。

├── __tests__
│   ├── components
│   ├── functions
│   └── pages
│       └── Novel.test.tsx
复制代码

如果要对 __tests__ 文件夹中的内容进行类型检查,需要安装jest类型定义包,并且在tsconfig.json文件夹中引入 __tests__ 文件夹。

npm i --save-dev @types/jest
复制代码

在tsconfig.json的include中添加上 __tests__ 目录:

...
  "include": [
    ...
    "__tests__/*",
    "__tests__/**/*",
  ]
}
复制代码

1.写一个例子

调整 src/pages/Novel/Novel.tsx 文件内容如下:

export default function Novel(): JSX.Element {
  const [ novels, setNovels ] = UseState(['一号小说', '二号小说']);
  function deleteNovel (index): void {
    if (!novels[index]) return;
    const nextNovels = [...novels];
    nextNovels.splice(index, 1);
    setNovels(nextNovels);
  }
  return (
    <div>
      <ul>
        {novels.map((novel, index) => {
          return <li key={index} onClick={deleteNovel.bind(this, index)} style={{ cursor: 'pointer' }}>{novel}</li>;
        })}
      </ul>
    </div>
  );
}
复制代码

组件渲染小说列表,点击某选项就删除该选项。比如点击渲染出的 <li>一号小说</li><li>一号小说</li> 就会被删除。

tests /pages/Novel.test.tsx 文件内容如下:

import { render, fireEvent, screen } from '@testing-library/react';

import Novel from '@/pages/Novel/Novel.tsx';

test('novel item should not exist after deletion', () => {
  const novelName = '一号小说';
  render(<Novel />);

  expect(screen.queryByText(novelName)).toBeTruthy();

  fireEvent.click(screen.queryByText(novelName));

  expect(screen.queryByText(novelName)).toBeNull();
});

复制代码

这个测试文件简单地测试以下内容:

​ 组件渲染成DOM后,textContent为'一号小说'的元素(A元素)是存在的,点击A元素之后,DOM中就没有A元素了。

关于代码中用到的test、fireEvent等更详细的信息可以查看 Jest官网--使用matcherstesting-library官网firEvent模拟事件

2.Jest配置

在前几篇文章的代码中,我们已经设置了React等全局变量,以及使用@作为src目录的路径别名。Jest中也需要做相应的配置,否则执行 npx jest 会报错。

在根目录下创建一个jest.config.js文件,用于进行 jest配置

touch jest.config.js
复制代码

jest.config.js文件内容如下:

module.exports = {
  setupFiles: ['<rootDir>/__tests__/jest.setup.js'],
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
  },
  testPathIgnorePatterns: ['<rootDir>/__tests__/jest.setup.js', '/node_modules/']
}
复制代码

(1)配置全局变量

jest配置中有一个globals属性用于配置全局变量,但是globals对象设置的对象必须是JSON可序列化的,而函数在使用JSON.stringify()转换的时候会被忽略,所以需要使用setupFiles属性。 <rootDir> 是jest配置文件所在的文件目录。

jest.setup.js文件内容如下:

import React, { useState } from 'react';

global['React'] = React;
global['UseState'] = useState;

复制代码

因为这里要测试的组件只用到的React和UseState,所以只配置了React和useState,需要用到别的全局变量时,可以按照相同的方法配置。

(2)配置路径别名

在moduleNameMapper属性中设置 '^@/(.*)$': '<rootDir>/src/$1' 用于配置路径别名,就像之前我们在Webpack和TypeScript中配置的那样,用@代表src目录。一开始我使用的是 '^@(.*)$': '<rootDir>/src$1' ,这样执行 npx jest 的时候会报错:

● Test suite failed to run

    Configuration error:
    
    Could not locate module @babel/code-frame mapped as:
    .../simple-scaffold02/src$1.
复制代码

经过反复 横跳 检查,两边的正则表达式都是没错的,但是仔细观察报错信息,发现这样配置把@babel/code-frame引用也映射到src路径下了,然而src路径下是没有babel/code-frame的,自然报错了。所以需要以加一根斜杠的方式匹配路径 '^@/(.*)$': '<rootDir>/src/$1'

(3)设置忽略文件

Jest默认会将匹配正则表达式 (/__tests__/.*|(\\.|/)(test|spec))\\.[jt]sx?$ 的文件作为测试文件,前面的jest.setup.js文件是放在 __tests__ 文件夹中的,但它是用来设置全局变量的,并不是测试文件。所以需要在testPathIgnorePatterns中配置忽略它。

(4)Webpack和TypeScript新增配置。

因为新增了一个全局变量UseState代表React的useState模块,所以在Webpack和TypeScript中也需要加上这个全局变量:

config/webpack.common.js:

new webpack.ProvidePlugin({
    	...
      UseState: ['react', 'useState'],
    }),
复制代码

typings/react.d.ts:

import React, { useEffect, useState } from 'react';

declare global {
	...
  const UseState: typeof useState;
}
复制代码

(5)修改 package.json

"scripts": {
  	...
    "test": "jest"
  },
复制代码

执行 npm run test 执行测试用例,得到结果:

> jest

 PASS  __tests__/pages/Novel.test.tsx
  ✓ novel item should not exist after deletion (33ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        3.241s
复制代码

(6)检查代码风格是否统一

执行 npm run lint 进行检查,发现因为新创建的jest.setup.js文件中的 import React, { useState } from 'react'; 造成了一个报错:

error  Parsing error: 'import' and 'export' may appear only with 'sourceType: module
复制代码

只有配置了 sourceType: module 才能使用import和export。之前的代码中也用了import,但因为是在ts文件和tsx文件中,所以eslint没有报这个错。

在**.eslintrc.js**中添加上以下内容即可:

module.exports = {
	...
  parserOptions: {
    sourceType: 'module',
  },
}
复制代码

其他

1.根据需要忽略不想使用git上传的文件

touch .gitignore
复制代码

文件内容如下:

.DS_Store
.vscode

dist
node_modules
复制代码

比较全的gitignore配置可以参考 gitignore例子合集

2.创建README.md

# npm start
  start up project.
# npm run build
  build for production environment.
# npm run buid:dev
  build for development environment.
# npm run lint
  use elint to find problems in code.
# npm run test
  user jest to test code.
复制代码

下一篇: 《使用Webpack等搭建一个适用于React项目的脚手架(4 - 优化)》

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章