There’s been a tremendous push in recent months to move all of the components used for building anything for the web in to JavaScript, and only JavaScript (abandoning the use of HTML for templates and CSS for styling). While I can understand how we’ve gotten to this point, I think we’re at risk of ignoring potentially more robust solutions. Rather than build up your application (or site) using a single monolithic JavaScript framework, I implore you to consider something that’s a bit more open-web and future friendly: continuing to build with component-based architecture using all of the current tools available to you, and looking forward to use web components.
Before we get into why I think component-based architecture is the correct way for now, and web components are the way of the future, let’s explore how we’ve gotten to where we are today.
It starts out innocently enough: you apply an ng-click
attribute to a link or a button in your template. Then you add a couple of additional lines of markup to handle an if/else pattern to that template (<span ng-if="truthy"></span><span ng-if="falsy"></span>
seem familiar?). Before you know it, you’re applying ng-mouseover and writing custom directives to handle events specific to your markup.
Others might be looking at this and decide that it’s lunacy to have all of that written by you, in-line in your template (and they’re probably right — if you’re adding a lot of logic to your template, you probably need to reconsider what you’re doing and approach the problem from a different angle). It’s difficult to maintain, tightly couples your template to the controller logic behind it, and it’s really not the template’s job to manage complex logic.
However, rather than refactor to use unobtrusive JavaScript patterns or to extract out difficult logic so it’s not present in the template, there’s a push (primarily from the React project) to build everything up using JavaScript. Your logic (controllers, models, etc), your templates, and even your CSS. The thinking is, for the most part, that if you are doing these complicated decision trees in the code, then it should be handled by something designed to handle said complicated decision making.
For those who’ve been building websites and applications for long enough, seeing something that looks like this:
<div onMouseOver="{() => this.setState({ hover: true })} onMouseLeave="{() => this.setState({ hover: false })}" style={{borderColor: this.state.hover ? 'red' : 'transparent' }} />
…should remind you of nightmares of years past. This largely disregards the efforts of WASP (Zeldman, Meyer, Holzschlag, et al) and, more recently, the Web Standards Sherpa that lead the charge on making the web more maintainable (proper HTML markup, separation of style, content, and behavior) and future friendly. Additionally, we return to a state where the separation of concerns is being tossed out altogether and we’re being asked to ignore years of learning in the favor or writing everything in JavaScript.
A typical React component would look something like this:
var buttonBase = { padding: 5, border: "1px solid #aaa", borderRadius: 5, backgroundColor: "#eee", };
var buttonBaseHover = _.defaults({ backgroundColor: “cyan”, borderColor: “magenta”, cursor: “pointer”, }, buttonBase);
var BasicButton = React.createClass({ getDefaultProps: function() { return { type: “button”, label: “Click me”, }; }, getInitialState: function() { return { isHovering: false, }; }, propTypes: { type: React.PropTypes.string.isRequired, label: React.PropTypes.string, }, handleMouseOver: function() { this.setState({isHovering: true }); }, handleMouseOut: function() { this.setState({isHovering: false }); }, render: function() { return ( <button type={this.props.type} style={this.state.isHovering ? buttonBaseHover : buttonBase} onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut} > {this.props.label} </button> ); } });
React.render(<BasicButton label=”Demo Button” />, document.getElementById(‘container’));
See the Pen React Button Component by Mike Tierney (@miketierney) on CodePen.
While this has the merits of being transportable (one of the better arguments for this style of development), it lends itself to higher maintenance issues. Like the common complaint of CSS, this is the sort of thing that quickly ends up creating bloat – it’s easy to envision a scenario where a “BasicButton1” gets added because a developer is uncertain about making changes to it. While conditionals could be added to the render method, you then have added processing impact on whether or how to render the button. Many developers will make the same mistake here that they do with CSS: “I don’t know where this is used, so I’ll just make a copy of it, modify it, and use that in my implementation.” Both are signs of maintenance issues, and both can be addressed with proper processes and practices (like code reviews).
One Tool For All Jobs
If we move towards using One Tool For All Jobs (in this instance, JavaScript), we neglect the fact that the tools we’re abandoning are actually quite good at doing what they need to do. CSS does a great many of the things that are related to styles far better than JavaScript and it has the added benefit of being cacheable, leveraging readily available resources (rather than littering your JS scope with event handlers for every element that needs to support interactivity; that can impact performance pretty heavily), and with proper use of the cascade you can more easily create variant styles as needed (site theming as a whole, for example).
To handle that hover effect from earlier in the post, you’d need to do this:
<div class="hover-div"/>
.hover-div:hover, .hover-div:focus { border-color: red; }
I’ve also added the :focus
pseudo-class so we also have keyboard support. It’d take more than that to add that same support into that inline sample, and you’re providing better support for your users. :focus
is a relatively small thing by itself, but think about the broader implications. A natural byproduct of this is to rebuild native components. As an example, we’re already rebuilding the select menu (because it’s too hard to style natively), and that’s just the start of a slippery slope. This should not be the norm – rebuilding existing browser components and behaviors in pure JavaScript might seem appealing (because you have total control over what it does, more or less), but the overhead of creating it in the first place is inevitably going to be greater than anticipated, and then you have to take into consideration the fact that you now have to maintain that. Furthermore, you now have to accommodate all current and future interaction patterns, UX experiences, and many other OS-level features that users are already familiar with in order to make your fancy widget not seem out of place. Is it really worth that? Do you really need (or have) that level of control? Why would you want to abandon future improvements that will roll out as browsers and standards improve? Finally, on this particular point, you’ll also need to factor in support for assistive technologies. Just because you’re building a “web app” doesn’t mean you get a free pass to just ignore those users.
Many of the proponents of using this all-in-javascript approach are focused solely on making sure that everything is managed locally (as opposed to globally, which is how CSS operates). They neatly solve this “pollution” of the global space by injecting all of the styles directly on the element being styled, which is truly the only way to avoid that issue. However, this comes at a cost — more complex elements can potentially have a significant number of style selectors (and let’s be honest here, we’re still using CSS whether it’s in the DOM or in a dedicated stylesheet). This leads to greater DOM bloat, and more re-draws of elements within a page when states change, content is updated, or the initial view is rendered. While these things may not impact the immediate load time of the page, there’s an overall impact on page rendering because of this. CSS isn’t free of this, certainly, but one advantage of loading those files once is that this is minimized. Additionally, for things like layout and global styles, you want to have a more global take on things. For re-usable components (buttons, form elements, links) – why make the browser evaluate and render style for every element one at a time when you can pre-load the styles into memory and when a node appears that matches the selector, the styles will be applied.
Component-based Architecture
My counter-proposal would be to use one of the better trends we’ve seen lately: component based architecture. Build small to large – much like the members of the React team who are advocating for using CSS in the JavaScript application want to do. This can be done with traditional techniques, however, and diligence to fending off entropy and bad practices. With tools like Sass and Less, we have the ability to create small files that are focused purely on building out a single component, which can then be stitched into the larger application as whole. When building a single page application, we can even start using an approach where our JS, HTML, and CSS all live together in the same directory, to later be concatenated,minified, and then moved into the appropriate locations. With a reasonable application of namespacing in CSS (pick your poison; I’m partial to SMACSS), we can accomplish the same goals – re-usable, easy to maintain, components – without jamming everything in to JavaScript.
Right now, I think a compromise is a mix of global and local styling. I still think styles added directly to DOM nodes is a mistake (with some exceptions — an argument can be made for inlining styles on canvas/svg-based charts, for example, and I’m sure there are other situations, but those should be the exception and not the rule). We should think about making our components themable as much as possible, re-using tools that are available to us (the Cascade naturally supports this sort of behavior), and embrace the fact that we never have 100% control over the way our web-based application will look and behave. There are simply too many variables that are out of our control.
If you’re concerned that your team is too large and you cannot communicate some of these practices widely, there are plenty of tools available to look for unused CSS, duplicate CSS, “bad” CSS, and inefficient selectors. These can be integrated in to your build process or development flow, depending on your preference.
One final point to make here is that we need to ensure that we are optimizing for our users. When we make the browser do all of the work for us because it makes maintaining a code-base easier, that’s fine up to a point, but we need to be mindful of the overall impact that that has on the experience of the end-user. This also means, rather uncomfortably, that we (as developers) need to embrace that there are a great many things that are out of our control, and we should build our solutions to be as flexible as possible. Otherwise, we’d still just be building it all using Flash.
Additional recommended reading:
- The Debate Around “Do We Even Need CSS Anymore?” (Chris Coyier)
- React is a Terrible Idea (Dan Yoder); note that I don’t necessarily agree with the title, but the overall premise is solid
- Practical Questions Around Web Components (Ian Feather); a thorough, if harsh, look at some of the Web Components spec’s short-comings