Search code examples
javascriptruby-on-railsdesign-patternsassetsimport-maps

Importmap for Rails with JS Module Pattern Error: "net::ERR_ABORTED 404 (Not Found)"


I'm using Importmap for Rails 7 implementing the JavaScript Module Pattern and I'm getting errors related to loading of custom helpers JS files in the production environment.

My app/javascript has a filesystem structure like this:

> javascript
  > controllers
    > application.js
    > index.js
    > first_controller.js
    > second_controller.js
    > ...
  > helpers
    > index.js
    > useMixinOne.js
    > useMixinTwo.js
    > ...
  > application.js

Below is the content extracted from files relevant to the question:

# config/importmap.rb
pin "application", preload: true
pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
pin_all_from "app/javascript/controllers", under: "controllers"
pin_all_from "app/javascript/helpers", under: "helpers"


# app/javascript/application.js
import "@hotwired/turbo-rails"
import "controllers"
import "helpers"


# app/javascript/controllers/first_controller.js
import { Controller } from "@hotwired/stimulus"
import { useMixinOne, useMixinTwo } from "helpers"
// Connects to data-controller="first"
export default class extends Controller {
  connect() {
    useMixinOne(this);
    ...
  }
}


# app/javascript/helpers/index.js
export * from "./useMixinOne"
export * from "./useMixinTwo"
...


# app/javascript/helpers/useMixinOne.js
export const useMixinOne = controller => {
  Object.assign(controller, {
    customFunctionFromOne(event) {
      console.log("Hello from One")
    }
  });
};


# app/javascript/helpers/useMixinTwo.js
export const useMixinTwo = controller => {
  Object.assign(controller, {
    customFunctionFromTwo(event) {
      console.log("Hello from Two")
    }
  });
};

...

When I use the above in development, it works.

However, when I use it in production it doesn't work: in the browser console I get errors like

GET https://www.mywebsite.com/assets/helpers/useMixinOne net::ERR_ABORTED 404 (Not Found)
index-1ee3e17fa548653761aab66bd7ab6d677cc5dc8be43616b1c86e694aee4c4b27.js:1 

GET https://www.mywebsite.com/assets/helpers/useMixinTwo net::ERR_ABORTED 404 (Not Found)
index-1ee3e17fa548653761aab66bd7ab6d677cc5dc8be43616b1c86e694aee4c4b27.js:1 

...

What's wrong? How can I solve errors related to the helpers JS assets with Importmap?


Solution

  • Same rules apply for export as for import. Don't use relative names:

    export * from "./useMixinOne"
    //             ^^^^^^^^^^^^^
    // this is a module name, just like for imports
    

    If you don't have a matching name in your importmap it will not be mapped to a corresponding digested asset url, which only works in development. Your imports/exports need to match something in the importmap:

    $ bin/importmap json
    {
      "imports": {
    ...
        "helpers":             "/assets/helpers/index-ab104765b4187e301d3391fb535a15e3754cb6b938bcfecd7984a44a0f1d0628.js",
        "helpers/useMixinOne": "/assets/helpers/useMixinOne-4e3c8cd641eae1b19feb43fb775e422c931fd9b40d74ba295881069447d1af59.js",
    //  ^^^^^^^^^^^^^^^^^^^^^
    }
    
    // app/javascritp/helpers/index.js
    
    export * from "helpers/useMixinOne"
    

    Technically, you can make relative names work if you want:

    # config/importmap.rb
    
    pin_all_from "app/javascript/helpers", under: "/assets/helpers", to: "helpers"
    

    which will give you this importmap:

    $ bin/importmap json
    {
      "imports": {
    ...
        "/assets/helpers":             "/assets/helpers/index-6f4938f0e494428e95c0211ec9aa57b42a0021726bcf0de91f1eb7c128a971c3.js",
        "/assets/helpers/useMixinOne": "/assets/helpers/useMixinOne-4e3c8cd641eae1b19feb43fb775e422c931fd9b40d74ba295881069447d1af59.js"
    }
    

    and make your relative names work:

    // app/javascritp/helpers/index.js
    
    export * from "./useMixinOne"
    
    // app/javascript/controllers/first_controller.js
    
    import { useMixinOne } from "../helpers"
    

    https://stackoverflow.com/a/76118219/207090