The push to use webpack with rails is getting too much to resist!

You can setup some good stuff with sprockets, but it’s a pain and a lot of it doesn’t seem too updated.

So… to get things like good ES6 support, minification, easier script managment with Yarn, and PostCSS support; It’s time for webpacker

Add webpack/webpacker to the app

Add to gem to Gemfile

gem 'webpacker'

then

bundle install
bundle exec rails webpacker:install
yarn upgrade

The default install didn’t add the rails stuff, so add other packages.

yarn add @rails/actioncable @rails/activestorage @rails/ujs turbolinks

Setup JS for Rails

This is what the default files for a Rails6 install look like. I didnt’ have anything changed from the default Rails5 stuff, so I just added this files.

app/javascript/channels/consumer.js

// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the `rails generate channel` command.

import { createConsumer } from "@rails/actioncable"

export default createConsumer()

app/javascript/channels/index.js

// Load all the channels within this directory and all subdirectories.
// Channel files must be named *_channel.js.

const channels = require.context('.', true, /_channel\.js$/)
channels.keys().forEach(channels)

The main entry point is the the app/javascript/packs/applications.js file.

Add some polyfill and default rails scripts

// Polyfill
import "core-js/stable";
import "regenerator-runtime/runtime";

require('@rails/ujs').start()
require('turbolinks').start()
require('@rails/activestorage').start()
require('./channels')

Lastly, update the javascript_include_tag to javascript_pack_tag in your layouts.

Restart your dev server and give things a try!

Move custom JS stuff over

I’m using Bootstrap on this project so added those deps.

yarn add bootstrap jquery popper.js

app/javascript/packs/application.js

import $ from 'jquery'
import popper from 'popper.js'
import bootstrap from 'bootstrap'

window.$ = window.jQuery = $
window.bootstrap = bootstrap
window.popper = popper

Use Webpack for CSS

Since we’re using it for JS, might as well use it for CSS.

Set extract_css to true in config/webpacker.yml

I needed to add postcss-cssnext for the default config to be happy. And sass for Bootstrap.

yarn add postcss-cssnext sass

Copy your current sass files over to app/javascript/css/.

I’m also using custom fonts, so copy those over and update any font-url calls in your sass files to url and fix the paths.

Install FontAwesome 4.

yarn add font-awesome

app/javascript/css/application.scss

$fa-font-path: "~font-awesome/fonts";
@import "font-awesome/scss/font-awesome";

Debugging

You can the dev server to see any errors. In some cases I had to restart the dev server, puma-dev in my case. Mostly for any config changes.

bin/webpack-dev-server

Using assets only available in gems

I had one dependency that’s bundled in a gem as a Rails engine. I decided to continue to use the Asset Pipeline for it. It’s only used on a few pages so loading it just on those pages works great.

Add the entry files to app/assets/js/app_recurring_select.js and app/assets/css/app_recurring_select.css.

Add the require code as needed to those files.

//= require recurring_select
/*
 *= require recurring_select
 */

Update the precompile config in config/initializers/assets.rb.

Rails.application.config.assets.precompile += %w[app_recurring_select.js app_recurring_select.css]

Add them to the needed pages

- content_for :js_head do
  = javascript_include_tag "app_recurring_select.js", 'data-turbolinks-track': 'reload'
  = stylesheet_link_tag "app_recurring_select.css", media: 'all', 'data-turbolinks-track': 'reload'

Don’t name the entry point file the same as the thing you’re require‘ing, like I did :p.

Cleanup

Remove the unused files from app/assets/ remove any gems you were using, like Bootstrap, that are now provided by Yarn.

EB config

As always, needed to customize the Eleastic Beanstalk deploy.

Install Yarn. Already had a script to install NodeJS so just added Yarn to that.

.ebextensions/install_node.config

commands:
  01_node_get:
    # run this command from /tmp directory
    cwd: /tmp
    # flag -y for no-interaction installation (visit https://rpm.nodesource.com for latest)
    command: 'curl --silent --location https://rpm.nodesource.com/setup_11.x | bash -'

  02_node_install:
    # run this command from /tmp directory
    cwd: /tmp
    command: 'yum -y install nodejs'

  03_yarn_package:
    cwd: /tmp
    command: 'curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | sudo tee /etc/yum.repos.d/yarn.repo'

  04_yarn_install:
    cwd: /tmp
    command: 'yum -y install yarn'

Next needed to run yarn install so all the webpack stuff is there for assets:precompile.

.ebextensions/yarn.config

files:
  "/opt/elasticbeanstalk/hooks/appdeploy/pre/09_yarn_install.sh":
    mode: "000755"
    owner: root
    group: root
    content: |
      #!/usr/bin/env bash
      # Using similar syntax as the appdeploy pre hooks that is managed by AWS
      set -xe

      EB_SUPPORT_DIR=$(/opt/elasticbeanstalk/bin/get-config container -k support_dir)
      EB_APP_STAGING_DIR=$(/opt/elasticbeanstalk/bin/get-config container -k app_staging_dir)

      . $EB_SUPPORT_DIR/envvars

      cd $EB_APP_STAGING_DIR
      su -s /bin/bash -c 'yarn install' webapp

Lastly, needed to update Nginx so CORS works for fonts.

.ebextensions/

files:
  /etc/nginx/conf.d/proxy.conf:
    mode: "000644"
    owner: root
    group: root
    content: |
      upstream custom_app {
        server unix:///var/run/puma/my_app.sock;
      }

      server {
        listen 80;
        server_name _ localhost; # need to listen to localhost for worker tier

        root /var/app/current/public;
        try_files $uri @app;

        location ^~ /assets/ {
          root /var/app/current/public;
          gzip_static on;
          expires max;
          add_header Cache-Control public;

          location ~* \.(ttf|ttc|otf|eot|woff|woff2|svg|font.css)$ {
            add_header Access-Control-Allow-Origin *;
          }
        }

        location ^~ /packs/ {
          root /var/app/current/public;
          gzip_static on;
          expires max;
          add_header Cache-Control public;

          location ~* \.(ttf|ttc|otf|eot|woff|woff2|svg|font.css)$ {
            add_header Access-Control-Allow-Origin *;
          }
        }

        location @app {
          proxy_pass http://custom_app;
          proxy_set_header Host $host;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

      }

container_commands:
  reloadnginx:
    command: "/etc/init.d/nginx reload"

Lastly, since I updated Nginx after testing the fonts, I had to invalid /packs/media/fonts/* in the AWS console to get them reloaded correcly in the CDN.

References