A deubgging story(book)

Posted on January 30, 2019Ā  - Ā šŸ‘©šŸ¼ā€šŸ’»Ā 7 min read

First thingā€™s first - Storybook is an awesome tool that allows you to develop/showcase your components. It has support for a whole range of frameworks and libraries and a bunch of useful addons. You should definitely check it out for your next project, or even your current one!

Preface

I maintain a component library and use Storybook as my developing environment. In addition, we use that same Storybook to showcase our components for consumers, that way they know what they can/canā€™t use.

Because we have Storybook, when getting a bug report, we can say things like ā€œYou need to supply steps to reproduce this in storybookā€. That makes things much easier to maintain, I donā€™t have to dig up through another personsā€™ codebase just to reproduce a bug, I have an environment they can reproduce it in - Storybook.

For that reason, we look at our storybook as a ā€œcleanā€ environment, it renders one component each time. That way if something isnā€™t working with a component in a specific application - it probably is a case of bad usage because our components work in isolation.

The šŸž

After a recent change to one component we got complaints that this component behaved unexpectedly in IE11. Me, the good developer that I am, went to storybook to check if for some reason this got missed and indeed the component behaved differently in IE11.

I was happy to see that it did, so then I replied to that bug with a response of ā€œSorry, I canā€™t reproduce this on my side.ā€ Things calmed down for about a day and then it came back, this time with a bit more info - it tried to invoke a function called e on an object, but e was undefined on the object.

That was both an ā€œaha!ā€ and šŸ˜³ moment.
ā€œaha!ā€ because I knew exactly where to look.
šŸ˜³ because it didnā€™t make sense.

The change

Before we dive deeper we need to talk about the actual code thatā€™s changed, we need to talk about the new requirement. This requirement was for a component to sanitize urls that arenā€™t whitelisted - for that we added a new whitelist parameter. This whitelist is an array that could contain a string, regex or function.

I knew that every object in Javascript has a constructor function and that every function has a name so instead of doing an ugly condition I opted to create a matchers object with 3 functions, string, regexp & function and then do matchers[matcher.constructor.name.toLowercase()] where matacher is the current matcher from the whitelist. this would invoke the right function and execute the right logic without introducing any conditionals. It was my way of doing OOPS in Javascript.

So, by now you probably know that trying to call e on that matchers object resulted in an exception because that object only had 3 functions and e wasnā€™t one of them. Thatā€™s where the ā€œaha!ā€ became šŸ˜³ - how is it possible? how could this be happening?

My first reaction was that I canā€™t reproduce this in Storybook which means that the consuming application is probably loading a faulty babel plugin or importing a bad polyfill that overrides the RegExp constructor name (the only whitelist element was a RegExp). And so, I again responded with ā€œIn Storybook the consructor name for RegExp works fine, this is something that needs to be fixed in the application not the component library.ā€

The clue

Another day passed and another clue came along with it - that same component didnā€™t work in IE11 for another application, not just that one specific application. This was odd, I canā€™t right this off to just coincidence, I need to go deeper.

I started looking around the web to see - was this something that just our apps didnā€™t have for some reason(a shared tooling problem maybe)? nope. Couldnā€™t find one site that has obj.constructor.name in IE11 - what gives? This had to be a polyfill issue, but how?

better check MDN for support for constructor.name which seemed fine but then scrolling down was the browser compatibility table down at the bottom: Function.prototype.name Browser Compatibility

This is a step closer, we now know that IE doesnā€™t support names on functions (seriously?!), but it stil doesnā€™t explain everything. How does this work in Storybook? it was time to take off the gloves, itā€™s time to question everything - this bug could be anywhere, even Storybook.

Being a šŸ•µā€ā™‚

It was time to take a look at our storybook. First thing was to remove all of the polyfills we loaded for the components, maybe one of them polyfilled another thing along the way? After removing all the polyfills and restarting storybook I was still amazed to see that /hello/.constructor.name === 'RegExp' evaluated to true.

Thatā€™s really weird, but itā€™s someplace else - next up came Storybook itself, maybe it loaded the polyfill for some reason? How would be even start looking the Storybook code? How about searching for polyfill? Thatā€™ll probably be a good start. This is what I got: Storybook polyfill files

There are a lot of angular specific stuff while I build my components in React.

Some of you already noticed the non-angular one but I missed it at first, I went down the wrong path and started looking at the client side code of the react app. Followed the main entry point of the react app, couldnā€™t find anything, looked for core-js in package.json and it was there! thatā€™s something right? couldnā€™t find where it was imported from inside the storybook react app.

After a few of these wild-goose chases I came to a full circle to search polyfills again - this time I saw it, I saw lib/core/src/server/common/polyfills.js and what do you know, thatā€™s the contents: The server polyfills

Weā€™re still not there though, letā€™s take a peek at airbnb-shims! In order to do it accurately we need something else first, the specific airbnb-shims version installed by storybook. If we look at the latest version it might not contain the culprit polyfill anymore.

Looking at the package.json in lib/core gives us "airbnb-shims": "^1 || ^2", so either version 1 or 2. Itā€™s time to look at airbnb-shims@2.x.x and from the looks of it the latest version (at the time) was 2.1.1 so we could just look at that. Digging through the code we see that all shims are loaded incrementally es5 loaded es2015, es2015 loads es2016 etc. airbnb-shims files

When getting to es2015 we see this lovely piece of code referencing function.prototype.name shim: es2015 shims code

Just to be 1000% sure that this is the real issue we do the same thing and find out the version of function.prototype.name used by airbnb-shims which is ^1.1.0 which is also currently the latest version, and again, we can take look at the current version. We open the shim.js and revel in our victory: Source for Function.prototype.name shim

Putting a breakpoint right before the defineProperty call and waiting for the app to pause. We then test /hello/.constructor.name to find itā€™s undefined, thatā€™s a good start. Now for the punch, step over the defineProperty call, test out /hello/.constructor.name to see that itā€™s RegExp! we have our answer! šŸŽ‰

All that was left to do now is submit an issue in Storybook.

Mystery solved!

Now that weā€™re done, we can look back and try to learn something from all of this. In the next few posts weā€™ll talk more about debugging and learn some techniques that will help us debug better and eventually solve more mysteries! šŸ”