A deubgging story(book)
Posted on January 30, 2019Ā - Ā š©š¼āš»Ā 7 min readFirst 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:
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:
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:
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.
When getting to es2015
we see this lovely piece of code referencing function.prototype.name
shim:
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:
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! š