Note: I use the company “we” in this article. This history begins several years before I joined Bluestem.
JSRender, Knockout, Angular (2012)
We were starting to take advantage of rich client-side functionality at this point but it was the wild west as far as development practices were concerned. Everyone did things differently, global variables were everywhere, and nothing had automated tests.
Knockout + RequireJS (2013–2015)
To get rid of the global variable problem and better organize our code we started organizing functionality into AMD modules and used RequireJS to deliver them to the browser. One of the biggest advantages of RequireJS has been the ability to use modules in CMS content without knowing ahead of time what modules need to be loaded on a page. We still use RequireJS for modules and it works well, but we switched from the r.js optimizer to asset-pipeline to bundle modules.
During this era we decided that writing unit tests for client-side code was a good idea. We built a test suite using QUnit running on PhantomJS. It was better than not having any tests but could take up to 10 minutes to run. Because of this we didn’t write as many tests as we should have and often didn’t run the existing tests before checking in.
Big releases and large refactors are scary, so one of our primary goals in modernizing our development practices is to improve our code incrementally. In order to accomplish this new modules use a different file extension (.es6). This lets our Babel know which modules should be transpiled and allows ESLint to apply different lint profiles to legacy .js and modern .es6 files.
ES2015 module format (2015)
Our legacy modules are all written in the AMD format and AMD doesn’t have very good IDE support. Conveniences like autocomplete, click-to-definition, and find usages just didn’t work, and this was really annoying. The ES2015 module format, aside from being less cluttered in my opinion, has first-class IDE support. We added the Babel transpiler to our pipeline to convert our ES2015 modules to AMD modules for browser consumption. This lets us incrementally convert our modules to ES2015 when we touch them in the course of regular development instead of performing a global refactor.
Faster tests (2015)
I wanted a test suite that runs in seconds, not minutes, and that meant ditching PhantomJS and running our tests on Node. To do this we have Babel convert the ES2015 modules to CommonJS syntax and use the Mocha test runner. In this environment we can’t test DOM manipulation, but with frameworks like React manual DOM manipulation is strongly discouraged anyways. We do still run a separate suite of functional tests using Geb that test end-to-end app functionality.
ESLint and style guide (2015)
if it’s worth complaining about, it’s worth fixing in the code. (And if it’s not worth fixing, it’s not worth mentioning.) — Golang FAQ
Failing the build because of lint issues may seem extreme, but in large projects with many contributors it’s too easy for warnings to sneak in. Real errors hide in piles of warnings and lint errors are easiest to fix when the code you wrote is still fresh in your mind. Plus, IntelliJ has built-in ESLint support, so we really have no excuse for checking in bad code.
React, Redux (2015)
Search was one of the oldest components of our platform and by far the buggiest. Similar to Facebook’s phantom notification problem, we played whack-a-mole with several issues that we could never entirely get rid of. The system was brittle, and while it wasn’t Knockout’s fault that we built a brittle system with it, I feel that Knockout didn’t provide us with enough guidance to build a robust system with it.
As a part of our re-factoring efforts, we knew we’d have to rewrite search so we took the opportunity to explore other frameworks. Some members of the team already had experience with Angular but none of us had tried out React or Redux. I put a proof-of-concept together and eventually we decided to implement all of search using React and Redux, in no small part because of the excellent developer tooling for those frameworks compared to Knockout. We are still using Knockout in other less complex areas of the site for server-side rendering, but as soon as we get React rendering on our servers I believe we’ll start using it in more places.
Robust AB testing (2016)
For the past few years we ran AB tests using cookies and some script tags in CMS content. Updates are instantaneous, don’t require a code deploy, and product owners can perform tests without needing to bug a developer. One major limitation of this client-side implementation is that some types of tests like complete page switches (send user to “/foo/A” or “/foo/B” based on a cookie value) aren’t reliable because a user won’t be cookie’d into one side of the test until after their first page loads. Another limitation of the system is that tests can be hidden in different CMS areas and there is no global view of the current state.
This arrangement worked well for a long time, but as our velocity has increased in the past two years it’s becoming apparent that we need a more robust solution that gives us global visibility and control over tests.
Server-side React rendering on Grails (2016)
We’re a Java shop and probably won’t be switching to a Node web-app anytime soon, but we still want to use the fun parts of the JS ecosystem like server-side pre-rendering with React. Fundamentally this isn’t new ground, React already supports server-side rendering using the Nashorn engine, we just need to hook it up with our application.
Third-party scripts and CMS content
Single-Site vs Platform
Originally published at code.bluestembrands.com on February 16, 2016.
The views expressed in this article are solely my own and do not necessarily reflect the views of Bluestem Brands, Inc.