Library Internals Do Not Exist 🔊

In JS, the documented behavior is the only thing that matters

A picture of the library of Strahov Monastery, Czech Republic
Listen to the audio version!

When we want to write some code using any programming language, there is no need to learn the whole specification in order to understand how it works. We read a few trusted resources, try it and see if we can get the desired outcome. If we can't, then we try it again, until it works. The specification is used as a reference, not as a documentation.

Everybody should know that.

A programming language will be harder to change. It will always thrive to be as backward compatible as possible. Authors of programming languages are usually conservative on what to remove, although there are some cases where things will break anyway.

That is similar to frameworks. However, unlike languages, a framework can create a "version 2" that rewrites everything. There will be a lot of work, but the responsibility of migrating lies on the consumer side. A framework is pluggable, a language is not.

And everybody should know that too.

But what do you do when you use a library? A library is supposed to abstract a specific problem, it should be much more open to new changes than a framework. A framework dictates how you write, a library provides a specific functionality that should be environment-agnostic. A library can be replaced easily, a framework cannot.

In JavaScript, there is no access modifier that can limit the visibility of a given API, everything is public by default. It's possible to use closures and other techniques, but that might not be enough to restrict access.

So how can we make sure that the internal functionality of a library is not going to be used by the developer that inspects the code in the browser console? How can we make sure the developer knows the contract that the library author is providing for consumption without relying on trial and error?

We document it.

Just because it "behaves" correctly, it doesn't mean that a library is being used the way it should. It's the documentation, A.K.A. the Public API, that serves as a reference to verify if somebody is using the API correctly. It is the contract between the author and consumer, the single source of truth. Nothing else.

Everything that is not documented is internal, and therefore consumers should assume that it doesn't exist, even if it is exposed for convenience because it can be removed anytime without notice.

If the author follows Semver, it is totally reasonable to remove an exposed (but undocumented) API in a minor or patch version, because Semver states that backward compatibility should be maintained only for the public API:

For this system to work, you first need to declare a public API

There are cases in which library authors might want to create additional rules and restrictions depending on the purpose of the library.

What is not explicitly documented in the public API does not exist.

Failing to read the docs of a library can cause the system to break because we will rely on how it behaves, not how it works. It is a recipe for breaking your code any time the dependency on the library is updated, instead of breaking only in a major version bump. Even if the system has proper tests, there is no guarantee.

It seems so fundamental that one would believe it to be common sense for everyone. However, I have met many developers that are not aware of it, and worse than that, nobody seems to talk about it more often. This forces people to learn the hard way through experience.

That is exactly what happened to me when I started.


At the time of this writing, jQuery has the $.trim utility function that is documented like this:

[…] removes all newlines, spaces (including non-breaking spaces), and tabs from the beginning and end of the supplied string.
— jQuery documentation

Many years ago, I was inspecting the jQuery source and noticed it was internally using String.prototype.trim as the first choice in case it was available in the browser (probably due to performance reasons), and then it had a fallback for a custom trim that had the restricted documented behavior. String.prototype.trim does more than just removing spaces and tabs, so I thought it made sense matching the full spec and even suggested it. But received a feedback that it didn't make any sense because it wasn't documented like that.

The trim() method removes whitespace from both ends of a string. Whitespace in this context is all the whitespace characters (space, tab, no-break space, etc.) and all the line terminator characters (LF, CR, etc.).
— String.prototype.trim on MDN

If someone used $trim like a language (trying to make it work through trial and error) and the browser supported the native trim method and the application relied on one of its different behavior for an important functionality, then the application would break once jQuery removed the String.prototype.trim from the algorithm.

Using a library like a programming language will eventually break your system.

There are some cases, though, that fixing a bug can break a lot of places because of an implicit functionality or philosophy. Take the hash selector breakage, for example, that was a legit bug fix that unfortunately broke an implicit contract between a fundamental jQuery philosophy and big consumers of the library (such as Wordpress). This is an example because it showed that sometimes the assumption of what is implicit between the library author and the consumer is not always clear and can be up to interpretation.

JavaScript Cookie / jQuery Cookie

I also happen to maintain a small library called js-cookie. At the time of this writing, that library had some leftovers from jquery.cookie code that was forked to the new project and which allowed an undocumented usage as a constructor like Cookies(String, String), even though the only documented APIs are Cookie.get, Cookies.set and Cookies.remove. However, because of the fact some folks didn't read the documentation, we had reports like this one that used examples of the undocumented API.

The point is: always follow the docs. If there is an internal behavior that you want to depend on, make a request to update the docs, so that the library authors can decide if that should exist as a supported feature or not.

If you want to use an internal behavior, start with the documentation.

There are cases, though, just like the hash change example from jQuery, in which library authors need to support an undocumented code, usually due to an unexpected violation of the Principle of Least Astonishment coupled with a huge amount of the wrong usage in the wild. This is a clear example of that.

There are some problems that programming languages cannot fix. The wrong usage at scale and potential of breaking the web make it impossible to do so.

Frameworks dictate how to write. They should be conservative in what to break, even if it is not documented. But once in a while, they can change everything so that progress doesn't stop.

JavaScript libraries are pluggable and its internals are not easy to hide. Therefore they have the opportunity to support only what is publicly documented for the sake of extensibility. But there are special cases in which one should be careful on what to expose.

Do not rely on a library internals. In practice, they don't really exist.

Edit April 28, 2017:

Among the things that composes your library public API, method binding might be considered one of them.

Thanks for reading. If you have some feedback, reach out to me on Twitter, Facebook or Github.