esbuild and managing JS: Migration Story

As an everyday backend developer it's daunting to hear about ever changing JavaScript world. CoffeeScript, webpack and esbuild all these can sometimes feel scary when you mostly code far away from the user in your favourite parlance - Ruby! However, there's always someone that encourages you not to be afraid and tells you they can help you. At Platogo we embark on the interesting journeys to help one another. We call these trips - Friday Projects. Join us to hear how we migrated our backend JavaScript.

Friday projects are an initiative we do at Platogo to help our teammates. It starts with an idea; whenever you have one, you’re welcome to invite other Platogos and get down to business. The ideas are various: ranging from helping our client team with Continuous Integration setup, learning how to set up a Redpanda cluster on a staging environment, to learning a new skill together. 


One of the ideas we came up with in the server team was improving our JavaScript. We wanted to make it modern while spending some cool time working with our client team. They know a lot about the quirks and features of JavaScript.


We have large scale applications that need to be managed by us or our Customer Relationship Management Team. This means we must have — you guessed it — an Admin Panel: our great Backoffice. We’ve had it ever since we started and long before I joined Platogo. It went under many rewrites and improvements. The constant to which was obviously JavaScript and its flavors, e.g. CoffeeScript.


We were hard working, implementing new client features in Backoffice. We used jQuery, Bootstrap and all the cool tech that was popular and modern up until 3-4 years ago. We do Rails apps which means managing JavaScript dependencies either via gems or by downloading the files. In general, it all boiled down to Sprockets. With one of the Rails upgrades we switched to webpacker to manage our dependencies because at the time that was the way to go.


In the first iteration of webpacker we still didn’t manage all of the dependencies, which means we still relied heavily on Sprockets and couldn’t really use any new modern JavaScript techniques. At some point we decided to have a look at modernizing our setup a bit. We set forth a couple of goals that we’d like to achieve:

  • No major rewrites
  • Move all dependencies to managed npm packages
  • Use a modern JavaScript bundler
  • Take the chance to work closely with our client team members


We didn’t want to rewrite our JavaScript code. As fun as it sounds, we have other things to do and we don’t work in JavaScript on a daily basis. We wanted to get rid of gems and vendored files wherever we could. Even if in some cases it meant still keeping the old library managed by npm. Everybody uses a bundler to produce modern JavaScript code. We also wanted to achieve that. We had a look and it turned out esbuild could be a good pick. 


We already used webpacker and webpack so we started by identifying our JavaScript dependencies. Next step was to move those from gems and vendored files into package.json and bundle them with webpacker. We found at least 10 dependencies that could easily be moved into package.json.

We started with none other than jQuery. Even though we don’t use jQuery on the client side of our application, we still rely on it heavily on the backend as it is required by almost all of our other JavaScript dependencies. To our surprise, it turned out that bootstrap, that we still bundle with Sprockets, stopped working. One of the client team colleagues checked the JavaScript console and was puzzled – jQuery was not available to bootstrap.


We dived deep into the investigation. We wanted to learn what the issue was. After our initial change we had two “main” JavaScript files: one created by webpack and the other by Sprockets. According to our changes in the HTML we loaded the former file first. Still as baffling as it was, the latter file, namely bootstrap in that file had no idea of jQuery – aucune idée.


We revisited a couple of articles on the migration subject. We dug deeper and deeper into the topic. As we were exchanging ideas and articles some of us decided to try something simple. It was the typical “what if?” scenario – “What if we assign jQuery into the window object like old times?”. To no surprise this simple thing did the trick – everything seemed to work!


This called for a huge celebration. Clicking through Backoffice we didn’t find any apparent issues – “works like a charm” that was the conclusion. It meant we could already safely release the changes. With that we managed to hit our first milestone – we didn’t break anything and we could keep working on the rest of the dependencies bit by bit.


We extended the project over multiple Fridays. Every time we met, we’d tackle a different JavaScript dependency. It wasn’t as hard and complicated as before. Most of the dependencies we worked on required the exact same treatment as jQuery – just add them to the window object. After the dependencies were all in their rightful home – package.json, we chose to look into some JavaScript files that’d been lurking around in our project. As mentioned before, our Backoffice underwent many iterations. This meant we already split most of our JavaScript code into logically separated files. All of which we bundled with Sprockets. 


As you probably know when it comes to the presentation layer, Rails applications rely mostly on rendering different views on the server. And as you may have guessed it, we did have some JavaScript code scattered around there. The goal of our Friday project was to use a modern JavaScript bundler. Before we could do that we wanted to have all of the JavaScript code in their respective separate files. That is why we extracted the JavaScript from the views’ files into separate JavaScript files.


This wasn’t easy. We encountered many errors related to the events not being triggered e.g. when clicking on the button. The JavaScript in the views was loaded at the time when the view was rendered. This meant we needed to be more careful and creative when attaching the events. This took some time but we managed to properly adapt all the code. Where we could, we tried to get rid of jQuery code and replace it with vanilla JavaScript. Everything seemed to work just as before. We were ready to move the importing of the JavaScript files from Sprockets to webpack. 


However, before we did that our colleagues reminded us about an important detail – linting. Linting your code is important. It improves readability, takes care of unused code and makes sure your code compiles with the modern standards. We consulted the client team and some articles and agreed to use Standard JS linter. Running the linter went quite smoothly. It did fix some offenses and pointed out some deprecated JavaScript functions that we replaced on our own.


Afterwards, we had only two dependencies in the Sprockets’ bundle. We couldn’t move them easily because of some ruby helpers they depend upon. No matter, we still put the cherry on top with moving away from webpacker to jsbundling-rails. This is the new cool kid on the block provided by Rails core team. It allows you to use any modern JavaScript bundler to deliver code via asset pipeline.


We decided to use esbuild as our JavaScript bundler. We saw many advantages, among them the speed of the bundling and configuration. In comparison to webpack with webpacker, using esbuild seemed like a zero-configuration tool. We managed to squeeze our new configuration file into merely 19 lines of code!    


This concluded our great adventure where we plunged deep into the meander of JavaScript. We had our client team colleagues on the way and we were glad they were there to shed some light on the issues that we encountered on this expedition. In spite of all the hullabaloo we managed to get many improvements done. All in all, it was an amazing project and we got many things in the process that we are pleased to boast about:      


  • We can use modern JavaScript
  • Improved the quality of our JavaScript code
  • Managed and optimized our build configuration
  • Our Docker build is faster
  • Great time with our client team members


by Grzegorz Jakubiak

Share me