8

I'm using Rails again after a few years away (last used Rails 4). I have multiple stimulus controllers that reference a file called metric_defaults.js. That file just contains a flat set of definitions such as:

Chart.defaults.elements.line.tension = 0.25;
Chart.defaults.elements.line.borderWidth = 5;

In rails 7 development env, this import works fine with import '../metric_defaults.js' from each stimulus controller, but in production I get:

Failed to load resource: the server responded with a status of 404 (Not Found) (metric_defaults.js)

I've spent a day trying to track this down, but all efforts have failed. A few tidbits:

  • I'm using a standard rails 7 configuration (importmaps, sprockets)
  • I've confirmed the production server is precompiling assets correctly. This includes metric_defaults.js, however it has a fingerprint in its name (metric_defaults-9032be9e....js), whereas the 404 seems to be trying to access the unfingerprinted file?
  • The production server is a Heroku instance
  • The path to the metric_defaults.js file is app/javascript/metric_defaults.js
  • The stimulus controllers are in app/javascript/controllers/
  • As mentioned, works fine in development
  • I'm at a loss

Any thoughts appreciated

2 Answers 2

4

Setup:

# config/importmap.rb
pin "application"
pin "plugin"
// app/javascript/application.js
import "./plugin";

See generated importmap:

$ bin/importmap json
{
  "imports": {
    "application": "/assets/application-6aad68dfc16d361773f71cfe7fe74ae0ace4eea0b74067bc717475bbbbf4e580.js",
    "plugin":      "/assets/plugin-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js"
  }#  ^              ^
}  # imports        urls
   # for you        for browser

It's pretty simple:

import "plugin";
// will match "plugin" from import-maps
"plugin": "/assets/plugin-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js"
// and turn it into
import "/assets/plugin-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js";
// browser sends request to this url ^

But:

import "./plugin";
// is relative to `application.js`, because that's where the import is.
// application.js is imported correctly with `import "application"` in the layout
"application": "/assets/application-6aad68dfc16d361773f71cfe7fe74ae0ace4eea0b74067bc717475bbbbf4e580.js"
//                      ^^^^^^^^^^^
// so "./plugin" is relative to this, which resolves to "/assets/plugin"
// which doesn't match the import-map
import "/assets/plugin";
//      ^
// browser sends request in development and production
Started GET "/assets/application-6aad68dfc16d361773f71cfe7fe74ae0ace4eea0b74067bc717475bbbbf4e580.js" for 127.0.0.1 at 2023-04-27 00:28:21 -0400
Started GET "/assets/es-module-shims.js-32db422c5db541b7129a2ce936aed905edc2cd481748f8d67ffe84e28313158a.map" for 127.0.0.1 at 2023-04-27 00:28:21 -0400
Started GET "/assets/plugin" for 127.0.0.1 at 2023-04-27 00:28:21 -0400
#                    ^
# NOTE: see how this one didn't get mapped to anything, it is just a plain url.

In development /assets is routed to sprockets that can handle digested and undigested assets and it works fine.

In production, web server does the work instead and it only has precompiled assets, /assets/plugin gets a 404.


Fix #1

Stop using relative imports.

Fix #2

Make an import-map that would match the relative import:

# config/importmap.rb

pin "application"
pin "/assets/plugin", to: "plugin"
#    ^ look familiar?
import "./plugin";      // this will be
import "/assets/plugin" // resolved to this
                        // and will match the import-map
Started GET "/assets/application-6aad68dfc16d361773f71cfe7fe74ae0ace4eea0b74067bc717475bbbbf4e580.js" for 127.0.0.1 at 2023-04-27 03:52:47 -0400
Started GET "/assets/plugin-c8122d51d5713808bd0206fb036b098e74b576f45c42480e977eb11b9040f1f4.js" for 127.0.0.1 at 2023-04-27 03:52:47 -0400
Started GET "/assets/es-module-shims.js-32db422c5db541b7129a2ce936aed905edc2cd481748f8d67ffe84e28313158a.map" for 127.0.0.1 at 2023-04-27 03:52:47 -0400

If you want to go this route, I'll borrow @cesoid's helper method, it'll get you started:

# config/importmap.rb

def pin_all_relative(dir_name)
  pin_all_from "app/javascript/#{dir_name}",
    under: "#{Rails.application.config.assets.prefix}/#{dir_name}",
    to: dir_name
end

pin_all_relative "controllers"
# etc
Sign up to request clarification or add additional context in comments.

Comments

0

If there's difficulty with the relative path, you can use absolute path.

// app/javascript/controllers/index.js

import { application } from 'controllers/application';

import DeleteController from 'controllers/delete_controller'; <= absolute path
application.register('delete', DeleteController);

import { eagerLoadControllersFrom } from '@hotwired/stimulus-loading';
eagerLoadControllersFrom('controllers', application);

This solves my problem when deploying to Render in production.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.