Add Webpack to Rails 5

Thu Aug 29 2019 | rails, rails5, webpack, webpacker, scss, bootstrap, font-awesome

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