Mastering Import Maps in Rails 8
A Step-by-Step Guide to Efficient JavaScript Management # Rails8Series — Episode 2
Introduction
Rails 8 introduces Import Maps as a modern way to manage JavaScript dependencies without relying on complex package managers like Webpack or npm. This tutorial will walk you through setting up an Import Map from scratch, integrating it with Rails’ Asset Pipeline, and adding third-party libraries.

Let’s Get Started!
Based on: How To Use Import Maps with Rails By GoRail
Step-by-Step Guide
0. Create a Simple App
My System:
OS: Ubuntu 24.04 LTS
Processor: Intel® Core™ i7–9750H × 12 - 8.0 GiB RAM
rails -v
Rails 8.0.1
Start the App:
rails new importmap
cd importmaps
rails g controller pages index
Routing:
config/routes.rb
root "pages#index"
app/views/pages/index.html.erb
<h1>Import Map</h1>
Modify your application.html.erb
layout:
Comment the following line:
app/views/layouts/application.html.erb
<%#= javascript_importmap_tags %>
1.Understanding Import Maps
An Import Map is a browser-native feature that maps JavaScript module paths directly in the browser. It eliminates the need for bundlers by serving JavaScript directly from your Rails app.
Example of an Import Map:
app/views/pages/index.html.erb
# In Development:
<h1>Import Map</h1>
<script type="importmap">
{
"imports": {
"example": "/example.js",
"hello": "/hello.js"
}
}
</script>
<script type="module">
import "example"
console.log("Import Map Example Loaded")
</script>
2. Setting Up Import Maps in Rails
Create JavaScript files under:
public/
touch public/example.js public/hello.js
Add example code in example.js
and hello.js
:
example.js
import "hello";
console.log("Hello from example.js");
hello.js
console.log("Hello from hello.js");
3. Run the App
Open WDT (ctrl+alt+i):

Issue: Your JavaScript is running, but it’s not being processed by Rails’ Asset Pipeline. Don’t worry! We’ll explain this thoroughly in a moment. Keep reading…

Review the DOM. You’ll notice our app is currently running scripts at bare metal. That’s not ideal! Let’s improve it!
4. Update your application.html.erb
layout:
Uncomment this line:
app/views/layouts/application.html.erb
<%= javascript_importmap_tags %>
5. Transfer your files to the app/javascript
directory:
For the correct Import Map syntax, reference the file directly by its name. Files located in app/javascript
don’t require the app/javascript
prefix in the path.
In the Terminal
, Run:
mv public/example.js app/javascript/example.js
mv public/hello.js app/javascript/hello.js
6. Pinned your JavaScript files in the import map for easy and efficient module resolution
Open config/importmap.rb
.
Pin your local JavaScript files:
pin "example", to: "example.js"
pin "hello", to: "hello.js"
7. Update application.js
to import your modules explicitly:
import "example"
import "hello"
This tells the browser to execute the modules once they are loaded.
8. Update your index page too:
app/views/pages/index.html.erb
# In Production:
<h1>Import Map</h1>
In production, everything is cleaner because Rails efficiently manages your JavaScript. Once you import scripts by their logical names (#7), as mapped in a special configuration file (#6), Rails takes care of the rest. With just one line to activate it all:
<%= javascript_importmap_tags %>
(#4). How convenient!That’s nice!
9. Run you app:
rails s
# Open WDT (ctrl+alt+i)


<head>
section for production, preloaded to enhance performance. That’s it! Rails handles it gracefully for you!10.If you open <head>
you will see:
<head>
....
<script type="importmap" data-turbo-track="reload">{
"imports": {
...
"example": "/assets/example-1293c9f1.js",
"hello": "/assets/hello-af42800b.js",
....
}
}
</script>
...
<link rel="modulepreload" href="/assets/example-1293c9f1.js">
<link rel="modulepreload" href="/assets/hello-af42800b.js">
...
<script type="module">
import "application"
</script>
</head>
It consists of three sections:
<head>
<script type="importmap" data-turbo-track="reload">
"imports":{
"example":" ",
"hello":" "
}
</script>
<link rel="modulepreload" href="...">
<script type="module">
import "application"
</script>
</head>
Script + Preloads + Script
The first defines the import map. The second enhances performance by downloading the scripts in parallel. The last one executes the module.
This snippet demonstrates how Rails uses import maps and modulepreloading to optimize JavaScript loading in a modern web application. Here’s a breakdown of the code:
1. <script type="importmap" data-turbo-track="reload">
The script defines an import map in JSON format. Import maps let you specify JavaScript module imports directly in the browser without needing a bundler like Webpack.
"imports"
: Maps module names to their corresponding asset URLs.
"example"
: “/assets/example-1293c9f1.js”: This defines a module named “example” and maps it to the compiled asset file.
"hello"
: “/assets/hello-af42800b.js”: Similarly, the “hello” module points to another precompiled file.
data-turbo-track="reload"
: Ensures Turbo Drive reloads the page if the import map changes, making it useful in hot-reloading scenarios during development.
2. <link rel="modulepreload" href="…">
These <link> tag preload JavaScript modules.
Purpose
: Module preloading helps the browser fetch these assets early, reducing load times when the modules are imported later.
Example
:
<link rel="modulepreload" href="/assets/example-1293c9f1.js">
: The example module is preloaded.
<link rel="modulepreload" href="/assets/hello-af42800b.js">
: The hello module is preloaded.
3. <script type="module">import "application"</script>
This script imports the application module, triggering the execution of the corresponding JavaScript file.
Type module
: Enables ES6 module functionality in the browser, such as using import and export syntax.
import "application"
: This uses the import map to resolve and load the application module, which could initialize your Rails application’s frontend logic.
Key Points
Import Maps
: Provide a lightweight and native way to manage JavaScript dependencies without relying on external tools like Webpack or Yarn.Preloading
: Optimizes performance by instructing the browser to fetch assets before they’re needed.Rails Integration
: Rails 7 includes native support for import maps, enabling a simple and modern approach to managing frontend assets.
How It Works
The browser reads the import map and resolves module imports (e.g., example and hello) to their corresponding asset URLs.
Preloaded assets (modulepreload) are fetched earlier, speeding up their availability for execution.
When application is imported, the browser loads and executes it, with any dependencies already preloaded.
This setup ensures efficient and modern JavaScript management for Rails applications.
11. More about Rails Asset Pipeline
In a Rails 8 application, the JavaScript assets in the <head>
tag appear with digested filenames (e.g., example-1293c9f1.js)
because Rails uses asset fingerprinting. This is part of the Asset Pipeline or similar tools designed to ensure that browsers cache the correct version of your assets.
Why Does This Happen?
1.Asset Fingerprinting
Rails appends a fingerprint (a hash like 1293c9f1) to asset filenames. This fingerprint is based on the content of the file, so it changes whenever the file is modified. This ensures that browsers load the latest version of an asset when it changes.
2.Cache Invalidation
By appending a unique hash to the filename, Rails forces browsers to reload the asset if the content changes. This prevents issues where outdated JavaScript files are cached by the browser.
3.Import Maps and Asset Paths
In a Rails 8 app using import maps, the importmap.rb
file maps logical names to their corresponding digested asset paths. When the browser loads the application, it references these digested paths to fetch the correct files.
Example Workflow:
1.Logical Name in Import Map
:
pin "example", to: "example.js"
2.Generated Import Map in <head>
:
<script type="importmap">
{
"imports": {
"example": "/assets/example-1293c9f1.js",
"hello": "/assets/hello-af42800b.js"
}
}
</script>
3.Usage in JavaScript
:
import { greet } from "example";
greet();
The browser resolves “example” to /assets/example-1293c9f1.js
as defined in the import map.
Benefits:
1.Performance
: Asset fingerprinting optimizes performance by leveraging browser caching.
2.Reliability
: Prevents the browser from loading outdated JavaScript files after a code update.
3.Automation
: Rails automatically generates and manages these digested paths during asset compilation (e.g., when running rails assets:precompile).
If you’re seeing this in the <head>
tag, it’s a sign that your application is configured correctly for production or development with asset digests enabled. Step #3 issue resolved successfully!
12. Integrating with the Asset Pipeline
To further explore Import Maps, let’s consider a scenario where you don’t want to upload your JavaScript assets upfront (a kind of lazy loading) but instead include them directly in your view (e.g., app/views/pages/index.html.erb
).
To proceed, you’ll need to:
- Modify the View
Add the necessary JavaScript imports directly to your view file (index.html.erb
). - Remove from
app/javascript/application.js
Delete the corresponding imports fromapp/javascript/application.js
to avoid redundancy and ensure the assets are only loaded through the view.
Begin by removing imports from:
app/javascript/application.js
import "example"
import "hello"
This approach demonstrates flexibility in managing assets using Import Maps while still leveraging the Rails Asset Pipeline.
Update your Import Map configuration directly from:
app/views/pages/index.html.erb
# In Production:
<h1>Import Map</h1>
<script type="importmap">
{
"imports": {
"example": "<%= path_to_asset 'example.js' %>",
"hello": "<%= path_to_asset 'hello.js' %>"
}
}
</script>
<script type="module">
import "example"
console.log("Import Map Example Loaded")
</script>
Run you app.


<head>
section includes an import map that correctly resolves example.js
and hello.js
. These scripts are preloaded and executed as modules in the <body>
. This configuration functions similarly to Step #3.Please compare with step #1:
When you changed the code in app/views/pages/index.html.erb
to use <%= path_to_asset ‘example.js’ %>
, you’re dynamically generating the paths to the assets using Rails helpers. Here’s what happens:
Original Code:
<script type=”importmap”>
{
“imports”: {
“example”: “/example.js”,
“hello”: “/hello.js”
}
}
</script>
Static Paths
: The paths /example.js
and /hello.js
are hardcoded. This works in development but may break in production if Rails uses digested filenames (e.g., /example-1293c9f1.js
).No Fingerprinting
: These paths won’t change based on the content of the files, meaning cached files might cause issues when the assets are updated.
What Changes:
1.Dynamic Asset Paths with Rails Helper
:
<%= path_to_asset ‘example.js’ %>
dynamically generates the correct path to the asset.
In development, it resolves to /assets/example.js
.
In production, it resolves to the digested version, e.g., /assets/example-1293c9f1.js
.
2.Asset Fingerprinting
:
The generated paths now include fingerprints in production, ensuring that the browser fetches the latest version of the assets if their content changes.
3.Preloading with <link rel=”modulepreload”>
:
These <link>
tags inform the browser to preload the modules before they are used in the page. This improves performance by loading the assets in parallel with the page.
4.Better Compatibility Between Environments
:
The updated code adapts to both development and production environments seamlessly. You don’t need to manually update paths or worry about cache invalidation.
Why This is Better:
1.Handles Asset Fingerprinting
: Rails generates paths that work in both development and production.
2.Improved Performance
: Preloading assets with modulepreload
reduces load time for the JavaScript modules.
3.Environment-Aware
: The use of path_to_asset
ensures that the paths adapt to the current environment (development or production).
4. Avoids Manual Errors
: You don’t need to hardcode paths, reducing the risk of errors when deploying.
13. Adding Third-Party Libraries
Rails simplifies adding libraries with bin/importmap
.
Pin a library (e.g., Tailwind CSS Stimulus Components).
On Terminal, Run:
bin/importmap pin tailwindcss-stimulus-components
bin/importmap pin tailwindcss-stimulus-components
Pinning "tailwindcss-stimulus-components" to vendor/javascript/tailwindcss-stimulus-components.js via download from https://ga.jspm.io/npm:tailwindcss-stimulus-components@6.1.3/dist/tailwindcss-stimulus-components.module.js
Pinning "@hotwired/stimulus" to vendor/javascript/@hotwired/stimulus.js via download from @hotwired/stimulus@3.2.2/dist/stimulus.js">https://ga.jspm.io/npm:@hotwired/stimulus@3.2.2/dist/stimulus.js
It leverages JSPM (JavaScript Package Manager) to efficiently manage and download packages.
JSPM provides package management for import maps, allowing any package from npm to be loaded directly in the browser fully optimized without further tooling.
This downloads the library to vendor/javascript/:

14. Register the component to the controller in:
app/javascript/controllers/index.js
// Import and register all TailwindCSS Components or just the ones you need
import { ColorPreview } from "tailwindcss-stimulus-components"
application.register('color-preview', ColorPreview)
15. Using the Component in Your Views
Paste the following into a view:
app/views/pages/index.html.erb
<div class=”mt-3 flex items-center” data-controller=”color-preview”>
<p data-color-preview-target=”preview” class=”h-10 w-10 mr-2 rounded-full text-2xl text-white text-center” style=”background-color: #ba1e03;”>A</p>
<span class=”ml-2">
<input data-action=”input->color-preview#update”
data-color-preview-target=”color”
type=”color”
value=”#ba1e03"
class=”block shadow-sm sm:text-sm border-gray-300 rounded-md” />
</span>
</div>
And there you have it!

16. Import Map Commands
Rails offers several commands to manage your Import Map:
bin/importmap pin [PACKAGE] # Pin a package
bin/importmap unpin [PACKAGE] # Unpin a package
bin/importmap update # Update pinned packages
bin/importmap audit # Run a security audit
bin/importmap json # View the Import Map as JSON
Example:
bin/importmap json
Outputs:
{
“imports”: {
“example”: “/assets/example-bfcdf840.js”,
“hello”: “/assets/hello-bfcdf840.js”,
“@hotwired/stimulus”: “/assets/stimulus-1fc53fe7.js”
}
}
17. Conclusion
Import Maps in Rails 8 streamline JavaScript management by leveraging browser-native features and Rails’ powerful Asset Pipeline. With tools like bin/importmap
, you can easily integrate local and third-party libraries while optimizing your app’s performance and security.
And that concludes my overview of Import Maps in Rails architectures!
If you found this helpful, please consider supporting it with a clap or sharing your thoughts in the comments — I’d love to hear from you! 🤗
Note — Problem Solving
”
(curly quotation marks) instead of the standard ASCII"
The error indicates that the browser is trying to fetch JavaScript assets from incorrect URLs, which include extra characters (%E2%80%9D
) corresponding to the Unicode encoding for right double quotation marks (”
).
Cause
This issue occurs because of incorrect or mismatched quotation marks in your import map or asset links. The ”
(curly quotation marks) have likely been used instead of the standard ASCII "
. Browsers interpret this improperly and include the encoded characters in the URL.
Solution
- Check Import Map or Links
- Inspect the code defining the import map or the
<link rel="modulepreload">
tags. - Replace any curly quotation marks (
“
or”
) with straight quotation marks ("
).
Related Posts
0 # Rails8Series — Managing Rails Versions — How to Handle Accidental Upgrades and Revert to an Older Version
1 # Rails8Series — Building a Rails 8 App from Scratch — A Modern Developer’s Foundation
2 # Rails8Series — Mastering Import Maps in Rails 8 — A Step-by-Step Guide to Efficient JavaScript Management (this one)