在node_modules中的Spelunking (附代码示例)

81 阅读7分钟

上周,我在离开一个多月后再次开始工作。当你离开那么久回来时,你做的第一件事是什么?为什么呢,当然是升级依赖关系特别是,这是为我的kcd-scripts和paypal-scripts项目。值得注意的是,Rollup、Jest和lint-staged都得到了一些很好的补充,所以我很兴奋地开始了工作

在大多数情况下,事情进行得很顺利。我在Rollup上遇到了一些弃用警告,这很简单。我需要为Jest中的变化做一个简单的修改(我想当它发布时我可以恢复)。

但后来我开始在我的几个项目中更新kcd-scripts 。事情进展得很顺利,直到我更新了downshift 。我不仅更新了kcd-scripts ,还更新了react ,这时麻烦就开始了。downshift有几个错误案例的测试(例如,在验证你如何与道具获取器交互时抛出的错误)。它有一些断言,在试图安装降档时做错了事,会抛出一个错误。这个测试特别导致了这个输出。

Error: Uncaught [Error: downshift: You provided the id of "foo" for your input, but the htmlFor of your label is "bar". You must either remove the id from your input or set the htmlFor of the label equal to the input id.]
    at reportException (/Users/kdodds/Developer/downshift/node_modules/jsdom/lib/jsdom/living/helpers/runtime-script-errors.js:66:24)
    at invokeEventListeners (/Users/kdodds/Developer/downshift/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:209:9)
    at HTMLUnknownElementImpl._dispatch (/Users/kdodds/Developer/downshift/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:119:9)
    at HTMLUnknownElementImpl.dispatchEvent (/Users/kdodds/Developer/downshift/node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:82:17)
    at HTMLUnknownElementImpl.dispatchEvent (/Users/kdodds/Developer/downshift/node_modules/jsdom/lib/jsdom/living/nodes/HTMLElement-impl.js:30:27)
    at HTMLUnknownElement.dispatchEvent (/Users/kdodds/Developer/downshift/node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:143:21)
    at Object.invokeGuardedCallbackDev (/Users/kdodds/Developer/downshift/node_modules/react-dom/cjs/react-dom.development.js:581:16)
    at invokeGuardedCallback (/Users/kdodds/Developer/downshift/node_modules/react-dom/cjs/react-dom.development.js:438:27)
    at renderRoot (/Users/kdodds/Developer/downshift/node_modules/react-dom/cjs/react-dom.development.js:10366:7)

...

我剪掉了输出,完整的输出请看这个gist

但有趣的是,所有的测试都通过了!此外,这些日志是来自对测试结果的分析。此外,这些日志来自于一个console.error 的调用,而该文件的顶部是模拟console.error ,使其根本没有任何日志!这就是我们避免噪音的方法。

beforeEach(() => {
  jest.spyOn(console, 'error')
  console.error.mockImplementation(() => {})
})

afterEach(() => {
  console.error.mockRestore()
})

这就是我们如何在一开始就避免了React记录错误的噪音(这是我们所期待的)!哦,错误并不是来自React...反正不是直接的...它实际上是来自JSDOM!还记得那个堆栈跟踪吗?它的顶部说。

at reportException (/Users/kdodds/Developer/downshift/node_modules/jsdom/lib/jsdom/living/helpers/runtime-script-errors.js:66:24)

当你看到这样的堆栈跟踪时,你首先会看到自己的代码中哪里出了问题。这可能会帮助你知道在你自己的代码中是什么原因导致了这个问题,你可以修复它。如果你不能从中找出问题,那么尽可能多地跟踪堆栈跟踪就会非常有帮助。通过调试器(例如在浏览器的DevTools中)来做这件事,是一个非常好的方法。

所以,这就是我们要做的事情。我知道我的代码是好的,因为在我升级我的依赖关系之前,它工作正常。所以我想,我的依赖关系中一定有什么东西改变了。那么我们该怎么做呢?呼,打开旧的node_modules 目录,看看有什么问题!

你知道吗,node_modules 目录中大部分都是JavaScript文件?看看这个吧。运行下面的命令,你会得到一份downshift 项目中node_modules 目录上的代码报告。

git clone https://github.com/downshift-js/downshift.git
cd downshift
npm install
npx cloc ./node_modules

这将需要相当多的时间,所以我将为你省去麻烦。

...
------------------------------------------------
Language     files  blank     comment    code
------------------------------------------------
JavaScript   21024  283714    342123    1735415
JSON         2807   1422      0         550205
Markdown     1736   66030     4         168463
TypeScript   1032   17525     56659     127105
HTML         60     2463      40        20773
XML          45     241       13        9377
C/C++ Header 20     1115      325       5571
...

我剪掉了输出,因为它太大了。关于最后一条命令的完整输出,请看这个gist

Wow

哇!这是个好主意。看看这些JavaScript(1.7 毫升代码(不包括空行和注释)......注意:这有kcd-scripts ,它有一些重的deps,但是WOW)!你猜怎么着!你可以在你的编辑器中打开它,对它进行修改!然后你可以再次运行你的脚本,它们会接收到你的修改。这有多酷啊!?

所以这就是我所做的。我跳进代码中,得到了这个结果(扰流警报)。Jest问题#5223。"jsdom控制台是不可模拟的"

下面是关于这个问题的TL;DR

好吧,到目前为止还不错(希望......现在跟着我!或者直接跳到结尾处,看看有什么收获)。但是为什么我对控制台的嘲弄没有嘲弄VirtualConsole 使用的控制台呢?

所以,我是这样做的。我注意到它在使用anyConsole.error ,而不是console.erroranyConsole 被传递到这个代码所在的函数中。我验证了 sendTo是通过console.然后,为了更加确定,我把这一行改成了这样(添加了console.log )。

this.on('jsdomError', e => {
  console.log(anyConsole === console)
  anyConsole.error(e.stack, e.detail)
})

然后我运行了我的测试。当返回true ,我只是部分地感到惊讶。一方面,这是有道理的,因为这就是传入sendTo 函数的东西,但另一方面,它的行为与我测试文件中的console 不一样。所以我在我的测试文件的顶部添加了这一行。

global.MY_CONSOLE = console

然后再把代码改成这样。

this.on('jsdomError', e => {
  console.log(anyConsole === global.MY_CONSOLE)
  anyConsole.error(e.stack, e.detail)
})

然后,噗!我得到了false!因此,我在测试文件中得到的console ,与正常的控制台一样。其实我已经知道了,因为Jest做了一些很酷的魔法,使你的控制台日志更有帮助(比如显示日志来自你代码中的文件和行)。但不知何故,Jest为你的测试提供的假控制台并没有进入JSDOM。

好吧,为了让长话短说,我找到了Jest创建假控制台的地方,事实证明,这也是Jest在自己的环境中运行你的代码的地方,这样它就与其他测试隔离了。这意味着它有自己的global ,因此有自己的console

咻 😌

所以我的修正包括将创建testConsole 的工作移到JSDOM初始化之前,在jest-environment-jsdom ,并将testConsole 传递给jest-environment-jsdom这样它就可以创建我们自己的 的实例VirtualConsole的实例,使用给定的testConsole 。当我在我的node_modules 中实现了这一工作时,我为这些修改提出了一个拉动请求(我甚至实况转播📺),我们对它进行了迭代**(我了一些)。 现场直播也做了一些**),最终它被合并了。🎉

所以,这个故事有点技术性,我进入了三个巨大项目(JSDOM、Jest和React)的细枝末节。而这是我的第一个收获!

这是一个惊人的学习经历!我不仅知道了更多关于依赖关系的工作原理,而且这些知识可以为我提供一个机会,将我的学习成果应用于其他问题。

而且这也是你在写代码时使用的语言。而且这是你可以修改和玩弄的代码。在一些地方添加console.logs(和throw new Error(JSON.stringify({foo: 'bar'}, null, 2)) ),以确保代码到达你认为的地方,并学习代码如何通过你的依赖关系流动。这将帮助你想出问题的解决方法,并帮助你知道你可以做什么来为拉动请求中的解决方案作出贡献。

我可以直接报告我的问题,并恢复我的升级,等待别人来解决我的问题。这很容易。但那样的话,我就不能得到我所期待的Jest 22中的甜蜜的新功能了(比如那个代码框架😍)!除此之外,当你npm install ,你就会对这个项目负责。我们都是我们使用的项目的维护者。

我希望这对你有帮助!祝你好运!👍