How to Inspect Node.js with Grunt-SWATCH (!watch) and Fiveo

I know, I know... the socket in the cover picture isn't really the type of socket we're talking about in this post, but I've been preoccupied lately with the idea of building a new workstation and the
我知道,我知道......封面图片中的插座并不是我们在这篇文章中讨论的插座类型,但最近我一直专注于构建新工作站的想法ThreadRipper is a monster! I mean it might actually be the solution to never feeling like my computer is never fast enough no matter what I upgrade to using (right now it's an Intel I7 8th Gen CPU).
是一个怪物!我的意思是它实际上可能是解决方案,从不觉得我的计算机永远不够快,无论我升级到使用什么(现在它是Intel I7第8代CPU)。

Every desktop/workstation I've ever used over the years (well there was one) has always left a lot to be desired. Waiting on your computer to COMPUTE sucks! Screen glitches, seemingly never ending progress spinners, lag time, and the like really break up productivity and workflow.

Anyway on to the topic and away from the...

NodeBB (Node.js Forum) Hacking {#nodebb-node-js-forum-hacking}

As I've written about recently, my hacking time of late has been spent on the forum software NodeBB. The build process that the developers of NodeBB put into place relies on the Grunt task runner, which itself is also build with Node.js. It's great when you can work within an ecosystem built primarily upon the frameworks you enjoy the most (for example Node.js ❤️).
正如我最近所写的那样,我最近的黑客攻击时间已经花在了论坛软件NodeBB上。 NodeBB开发人员的构建过程依赖于Grunt任务运行器,它本身也是用Node.js构建的。当你可以在一个主要基于你最喜欢的框架构建的生态系统中工作时(例如Node.js❤️),这很棒。

However when it comes to debugging, and when your build tooling and other layers of software are all built with Node.js, sometimes things get a little tricky. Like when you want to pass the
但是,当涉及到调试,以及当您的构建工具和其他软件层都使用Node.js构建时,有时事情会变得有点棘手。就像你想要通过时一样--inspect flag to node executable to start a debugging session, having the intent of debugging your plugin code, and not the layers above it (Grunt, NodeBB).

I am not aware of any command line options specific to the Grunt cli that can be used to pass your intent to start a Node debugging session down to the task level. I tried several things to no avail, however there were still a few options to get it done:
我不知道任何特定于Grunt cli的命令行选项,可用于将启动Node调试会话的意图传递到任务级别。我尝试了几件事无济于事,但仍然有一些选择可以完成它:

  1. Start Grunt by calling Node directly, ala node --inspect /path/to/grunt
  2. Start the Node Inspector programmatically using the still experimental Inspector API
  3. Start the Node Inspector after the fact using Linux signals, SIGUSR1 to be exact.

Tradeoffs {#tradeoffs}

Of course, each of these solutions provided obstacles of their own, and as with most things included both positive and negative aspects!

In this post I'll talk about each of these solutions, detailing the issues I faced using each one. We will see how leveraging the Inspector API made the
在这篇文章中,我将讨论这些解决方案中的每一个,详细说明我使用每个解决方案时遇到的问题。我们将看到利用Inspector API如何利用NPM module fiveo possible, and how that tool makes using Linux signals with Node.js even more powerful. And finally I will show how in the scenario presented herein, option #3 proved to be the best solution. And how choosing option #3 served as a catalyst to write the grunt-swatch plugin, what that plugin currently does, and what it could do with a little more work.

So this command works perfectly well to start the debugger:

node --inspect /home/batman/.nvm/versions/node/v10.16.0/bin/grunt

and grunt will continue doing its thing which is to perform a bunch of build steps before actually starting the NodeBB server. Yet, note the important fact that starting that initial Node process by calling node with
并且grunt将继续做其在实际启动NodeBB服务器之前执行一系列构建步骤的事情。然而,请注意通过调用节点来启动初始Node进程的重要事实--inspect is going to present its own challenges when Grunt launches entirely new processes.

Wonderfully when node child processes are started and the parent process has been called with the inspect flag set, the children will inherit that setting. But it's for that same reason that if you call node with
很奇怪,当启动节点子进程并且在设置了inspect标志的情况下调用父进程时,子进程将继承该设置。但是出于同样的原因,如果你用节点调用节点--inspect as we did, you are faced with these nice messages 😒 staring at you in the console:

failed: address already in use

那些failed: address already in use messages occur because the inspector, which is a socket server, has already been started on the parent process which in our case is Grunt. Thus when the children start with the inherited
消息的发生是因为作为套接字服务器的检查器已经在父进程上启动,在我们的例子中是Grunt。因此,当孩子们从遗传开始--inspect flag who's default arguments are set to
flag的默认参数设置为localhost:9229, Node tries to start up the inspector socket server (we'll call it the "
,Node尝试启动检查器套接字服务器(我们称之为" inspect process " from now on) using the default port 9229.

A workaround for this would be to change our initial command to:

node --inspect=0 /home/batman/.nvm/versions/node/v10.16.0/bin/grunt

"=0" causes the inspect process to choose a random port, as you can see 39380 and 46704 have been chosen.
导致检查过程选择随机端口,如您所见,已选择39380和46704。Random Insepctor Ports

Which is great because now we have two inspector processes running! The part that is not so great is that we don't care about either of them... yet.

NodeBB's Build Setup {#nodebb-s-build-setup}

I can't completely explain the
我无法完全解释 WHY of the
Grunt flow that makes up NodeBB's Gruntfile:
组成NodeBB的Gruntfile:NodeBB Gruntfile.js snipit

But I can say that
但我可以这么说 WHAT it is doing is basically forking an initialization sequence which takes care of building the css, language files, templates, building/bundling Javascript, etc... and then a second process is being forked to actually start the NodeBB server with assets ready and good to go.

Going further, each time a change is detected thanks to the watch process (
更进一步,每次检测到更改都归功于监视过程(grunt-contrib-watch), the current NodeBB process is killed and new one started. And with that new process comes... exactly, a new random debug port is going to be generated upon each cycle.

Which again complicates our debugging efforts and raises a few questions.

  • How do we keep track of all of these random inspector ports?
  • Further as we are working on a remote server, how do we handle port forwarding?
  • Do we really care about the intermediate inspector sessions?

While we ponder 🤔 on those, let's fork ourselves to...

Doing so requires a more "invasive" approach when it comes to our initial desire to debug OUR own code. This option requires the inclusion of the inspector module, which in and of itself isn't a big deal. We require code all the time and the inspector module is a core Node.js module, and not some 3rd party piece of code.

But, for that module to really be of any use, additional code must be written and added to our codebase.

const inspector = require('inspector')

To be quite...

stepped away to hack on some other code...

Last Night! {#last-night-}

So last night while I was writing this, I was starting to write that
所以昨晚我写这篇文章时,我开始写这篇文章了 to be quite honest, I hadn't given the inspector module much of a look before. And while doing so in the effort to write this post in the most informed manner possible, I was sent down a bit of a rabbit hole.

One of which I emerged from having written a tiny library that adds some sugar on top of the core inspector module, which as it turns out is pretty cool. Now, after having written said tiny library, I would recommend that instead of requiring the inspector module, one would be better off using
其中之一我从编写了一个小型库,在核心检查器模块的顶部添加了一些糖,结果证明它非常酷。现在,在编写了这个小型库后,我建议不要使用检查器模块,最好不要使用fiveo which in turn does that for you, while adding some nifty features such as using a port other than 9229 sort of like
这反过来为你做了,同时添加一些漂亮的功能,如使用9229之类的端口this GitHub issue is about.
是关于。Fiveo Demo Gif

Still, you may not like my tiny library 🙁, and you may be uninterested in writing your own. The fact that using the inspector api requires adding additional code to your own still exists. And that might be a factor which makes this second option a bad choice for your project. Which leads us to the 3rd and final option...

So ultimately the best solution I found was to use UNIX/Linux
所以最终我找到的最佳解决方案是使用UNIX / Linuxsignals. That's a link to the manpage which gives you an overview of what signals are exactly. The long and short of it is that signals can change the behavior of processes that receive them.
。这是一个指向联机帮助页的链接,可以让您全面了解哪些信号是准确的。它的长短是信号可以改变接收它们的进程的行为。 Note that signals are not supported on Windows. And from Node's official docs:

Node.js will also start listening for debugging messages if it receives a SIGUSR1 signal. (SIGUSR1 is not available on Windows.)
如果收到SIGUSR1信号,Node.js也将开始侦听调试消息。 (SIGUSR1在Windows上不可用。)

The Plan {#the-plan}

The overall idea is that we can deliver the SIGUSR1 signal to the Node process specific to our code at the time we need it, and not before then, thus eliminating all the noise that we don't care about. Noise like what NodeBB is doing during the init phase (remember it forks a bunch of stuff), or what the Grunt code is getting into, etc.

The point that we're ready to start the debugger is the point after Grunt does its init tasks, starts the NodeBB server, and the forum can be reached via the port it's configured to run on
我们准备启动调试器的重点是Grunt执行其init任务,启动NodeBB服务器之后的点,并且可以通过配置为运行的端口访问论坛tcp/45670. At that time we need to determine the process id that NodeBB is listening on, because we need a process id in order to deliver our signal to the appropriate place. Upon receiving the
。那时我们需要确定NodeBB正在监听的进程ID,因为我们需要一个进程ID来将信号传递到适当的位置。收到后SIGUSR1, Node will start the inspector process and we can begin debugging!

What we just described in the preceeding paragraph is exactly what our Grunt plugin
我们刚才在前一段中描述的正是我们的Grunt插件 grunt-swatch does. It's similar to
确实。它类似于 grunt-contrib-watch in that it continuously watches for changes in your environment, the difference is in that
因为它不断观察你环境的变化,不同之处在于 grunt-swatch doesn't watch the filesystem but rather the network, thus the name, derived from
不看文件系统而是网络,因此名称来源于 socket watch .


Run predefined tasks whenever watched file patterns are added, changed or deleted

One should be able to write other "actions" for the plugin, however I've only written the nim (aptly named but also a callback to
一个应该能够为插件编写其他"动作",但是我只编写了nim(恰当命名但也是一个回调函数)NiM) action
Code for nim action showing SIGUSR1

You can see that it's rather simple in what it does, but exactly what we need. It uses the Linux
您可以看到它的功能相当简单,但正是我们所需要的。它使用Linuxkill command (also
命令(也是an entertaining Sci-Fi by the way!) to send the
顺便说一句!)发送SIGUSR1 signal to our
发信号给我们 swatched process. As you can see the
处理。正如你所看到的那样close() function currently doesn't do anything and that's because prior to writing
函数目前没有做任何事情,那是因为在写作之前fiveo, there was no way to close the Node inspector via the signal method. However with fiveo included, we have access to
,无法通过信号方法关闭节点检查器。但是如果包含fiveo,我们可以访问SIGUSR2 which can close the inspector process... leaving things a bit more tidy 🧹.
这可以关闭检查员的过程......让事情更加整洁🧹。Code for nim action showing SIGUSR2

And here is the output where you can see from the
以下是您可以从中看到的输出swatch:nim log output, that the nim action is actually closing the Node inspector socket that was previously opened. In the screenshot below you can see the complete open/close cycle of this websocket:
Log for nim action showing SIGUSR2

Grunt with my grunt-swatch task loaded and configured appropriately will ensure that during my development process, the inspector will intelligently be stopped and started when I need it to.


进一步NiM will ensure that DevTools is always right where I need it, opened to the correct inspector websocket and ready to go.
将确保DevTools始终在我需要的地方,打开正确的检查员websocket并准备好。NiM Popup Screenshot

And there we have it. By using grunt-swatch, fiveo, along with
我们终于得到它了。通过使用grunt-swatch,fiveo,以及NiM the
Chromium Extension, our NodeBB plugin development workflow is greatly improved! I certainly don't miss the manual process of running this command over, and over, 🔁 and over again:

pid=`netstat -lnp|grep 45670|awk 'BEGIN {FS=" "}{print $7}'|cut -f1 -d"/"'`
kill -SIGUSR1 $pid

Some next steps could be to devise a method of communicating to the debugee process in order to change the debugger port dynamically. To be able to set the debug port from the Grunt config and in essence force the Node application to open a debugger on a preconfigured (in development, post runtime) port would be ideal!

I hope you found this post helpful. Here are the relevant links to stuff: