Rails 7: Using ImportMaps + Bootstrap

With Rails 7, the default way to use Rails is with a new tool called ImportMap. However, the situation gets a little more complicated when you consider two more tools also introduced with Rails 7: JSBundling Rails, and CSSBundling Rails.

With Webpacker officially gone, these are now your options for deploying and building modern CSS, Javascript, and Typescript using Rails 7.

Start by choosing whether you want ImportMap-Rails, JSBundling, or Shakapacker app.

HOW DO I CHOOSE BETWEEN IMPORTMAPS, JSBUNDLING, and SHAKAPACKER?

ImportMapsJSBundlingShakapacker
Pros• Newest paradigm.

• Quickly/easily add importable JS to your app.
• Default for Rails 7
• Can use ESBuild, Webpack, or Rollup as the compiler.
• Can use ESBuild, a faster alternative to Webpack.
• A good, quick way to use node packages via Node Package Manager or Yarn.
• A good, quick way to get Typescript or JSX compiling with Babel.
• Offers a complete deployment & development pipeline for developing with Typescript and React (including JSX)
Cons• Only works for importable JS (Javascript or Typescript written as ES Modules.)
• Does not bundle or build compiled javascript (like something you would compile with Bable, e.g. Typescript/React)
• Does not offer a complete solution for building images within React or React-on-Rails apps.
• A complex tree of node dependancies can make your app unwieldy.
• Uses Webpack which is can be slow to compile with many npm dependencies.
• A complex tree of node dependancies can make your app unwieldy.

DO I WANT IMPORTMAP For Rails 7 Bootstrap?

YES? Keep reading this post.

This post will walk you through how to set up Bootstrap with Sprockets and ImportMaps.

You will pin your JS dependencies — in our case Bootstrap and Popper — in any one of these 3 ways:

(1) Via a CDN,

(2) from the vendor folder of a Gem, or

(3) from your own vendor/javascript folder.

NO? If you want to use JSBundling, see Section #2 of Rails 7 Up & Running JS Bundling instead. If you want Shakapacker, go to this tutorial instead.

How to Get Rails 7 Bootstrap from Sprockets and Sassc-Rails by Pinning with Importmap

Under the ImportMap paradigm, you can’t load a Javascript library via a package manager, like NPM, but you can load it with any ES Module Javascript framework.

Note that sass-rails is now no longer enabled in the Rails 7 default installation, but it is in your Gemfile as a commented out gem. (So you will need to uncomment and bundle install to use it, which are in the steps below.)

Remember “Sprockets” is another name for “asset pipeline.” For a detailed history of the Rails gem defaults check out my blog post about Hampton Lintorn-Catlin’s petition for Rails to adopt a Node-based Sass compiler. The rejection of this petition signaled that Rails 7 was switching to to the C-based compiler in the sassc-rails gem.

What makes sassc-rails different from sass-rails is that sassc-rails uses the C-compiler to transpile the SCSS/SASS (it’s very fast!). The alternative, sass-rails (what got ejected from Rails defaults) was preferred by the Node community uses a Node (Javascript) compiler. However, sass-rails is pretty slow so the Rails community didn’t like being dependant on it.

That means that sprockets-rails was made part of the default Rails Gemfile. Before Rails 7, both sass-rails and sprockets-rails (by being a dependency on sass-rails) were part of what got installed in default new Rails apps.

However, under Rails 6 it was confusing to work between Sprockets — a paradigm primarily geared towards a single JS payload to reduce initial pageload time, and Webpack — a paradigm to build new-style Javascript in a backwards-compatible way (‘transpiling’).

This confusion was not helped by the fact that having both the Asset Pipeline (Sprockets) and Webpacker enabled you to pick and choose.

Now, with Rails 7, we have officially ejected Node (webpacker) for three primary reasons:

ImportMap, a feature where we can load ES modules dynamically at runtime, is natively available in Chrome and Firefox and works with a shim with the other modern browsers.

• Internet Explorer (IE) is finally dead

• HTTP2, which is now widely available on all modern browsers too, doesn’t have the ‘each network request’ cost associated with HTTP1 that the traditional web had

For this tutorial, we will add the sassc-rails into a brand new Rails 7 app.

Today’s Goal

• Get Rails 7 working with Bootstrap via Importmaps

• Sassc-Rails

Let’s start by making a new app.

rails new MyGreatApp

By using none of the flags when creating a new Rails 7 app, you will be creating a Rails app with Sprockets, Imoprtmaps, Stimulus, and Turbo (sprockets-rails, importmap-rails, stimulus-rails, turbo-rails).

1. Hello sassc-rails Gem

Start by uncommenting the scssc-rails gem which is commented out by default in the Gemfile:

gem "sassc-rails"

2. Turn on Inline Source maps (so we can debug Rails 7 Bootstrap)

in config/environments/development.rb add this line:

 config.sass.inline_source_maps = true

Also, the sassc-rails gem README advises this note as well:

https://github.com/sass/sassc-rails

3. Switch application.css to application.scss

Delete this file:

application.css becomes application.scss

Now make a new empty file called application.scss in the same folder app/assets/stylesheets/

4. Import Rails 7 Bootstrap into your Application

Add to Gemfile, add

gem 'bootstrap', '~> 5.1.3'

In this case we are using the old Bootstrap gem for its CSS only as a bridge between the old Sprockets world and Bundling world. In this example, our JS will be served through Importmap’s new paradigm.

Also, in the application.scss file we created in Step 3, add this:

@import "bootstrap";

5. Add .scss to Asset Precompilation (because it’s needed by Rails 7 Bootstrap)

Go to config/initializers/assets.rb

We want to uncomment and change this line:

Rails.application.config.assets.precompile += %w( application.scss )

Notice that we do not have to make any changes to our application.html.erb, which by default looks as below. This is handy and happens because the stylessheet_link_tag just references the stylesheet file without the extension type, which means it picks up the SCSS instead of the CSS file when we switched it from one to the other.

<!DOCTYPE html>
<html>
  <head>
    <title>MyGreatApp</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
    <%= javascript_importmap_tags %>
  </head>

  <body>
    <%= yield %>
  </body>
</html>

No changes here.

6. Dummy Controller & Route

Create the articles controller with:

rails generate controller Articles

Add an empty def index to it:

class ArticlesController < ApplicationController
  def index
  end
end

in config/routes.rb, uncomment this line:

root "articles#index"





And finally, create a Hello World file at app/views/articles/index.erb

Hello World!

Now, load your website in your browser and click “View Source”

Sprockets has included what we told it to in our case just the @import statement

Notice here that Sprockets has applied a thumbprint to this specific build. When we change anything in our CSS, Sprockets will recompile the application file and give it a new thumbprint.

7. Now add Something for Rails 7 Bootstrap

If we look in our browser, our existing layout looks like so:

The shrewd eye might notice that Bootstrap is in fact applied, but we would hardly notice since all our app does is say “Hello World”‘

So let’s add a Bootstrap navbar:


    <nav class="navbar navbar-expand-lg navbar-light bg-light">
    <div class="container-fluid">
      <a class="navbar-brand" href="#">Navbar</a>
      <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
      </button>
      <div class="collapse navbar-collapse" id="navbarSupportedContent">
        <ul class="navbar-nav me-auto mb-2 mb-lg-0">
          <li class="nav-item">
            <a class="nav-link active" aria-current="page" href="#">Home</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="#">Link</a>
          </li>
          <li class="nav-item dropdown">
            <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
              Dropdown
            </a>
            <ul class="dropdown-menu" aria-labelledby="navbarDropdown">
              <li><a class="dropdown-item" href="#">Action</a></li>
              <li><a class="dropdown-item" href="#">Another action</a></li>
              <li><hr class="dropdown-divider"></li>
              <li><a class="dropdown-item" href="#">Something else here</a></li>
            </ul>
          </li>
          <li class="nav-item">
            <a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
          </li>
        </ul>
        <form class="d-flex">
          <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
          <button class="btn btn-outline-success" type="submit">Search</button>
        </form>
      </div>
    </div>
  </nav>

Ok now let’s take a look:

Our app now has what looks like a Bootstrap Navbar, but if you examine it closely, you’ll notice it is not working.

That’s because we haven’t yet added the correct Bootstrap + Popper javascript to our Importmaps.

8. Choose to either use your JS via CDN or as vendor file

We’re going to pin this file using ImportMap. This small little piece of magic needs to be done under the hood to make ES Modules work, and fortunately, the Rails core team has done most of the hard work for you.


What you need to do is pin the name bootstrap, popper (or anything else in the future) to those “named” imports. A shim allows for the browser to automatically import those dependencies whenever it encounters an ES Module that needs them.

Option #1: Use Via a CDN

Pin Bootstrap and Popper:

./bin/importmap pin bootstrap@5.1.3

./bin/importmap pin @popperjs/core@2.11.2

Go to config/importmap.rb and look at the lines that got added:

pin "bootstrap", to: "https://ga.jspm.io/npm:bootstrap@5.1.3/dist/js/bootstrap.esm.js"
pin "@popperjs/core", to: "https://ga.jspm.io/npm:@popperjs/core@2.11.2/lib/index.js"

Add Bootstrap to the Application

Because Bootstrap requires Popper, we just need to Import bootstrap once in the application.js file to make it available via the importmap.

go to app/javascript/application.js and add the Bootstrap import

import "bootstrap"

Now start the Rails server.

The Boostrapy interactions work!

Also, because this is built-in to the Navbar we built, if you shrink your browser window a bit you’ll see the hamburger nav for mobile:

bootstrap with working navbar

Option #2: Use Bootstrap & Popper Bundled from the Gem

What we did above is called loading the from a CDN. That means we tell the browser to load the JS dependencies (Bootstrap, and Popper) directly from the sky. Optionally, we can pack up the JS into our application from the compiled source in the bootstrap gem.

However, to do that we need to tell Sprockets to load it.

As DHH explains in his Aug 12, 2021 blog post:

That means that under this paradigm, you can’t load a Javascript library via a package manager, like NPM. If you need to do that, instead take a look a these instructions for setting up Shakapacker on Rails 7.

In order to discourage adding useless Gems, you should no longer be using “shell only” Javascript wrapper Gems as we did in the past, so you should not install the jquery-rails gem. Bootstrap 5 no longer needs Jquery at all.

in config/initializers/assets.rb

Add bootstrap.min.js popper.js

Rails.application.config.assets.precompile += %w( application.scss bootstrap.min.js popper.js )

In config/importmap.rb add

pin "popper", to: 'popper.js', preload: true
pin "bootstrap", to: 'bootstrap.min.js', preload: true

Finally, in app/javascript/application.js:

import "popper"
import "bootstrap"

Option #3: Download Bootstrap JS & and Popper and Bundle them Directly into your App vendor/javascript/ folder

NOTE: This option doesn’t really make a lot of sense, but is presented here for completeness. The reason it doesn’t make sense is that for this option, we are using Sprockets to bundle Bootstrap’s CSS (as with the options above), but then packing up the Javascript into our own app.

GO TO https://getbootstrap.com/ and click Download

Unzip the zip archive and move the JS files into vendor/javascript in your app

Pinning the Jquery library into javascript/vendor

Now do the same for Popper which you can do at https://popper.js.org/

in config/initializers/assets.rb

Add bootstrap.min.js popper.js

Rails.application.config.assets.precompile += %w( application.scss bootstrap.min.js popper.min.js )

In config/importmap.rb add

pin "popper", to: 'popper.min.js', preload: true
pin "bootstrap", to: 'bootstrap.min.js', preload: true

Finally, in app/javascript/application.js:

import "popper"
import "bootstrap"

Note that in Rails 7, there is now another file called application.js also, inside of the Stimulus controllers, which you should not confuse with this application.js file.

Example App Option 1 Example App Option 2 Example App Option 3

5 thoughts on “Rails 7: Using ImportMaps + Bootstrap

  1. Bootstrap 5 no longer requires jQuery to function. Following instructions above for CDN, it will work with or without pinning jQuery!

    Great article!

  2. Hi, I am learning Rails7 and trying to re-building a website using Rails7 and Bootstrap4, which was already developed in Rails5 and Bootstrap3. I have faced some problem of loading jquery and css files. I followed the bellow steps for website development :
    1) First created new rails7 app with mysql database
    2) Created a controller with index
    3) Created db
    4) Added gem ‘bootstrap’, ‘~> 4.3.1’, gem ‘font-awesome-rails’ and gem “sassc-rails”
    5) Run bundle install
    6) Download the jqury-3.1.1.min.js, pace.min.js, jquery.peity.min.js, jquery.slimscroll.min.js, jquery.metisMenu.js, wow.min.js files and saved into project’s vendor/javascript folder.
    7) Download the animate.css file and saved into project’s assets/stylesheets folder
    8) Add pin command in importmap.rb file:
    pin “jquery”, to: “jquery-3.1.1.min.js”, preload: true
    pin “bootstrap”, to: “bootstrap.min.js”, preload: true
    pin “popper”, to: “popper.js”, preload: true
    pin “pace”, to: “pace.min.js”, preload: true
    pin “peity”, to: “jquery.peity.min.js”, preload: true
    pin “slimscroll”, to: “jquery.slimscroll.min.js”, preload: true
    pin “metismenu”, to: “jquery.metisMenu.js”, preload: true
    pin “wow”, to: “wow.min.js”, preload: true
    9) Add into application.js file:
    import “jquery”
    import “popper”
    import “bootstrap”
    import “pace”
    import “peity”
    import “slimscroll”
    import “metismenu”
    import “wow”
    10) Created new application.scss file and delete older application.css file
    11) Add into application.scss file:
    @import “bootstrap”; @import “animate.css”; @import “font-awesome”;
    12) Add into assets.rb file:
    Rails.application.config.assets.precompile += %w( application.scss jquery-3.1.1.min.js bootstrap.min.js popper.js )
    13) Set the index path in routes.rb file
    14) Added custom jquery functions in the application.html.erb file:
    $(function() { $(‘body’).scrollspy({ target: ‘.navbar-fixed-top’, offset: 80 }); });
    new WOW().init();
    15) Run the rails server

    Then I opened the chrome browser and run “http://localhost:3000/” and getting bellow errors:
    i) Failed to resolve module specifier “popper”. Relative references must start with either “/”, “./”, or “../”.
    ii) Uncaught TypeError: $(…).scrollspy is not a function
    iii) Uncaught ReferenceError: WOW is not defined at HTMLDocument

    I am trying lot of ways but no fruitful result came. So please help me if I am doing any thing wrong here. Thanks.

Leave a Reply





Your email address will not be published. Required fields are marked *