Taka’s blog

A software engineer's blog who works at a start-up in London

Separate JavaScript Easily and Moderately from Rails with WebPack

Introduction

To dive into the world of front-end, I came up with an idea to separate JavaScript easily and moderately from a Rails project.

Some have already shared various ideas, but I found them too much or required more than couple of tools so they seemed like high hurdles. That's why I sought for easier ideas.

TL;DR

I've committed all settings that I mentioned here on initial commit.

Advantages

  • To utilize good points of Sprockets
  • To introduce only WebPack as additional tool, practically
  • To set up a few items, which are easy to do
  • Almost maintenance-free, I believe

Summary

As the following flow, you will program, build and publish your application with JavaScript:

  1. Program JavaScript under frontend directory
  2. Build JavaScript you developed under frontend to app/assets/javascripts
  3. Publish files under app/assets/javascripts without combining files

I think a good point of Sprockets is to publish files with fingerprint in the 3rd part of the flow. It costs a lot to cover this feature using other build tools for front-end.

In other words, if you leave it to Sprockets, you can separate JavaScript from Rails without large scale settings.

Views are still in Rails but the separation of this extent is good enough because if you want to clear all front-end from Rails, it might be okay to use Rails API in the first place.

Concrete Flow

To concretize summary a little, the flow would be as below:

  1. Do modern JS development in frontend/src/javascripts/hoge.js
  2. Build frontend/src/javascripts/hoge.js with WebPack and output assets/javascripts/hoge.js
  3. Publish assets/javascripts/hoge.js with fingerprint using Sprockets

Tools and Versions

Basically, only WebPack is the additional tool. To transpile them on a build with WebPack, Babel is introduced.

  • Sprockets 3.4.1 (+ Rails 4.2.5)
    • Publication (with MD5 fingerprint)
  • WebPack 1.12.9 (+ Babel 6.1.18)
    • Build

Structure of Directories

Add frontend directory to the root of a Rails project.

$ ls
Gemfile       README.md     app/          config/       db/           lib/          spec/
Gemfile.lock  Rakefile      bin/          config.ru     frontend/     public/       vendor/

The structure of the frontend directory is as below:

$ tree frontend -I node_modules
.
├── config
│   ├── development
│   │   └── webpack.config.js
│   └── production
│       └── webpack.config.js
├── package.json
├── src
│   └── javascripts
│       └── application.js
└── test
    └── javascripts

Roughly, it consists of for elements:

  • package.json
  • Config of WebPack
  • Directory for source files
  • Directory for test files

Procedure of settings

I'll explain the procedure of settings.

  1. Set Sprockets to publish JS files separately
  2. Add settings of JS build to WebPack
  3. Hook build by WebPack to Precompile

1. Set Sprockets to publish JS files separately

As the default settings, Rails binds all JavaScript files into application.js to publish it, so we need to change this setting.

Clean up under app/assets/javascripts

Make the directory empty because app/assets/javascripts will be the directory where built JS files are placed.

Add the following lines to .gitignore to exclude this directory from git management.

!/app/assets/javascripts/.keep
/app/assets/javascripts

Plus, as I wanted to publish CSS files separately, I removed *= require_tree . from app/assets/stylesheets/application.scss.

Add targets of precompile by Sprockets

As only application.js and application.css are the targets as the default, add settings to config/initializers/assets.rb to change all JavaScript and CSS files to be compile targets.

Rails.application.config.assets.precompile += %w(*.js *.css)

Exclude files whose names start with underscore

Some gems related to views sometimes raise errors at precompile.

In this case, it's fine to use the following setting which excludes files starting from underscore from the targets instead of the above setting.

Rails.application.config.assets.precompile << /(^[^_\/]|\/[^_])[^\/]*(\.js|\.css)$/

Supplements: How to include JavaScripts and CSSs in Rails

Respectively, calling the following methods inside view templates enables you to use them:

  • javascript_include_tag helper
  • stylesheet_link_taghelper

2. Add settings of JS build to WebPack

Install WebPack and Bable which is used as transpiler with WebPack. If package.json isn't right under frontend, execute npm init.

$ npm init
$ npm install -D webpack babel babel-loader babel-core

Example: Use React.js, ES2015 and Stage2

As an example, do settings to use React.js (0.14.3), ES2015 and Stage2.

At first, install React.js:

$ npm install --save react react-dom

After that, install required presets of Babel:

$ npm install -D babel-preset-react babel-preset-es2015 babel-preset-stage-2

To enable WebPack to transpile JavaScript files, make setting files:

module.exports = {
  devtool: 'inline-source-map',
  entry: {
    application: './src/javascripts/application.js',
  },
  output: {
    path: '../app/assets/javascripts',
    filename: '[name].js'
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel?presets[]=react,presets[]=es2015,presets[]=stage-2'
      }
    ]
  }
}

If you exclude devlool options, you can use it as production settings, I believe:

module.exports = {
  entry: {
    application: './src/javascripts/application.js',
  },
  output: {
    path: '../app/assets/javascripts',
    filename: '[name].js'
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel?presets[]=react,presets[]=es2015,presets[]=stage-2'
      }
    ]
  }
}

Now, if you execute webpack --config config/{enviroment}/webpack.config.js, JavaScripts registered as entries will be built to app/assets/javascripts/*.js.

Using the above settings, frontend/src/javascripts/application.js will be built to app/assets/javascripts/application.js.

You just need to add new entries and include them in views using helper if you want to add another JavaScript files.

Using npm run

Preparing commands such as npm run build is handy, so register the following three commands:

  • release: webpack --config config/production/webpack.config.js"
  • build: webpack --config config/development/webpack.config.js"
  • watch: webpack --watch --config config/development/webpack.config.js"
{
  "scripts": {
    "release": "webpack --config config/production/webpack.config.js",
    "build": "webpack --config config/development/webpack.config.js",
    "watch": "webpack --watch --config config/development/webpack.config.js"
  }
}

Then, if you type npm run build right under frontend, build with WebPack will be exceuted.

3. Hook build by WebPack to Precompile

Finally, hook npm run release just before executing assets:precompile to do nothing at deployment:

task :build_frontend do
  cd "frontend" do
    sh "npm install"
    sh "npm run release"
  end
end

Rake::Task["assets:precompile"].enhance(%i(build_frontend))

Now, if you type rake assets:precompile, npm run release will be done just under frontend before assets:precompile.

Conclusion

To separate JavaScript easily and moderately from Rails, I adopt this approach:

  1. To build JavaScripts to app/assets/javascripts with WebPack
  2. Publish files under app/assets/javascripts as they are with Sprockets

The pros of this approach are that there are only a few settings and additional tools, and easy to maintain. It makes settings of building JavaScript simple and not required to replace Sprockets forcefully.

After I read Why we should stop using Grunt & Gulp, I've been wondering if I can set it succinctly without Gulp. I've realized that thanks to accepting Sprockets easier than I expected.

The isolation level seems to be moderate, I think.

Postscript: Complete separation

I said that the benefit of Sprockets is to publish them with fingerprints but I found a plugin which can do it for WebPack.

You can separate JavaSscript from Rails completely because WebPack with this plugin will also publish them with fingerprints.

After building JavaScripts, JSON as below will be outputted for resolving paths.

{
    "one": {
        "js": "/js/one_2bb80372ebe8047a68d4.bundle.js"
    },
    "two": {
        "js": "/js/two_2bb80372ebe8047a68d4.bundle.js"
    }
}

You can see an example with Rails at Using this with Rails in README and it looks really easy.

References

The original post was published on Nov. 29, 2015, by me in Japanese.