Rails with Webpack - not for everyone
TL;DR
Webpack may be an excellent choice for front-end web apps. But in a standard Rails app I find it to be volatile (configuration still changes) and surprisingly unfriendly (follow my adventure below). Sprockets 4 was a better trade-off for me in a standard Rails project.
Yarn in Rails on Heroku
I've been using Yarn in Rails for some time now, so I can pull in CSS and JS dependencies directly from the source npm packages. The advantage is that I don't have to wait for someone to update a ruby gem wrapper whenever a new version comes out. In fact, I don't necessarily want my Gemfile to be bloated with gems that wrap frontend assets at all.
Deploying a Ruby app that uses Node on Heroku without buildpack is luckily as trivial as adding gem "webpacker"
to your Gemfile. However, just to use Yarn, I now needed Webpack. In fact, I needed Webpack to be there without actually doing anything, because my assets were still compiled with Sprockets.
So, I ran rake webpacker:install
and, at that time, the config files looked very cluttered. But I somehow managed to have rake assets:precompile
run Webpack as a no-op by creating some empty files here and there. All this just so that I could use yarn out of the box.
And... I forgot about it (I did not, however, forget about the cluttered config files spread all over my Rails application.)
Fast-forward 6 months.
Obstacles when replacing Sprockets with Webpack
I'm in a project with several hundred SASS files and every time I change one of them, I need to wait 4 seconds for every browser reload because Sprockets re-compiles my CSS. Five days later I had to fix this.
Somewhere I read that Webpack is smart and fast, so I thought I'd give it a more serious attempt. If you google "Rails Webpack" you will find happy adventurous blog entries about how to replace the asset pipeline with Webpack in Rails 5.1. It had a hype air of "finally we're free from sprockets" and "using modern technology" and "custom JS processing" about it. Sure, why not.
So I ran rake webpacker:install
(again) and began moving my JS files to app/javascript/packs
. But then came my first stumble block. Would I really create a directory called app/javascript/packs/stylesheets
? My SASS files don't feel happy in a directory that includes javascript
in its name. But I continued.
The next pebble on my way was that there is no glob import support for SASS in Webpack, so I couldn't do import something/**/*
(while this is usually frowned upon, there are valid use-cases such as Harry Roberts ITCSS where components don't conflict with each other). I noticed that webpacker uses sass-loader which in turn recommends another loader to achieve glob imports.
A quick google for "webpack sass glob" made me suspicious. There were no less than 4 Webpack loaders that did the same thing: import-glob-loader, node-sass-glob-importer, import-glob, sass-resources-loader. (This made me feel as if the JS community was the Wild West that has not reached the same level of conformity than the Ruby community).
When you use Webpack in Rails, there are a bunch of loaders pre-configured for you. Each of these reads your asset file, may modify it, and passes it on to the next loader. By this magic, globbing can be achieved if a loader parses your import **/*
and replaces it with individual import statements for each file.
Unfortunately, all of these loaders only work with .scss
files and not .sass
which was exclusively used in the current project. Ok, I thought, let me create a few entry files (such as application.scss
and admin.scss
) where I use SCSS instead of SASS. It's a shame, but I won't add number 5 to the list of import glob loaders.
Yet, because loaders work the way they do, it parses the initial SCSS file, resolves the glob statements, but then the regular sass compiler takes over and every included file cannot use glob statements. For example, my application.scss
would use **/*
to load a components.scss
but in the latter I could not use **/*
any more. But I can work even around that. I just need to adapt the way I work to the framework (right?).
Even more obstacles
Then I had a really bad feeling when I had to add this to my JS files:
# This is a Javascript file
import 'stylesheets/something.scss'
import 'images/background.png'
I had to seriously import CSS and blog image files in Javascript in order to tell Webpack that they exist? This may make sense in some rich-client app, but not in my standard Rails project.
But, I got temporarily distracted by this error message:
I know that Webpack was created by a German but this error message reminded me a little bit too much of theoretical courses from university.
Invalid configuration object.
"Ah, my configuration file must have invalid YAML syntax", I thought. But after an hour, or so, lost on googling "debugging webpack", I finally, and accidentally, fixed the mistake. What was the problem? My webpacker.yml
had a source_entry_path: some_path
where some_path
was a directory that did not exist. That's all.
Why did Webpack not tell me that a directory is missing? I can only assume that this would have been a no-brainer in other contexts. But I still did not give up my hopes that the JS community would eventually become more user-friendly. So I ran bin/webpack
and all assets compiled in the end. At least I think so, because the output was, again, not as clear as I would have wished and compiling errors were so long that I had to scroll up 3 pages in my Terminal. (I will omit how I struggled to load the URL of a background image into my CSS, due to lacking documentation).
(Just to be clear, I am by no means badmouthing Webpack, but I present to you my experience with how Webpack presented itself to me.)
Yet, why were all my compiled JS files empty? Because of a bug reported in 2015 that has not been solved yet. If two files exist with the same basename, e.g. application.sass
and application.js
, then some plugin will remove all content from the JS file.
So I went through this "out-of-the-box" Webpack experience and gained... a SASS compiling improvement from 4 to 3 seconds. But I had to keep an eye on the Terminal window where the Webpack compiling took place, before I could reload the browser. So I didn't gain much at all.
I give up for now
At this point I called my buddy who is an Ember.js consultant and asked him about his experience with Webpack. He just laughed and said: "Webpack is terrible. All these loaders, you never know which one actually runs, then everybody creates their own little loader functions, you spend a year configuring it and the output is not beautiful and you don't even notice whether SASS compiles via Ruby or C."
There he summarized my last weeks exact experience in one sentence.
Finally, I realized that Sprockets 4 (which has been hiding as beta on Rubygems for two whole years now) supports SassC as a drop-in replacement of the Sass compiler implemented in Ruby. I gave it a try and, to be fair, my SASS compiling time has still not gone below 3 seconds. But at least I'm enjoying a true out-of-the-box experience for all assets.
Summary
- Webpack is more difficult to configure than it should be. I really can't point my finger at it, except that it is far from trivial even for a bug-hunter. My experience with Ember-CLI makes Webpack look like a dragon in comparison.
- Sprockets automatically runs on browser request. No need to have a webpacker-dev-server running that looks for file changes, then compiles, and then you may reload your browser window (no, I don't want my browser to auto-reload after compiling).
- I can't get rid of the feeling that Webpack is primarily concerned with JS and not as much with PNG, WOFF, SVG, etc. In that respect, the Rails Asset Pipeline (apple) is just not comparable with Webpack (
pairpear). - I can not confirm that Webpack overall is faster than Sprockets in a large project. After all, it's still underlying libraries such as Babel or SASS that do the actual work.
(Title image: "Eugène von Guérard - Lake Wakatipu with Mount Earnslaw", public domain from the Google Art Project.)
I’ve tried using webpack in rails for stylesheets and images and was not a fan.
On all my rails projects I use sprockets for stylesheets, images, fonts and other assets and webpack only for JavaScript…
Why do you need webpacker to use yarn/npm? Can’t you simply add a path to your config like so to expose it to sprockets?
Maybe for clarification: I wanted to replace sprockets with webpacker to see if it was faster. I was already using yarn even before that in the way that you describe.
Additionally, when Heroku detects webpacker in the Gemfile, it installs yarn out-of-the-box. If webpacker is not present in the Gemfile, as far as I know, I have to use a custom buildpack so that both Ruby and Node are available at deployment.
I am not entirely in agreement with the conclusion.
[x] You should try to use Webpack without webpacker, webpacker has a lot to improve.
[x] Configure Webpack is not difficult, just avoid third-party loader.
But I identify a lot with your article a few days ago I tried to use webpacker with its configuration for React, and I lost many hours. At the end I decided to use create-react-app. And serve the files it generates.
Believe it or not, that comforts me a little bit ;) Thank you for your comment!
Interesting! I didn’t even think about doing that. Because the convention over configuration paradigm is so deeply rooted in me (as it increases code maintainability in a team).
Isn’t the whole point of using Webpack that you have more flexibility in which features (read: loaders) you want to use? If I use an out-of-the-box Webpack, then I’m not sure there is any benefit.
…unless, and here I repeat my caveat, unless you’re working on a react app in Rails. I think then there are no rules whatsoever and you should try whatever setup works best 😄
You right, but a lot of people make weird loader when al ready exists one that solve the problem
Have you already checked this project?
Check this https://github.com/parcel-bundler
It’s good to avoid bug, but not so open to configurations.
And you have to wait Webpack 4 friend, good things come
Yeah parcel looks interesting. Also worth noting that webpack 4 which is in beta has zero config option too