This year front-end developers working in the Reviews Experience team have been given a great challenge. We needed to lay out the foundations for an upcoming redesign of our core product – Reviews and Advice.
The number of people working on the project has been constantly growing. In October 2015, there was one team with four front-end developers. By March 2016, there were ten front-end engineers across three teams! There have been more and more feature requests from various stakeholders in our company. And we have encountered many issues with our legacy front-end codebase – it’s hard to maintain, inconsistent, lacks guidelines and linting, and doesn’t have sufficient test coverage.
Consequently, we decided to carry out a front-end Sprint Zero by the end of 2015. The purpose of this two-week timespan was to investigate various frameworks and technologies and choose which we would like to work with for Reviews and future projects.
Our chosen technology had to meet several requirements:
- be easy to learn and allowing us to deliver new features quickly
- be maintainable and providing separation of concerns
- allow reuse of components (to accomplish the same look and feel across all websites under the which.co.uk domain, as well as to avoid implementing the same UI elements on each site)
- be SEO-friendly
- possess excellent performance capabilities
- allow testing easily
Ideally, it would also be well known in the community so that we can hire developers that are able to contribute to our codebase on their first day.
The winner was (of course!) React, and a set of tools to achieve all of the aforementioned goals.
What tech stack did we go for
Transpiler – Babel
Initially, we took the purist way and used only features that were a part of ES6’s spec. Eventually the pragmatic way won and we switched to using some plugins for experimental syntax such as class properties, decorators and object rest spread syntax. Even once all browsers support ES6 including modules and HTTP/2 will allow us not to bundle dependencies in one big file, we will be transpiling JSX and be dependent on Babel or another compiler anyway.
We decided to restrict the number of experimental features to a minimum and use only the ones that really improve our productivity, making any future migrations simpler.
Module bundler – Webpack
Although this has been criticised for poor documentation, it still seems to be the best and most flexible choice for handling dependencies and bundling front-end code. We use the following plugins and tools:
- PostCSS – powerful tool for transforming CSS styles
- Autoprefixer – allows us to avoid writing vendor prefixes for CSS rules
- ExtractTextWebpackPlugin – takes stylesheets from all CSS modules and bundles them into one file
- WebpackNotifierPlugin – notifies us once Webpack bundles have been rebuilt
- DefinePlugin – allows us to use the production version of React (see https://facebook.github.io/react/downloads.html#npm)
Styling – CSS Modules
They allows us to create component-scoped styles with succinct class names and make the styles really maintainable and simple.
CSS Modules in itself is just a concept or methodology – it is Webpack’s css-loader that makes it work.
There are a couple of things you should be careful about – for example, CSS declaration still cascades, so make sure you always set things such as font size explicitly (the same applies to styling with BEM or other naming conventions).
The core benefit is that you don’t have to maintain long names and don’t need to worry about following a specific format.
Data/state management – Baobab
No. We didn’t go mainstream. We didn’t choose Redux. We picked Baobab instead.
Why? At present, Redux is almost an obvious choice, but it wasn’t actually that popular when we made the decision. Nobody on our team had any previous experience with Redux.
On the other hand, I’d had a positive experience with Baobab, so after presenting it to the team we decided to give it a go. Now, having worked both with Redux and Baobab, I dare say Baobab has much smoother learning curve and doesn’t have as many enigmatic concepts as Redux does (reducers, action creators, middlewares, etc.). It doesn’t embrace functional programming nor provide an excellent API for unit testing, but it still does the job and is testable. And in this case, it made onboarding new team members much simpler.
However, a potential transition to Redux is still on our list of things to consider once we complete the rearchitecture.
We love shipping high-quality products and care a lot about testing. Many of us write code using Test-Driven Development, therefore we built our test stack carefully, making sure it executes quickly and gives us instant feedback.
For local unit testing, we use Mocha with jsdom – it’s a lightweight setup and guarantees fast execution. Additionally for CI testing we use Karma, which allows us to run tests against different browsers.
In order to unit test properly, dependencies need to be mocked. For this, we use rewire and babel-plugin-rewire. The drawback of this plugin is that it couples our tests to Babel. It is not ideal, but this does allow us to run the tests with both jsdom (in-memory DOM representation for Node.js environment) and real browsers.
For testing components, we use Enzyme. It’s a really powerful library for testing that allows us to write concise, maintainable selectors for both full and shallow rendering, as well as to write assertions in “expect to contain given JSX” style. In the past we also used React’s TestUtils, but found their API to be very verbose and limited.
We use the following polyfills to ensure compatibility with older browsers whilst being able to use the latest APIs:
Collaboration with UX & design team – react-demo
One goal of our front-end rearchitecture was to build a set of reusable components that could be used across websites we develop in-house and in collaboration with third parties. To support this process, we created a catalogue of components using the react-demo library.
This has made the design and development cycle much simpler. Everyone can review a list of components, play around with them, and try them out with different input values. Components can be easily tested in isolation and demoed without implementing on a site.
In the next post I’ll tell a bit more about where we are, share with you some challenges we’ve come across during the rearchitecture, show our approach to structuring React components and shed some more light on our future plans to transform the application to an single-page application served by Node.js.