What we had - Our baggage
Also, the requirejs gem added a lot of complexity in our setup. We had to “double compile” our assets. Sprockets had to produce both digested and non-digested files to be used in RequireJS bundling, we had to maintain 2 manifest files, and we had to decide manually which file is needed in which bundle. This resulted in error prone builds that very few of our devs understood how to fix.
We also had to find a “manual” way (commit-hash based) for cache-busting. Rails’ asset cache busting mechanism was not an option because we were using requirejs-generated bundles that we lazy-loaded. Last but not least, we had no easy way to incorporate gradually in our stack ES2015 (we were using Coffeescript at the time).
For adding and managing external libraries to use in our product we used Bower. Although Bower was a good solution and has made it until today with us, we had also some issues with it. For example some packages we needed were not in the registry. The main problem though was that we did not trust the availability of packages so we were checking in our packages to version control. This made the workflow of package management seem more complicated and less intuitive to our devs. We wanted to try the promising Yarn package manager and an easy way to incorporate it in our stack and use our external dependencies.
Here comes Webpack - Define the destination
For us the minimum requirements were:
1) Play nice with Rails
Performance is quite important for us (even more than it is for everyone). Since the app is getting bigger and bigger every day, we need features such as lazy loading, bundling and serving only what is necessary on every page (or after different user actions) without much manual configuration. We try to keep our bundles minimal and safe from developers bloating the critical ones by mistake.
Since our product is being actively enhanced every day (various deploys daily), we do not have the luxury to freeze our feature development and make thorough changes on our codebase. Therefore we needed to pick a tool that would enable us to make changes on our stack gradually without holding back our development at all. In order to make the initial minimum changes and then apply any changes gradually, we needed a tool that would support various things from our current stack, like AMD modules for example.
4) Actively supported
It was also important to pick a tool that would stay with us for a long time, so being actively supported and seeing a future in our tool of preference was a priority too. While trying out Webpack 1, Webpack 2 was already on its way to be released and we had to migrate. The active community behind the tool and its ecosystem, as well as the seamless migration to v2 made us feel safe for its support and confident for the growing community behind it.
Webpack met the above requirements and a lot more as we found out on the way.
Let’s do it! - The journey begins
In the rest of this post I’ll try to explain the problems we came across and how we tried to solve them one step at a time while migrating to Webpack.
In order to add Webpack and Yarn to our stack we needed to upgrade our development and deployer machines’ from Node 0.10 to version 4.7.2 since Webpack requires at least node v4.3. It might come as a surprise that we had such an old Node version but it’s not really given that our machines run on Debian Jessie stable (see here). At that time we had to backport NodeJS 4.7 and added Yarn to our internal Debian repository.
We were now able to add Webpack to our codebase through Yarn. We also added the webpack-rails gem to our Rails project mainly to get some asset path helpers and setup out of the box. We also added webpack-dev-server for the development environment. Ιn order to simplify the workflow for our developers we added Foreman to run the Webpack dev server concurrently with the Rails server.
Here is a (simplified) part of our Webpack config:
Lines 1-9: Here we tell Webpack where to look for modules and which file extensions to look for. We have simply added Rails
Lines 11-13: Here we add the entry file of our application. All of its dependencies and the dependencies’ dependencies are resolved and added to a bundle automatically!
Lines 15-22: Here we define how the output of Webpack should be. Namely, the files should be digested and put in
Lines 24-32: Here we define some rules for our modules. For example highcharts and modernizr libraries should return a global variable as their module export. Also, .hbs and .coffee files should be preprocessed by their corresponding loaders.
With webpack’s async code-splitting feature we were also able to easily define split points to our application where we load extra files asynchronously when they are needed.
Since our product is translated into 3 languages (skroutz.gr, alve.com, scrooge.co.uk) we are using the FastGettext gem as the backend of I18n. We also use the gettext_i18n_rails plugin for integrating FastGettext into Rails. In order to be able to use gettext methods in our .js and .hbs files we were using once again embedded Ruby, which we had to drop.
__("Hello") is being evaluated at compile time to “Hello” but the translation of
n__("shop", "shops", shop_count) is being replaced with a method that decides at runtime if it should be “shop” or “shops” according to
After +1804/−1613 lines of code and 650 files changed, we shipped the minimum possible changes for switching to Webpack.
Positive migration side-effects
- Content-based cache-busting
- Livereload and sourcemaps in development out of the box
- Assets compilation time 20% down
- JS tests running much faster
- Ready for Rails future updates