How to assemble Rails frontend using Webpacker?

Frontend developer's tips for a painless migration from Sprockets
05 July

What's wrong with Sprockets

Sprockets, aka RoR Asset Pipeline, has become obsolete long time ago. Frontend is evolving with leaps and bounds, and Sprockets is not keeping pace. As a result we have plenty of problems. Some of them:

  • Limited support for new frontend tools. For example, you will face issues using PostCSS plugin or use a much faster SASS libsass compiler instead of written in Ruby.
  • No support for ES2015 transpiling
  • No support for javascript modules
  • No support for sourcemaps, which has existed for many years

Also, Sprockets configuration has low flexibility. Guess what Sprockets will do if you disable minification and pass through it some minified javascript. Sprockets will deminify the code and insert comments everywhere trying to emulate sourcemaps. I've never found how to disable this behaviour.

Ruby on Rails

Ruby on Rails (RoR) - a framework written in Ruby programming language.

Sprockets is being slowly developed. Version 4 was supposed to solve some problems and it was promised to be released with the Rails 5, but this has not happened so far.

At the same time, Sprockets is a rails-specific tool, but the frontend evolves by itself and the community prefers to use and create universal tools that don't have any specific limits. So, with help of node.js there were created task runners (for example, pretty popular gulp) and module bundlers (webpack, which is able to resolve dependencies between javascript modules, concatinate everything in the right order and remove the redundant code).

It is worth mentioning package managers in the context of frontend. If you use Sprockets, most likely you use gems to import any frontend-associated libraries. Because it's a common way. The problem is that authors of such libraries don't care about creating and maintaining gems, they use npm and bower repositories. So, you have to use a third-party gem and hope that it will be somehow maintained or you can use crutches like rails-assets.org.

All this has bothered me for a long time and not just me. As a result we had various attempts to tape gulp, webpack and Rails together. It worked but it also took time and nerves. And now it can be left behind.

What's great about Webpacker 

Webpacker is a Rails gem that provides smooth and standard integration with webpack module bundler and yarn package manager (it's like modernized npm). Optional integration with popular frameworks / libraries such as react, angular, vue is also available. The current version of Webpacker is 2.0 for the moment when I write this article.

Requirements are:

  • Rails 4.2+
  • Ruby 2.2+
  • node.js 6.4.0+
  • yarn

If you have Rails 5.1+ you can use the --webpack option, because Webpacker is delivered "out of the box" with this version. For example:

rails new webpacker-example-app --webpack
// or this to setup react or angular or vue 
rails new webpacker-example-app --webpack=react
rails new webpacker-example-app --webpack=angular
rails new webpacker-example-app --webpack=vue

In case of Rails 4.2+, you can install Webpacker by adding it to your Gemfile and running the following command after installing the gem: 

rails webpacker:install
rails webpacker:install:[react, angular or vue] (optional)

This command will check the compliance with the requirements and take care of creating: 

  • Scripts for running webpack and webpack-dev-server in the bin directory
  • The app/javascript directory
  • The config/webpack directory and webpacker.yml
  • The packages.json (analogue of Gemfile for npm) with necessary stuffing. Then it will run the packages installation with yarn, which will create the node_modules directory and the yarn.lock file

Default packages list includes Babel, so you will be able to use the newest Javascript syntax.

Setting everything up

Webpacker suggests to follow certain conventions, but you can change everything quite easily, look into the config/webpack directory and config/webpaker.yml.

Webpack Configuration

The config/webpack directory has corresponding configuration file for each Rails environment. Also there is shared.js file, that is common for all environments, and configuration.js file, which is responsible for processing settings from config/webpacker.yml.

The production.js configuration by default commands webpack to assemble all modules into single bundles, to minify the code, to add fingerprints to file names, generate sourcemaps and even compressed gzip versions. So that is even more than Sprockets able to do, but everything can be configured as you like. You can add or remove steps from an assembly process and you can modify them. For example, you don't need gzip versions. No problem, just remove them from the config.

The config/webpack/loaders directory, contains loaders which are responsible for processing and transforming files according to certain rules. They are similar to tasks from other build tools. The babel.js loader is responsible for .js and .jsx files and instructs the babel transpiler to convert code written with ES2015 syntax to ES5 code. The coffee.js loader is responsible for .coffee files and also turns code into ES5. Again, everything can be configured, added or removed.

Styles Processing

Webpacker was originally intended to solve problems with javascript, not for replacing Sprockets. It was assumed that Sprockets will continue handle styles and other assets. However, the styles code, just like javascript, can be handled by your favourite preprocessor's loader. Webpacker by default generates the config/webpack/loaders/sass.js file, which contains configuration for processing styles written in sass/scss. It is done to make working with styles easier for applications built with component approach, React.js or other similar tools that allows you to include styles directly in components. But you don't have to follow this approach, so I recommend to use webpack to assemble styles.

The Webpacker installation also integrates PostCSS into the assembly. It adds a lot of possibilites and flexibility to styles management. The PostCSS configuration is in the .postcssrc.yml file in a project's root. There are list of plugins:

  • precss, not so useful when we already have scss
  • autoprefixer, a great tool that allows you to forget about writing browser vendor prefixes

Unfortunatelly, there is no "out of the box" feature to conveniently include assets in css code. I mean functions like asset-url and asset-data-uri from Asset Pipeline. But there is a solution - the postcss-assets plugin. In order to install it execute yarn add postcss-assets -D. This will add the package to the packages.json list and install it. Then we need to add changes to .postcssrc.yml, my config looks like this:

plugins:
  postcss-smart-import: {}
  precss: {}
  autoprefixer: {}
  postcss-assets: {
    loadPaths: ['images', '../app/assets_src/images/'],
    basePath: 'public',
    cachebuster: true,
  }

Your loadPaths most likely will be different. I recommend to look at readme file and to play with configuration.

As a result, you will be able to use resolve and inline functions in your css code.

Directory structure

Webpacker creates the new app/javascript directory and it is suggested to store there all the javascript code and entry points for webpack (so called packs). The convention is that entry points are placed in the app/javascript/packs directory, and javascript modules are placed in the app/javascript.

For various reasons styles are expected to be placed in the same app/javascript directory. But the good thing is that it's easy to configure it in config/webpack/shared.js. Look for resolve.modules which is an array of paths used by webpack to search for files. The first item is resolve(settings.source_path). source_path here is taken from config/webpacker.yml. So, for my own project I've set the source_path: app/assets_src, and modified resolve.modules like this:

modules: [
  resolve(`${settings.source_path}/javascripts`),
  resolve(`${settings.source_path}/styles`),
  'node_modules'
]

Launch

Webpacker adds two scripts for running webpack:

  • bin/webpack for a one-time assembly
  • bin/webpack-dev-server for 

I recommend to check the content of this files.

Also, in order to build your assets in production mode, for example for testing purposes, you can pass the --config param with a full path to the corresponding config file.

Note that if you or your colleague has added some modules to packages.json it will require installation. Otherwise webpack will throw an error saying that a module is missing. Just run yarn command in the project's directory, just like bundle install but for npm packages.

Deploy

Webpacker adds a new task webpacker:compile, which runs every time when the assets:precompile is run. If you have completely abandoned Sprockets, then you can manually run the bundle exec rails webpacker:compile command during deploy.

Including bundles into views

Very similar to the Asset Pipeline. Use new javascript_pack_tag and stylesheet_pack_tag helpers. They will add necessary HTML tags with links to assembled packs.

Setting up Continuous Integration

Continuous integration

The practice of merging all developer working copies to a shared mainline several times a day

 

Vexor

Cloud Continuous Integration service, unlimited parallelis

If you are using a CI system, then of course you will need to configure it: add node.js and yarn.

At Evrone, we successfully use Vexor.io. So, the configuration for Vexor with Ubuntu server will look like this.

The vexor.yml file in the project's root will contain

rvm:
  - 2.3
node_js:
  - 6.11.0
 
before_install:
  - ./install_yarn.sh
 
install:
  - bundle install
  - yarn
  - bin/webpack

The install_yarn.sh script looks like this:

#!/usr/bin/env bash
set -ex
 
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
 
sudo apt-get update && sudo apt-get install yarn

Don't forget to make the file executable: chmod +x install_yarn.sh

Conclusion

I've moved my current project to Webpacker but I advise you to think carefully before doing the same. Think about if it will worth for you some time that the transition will require. Nevertheless, you can use it for all new Rails projects.

DateTime, Timestamp, Time and Date in Rails

Learn about key differenece between DateTime, Timestamp, Time and Date in Rails
31 October

The difference between different date/time formats in ActiveRecord have little to do with Rails and everything to do with whatever database you're using.

Using MySQL as an example (if for no other reason because it's most popular), you have DATEDATETIMETIME and TIMESTAMP column data types; just as you have CHARVARCHARFLOATand INTEGER.

So, main differences: DATE only stores a date, TIME only stores a time of day, while DATETIME stores both.

The difference between DATETIME and TIMESTAMP is a bit more subtle: DATETIME is formatted as YYYY-MM-DD HH:MM:SS. Valid ranges go from the year 1000 to the year 9999 and everything in between. While TIMESTAMP looks similar when you fetch it from the database, it's really a just a front for a unix timestamp. Its valid range goes from 1970 to 2038. The difference here, aside from the various built-in functions within the database engine, is storage space. Because DATETIMEstores every digit in the year, month day, hour, minute and second, it uses up a total of 8 bytes. As TIMESTAMP only stores the number of seconds since 1970-01-01, it uses 4 bytes.

You can read more about the differences between time formats in MySQL here.

In the end, it comes down to what you need your date/time column to do. Do you need to store dates and times before 1970 or after 2038? Use DATETIME. Do you need to worry about database size and you're within that timerange? Use TIMESTAMP. Do you only need to store a date? Use DATE. Do you only need to store a time? Use TIME.

Having said all of this, Rails actually makes some of these decisions for you. Both :timestamp and :datetime will default to DATETIME, while :date and :time corresponds to DATE and TIME, respectively.