[环境配置] React + Typescript + Enzyme + Jest + Less + bootstrap

1,543 阅读3分钟

这篇只讲环境配置

万事开头难,写bug容易,环境难配。记录一下,给自己日后参考。

React

  1. 因为是npm 6+, 用了init来初始化项目 npm init react-app my-app

装的 React 17:

"react": "^17.0.2",
"react-dom": "^17.0.2",

Typescript, Enzyme, Jest

  1. 装 ts, enzyme, jest 依赖 npm i --save awesome-typescript-loader source-map-loader typescript webpack @types/react @types/react-dom
"awesome-typescript-loader": "^5.2.1",
"source-map-loader": "^3.0.0",
"typescript": "^4.4.3",
"webpack": "4.44.2",
"@types/react": "^17.0.24",
"@types/react-dom": "^17.0.9",

npm i --save-dev enzyme enzyme-adapter-react-16 jest react-test-renderer @types/enzyme @types/jest

"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.6",
"jest": "^27.2.1",
"react-test-renderer": "^17.0.2",
"@types/enzyme": "^3.10.9",
"@types/jest": "^27.0.1",
  1. 暴露配置 npm run eject

Project 结构

  1. 前3步之后,会得到这样的一个项目。 标注“(新建)”的是手工新建的文件
.\
├── src\
│ ├── components\
│ │ ├── Hello.tsx (新建)
│ │ ├── Hello.module.less (加styles的时候再新建)
│ │ └── Hello.spec.tsx (新建)
│ ├── App.tsx (把.js改成.tsx)
│ └── index.tsx
├── config\
│ ├── jest\
│ ├── ...
│ ├── webpack.config.js
│ └── webpackDevServer.config.js
├── public\
│ ├── index.html
├── test-preprocessor.js (新建)
├── test-setup.js (新建)
├── test-shim.js (新建)
├── package.json
├── tsconfig.json (新建)
├── externals.d.ts (新建)
└── ...
  1. Hello.tsx 和 App.tsx
// Hello.tsx
import React from 'react'; 
interface Props {}

const Hello: React.FC = (props: Props) => {
    return <h1>Hello</h1>
  }; 
  
  export default Hello;
// App.tsx
import React from 'react'; 
import Hello from 'src/components/Hello'; 

const App = () => {
  return <Hello />
}; 

export default App; 
  1. config/path.js
// ... 
const moduleFileExtensions = [
  'web.mjs',
  'mjs',
  'web.js',
  'js',
  'web.ts',
  'ts',
  'web.tsx',
  'tsx',  //must contain tsx
  'json',
  'web.jsx',
  'jsx',
];
  1. tsconfig.json
{
    "compilerOptions": {
		"allowJs": true,
		"baseUrl": ".",
		"esModuleInterop": true,
		"isolatedModules": true,
		"jsx": "react",
		"lib": [
			"esnext",
			"dom"
		],
		"moduleResolution": "node",
		"noUnusedLocals": true,
        "outDir": "./target/",
		"paths": {
			"@src/*": [
				"./src/*"
			]
		},
        "sourceMap": true,
        "skipLibCheck": true,
		"removeComments": true,		
        "noImplicitAny": true,
        // "module": "commonjs",
		"resolveJsonModule": true,
		"strict": true,
        "target": "es5",
		"plugins": [
			{
				"name": "typescript-plugin-css-modules"
			}
		]
    },
    "include": [
		"./src/**/*",
		"src/externals.d.ts"
    ],
    "exclude": [
        "node_modules"
    ]
}
  1. 这个时候,npm run start 应该能跑起来了

-- 开始搞测试 ----

测试

  1. test-preprocessor.js
/**
 * Transpiles TypeScript to JavaScript code.
 *
 * @link https://github.com/facebook/jest/blob/master/examples/typescript/preprocessor.js
 * @copyright 2004-present Facebook. All Rights Reserved.
 */
const tsc = require('typescript');
const tsConfig = require('./tsconfig.json');

module.exports = {
    process(src, path) {
        if (path.endsWith('.ts') || path.endsWith('.tsx') || path.endsWith('.js')) {
            return tsc.transpile(src, tsConfig.compilerOptions, path, []);
        }
        return src;
    },
};
  1. test-setup.js
/**
 * Defines the React 16 Adapter for Enzyme. 
 *
 * @link http://airbnb.io/enzyme/docs/installation/#working-with-react-16
 * @copyright 2017 Airbnb, Inc.
 */
const enzyme = require("enzyme");
const Adapter = require("enzyme-adapter-react-16");

enzyme.configure({ adapter: new Adapter() });
  1. test-shim.js
/**
 * Get rids of the missing requestAnimationFrame polyfill warning.
 * 
 * @link https://reactjs.org/docs/javascript-environment-requirements.html
 * @copyright 2004-present Facebook. All Rights Reserved.
 */
global.requestAnimationFrame = function(callback) {
    setTimeout(callback, 0);
};
  1. package.json
{
  "name": "my-app",
  ...
  "jest": {
    "setupFiles": [
      "<rootDir>/test-shim.js",
      "<rootDir>/test-setup.js"
    ],
    "moduleFileExtensions": [
      "ts",
      "tsx",
      "js"
    ],
    "transform": {
      "^.+\\.(ts|tsx)$": "<rootDir>/test-preprocessor.js"
    },
    "testMatch": [
      "**/__tests__/*.(ts|tsx|js)"
    ]
  },
  "scripts": {
    "jest": "./node_modules/.bin/jest"
  },
  ...
}
  1. Hello.spec.tsx
import React from 'react'; 
import {shallow} from 'enzyme'; 

import Hello from './Hello'; 

it('renders the heading', ()=> {
    const result = shallow(<Hello />).contains(<h1>Hello</h1>); 
    expect(result).toBeTruthy(); 
})
  1. npm run jest
> my-app@0.1.0 jest <myPath>\my-app
> jest

 PASS  src/components/Hello.spec.tsx
  √ renders the heading (16 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        5.934 s
Ran all test suites.

-- 开始加styles ----

Less

  1. 用的Less,因为bootstrap 天然支持less,就不费劲搞scss了。 最好先查下webpack版本,webpack 4 貌似最多只支持到less-loader@7。 npm install --save-dev less less-loader@7
"webpack": "4.44.2""less": "^4.1.1",
"less-loader": "^7.3.0",

改 config\webpack.config.js

const cssRegex = /\.(css|less)$/;
const cssModuleRegex = /\.module\.(css|less)$/;

//... 

module.exports = function (webpackEnv) {
    // ...
    resolve: {
        extensions: paths.moduleFileExtensions
            .map(ext => `.${ext}`)
            .filter(ext => useTypeScript || !ext.includes('ts')),
    },
    module: {
        rules: [
            {
              test: cssRegex,
              exclude: cssModuleRegex,
              use: getStyleLoaders({
                importLoaders: 1,
                sourceMap: isEnvProduction
                  ? shouldUseSourceMap
                  : isEnvDevelopment,
              },
              'less-loader'),  // 加上 less-loader
              // Don't consider CSS imports dead code even if the
              // containing package claims to have no side effects.
              // Remove this when webpack adds a warning or an error for this.
              // See https://github.com/webpack/webpack/issues/6571
              sideEffects: true,
            },
            // Adds support for CSS Modules (https://github.com/css-modules/css-modules)
            // using the extension .module.css
            {
              test: cssModuleRegex,
              use: getStyleLoaders({
                importLoaders: 1,
                sourceMap: isEnvProduction
                  ? shouldUseSourceMap
                  : isEnvDevelopment,
                modules: {
                  getLocalIdent: getCSSModuleLocalIdent,
                },
              },
              'less-loader'),  //加上 less-loader
            },
        ]
    }
}

  1. Hello.module.less
// src\components\Hello.module.less
.hello {
    color: red;
}

改Hello.tsx

import React from 'react'; 
import * as styles from './Hello.module.less';  // 引入less

interface Props {}

const Hello: React.FC = (props: Props) => {
    return <h1 className={styles.hello}>Hello</h1>  
  }; 
  
  export default Hello;
  1. 如果报错 cannot find module Hello.module.less 新建 src\externals.d.ts
declare module '*.less' {
    const resource: {[key: string]: string};
    export = resource;
  }

改 tsconfig.json,确保"src/externals.d.ts"在"include"里面,注意路径别错。

"include": [
    "./src/**/*",
    "src/externals.d.ts"
],

这时候,"cannot find module" error 应该消失了。

  1. 如果报错 TypeError: this.getOptions is not a function, 又可能是当前 webpack版本不支持当前less-loader 版本。我用的webpack@4, 支持less-load@7
./src/components/Hello.module.less (./node_modules/css-loader/dist/cjs.js??ref--5-oneOf-7-1!./node_modules/postcss-loader/src??postcss!./node_modules/resolve-url-loader??ref--5-oneOf-7-3!./node_modules/less-loader/dist/cjs.js??ref--5-oneOf-7-4!./src/components/Hello.module.less)      
TypeError: this.getOptions is not a function

现在 npm run start, 应该能看到Hello变成红色了.

Bootstrap, Reactstrap

  1. Bootstrap npm i --save-dev bootstrap @types/bootstrap reactstrap @types/reactstrap 因为咱用了ts,所以那两@types/xx是必须的哈

改 src\components\Hello.tsx

import React from 'react';
import * as styles from './Hello.module.less';
import { Button } from 'reactstrap';

interface Props {}

const Hello: React.FC = (props: Props) => {
  // return <h1 className="hello">Hello</h1>
  return <>
    <h1 className={styles.hello}>Hello</h1>
    <div>
      <Button color="primary">primary</Button>{' '}
      <Button color="secondary">secondary</Button>{' '}
      <Button color="success">success</Button>{' '}
      <Button color="info">info</Button>{' '}
      <Button color="warning">warning</Button>{' '}
      <Button color="danger">danger</Button>{' '}
      <Button color="link">link</Button>
    </div>
  </>
};

export default Hello;

改 src\components\Hello.tsx,我们来试试效果

import React from 'react';
import * as styles from './Hello.module.less';
import { Button } from 'reactstrap';

const Hello = () => {
  // return <h1 className="hello">Hello</h1>
  return <>
    <h1 className={styles.hello}>Hello</h1>
    <div>
      <Button color="primary">primary</Button>{' '}
      <Button color="secondary">secondary</Button>{' '}
      <Button color="success">success</Button>{' '}
      <Button color="info">info</Button>{' '}
      <Button color="warning">warning</Button>{' '}
      <Button color="danger">danger</Button>{' '}
      <Button color="link">link</Button>
    </div>
  </>
};

export default Hello;

npm start, 如果报这个error:

<my project path>/node_modules/reactstrap/dist/reactstrap.cjs.js' implicitly has an 'any' 
type.
  Try `npm i --save-dev @types/reactstrap` if it exists or add a new declaration (.d.ts) file containing `declare module 'reactstrap';`  TS7016

    1 | import React from 'react';
    2 | import * as styles from './Hello.module.less';
  > 3 | import { Button } from 'reactstrap';

去 src\externals.d.ts 声明 reactstrap

declare module '*.less' {
  const resource: { [key: string]: string };
  export = resource;
};

declare module 'reactstrap';  

npm start, 这回倒是不报错了,可是buttons 没颜色,我了个去,原来bootstrap不会自动引入css文件,所以要手工引入。在 src\index.js 加import

import 'bootstrap/dist/css/bootstrap.min.css';  

npm start, 那几个buttons 终于有颜色了!!! bootstrap 算是搞定了,但是我中途遇到过好几次 “TypeError: this.getOptions is not a function”,所以试了好几个版本,心好累,不多说了,放在这里以防万一。

"@types/bootstrap": "^5.1.6",
"@types/reactstrap": "^8.7.2",
"bootstrap": "^4.6.0",
"reactstrap": "^6.5.0"
  1. React-router npm i --save-dev react-router-dom @types/react-router-dom @types/query-string

环境真难,搞的差点崩溃,您都看到这儿了,点个赞再走?

---- References ----------------------

# Configuring React 16 + Jest + Enzyme + Typescript

enzyme-adapter-react-16

Create React App

typescript can't find module less

reactstrap

bootstrap

How to use @types/bootstrap in my Typescript and React application


package.json