In long term projects, choices made some years ago may be a bit outdated or impractical compared to today’s offering. You can do just fine if you have a smaller or medium scale project with some infrequent development needs in the future. When you are facing a lifespan of possibly twenty years with active development, you may need to think of doing some changes and keeping your stack up to date and developers motivated. In addition to this you should also have your architecture in good shape. You should have plenty of time for larger refactorings and paying technical debt instead of building new features. Most of all you need to have a client who understands why changes have to be made. In our case this was the situation.
In spring 2017 we were facing the situation that our application codebases had become pretty massive and coding quite hard and time consuming. Our application states were all over the place and updating something needed a developer to make changes in many places. At the same time we got a requirement that our applications needed better performance. Especially we needed to have a lightning fast undo-redo functionality.
To overcome this, we decided to implement Flux application architecture with centralized state and actions modifying it. With Flux it could be easier to respond to changes following the action pattern and a possibility to time travel in the state would also make undo-redo less troublesome. After evaluating different Flux alternatives we decided to go with Redux. As Redux had kind of become the de-facto Flux implementation, it also had a good documentation, large community support with, great developer tools and also ready made redux-undo library for undo-redo functionality. Along with that we chose redux-thunk for handling asynchronous operations and ImmutableJS just to make sure our data stays untouched. Since we were using RequireJS we needed to use UMD builds for these new libraries, because they did not support AMD anymore.
Moving everything to Redux was a lot of work. First of all our applications had lots of data. Second, we were using Backbone Marionette as the UI layer, so we needed to build custom mechanics to respond to state changes. In addition to custom graph structure diffing we used Redux selectors with custom RxJS observables to keep our UI changing when the state changed. Also immutable data structures gave us some extra head scratching and made things maybe a bit too complicated.
In the end we managed to make everything work and improved performance along the way, with clear state structure to make debugging much easier.
As it turned out during Redux implementation, having modern libraries with AMD support is not so common anymore. RequireJS was being replaced with more versatile alternatives and browsers already had native support for ES6 modules. Also running Grunt scripts manually every time we would change our styles, took unnecessary time and created extra steps to development flow. We needed to do something about it. Enter the Webpack module bundler.
We formed a temporary team with one person from each application team, whose goal was to take Webpack into use. We needed to get rid of the previous RequireJS configurations and replace them with Webpack plugins. Basic migration went without major problems, but then we were faced with our custom AMD loaders, which we had built on top of RequireJS and which were quite critical to make our applications to work. These could not be handled with Webpack, since it was missing runtime module loading capabilities. Also the libraries in use with only AMD support, and unit tests with heavily mocked dependencies caused extra grey hairs.
Eventually we could not get rid of the custom modules, so now we have Webpack joined with RequireJS. Despite that we managed to get incremental module bundling and a possibility to use ES6 modules. We also got rid of Grunt tasks completely. No need for manual steps anymore, except for refreshing the browser to see the changes.
Choosing a new UI library was the biggest decision made on our library renewal track. We had had discussions about the new library already before Redux implementation. Actual need for it came up when we noticed that Backbone Marionette was not performing very well with the Redux change-stream architecture and all the customizations that needed to be made created extra bloat. We just didn’t have the correct enablers in place before. After Webpack support was implemented we were ready to make the decision.
For a long time there were two candidates from currently popular UI library options, React and Vue. Both were very good libraries and either one them would’ve suited our purpose. In the end React was chosen, some might say a bit accidentally. One of our application developers had implemented one larger component with React just for fun before we had officially decided to choose it. That accelerated the discussions a bit and of course we already had Redux in use which has good bindings with React so the final decision wasn’t too difficult to make.
To make React components work with our current Backbone Marionette UI we needed to write custom Marionette view wrappers where we injected React DOM and the components implemented with React (a good Medium article about it). As our state was already handled by Redux, mapping state to props and back could be handled without any custom RxJS observables anymore so it simplified things. Having a proper component structure already before we had React helped making it to live alongside Backbone Marionette. We could then continue our work with either one of them.
The last part wasn’t in any means a necessity but now that we were on a path of renewing things and had some eager developers, we decided to follow the path of Prettier. We have had Eslint in the project for linting purposes for a few years now. That still hasn’t changed the fact that developers have always argued about code styling issues, since everyone has their own opinion about it. With Prettier being opinionated code formatter, we could shut down the discussion and everyone’s code would be formatted the way Prettier wants it to be. There are minor cases which Prettier does not handle too well but Eslint covers those for us. After some initial groaning everyone have settled for what we have and the amount of code style complaints have decreased. Now we have more time to focus on the things that actually matter.
One year from start our frontend stack consists of Backbone Marionette, React, Redux and Typescript with Webpack module bundling with a hint of RequireJS. It certainly isn’t the simplest setup and may seem overwhelming for especially new developers but now we have enablers for continuing development using technologies from today and replacing old ones as we go forward.
Here are some learnings from our continuing journey renewing our frontend stack
Define current and planned state and steps how to get there e.g. what needs to be done first before another step can be taken