Skip to content

Instantly share code, notes, and snippets.

@masasakano
Created October 17, 2022 00:25
Show Gist options
  • Save masasakano/b856ce5e2b3aa3510c2580318c5f4c76 to your computer and use it in GitHub Desktop.
Save masasakano/b856ce5e2b3aa3510c2580318c5f4c76 to your computer and use it in GitHub Desktop.
How to upgrade/migrate from Rails 6.1 to 7.0 with bootstrap

Upgrade/Migrate from Rails 6.1 to 7 with bootstrap

Background

It is a major version up, in particular:

  • Rails 6 almost dropped Sprockets in favour of Webpack
  • Rails 7 almost dropped Webpack in favour of jsbundling-rails + import maps (or esbuild) + Sprockets.

The backgrond is

  • Webpack packs all JavaScript into a single file when called.
  • Modern web browsers can separate small JS files, not requiring such single large JavaScript
    • Hence, Rails-default asset-generator Sprockets does a good job, separating JS and Ruby parts nicely?

In short, in Rails 7, you can even ditch Node/npm/yarn in addition to webpack, providing you use importmaps, and it seems that is the way forward in the mind of the Rails core team.

However, not all the projects can straightaway follow the path, because some bundling/packing mechanism is required for some JavaScript modules. If that is the case, you perhaps want to migrate from Webpack to Esbuild, which is much lighter and faster and is seemingly more favoured (hence better supported?) in Rails 7, though it is perfectly possible to keep using Webpack in Rails 7.

Perhaps because of that, the default new Rails setup with Bootstrap includes Esbuild:

% bundle exec rails new . --css=bootstrap

In this article, I summarise how to migrate

  • from Rails-6 + Webpacker + Bootstrap + jQuery
  • to Rails-7 + Esbuild + Bootstrap + jQuery

on the basis of my limited experience.

NOTE: how to set up from scratch without Esbuild

For how to use importmaps with Bootstrap, refer to "Simple way how to use Bootstrap 5 in Rails 7 - importmaps & sprockets" by BootrAils and "Rails 7, Bootstrap 5 and importmaps without nodejs". In short,

  1. bin/rails importmap:install
  2. bin/importmap pin bootstrap

is the way, seemingly. And there will be even no package.json

From Webpacker (Rails-6 default) to esbuild (Rails-7 default JS bundler)

Suppose you keep using Node/npm/yarn in your Rails-6 project but ditch webpack, which is in practice replaced with esbuild.

Here is the way for it.

Basically, Webpack uses the slightly non-standard-sounding directory /app/javascript/packs/ as the default directory. Esbuild uses /app/javascript/ for it. All JS files must be moved, accordingly.

Also, turbolinks JS is replaced with turbo (they have similar names but are different).

In Webpack, you can include JS files anywhere in an HTML file. However, in Esbuild, all JS files are strongly recommended to be placed in the HTML <head>. You may need be careful about this point.

So far, I do not know how to selectively load JS files, depending on HTMLs. In default, all JS files are read in any pages.

As for jQuery, here I use jquery-ui-dist to use all jQuery-UI functions (you need yarn install it). You may import only your preferred UI functions instead (see below).

Procedure

First, you should upgrade Rails from Versions 6.1 to 7, following the standard way according to the official doc "Upgrading Ruby on Rails".

  1. In Gemfile, add gem 'jsbundling-rails'

    • would have been done in a default clean build with bundle exec rails new . --css=bootstrap

    • It must contain gem "sprockets-rails"

    • Replace gem turbolinks with turbo-rails

    • Replace gem sass-rails with gem "cssbundling-rails"

    • needs sassc-rails (even though the latter does not exist in default in Rails 7)

    • gem 'listen', '~> 3' seems necessary (even though not in default in Rails 7)

    • needs gem "stimulus-rails"

    • remove the line for webpacker or webpack

    • In development/test environments, replace gem 'byebug' with gem "debug", platforms: %i[ mri mingw x64_mingw ] (Ruby-3 default! Not applicable in Ruby 2, I believe.)

    • In development, remove gem 'spring' and gem 'spring-watcher-listen'

    • gem 'rails-i18n', '~> 6.0' should be upgraded to gem 'rails-i18n', '~> 7.0.0'

    • In Ruby 3.1, you need the following (see https://stackoverflow.com/a/72474475/3577922); also you may need gem 'matrix' if you use matrixes.

      gem 'net-smtp', require: false
      gem 'net-imap', require: false
      gem 'net-pop',  require: false
  2. ./bin/rails javascript:install:esbuild

    • This creates, among others, app/assets/builds directory, Procfile.dev, bin/dev

    • would have been done in default with bundle exec rails new . --css=bootstrap

    • package.json should include:

      {
      "scripts": {
        "build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --public-path=assets",
        "build:css": "sass ./app/assets/stylesheets/application.bootstrap.scss:./app/assets/builds/application.css --no-source-map --load-path=node_modules"
      }}
  3. Additionally, if you need a more complex JS bulid scenario, you can create esbuild.config.js (in the Rails top directory!) as:

    require('esbuild').build({
      entryPoints: ['app/javascript/application.js'],
      bundle: true,
      sourcemap: true,
      watch: process.argv.includes("--watch"),
      outdir: 'app/assets/builds',
    }).catch(() => process.exit(1))

    and replace the relevant line in package.json with

    "build": "node esbuild.config.js"
  4. If you use ActiveStorage, you should edit package.json so it includes (I am not sure about ActionCable, though!):

    "dependencies": {
      "@rails/actioncable": "^7.0.4",
      "@rails/activestorage": "^7.0.4"
    }
  5. Copy the contents of (the Webpacker entry point) app/javascript/packs/application.js to (esbuild entry point) app/javascript/application.js

    • Make sure to (appropriately) replace require with import (see the reference above for how to)
    • For example, convert require("channels") into import "./channels"; n.b., in this particular case, it did not work well and I ended up deleting the entire line, which does not cause any apparent problems.
  6. Edit the beginning of app/javascript/packs/application.js as

    //import "@hotwired/turbo-rails"  // Rails 7 default
    import { Turbo } from "@hotwired/turbo-rails"
    Turbo.session.drive = false
    import "./controllers"
    import * as bootstrap from "bootstrap"
    • Here, the first line (which is in defualt in Rails 7) is replaced with the second and third lines. Otherwise, JavaScript is not fired when a page is loaded for the first time but only after the page is reloaded. See Stackoverflow answer and also Turbo-Rails README in Github
  7. Do a project wide search for all instances of javascript_pack_tag and remove them.

  8. Another key file is config/webpack/environment.js, where some settings are written. Transfer contents to app/javascript/application.js

    • Check out the directory config/webpack/ and delete it.
  9. Edit app/assets/config/manifest.js . Rails 7 version does not include: //= link_directory ../stylesheets .css but you may need it, in particular if you have your custom CSS in the directory. Also, activestorage is optional.

    //= link_tree ../images
    //= link_directory ../stylesheets text/css
    //= link_tree ../builds
    //= require activestorage
  10. If you have app/assets/stylesheets/application.scss, there will be a collision with app/assets/builds/application.css. So, (git) mv application.scss application_extra.scss

    1. Specify its import in /app/assets/stylesheets/application.bootstrap.scss (providing you use Bootstrap):

      @import './application_extra.scss';
  11. In app/views/layouts/application.html.erb, add <%= stylesheet_link_tag "application_extra", "data-turbo-track": "reload" %> (after loading other default CSSs).

    1. Accordingly, remove the related statements (2 lines) in /app/javascript/application.js
  12. Run yarn remove turbolinks, which removes turbolinks from package.json

  13. Custom JS files in /app/javascript/packs/ should be moved to /app/javascript/ and the former directory should be deleted.

  14. Run yarn install

  15. For jQuery, see "How to use jQuery & jQueryUI with Esbuild" (GoRails)

    1. yarn add jquery

    2. Create /app/javascript/add_jquery.js with (for global setting); n.b., apparently, it is important to create a separate file for it and not write the sentences directly in application.js because of hoisting of JavaScript (see GoRails or comment in Stackoverflow)

      import jquery from "jquery"
      window.jQuery = jquery
      window.$ = jquery
    3. In /app/javascript/application.js, add import "./add_jquery", followed by import "jquery-ui-dist/jquery-ui" (Reason: the latter must be read after window is defined. Make sure the path is correct! Note that you could alternatively download your own selection of jQuery functions, as explained in the GoRails instruction.)

    4. Because the global setting is done, the related setting in each JS file must be deleted.

  16. You should try bin/rails assets:clobber (bin/rails assets:clean is much less aggressive.)

  17. If public/packs/ and public/packs-test exist, delete them. (see Stackoverflow; though it may not be enough...)

  18. for rails_admin, delete the line config.asset_source = :webpacker in /config/initializers/rails_admin.rb

    • Run rails g rails_admin:install so that it is set up for the Esbuild configuration, as opposed to the Webpack(er) configuration. Or, you may need DISABLE_SPRING=1 bin/rails g rails_admin:install (see https://stackoverflow.com/a/72674116)
  19. You may want to add a few lines in .gitignore (they may be added in default).

    • Compare your .gitignore with the one in a freshly created project.

References

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment