written by Chris Biscardi, UI Engineer, Docker
There are more people at Docker that write Go than write JavaScript. In early August 2014, I joined Docker as the only person whose primary job description was to ship UI. My long term task was to figure out how to enable the UI Engineers and Designers we were assuredly going to hire. Remember, the task of Docker is to “build tools of mass innovation” not “write maintainable css through the proper application of BEM“.
The state of the world when I was hired resembled something pretty typical in the early days of a project. We mostly had Django rendered pseudo-static html pages which were then enhanced in various ways with client-side javascript. Maintainability was generally poor and a rewrite was called for for various reasons. Docker has seen tremendous growth over the past year. Along with that growth comes more projects and a greater need to build quickly to test ideas.
One Year Later
The story of the last year is for another blog post, this blog post is focusing on where we ended up after a year from a technical standpoint. The following is the living standard at this moment for shipping UI at Docker.
JavaScript
We standardized our JavaScript in the following ways.
babel: stage 0
Babel is a huge piece in our pipeline, as it is in many pipelines at this point. All of our code is written in stage 0 style Babel and we are ecstatic that babel 6 is moving to a very PostCSS-like method of using plugins.
The features babel provides which have helped us the most are:
“Also, autobinding”
(class properties, fat arrows and class syntax)
Autobinding gets a small mention in the React 0.13.0-beta-1 blog post but receives a fuller treatment in the transposing property expressions into the constructor section of the ES7 Class Properties gist linked. Finally, checkout the actual implementation in the babel repl to get a really solid view of what’s happening in the compiled code.
Basically, Fat Arrows keep the same lexical this
as their surrounding code and property initializers get “raised” into the constructor when desugared. This means the lexical scope is the same scope as the body of the constructor.
javascript class Thing extends Component { myFunc = () => { // automatically bound this debug('a prop!', this.props.whaaaat); } }
Computed Keys
Using an excerpt from our internal library (this is a FontAwesome component), we often use computed keys to simplify logic in reusable elements. This, combined with propTypes, can make it so that illegal states are unrepresentable, reducing errors and speeding development.
javascript const classes = classnames({ 'fa': true, 'fa-fw': fixedWidth, 'fa-inverse': invert, [icon]: true, [`fa-${animate}`]: _.includes(animations, animate), [`fa-${size}`]: _.includes(sizes, size) && !stack, [`fa-flip-${flip}`]: _.includes(flips, flip), [`fa-rotate-${rotate}`]: _.includes(rotations, rotate), [`fa-stack-${size}`]: _.includes(sizes, size) && stack });
Destructuring
We now spec out almost all of our functions using destructuring. This has made it infinitely more clear as to what arguments a function accepts. This benefit could also be achieved through meticulous documentation, but we have found that destructuring is a far better solution for our purposes (and it hasn’t replaced documentation).
javascript export function doStuff({ name, namespace }) { if(namespace) { // someone owns it! } }
React
React’s benefits are less about the DOM and more about a coherent story for building abstractions. For example, we can revisit the FontAwesome element from earlier:
javascript import React, { Component, PropTypes } from 'react'; const { oneOf, string, bool } = PropTypes; import classnames from 'classnames'; import _ from 'lodash'; const sizes = ['lg', '2x', '3x', '4x', '5x']; const animations = ['spin', 'pulse']; const flips = ['horizontal', 'vertical']; const rotations = [90, 180, 270]; export default class FontAwesome extends Component { static propTypes = { icon: string.isRequired, animate: oneOf(animations), fixedWidth: bool, flip: oneOf(flips), invert: bool, rotate: oneOf(rotations), size: oneOf(sizes), stack: bool } render() { const { animate, fixedWidth, flip, icon, invert, rotate, size, stack } = this.props; const classes = classnames({ 'fa': true, 'fa-fw': fixedWidth, 'fa-inverse': invert, [icon]: true, [`fa-${animate}`]: _.includes(animations, animate), [`fa-${size}`]: _.includes(sizes, size) && !stack, [`fa-flip-${flip}`]: _.includes(flips, flip), [`fa-rotate-${rotate}`]: _.includes(rotations, rotate), [`fa-stack-${size}`]: _.includes(sizes, size) && stack }); return (); } }
This element will warn the developer when it’s used inaccurately (barring an error where you can actually input anything you want in the icon
field).
javascript import FA from 'common/FontAwesome';
React Router
I’m not going to say a whole lot here. We’ve standardized on React Router for routing across client, desktop, universal and (hopefully soon) native environments. It has been a joy to work with and significantly reduced the complicatedness of routing in our applications. 1.0 also looks pretty sweet with the addition of history being broken out.
Redux
Redux is probably the newest addition to our standard and possibly one of the most exciting. Redux is an isolated “flux” implementation written in a very functional style. Some of the big wins when using redux include the assortment of devtools and middleware which are popping up more and more.
Redux attempts to make state mutations predictable
Taken from the Motivation for redux.
CSS
Christopher Chedeau’s “CSS in JS” talk from NationJS in November, 2014 was something we took a huge amount of inspiration from at Docker. The most impactful slide is this one, which lists the problems outstanding with CSS.
We chose css-modules, PostCSS and integrated them with webpack.
css-modules
css-modules uses the ICSS standard compile target to make css local by default, which allows us to rethink best practices and write excessively generic class names. Given a css file:
css /* Heading.css */ .heading { color: pink; font-weight: 800; }
We can use that class, for example in a React Element.
javascript import React, { Component } from 'react'; import styles from './Heading.css'; class Classy extends Component { render() { return<h1 className={styles.heading}>Heads.</h1> } }
When rendered, our h1
looks like:
<h1 class='Heading__heading__basz'>Heads.</h1>
and the compiled CSS (extracted into a single stylesheet) looks like:
css .Heading__heading_basz { color: pink; font-weight: 800; }
PostCSS
I can literally not say enough good things about PostCSS. If you like and are happy with SASS, there may be no reason for you to switch, but I heavily encourage you to try it out.
We are using cssnext but have been putting thought into how we might modularize the requirements specified by dux elements. Specifically concerning the fact that ordering of plugins matters.
See the features list for more on cssnext. We also use (or are experimenting with) a number of other plugins such as modular-scale, local-constants and various linting tools. The small core and plugins model is powerful.
css :root { --mainColor: #ffbbaaff; } @custom-media --mobile (width <= 640px); @custom-selector --heading h1, h2, h3, h4, h5, h6; .post-article :--heading { color: color( var(--mainColor) blackness(+20%) ); } @media (--mobile) { .post-article :--heading { margin-top: 0; } }
dux
Over the next few months you will see us rolling out an internal framework we are calling dux
. dux is really a collection of libraries arranged in a standardized way. It includes:
• A slim css base built on normalize and some of the same ideas as bootstrap’s reboot.
• A base set of abstractions with familiar names like Card
and Button
but also, Markdown
, FontAwesome
and LiLink
. These abstractions are built on React and include CSS. Sample usage might look like:
javascript import Card, { Title, Content, Footer } from 'dux-card'; import Moment from 'dux-moment'; class ProfileDescription extends Component { render() { const { username, avatar, bio } = this.props; return ( <Card img={avatar}> <Title>{username}</Title> <Content>{bio}</Content> <Footer>Joined <Moment utc fromNow>{date_joined}</Moment></Footer> </Card> ) } }
• Project templates (iOS, client-app, universal-app, static-site, etc), which kick-start projects by pre-configuring build toolchains based on a standard template (webpack, PostCSS, css-modules, lost-grid, etc)
• Individually versioned elements with a small core. We are taking the lead from babel, PostCSS and Docker itself with regards to splitting out elements/plugins from the core engine. One issue we are anticipating making easier with this move is “CSS as API” and minimizing major version bumps for such breakages in individual elements.
A main goal of the dux
project is to eliminate duplicate work. To that end, we have decided to remove, as much as possible, the requirement to write CSS on the consuming side. Most of our CSS is written author-side instead of project-side. Backdoors are still provided in some cases: In fact, we have a generic API which allows the passing in of style-objects.
side-note: Style-Objects are simply objects whose keys are a “generic” classname (such as heading
) and whose values are the actual classname (such as Card__header__bgzj4
if it’s compiled). This allows us to re-use our elements even in environments without css-modules (by passing in an object with normal, global classnames as values).
Further Reading
• icss
• PostCSS
• cssnext
• rucksack
• babel
We hang around in reactiflux and you should too 😉
Learn More about Docker
- New to Docker? Try our 10 min online tutorial
- Share images, automate builds, and more with a free Docker Hub account
- Read the Docker 1.8 Release Notes
- Subscribe to Docker Weekly
- Register for upcoming Docker Online Meetups
- Attend upcoming Docker Meetups
- Register for DockerCon Europe 2015
- Start contributing to Docker
2 Responses to “How UI Became a Thing at Docker”
Thomas
Nice writeup of the problems of current CSS approaches.
But please have a look at the soon-to-be-released VCL:
http://vcl.github.io/
https://github.com/vcl/doc
http://vcl.github.io/presentation/index.html#1
03-09-2015 - Links - Magnus Udbjørg
[…] HOW UI BECAME A THING AT DOCKER […]