I’ve spent the last many years religiously honoring the principle of separation of concerns. Separating concerns produces code that’s easier to read, maintain, and test. Right? Well, as I’ve found, virtually every “best practice” has exceptions. We have to consider the context and the implications of following any principal. And for HTML and JavaScript, separating concerns doesn’t always play out so well.
You see, HTML and JavaScript are joined at the hip. Changes to one directly impact the other. The problem is, there’s no explicit interface between these two technologies. This means when you change a line of HTML, you need to read every line of JavaScript to assure you haven’t just produced a bug (or lean heavily on your automated UI tests, if you have any).
The Unobtrusive JavaScript Movement is Dead
Let’s rewind the clock nearly a decade to explain why many have come full circle on this issue. In 2006, jQuery was on its way to becoming the de-facto standard for creating interactive web apps. About that same time the concept of unobtrusive JavaScript was picking up steam.1 I was a big proponent of the technique. It was exciting to finally have HTML that was pure HTML. Finally, through the power of jQuery selectors and event handlers residing in separate JS files, JavaScript was no longer “tainted” with HTML. And this perfect separation of concerns seemed wonderful…at first. But then reality set in.
Wait, when I want to change a piece of HTML, how do I know I’m not breaking the app? Due to the complete separation, every change to a piece of HTML required reviewing every single jQuery selector to assure I wasn’t breaking behavior. The flip-side was true as well. Every time I changed the JavaScript, I had to carefully review the HTML to assure I hadn’t regressed the display. And since jQuery simply returns an empty array when a selector doesn’t match anything, the application would often quietly fail when selectors didn’t match. Yes, one can create a fully comprehensive suite of automated UI tests via Selenium, et. al, but that’s far from free and incurs a big maintenance cost due to the brittle nature of automated UI tests.
The unobtrusive JavaScript movement treated separation of concerns as a global best practice without considering whether it truly made sense in this instance. The fact is HTML and JavaScript are NOT separate concerns. They are fundamentally intertwined. JavaScript without some corresponding UI isn’t very useful. And HTML without corresponding JavaScript isn’t interactive. The two must move in lockstep to work at all, and there’s no strongly typed interface to assure that the two sides remain compatible. Separating HTML and JavaScript isn’t a separation of concerns – it’s a separation of technologies.
Still not sold? See if React’s Pete Hunt can sway you.
The Solution? How About Components?
Since there’s no strongly typed interface between my HTML and JavaScript, maybe we should rethink the value of all these MV* frameworks. I’ve worked in Knockout/Durandal and Angular and found the common frustration is typos. When I’m in the HTML, I get no intellisense support since the markup has no knowledge of the corresponding data structure. This also makes refactoring more time-consuming since typos in the markup lead to cryptic run-time errors.
Contrast this experience with React. React eschews the common MV* style and instead focuses on a small simple problem: components. Each component is autonomous and can be composed of other components. The beauty of React is your markup is conveyed in JavaScript. Woah there, put down your torches. Re-read the paragraphs above if you don’t see the obvious win here. Once you convey your markup and behavior in the same file, then you get three big wins:
- Rich intellisense support – Since React uses JavaScript to create markup, you get intellisense support as you reference variables.
- Precise error messages – Errors in React typically point you to a specific line in the JavaScript. Make a typo in your markup in Angular or Knockout and the errors are often more ambiguous and do not provide a specific line number in the markup where the error is occurring.
- Leverage the full power of JavaScript – Composing your markup within JavaScript means you can enjoy all the power of JavaScript when working with your markup instead of a small proprietary subset that is offered within the HTML of frameworks like Angular and Knockout.
So I’m React Fanboy?
No, I really enjoy KO and Angular as well for different reasons. I enjoy the two-way binding offered in Angular and KO and find the extra plumbing in React/Flux tedious for all but the most complex apps. I could go on about other things I prefer in KO/Angular over React/Flux, but that’s another post. The point of this post is I now see the merits of composing behavior and markup using the same powerful technology: JavaScript. This idea of unifying tightly related concerns is gaining traction, as my friend Nik Molnar outlines in his recent post Something Razor Should Learn from JSX.
If two things go together logically, then it sure would be nice if they go together physically as well.
-Nik Molnar
The great news is everyone in the JS space seems to be quickly learning from each other and borrowing ideas. Thanks to React, I’ve come full circle on unobtrusive JavaScript movement, and I hope the idea catches on elsewhere. Whether you’re using React, Angular Directives, Knockout Components, Ember Components or native Web Components, self-contained components look like the future. And for self-contained components, I’m sold on composing markup and JavaScript in the same file.2
Footnotes
1. I’m referring solely to the separation of HTML & JS. The Wikipedia article lumps progressive enhancement under the unobtrusive JS banner. Progressive enhancement is an orthogonal concern in my mind and certainly continues to be a useful pattern.
2. I’m not advocating placing all JavaScript inline in the HTML. I’m merely advocating composing markup and JavaScript together when creating self-contained components. For instance, there’s still value in placing libraries and other non-component JavaScript in separate files for caching and reuse purposes.
Good post Corey.
JSX is interesting, and more successful in my mind because it’s putting declarative markup into procedural code. Littering HTML with JavaScript was unsightly and difficult to maintain, and I think one of the motivating factors behind unobtrusive JS. Re-said: a little HTML in JS is much better than a little JS in HTML.
Also of note, this isn’t the first time that “outside concerns” were moved into imperative code. Declarative stored procs have been assimilated into C# without any consternation. The reality is, you can do it however pleases you, and that’s what *really* matters.
I don’t see a problem with the current usage of MVVM vis a vis Knockout et al. You can see which elements are affected in the html due to the data-bind attribute without ever looking at the js file. There’s very little you need to do on a page that can’t be handled using this pattern.
Thanks for the comment. I agree that the KO data-binding pattern is effective and works well. I’ve just found that having the model and markup in the same file is preferable for reasons outlined above.
But doesn’t this approach break down when working with designers?
The markup is now buried in some code which is hard for designers to read. There’s also a disconnect between css classes used and where they are declared.
With markup and code in separate files, I can open them in two windows and position them side by side. I can’t do that when they are in the same file.
I use knockout to update my html and take care to only bind to properties. Anything resembling logic is moved behind a computed property.
There will always be something which need to be in sync between markup and code. The easiest fix is to keep both files next to each other.
There are many good things in React, but inlining html in code isn’t one of them.
Since JSX looks almost identical to HTML, I don’t see a big problem. While you’re sharing a common concern, I’ve already read accounts of devs working with designers who had no issues training the designers on the minor differences. In KO and Angular designers have to understand proprietary tags such as ng-repeat and data-repeat, etc. In React, they have to understand class is now className, and for is HtmlFor. It’s a pretty shallow learning curve on either side.
How could “having the model and markup in the same file be preferable”?
Why reinvent and break the already existing separation of concerns given by HTML and JS?
What about reusability and extensibility?
What if I want to reuse a view-model for a different view or extend an existing view-model and use it for a different view?
With React, separation of concerns is provided at the component level. Each component is autonomous and provides a clear interface. I find component-based decomposition more useful because the separate HTML and JS files in other frameworks must move in lockstep anyway.
Regarding reuse, well designed components are of course reusable. And you could enjoy the same “viewmodel” or “view” reuse via React if desired.
Hi
I think you have really made a valid point about html and javascript are intertwined. I love your article, from the beginning and right up to this line. “Contrast this experience with React”. The rest unfortunately I believe is misguided.
HTML should work without javascript, and that is more important than the problem of dealing with some modifications of html and javascript manipulation.I will tell you why:
There are two advantages to this.
1) It automatically makes the site mostly accessible (disclaimer – most, not all ). Reducing extra aria, reducing extra accessible data information and updates, simplifying the architecture.
2) It speeds up the development process, and this can be an expensive time. None javascripters, ba’s, project managers, simpler automation scripts, and any other user can follow the happy path journey of the website during development and not to be blocked by unfinished javascript. (Note: Unfinished is pretty much most of the development phase , through regular updates, change requests, edgecases etc).
That is why javascript is separated from it and that is why it became popular to separate the technologies.
Now though, with the introduction of data binding, we have an additional problem. There are many frameworks that exist now – and that means more learning, and less time being good at the thing we knew how to do – fluency in javascript, and fluency in dom manipulation and fluency in styling.
“HTML should work without javascript”
React can indeed do so by being rendered first on the server (Isomorphically/Universally). When done so, no accessibility is sacrificed. That said, the choice to support people running without JS is up to the business. Utilizing progressive enhancement techniques is far from free. For many applications that sit behind a login, supporting those with JS off doesn’t pay off enough to justify the cost. And despite your claim, separating HTML doesn’t speed the dev process. Quite the opposite – One must carefully keep the HTML and JS in sync without a strongly typed interface which leads to higher mental load and cryptic run-time failures. In a new world that is embracing component-based decomposition, splitting on technologies for work sharing purposes no longer makes sense.