Webpack - Rails (Webpacker)


  1. https://github.com/rails/webpacker
  2. https://github.com/rails/webpacker/tree/master/docs
  3. https://www.neontsunami.com/posts/replacing-rails-asset-pipeline-with-webpacker

https://news.ycombinator.com/item?id=15883425

Webpacker is an anti-pattern: pollute the ruby sphere with JS and pollute the JS sphere with Ruby because you don’t wanna read the Webpack tutorial.

notes

environments

  1. https://github.com/rails/webpacker/blob/master/docs/webpack.md
  2. https://github.com/rails/webpacker

NODE_ENV values:

assets are compiled in NODE_ENV mode but use/load RAILS_ENV configuration (production environment is used as a fallback one) from config/webpacker.yml.

binstubs compile in development mode, rake tasks compile in production mode:

# compiles in development mode unless NODE_ENV is specified
./bin/webpack
./bin/webpack-dev-server

# compiles in production mode unless NODE_ENV is specified
bundle exec rails assets:precompile
bundle exec rails webpacker:compile

packs

  1. https://github.com/rails/webpacker/blob/master/docs/assets.md

pack (Webpacker term) == entry (Webpack term)

https://webpack.js.org/configuration/entry-context/

The entry object is where webpack looks to start building the bundle. The context is an absolute string to the directory that contains the entry files.

https://github.com/rails/webpacker/blob/master/docs/folder-structure.md#packs-aka-webpack-entries

Packs a.k.a. webpack entries

“Packs” is a special directory made only for webpack entry files so don’t put anything here that you don’t want to link in your views.

https://github.com/rails/webpacker/issues/581#issuecomment-316937091

‘packs’ is a special directory meant only for entry files i.e. written either in JS, Coffee or TS (as long as it compiles to JS). Every other thing should be outside packs and may live under app/javascript or anywhere you wish to be as long as you can reference it. If you reference styles in your pack all styles will be extracted to [pack_name].css, which you can reference in your views.

https://github.com/rails/webpacker/issues/471#issuecomment-306115940

by convention one should only put .js packs inside packs directory since they are treated in a special way by webpack. Other types of files like - styles, fonts and images should be placed outside of the packs directory and can be referenced from there.

https://github.com/rails/webpacker/blob/master/docs/assets.md#import-from-node-modules

Please note that your styles will always be extracted into [pack_name].css

=> only JS files are allowed to be packs (entries), CSS files imported inside packs will be extracted into <pack_name>.css file which can be referenced in layout with stylesheet_pack_tag helper.

assets

  1. https://webpack.js.org/guides/dependency-management/#require-context

assets can be imported one by one using import statements or recursively using require.context() function:

// app/assets/images/application.js

// import specific image
import './images/foo.jpg';

// all images in directory recursively
require.context('images/', true, /\.(gif|jpeg|jpg|png|svg)$/i);
// app/assets/javascripts/application.js

// import specific npm module
import Toastr from 'toastr';

// import all JS files in directory recursively
const r = require.context('./pages', true, /\.js$/);
r.keys().forEach(r);

loaders

https://github.com/rails/webpacker/blob/master/docs/webpack.md#loaders

You can also modify the loaders that Webpacker pre-configures for you.

basic loaders are installed as dependencies of @rails/webpacker npm package:

# yarn.lock

"@rails/webpacker@3.5":
  version "3.5.3"
  dependencies:
    babel-loader "^7.1.4"
    css-loader "^0.28.11"
    file-loader "^1.1.11"
    postcss-loader "^2.1.4"
    sass-loader "^6.0.7"
    style-loader "^0.20.3"

these loaders are also pre-configured by Webpacker (that is rules for loader modules are added to webpack.config.js Webpack config that must be generated by Webpacker under the hood).

print environment.loaders property inside config/webpack/environment.js for the list of configured loaders:

  // config/webpack/environment.js

  const {environment} = require('@rails/webpacker');

+ console.log(environment.loaders);
  module.exports = environment;

configuration

Babel

create Babel config:

// .babelrc

{
  "presets": [
    [
      "env",
      {
        "modules": false,
        "targets": {
          "browsers": "> 1%",
          "uglify": true
        },
        "useBuiltIns": true
      }
    ]
  ],
  "plugins": [
    "transform-object-rest-spread",
    [
      "transform-class-properties",
      {
        "spec": true
      }
    ]
  ]
}

plugins

source directory

  1. https://github.com/rails/webpacker#paths
  2. https://github.com/rails/webpacker/blob/master/docs/folder-structure.md#source

change source directory to app/assets/:

  # config/webpacker.yml

  default: &default
-   source_path: app/javascript
+   source_path: app/assets
    source_entry_path: packs
    public_output_path: packs
    cache_path: tmp/cache/webpacker

pack

  1. https://github.com/rails/webpacker/blob/master/docs/assets.md#import-from-node-modules

import all assets in pack file:

// app/assets/packs/app.js

import 'css/app';
import 'images/app';
import 'js/app';

see next sections for details on how to import specific assets.

packs are then included in layout file using javascript_pack_tag and stylesheet_pack_tag helpers:

https://hype.codes/how-assemble-rails-frontend-using-webpacker

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.

/ app/views/layouts/application.html.slim

/ app.js
= javascript_pack_tag('app')
/ app.css
= stylesheet_pack_tag('app')

styles

  1. https://github.com/rails/webpacker/blob/master/docs/css.md

npm package

  1. https://github.com/rails/webpacker/blob/master/docs/assets.md#import-from-node-modules
  2. https://gist.github.com/MFry/41fd51e8057152b9b914ecd1379a53d2

say, we want to use toastr JS library which consists of CSS and JS files.

jQuery

$ yarn add jquery

there are 2 ways to use jQuery:

Bootstrap

  1. https://github.com/rails/webpacker/blob/master/docs/css.md#add-bootstrap
  2. https://github.com/rails/webpacker/issues/1267
$ yarn add bootstrap
// app/assets/css/app.scss

@import '~bootstrap/dist/css/bootstrap.min';

images

  1. https://github.com/rails/webpacker/blob/master/docs/assets.md#link-in-your-rails-views
  2. https://github.com/rails/webpacker/issues/705#issuecomment-325394754
  3. https://github.com/rails/webpacker/issues/1101
  4. https://medium.com/@coorasse/goodbye-sprockets-welcome-webpacker-3-0-ff877fb8fa79#46fd
  5. https://stackoverflow.com/questions/49222136
  // app/assets/packs/app.js

  import 'css/app';
+ import 'images/app';
  import 'js/app';
// app/assets/images/app.js

require.context('images/', true, /\.(gif|jpeg|jpg|png|svg)$/i);

make sure specified image extensions are listed in Webpacker config:

# config/webpacker.yml

default: &default
  # ...
  extensions:
    # ...
    - .gif
    - .jpeg
    - .jpg
    - .png
    - .svg

use asset_pack_path helper to get image URL in views:

/ app/views/test/show.html.slim

img src=asset_pack_path('images/foo.jpg')
/ => /packs/images/foo-095e9f86a13789b1d0b86b9b5a9ff94a.jpg

Procfile

  1. https://github.com/rails/webpacker#development

https://github.com/rails/webpacker#development

This process will watch for changes in the app/javascript/packs/*.js files and automatically reload the browser to match.

Once you start this development server, Webpacker will automatically start proxying all webpack asset requests to this server. When you stop the server, it’ll revert back to on-demand compilation.

use Procfile to start Rails server and Webpacker development server:

# Procfile

# for some reason port 5000 is used by default
rails: rails server -p 3000
webpack: bin/webpack-dev-server

BTW when running Webpacker development server (webpack-dev-server) you don’t have to reload page constantly after changing asset files:

https://github.com/rails/webpacker#development

This process will watch for changes in the app/javascript/packs/*.js files and automatically reload the browser to match.

Yarn integrity check

Yarn integrity check might produce false positives:

$ yarn install
$ rails assets:precompile
...
========================================
  Your Yarn packages are out of date!
  Please run `yarn install` to update.
========================================

disable Yarn integrity check in development environment (it’s disabled in production environment by default):

# config/environments/development.rb

Rails.application.configure do
  config.webpacker.check_yarn_integrity = false
  # ...
end

deployment

  1. https://github.com/rails/webpacker/blob/master/docs/deployment.md

https://github.com/rails/webpacker/blob/master/docs/deployment.md

Webpacker hooks up a new webpacker:compile task to assets:precompile, which gets run whenever you run assets:precompile. If you are not using Sprockets, webpacker:compile is automatically aliased to assets:precompile.

NOTE: public/assets/ directory is created automatically in shared/ by Capistrano even if it’s not listed in linked_dirs.

Rake tasks compile in production mode by default (see about environments section above) - no need to set NODE_ENV=production manually when running assets:precompile task.

Capistrano

NOTE: don’t link public/packs/ and node_modules/ directories - this might cause problems in the long term.

precompile assets on production server

  1. Webpack - Troubleshooting
  2. https://rossta.net/blog/from-sprockets-to-webpack.html#deploying-with-capistrano-and-nginx
# Capfile

require 'capistrano/rails/assets'

set correct assets prefix so that deploy:assets:backup_manifest task doesn’t fail when performing deployment (see Webpack - Troubleshooting post):

# config/deploy.rb

set :assets_prefix, 'packs'

precompile assets locally

  1. https://github.com/stve/capistrano-local-precompile
  2. https://github.com/capistrano/rails/blob/master/lib/capistrano/tasks/assets.rake
  3. https://github.com/rails/webpacker/blob/master/lib/tasks/webpacker/compile.rake

currently I precompile assets locally (on development machine or CI server). also I don’t rely on deploy:assets:precompile task (which is redefined by Webpacker) and call webpacker:compile task directly.