2021
年了,你的前端项目不会还在“裸奔”吧?
Jest
已经成为了大部分前端项目的标配,每次说到
Jest
、
Webpack
、
ESLint
等配置,脑瓜子都嗡嗡的🤯在诸多配置中,有时一个“铆钉大”的配置,就能让程序或测试的运行效率大幅下降,“万花丛中过”难免有片叶粘身。至于为啥要写这篇文章,就是因为目前所在的项目因一处
Jest
配置的问题,导致60多个
test case
在
--no-cache
条件下要跑足足
790s
☠️
所以就记录分享一下
Jest
的一些常用配置😉
module.exports = {
setupFiles: [
'react-app-polyfill/jsdom',
'<rootDir>/test/unit/jest.setup.js',
'core-js',
testMatch: [
'<rootDir>/src/**/__tests__/**/*.{spec,test}.{js,jsx,ts,tsx}',
'<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}',
transform: {
'^.+\.(js|jsx|ts|tsx)$': '<rootDir>/node_modules/babel-jest',
'^.+\.(css|less)$': '<rootDir>/config/jest/cssTransform.js',
'^(?!.*\.(js|jsx|ts|tsx|css|json)$)': '<rootDir>/config/jest/fileTransform.js',
transformIgnorePatterns: [
'[/\\]node_modules/(?!(antd)/)[/\\].+\.(js|jsx|ts|tsx)$',
moduleNameMapper: {
'^react-native$': 'react-native-web',
'^.+\.module\.(css|sass|scss|less)$': 'identity-obj-proxy',
'\.svg$': 'identity-obj-proxy',
'test/(.*)': '<rootDir>/test/$1',
'^src/(.*)': '<rootDir>/src/$1',
moduleFileExtensions: [
'web.js',
'js',
'web.ts',
'ts',
'web.tsx',
'tsx',
'json',
'web.jsx',
'jsx',
'node',
对于 Jest
的配置优化无外乎下面两点:
更少:减少不必要的元素(比如图片、样式等);
更精确:减少在文件系统中查找匹配的时间;
在看下面如何优化之前,可以先看下这份 Jest
配置,看一下有没有什么可以想到的优化点🤔
setupFiles
古语云:“思则有备,有备无患”,但跑测试时一定要“万事”俱备才行吗?
setupFiles
可谓是 Jest
的“内务府大臣”,位居二品!此大臣就是来准备测试所需要的一些环境或 mock
一些全局状态的,比如 @testing-library/jest-dom
就常在 setupFile
中用到,它允许我们可以在 Jest
中断言一些关于 DOM
的状态。而我们再回头去看上面的配置:
"setupFiles": [
"react-app-polyfill/jsdom",
"<rootDir>/test/unit/jest.setup.js",
"core-js"
react-app-polyfill/jsdom
做了什么:
if (typeof window !== 'undefined') {
require ('whatwg-fetch');
whatwg-fetch
和 core-js
可以简单理解为对当下的一些新标准做 polyfill
,但我们有 babel-jest
呀,还要你二者何用?果断“开掉”,节省开支🙊
"setupFiles": ["<rootDir>/test/unit/jest.setup.js"]
moduleFileExtensions
moduleFileExtensions
就是 Jest
中各个“国家”(模块)的“通关文牒”,有此文牒方可游历各国。游遍各国也得有个顺序不是,不然会徒增“食宿饮马等费用”,要是搁徐霞客身上,若不是他家业富足,不然大可能会饿死途中🐶张骞通西域朝廷会有路线规划(默认配置),当然他也能随机应变(自定义配置),默认配置大部分情况下是行得通的,只不过可能要在“路上”多花些时间:
["js", "jsx", "ts", "tsx", "json", "node"]
moduleFileExtensions
会 从左到右 查找对应的 extension
,但如果在 TypeScript + React
项目中可能稍微调整一下会更好:
["ts", "tsx", "js", "jsx", "json", "node"]
这样就能减少些查找 extension
的次数,省点“油钱”。
moduleNameMapper
一般 npm
依赖中的源码分为 esm
和 cjs
模块,当然像 react
之类的是分为 cjs
和 umd
。以 antd
为例,其结构如下:
|- lib/
|- es/
|- package.json
其中,在 package.json
中可以制定 esm
和 cjs
打包文件的目录,以 antd
中使用到的 rc-select
组件包为例:
"version": "12.1.5",
"main": "./lib/index",
"module": "./es/index"
如要使用 cjs
规范的打包文件,工具会查询 main
字段对应文件路径内的入口文件,使用 esm
规范的打包文件,工具则会查询 module
字段对应的入口文件。
但在 rc-util
中,并没有指明 main
及 module
字段,那么其使用方式就像下面这样:
import toArray from 'rc-util/es/Children/toArray';
“聊 Jest
呢,叨叨上面这么多干嘛呢?”
因为 Jest
目前支持的是 cjs
规范,项目中又用到了 antd
,所以对于其使用的 rc-util
这种依赖,Jest
无法处理,需要手动转换一下,这就需要引入一个 Jest
配置字段 —— moduleNameMapper
,关于该配置字段的描述文档如下所述:
A map from regular expressions to module names that allow to stub out resources, like images or styles with a single module.
说白了就是用来 stub
一些资源文件或 module
使用的,可以把匹配到的内容映射为你指定的内容,哪怕是“指鹿为马”也是行得通的!在前端的单元测试中,时常有许多内容是不需要的,比如:静态资源、样式文件等。那么这个时候就可以将这些“鹿”指成“马”了。
我们常把“鹿🦌”指为 identity-obj-proxy
这个工具,虽然 identity-obj-proxy
上次发布是5年前了,但确实很好用,并且源码也十分简单(2分钟你看不完源码你顺着信号📶来打我)!
module.exports = {
moduleNameMapper: {
'\.svg$': 'identity-obj-proxy',
'\.css$': 'identity-obj-proxy',
对于上面说到的将 antd
的 es
指到 lib
也就很简单了:
module.exports = {
moduleNameMapper: {
'antd/es/(.*)': 'antd/lib/$1',
通过 moduleNameMapper
就可以做到 更少 这个原则,当然下面要介绍的 transformIgnorePatterns
以及其它 ignore
等相关字段也可以让处理的资源或无关的资源更少。
transform
《天龙八部》中,马大元夫人康敏将一招“借刀杀人”发挥地淋漓尽致,而在 Jest
中 transform
也“借他人之手除掉异己”,至于康敏居心何在下回再讲,这次就说 transform
为何要“下此毒手”。所谓“异己”一般是脱离自己控制的资源,上面说到 Jest
支持的是 cjs
,但在现在的前端项目中,一般都是使用 import/export
等 esm
规范来模块化开发,所以对于这种资源,我们需“借他之手”处理:
module.exports = {
transform: {
'^.+\.(js|jsx|ts|tsx)$': 'babel-jest',
但这么看来,transform
也可以将 antd
中的 esm
资源转为 cjs
,但既然可以“礼仪教化”,又何必“兵刃相接”呢?
transformIgnorePatterns
通过名字就能看出来,此配置的内容是“康敏”理都不想理的内容,该值默认是 ['node_modules']
,也十分好理解。但我们回到文章最初的配置去看看:
module.exports = {
transformIgnorePatterns: [
'[/\\]node_modules/(?!(antd)/)[/\\].+\.(js|jsx|ts|tsx)$',
项目的初衷是使用 transform
去处理引入的 antd
的资源,但这也就导致了在 transform
时去遍历了整个 node_modules
文件系统,node_modules
内容是非常多的,所以在扫描时耗费了大量的时间,测试跑完发现“乔峰找着他爹了”🌚
所以本文最开始所说的“一处配置问题”就是这儿,删掉 transformIgnorePatterns
转而使用 moduleNameMapper
会快很多,不信你试试!
当优化、最佳实践等问题脱离具体项目场景时,那就是“耍流氓”!上学时老师经常说:“具体问题,具体分析”,对开发来说也是如此。经过优化后,测试消耗时间从 790s 缩减到了 40s,还算是一个可以接受的时间吧✌️
祝各位国庆快乐!