A couple of years ago I wrote a blog post entitled Debugging a Broccoli Tree. That has been the most common resource I give to folks that are having a hard time understanding why a given build pipeline is not behaving as they intended it to. However, over the years I have slowly become increasingly unsatisfied with editing the build pipeline and sprinkling around a bunch of stew.debug statements every time I need to debug something.

Issues with stew.debug 😫

I believe that using stew.debug is much like using a debugger statement: an extremely useful tool that you would generally use when you are actively working on a specific section of code. It is not something you would leave strewn about your build pipelines, because:

  • It becomes quite noisy to have a bunch of DEBUG-* folders) laying around in your project.
  • It has a fairly significant impact on your build speed (due to the fact that the stew.debug nodes are creating a bunch of additional I/O operations to duplicate the contents of their input nodes).

In addition to these issues with leaving stew.debug's around there is a massive impedance mismatch. Most of the time folks are debugging something in a build, they are not actually working on the build pipeline itself. They're generally just trying to figure out why a given addon isn't working correctly, or why some content that they assumed would end up in the output is not present. In those cases, adding stew.debug's is the farthest thing from their mind.

Hmm, so how can we address all of these concerns? They seem at odds with each other. How can we have the ability to easily debug build related things and not have to modify build pipelines that are generally out of our control?

Identifying a Longer Term Solution 👀 🤔

The first step in solving this conundrum is defining the constraints that we need to address for both of the main use cases (pipeline author and pipeline consumer). I came up with these primary constraints:

  • Pipeline Authors:
    • Must allow the build pipeline author to leave the debugging capabilities in the build.
    • Must not incur an additional performance penalty when it is unused.
  • Consumers:
    • Must be easy to enable.
    • Must be able to limit the output to just the nodes we are interested in.

Having this list of constraints was actually very helpful in identifying a path forward. As you might notice, this list of constraints is very similar to how we handle logging in most of our Node packages: via the debug package.

For those of you that are unaware of debug, it is a utility that lets users opt-in to logging at run time while leaving the log statements in the package. The general API for debug is:

In normal runtime conditions these log messages do not emit console output, but if the user provides a special environment variable it will begin logging these messages to the console. For example the following would enable logging for any usage of debug that specified common-prefix-here as their namespace:

DEBUG=common-prefix-here:* node ./script.js

Enter: broccoli-debug 👷

Now comes the easy part! We have identified exactly what we want, and we can finally start building something.

broccoli-debug has exactly the API that we enjoyed about the debug package: we can leave the debug nodes in the build pipeline without worrying about impacting the build performance and our users can opt-in to more detailed logging output whenever they need to.

The best way to use broccoli-debug is by creating the debug tree helper with your prefix, then invoke the callback with the tree you are debugging along with a label:

Lets consider an example addon that does something special in its treeForAddon hook. It might be useful to inspect the result of that custom processing before calling super. You could implement that functionality with something similar to:

Normally, when this addon is used no special debugging output will be generated, but when the user invokes our build with the proper environment variable set they can inspect the exact contents of that tree:

BROCCOLI_DEBUG=some-awesome:* ember serve

The above statement will create a DEBUG/ folder in the project root, which will now have the following folders:

├── some-awesome-including-addon-special-sauce:input
└── some-awesome-including-addon-special-sauce:output

These folders will have the exact contents of the nodes that were passed in to this.debugTree.

Conclusion 🎩

I will continue to use broccoli-stew's stew.debug helper it is still an incredibly powerful tool for debugging a build pipeline while working on it. However, I am beginning to slowly incorporate broccoli-debug into more of the build pipelines that I help maintain (see ember-engines/ember-engines#407), and I think you should too!