Dev Blog
When we build our application or library, we often use external packages which provide many kinds of features and are often battle tested and popular. Especially there are a lot of open source libraries made by independent developers. For instance, we can use libraries to perform single sign on authentication, to manage browser history states or to access remote API of other web application. In fact our project might consist of even hundreds of such dependencies.
The pros
We can create more robust applications with less effort. And we also don't repeat creating of something whats already developed. Usually we can also help maintainer by fixing bugs or improving functionality.
Someone else keeps library updated to meet current requirements. That is, we expect that library is being updated and maintained, especially if it is widely used and have many developers involved. Even if something is outdated, we can help to make it up to date.
The risks
The interned is evolving, as well as browsers and server side systems. So it might happen that some of our dependency is no longer working properly. Of course we can fix it, as stated in previous paragraph. But how much effort we could involve in such fix? Often we would need to dive into project details, setup proper environment. If it is critically important we can do it, however often we do not have enough resources to handle development of external libraries and own projects. As we can find on History.js library front github page, it turned out that many companies prefer to develop own version instead of supporting Open Source project. Seems that money donations were also too low to keep library updated, thus development has stalled. At some point, the History.js stopped working properly, as some improvements were made in browsers. And bang! Whole application might be broken.
Another problem might arise, when we find a better library, but we already have used former one in hundreds of places. And it has slightly different API. So that we get to the point when switching library is impractical.
The solution
If we use library directly in our code, we actually hard code dependency. So that we are being hard-bound to dependency. But what if we had created lightweight wrapper for library function calls? Or even better a lightweight adapter which will call external library functions in a manner more appropriate to our library?
Creating Adapter
That will help us with using external library, as well it will provide better upgrade/migration options for future. We do not need to implement adapter for all features of used library, just the ones we require. When we switch to other library, or even create our own - the application might work properly - depending of adapter robustness. Creating such adapter is additional effort needed when using library. So we need to consider carefully if we should create it or use library directly. We can also create adapter when external library have been used in many places. Such adapter should have same function signatures as original library, and often is called shim or shiv.
Example with History.js
As mentioned previously, that History.js is no longer maintained,
however my application was using it. At some point behavior of
navigation started to be dizzy, so that replacing state actually
created new state. Making navigation clumsy and less intuitive.
After reviewing code, it turned out that its usage is
concentrated in just two JavaScript classes. The solution
was to completely remove library and use browser's native history
object. I quickly realized that it behaves a bit different, as it
does not fire event on replace state! That was crucial
requirement. It turned out, that only
replaceState
method need to be shimmed.
Step one was to create custom replaceState
method,
with same signature as original one. The code calls native
history method if possible and then raises events. The below
example is in CoffeeScript:
app: history: replaceState: (state, title, url) -> if window.history and window.history.replaceState window.history.replaceState(state, title, url) app.history.state = state if url[0] is '#' jQuery(window).trigger('anchorchange') else jQuery(window).trigger('statechange') state: {}
The second step was to find calls to
History.replaceState
and replace with
app.history.replaceState
. That's it, it works now:)
Mostly;)