Introduction to vanilla, goals, principles
People ask me one of two questions:
- How do I get started with vanilla?
- Do you have any code for a large vanilla project I can take a look at and verify what you're saying is true?
Only one of those questions is asked in good faith. I'll let you guess which one it is.
Anyway, today I'm going to talk about the first question. I've been postponing this post for a while now, not just because I'm currently in the process of moving to a different Earth hemisphere, but also because it's not as simple as just giving you a 1, 2, 3.
The whole point of vanilla is that it allows a 360 degrees of freedom, and pretty much anything goes. Including bad things. Part of the bad reputation vanilla got over the years is precisely the bad things bad programmers did. If you've been following my work, you know about my anti-framework sentiment, but here's the thing: if you're gonna do any of the bad stuff, then just use a framework for crying out loud. It's marginally better than bad code.
We have a choice between me talking about all 360 degrees of freedom and listing the pros and cons of each degree, which would take me years, or just giving you my opinion in good faith and saying YMMV. I believe I'm far better-qualified for the latter and it would certainly take less time, so let's go with that.
The goals
So where do you get started? In order to know where you need to start, you need to know where you want to go. Vanilla has different shades, and associated opinions about what is best. This part of the series is going to be all about the goal.
One foot in the door
You want to keep the framework, and try a bit of vanilla here and there.
The first option is to write snippets of vanilla within a legacy framework-based code. This one you can't really avoid for long anyway if you're serious about our work. (You can avoid it forever if you're not, but I'm guessing you're not in that camp if you're asking about getting started with vanilla.)
No framework can completely do away with vanilla. It's simply not possible because today's framework all want to be 100% declarative, and the browser APIs are mostly the polar opposite – imperative. Because of this, no matter how hard you try, you still need some vanilla code to get some stuff done. Some examples of this include dragging and dropping elements, opening a modal dialog, making HTTP requests, working with canvas or audio/video elements. Some frameworks cover some of these bases, but most don't cover any.
Without ditching your framework completely, you can get started with vanilla by doing one of the following:
- Become aware of what's missing in your framework, and try to implement those missing bits without resorting to 3rd party libraries
- Learn how to leverage HTML and CSS better (e.g., ditch CSS preprocessors) and pay closer attention to the built-in behaviors of various elements
This is what I call the one-foot-in-the-door approach. It's probably the most comfortable way to get acquainted with the browser APIs if you're not feeling ready to commit. You can always go back to how you were doing things by replacing your failed vanilla code with a library, so it's a cheap way to experiment.
Enclave
You want to keep the framework but transition towards a full vanilla app or keep the framework and significantly improve the performance of a large part of the UI.
The one-foot-in-the-door approach is great if you just want to get your feet wet. However, it won't teach you much about how to structure larger pieces of code. If you're not ready to ditch your framework yet, but you're looking for a challenge, you could try to create a large, isolated block of UI within your app that is entirely implemented without frameworks.
Other than satisfying your curiosity, there are legitimate reasons why you'd do this. For instance, although the frameworks are typically ok for trivial UI, they struggle with more complex scenarios – not in terms of quantity, but in terms of the density of features and possible scenarios, and the complexity of interactions. Most popular frameworks are also not up the the vanilla standards when it comes to raw performance (e.g., real-time peer-to-peer interaction such as a chess tournament).
The enclave approach requires you to set up some kind of isolation between the
host app (which is written using a framework). As of this writing, a custom
element gives us the easiest solution to this problem: you can create a custom
element to host the entire piece of UI that should be implemented in vanilla
(e.g., <my-chess-board>, <my-dashboard>, etc.). Within the custom element,
you are free to do anything you wish keeping in mind that the this within the
custom element class represents the custom element node itself. The rest is
more or less similar to how you'd do DOM regular manipulation.
As far as the host app is concerned it only renders a custom element and that's it. It treats the element as a black box.
The enclave approach has two major benefits. First, it's a viable way to organize apps in general, not just within framework-based apps. Second, the resulting code is portable. Third, it doesn't matter if you render the custom element on the server or on the client, it works the same way. You can reuse it across frameworks as well as in 100% vanilla projects later. It's a good way to gradually phase out frameworks or limit their role.
A drawback of the enclave approach is that you will incur some complexity when it comes to transferring non-trivial data between the host app and the enclave – I recommend just using a global variable and/or an event bus for it.
Vanilla lite
You want to take advantage of the flexibility, and you're ok with using libraries and tooling.
If you want to ditch the frameworks, but you don't want to go all the way to hard mode, you want to have some of the framework features, or you just don't feel like going hard mode is the best business choice ever, you could try libraries that provide some assistance without sacrificing the 360 degrees of freedom.
The most well-known of these libraries is jQuery. People hugely underestimate jQuery for being one of the oldest on the block. And I'll tell you why they're mistaken. Of all the libraries out there (including most frameworks), there is no better-documented and better-supported front end library ever written. Period. If I could have a dollar every time I look up some vanilla-related question and run into a jQuery-based solution, I'd have... let me see... about 30 dollars. Ok, that's not a lot, but if you consider that I don't really look things up that often, it's a large percentage. Probably one in three.
If you want to try something more modern, there are libraries like BackboneJS, KnockoutJS or Lit.
In any case, you will be using some of the usual creature comforts such as non-native modules, bundlers, etc.
I know people who use these in production, just as I have a long time ago. Depending on the amount of effort you're willing to invest in mastering them, you can create setups that are far more efficient than anything you can build with modern frameworks, both in terms of performance of the final app, as well as in terms of how fast you can get things done.
The hard mode
You want to go for the uncompromised performance and flexibility, and enjoy a challenge. The 'hard' bit refers not to the amount of knowledge and experience required, but the requirement that you should not reinvent a framework. If you're used to frameworks, it's a bit harder than you'd think initially – it breaks all your intuition about how tings should be done.
The hard mode is a purist approach where you do not use tools or libraries for anything that is already covered by the platform.
Here, the "platform" is the Web (including the browser, and also HTTP, servers, CDNs, or even your IDE). In other words, if any of those components can do something for you, don't introduce tools and libraries (dependencies).
Here's a few examples of what I mean:
- DOM manipulation – don't need React, jQuery, etc, cause browser already provides an API for it (and whether you like it or not is irrelevant).
- HTTP – just use
fetch()orXMLHttpRequest, no need for Axios. - HTTP caching - use a service worker which already provides good facilities for this, and as a bonus enjoy the benefits of separating this particular concern out.
- Date and time manipulation - learn how to use the
DateAPI. - Asset compression - write less code so that you don't even need to do it, and/or leverage the features in your CDN or your HTTP server.
- Creating elements - the HTML shipped to the browser in the first request is the easiest way to create elements
And so on. Are there still cases where you'd need a library? Of course there are. This is not about being anti-library as a whole. It's about leveraging the platform where you can. You will use libraries for things that are realistically going to take a lot of time to get done without them.
Speaking of time, though, keep the following in mind:
- Unrealistic deadline is not a technology problem
- The time you need to do something today is always more than the time you'll need in future – as long as you are investing some time in practice
The basic principles
As I've said before, the truth is there aren't that many rules when it comes to vanilla. 90% of what you'll be doing is figuring out what works and what doesn't. That doesn't mean there are no principles that have surfaced throughout the years of collective experience (even if those principles are sometimes unknown or unfollowed.)
Regardless of what your goal is and where you decide the start, there are some common principles that I recommend you follow.
The overarching theme is reduction of moving parts – simplicity. The less moving parts you have, the less you spend time testing and re-testing things, the less effort it takes to make it, the faster it works, and (generally but not always) requires less code to be shipped to the client. Drawbacks? It's harder to get right sometimes. (Did you notice that simple is not the same as easy in my dictionary?)
The rule of least power
The first rule you want to observe is the rule of least power. The tl;dr is you use the least powerful language that will get the job done. In front end's case, the languages are ordered from least powerful to the most powerful roughly like this: HTML, CSS, JavaScript. (HTML and CSS are probably about the same but it doesn't matter as long as JavaScript comes last.) Here, 'powerful' refers not to your own capability using the language nor its ease of use, but the inherent potential of the language: some people erroneously conclude that the rule of least power implies machine language should be used for everything, but that's completely backwards – machine language is the most capable option there is.
The principle of appropriate flexibility
The second principle is the principle of appropriate flexibility. Although flexibility is your main asset in getting shit done fast, if you go out of your way to introduce it, you'll end up with baggage that will slow you down with no added benefit. In every project there are things that are less likely to change (e.g., position of the logo in one of the internal pages, general layout of a use profile page), and things that are more likely to change (e.g., hero area of the landing page). The principle of appropriate flexibility says that the effort you invest in adding flexibility to code should be proportional to its likelihood of change. Or, in plain English, don't add flexibility for the sake of it or just because you can. Example of efforts that add flexibility includes things like making code modular or reusable. This principle says you should postpone these until there's a need (basically YAGNI).
The principle of natural flexibility
The next principle is the principle of natural flexibility. Flexibility is the lack of resistance to change. Everything we use to develop software has some amount of flexibility to them, some more than others. E.g., dynamic languages are more flexible than statically typed ones, end-to-end tests are more flexible than unit tests, etc. Moving fast means changing things quickly, and the less resistance you get, the faster you can go. This principle says you should prefer tools, languages, and approaches that have a higher degree of natural flexibility. Although more flexible things may contain more moving parts of their own, they ultimately reduce the number of moving parts you need to add yourself.
The principle of ugly interfaces
The last principle I'll leave you with is the principle of ugly interfaces. The name of this principle is somewhat of a misnomer. I used to call it the principle of natural interfaces, but I intentionally call it the wrong name, because it highlights an important aspect of this principle, which is that aesthetics should never take precedence over simplicity. Back to the what the principle means: it says prefer approaches that more closely match the platform API over those that are more beautiful.
For instance, custom elements' natural interface is classes. That's how the specs intended them to be implemented. You could technically jump through the hoops to avoid classes because you don't like them (e.g., write a function that returns a class), but you end up with more code and more moving parts so it's overall a big loss. In the browser, some interfaces are naturally imperative, while some are naturally declarative. You should learn to switch styles depending on what you are working with regardless of your preferences. And so on.
This doesn't meant that you should write ugly, sloppy, convoluted code. Whenever you can, you should strive to write beautiful code. However, if it's between doing that, or incurring complexity, performance, or other penalties, you should drop beauty in favor of these other factors.
In the next episode
In the part 2 of this series, I'll start walking you through a simple app and highlight some of the ideas I have about how we go about developing vanilla apps.