Search code examples
javascriptruby-on-railsasset-pipelineassetsstimulusjs

Custom JS files in app/assets/javascripts/ break the Stimulus assets pipeline (Rails 7)


I'm trying to add custom JavaScript in a Ruby on Rails 7 project, so:

  1. I created the file app/assets/javascripts/application.js containing my custom code.

  2. In app/assets/config/manifest.js I added (found) the following code.

File app/assets/config/manifest.js:

//= link_tree ../images
//= link_directory ../stylesheets .css
//= link_tree ../../javascript .js
//= link_tree ../../../vendor/javascript .js
  1. I restarted the server.

Now, when I visit my app pages then Stimulus doesn't work anymore even if in the browser's Console tab I don't get any error. And I have this problem even if the file created at step 1 is an empty file.

The only thing I noted in the browser's Network tab is that when the application.js file is present in the app/assets/javascripts/ folder then the Stimulus controller files don't load. When I remove the application.js file from app/assets/javascripts/ everything works again (after restarting the server).

Without app/assets/javascripts/application.js

enter image description here

With app/assets/javascripts/application.js

enter image description here

What is the problem? Am I doing something wrong or forgetting something?


Solution

  • You're overriding application.js from app/javascript. All the asset helpers will look for a matching file in asset paths and return the first match:

    >> Rails.application.assets.find_asset("application.js").filename
    => "/home/alex/code/stackoverflow/app/javascript/application.js"
    #                                     ^          ^
    
    >> Rails.root.join("app/assets/javascripts/application.js").write("var GLOB;")
    
    >> Rails.application.assets.find_asset("application.js").filename
    => "/home/alex/code/stackoverflow/app/assets/javascripts/application.js"
    #                                     ^                  ^
    # now it returns a different file for the same "application.js"
    

    In your layout, it does the same, just through javascript helpers:

    >> helper.javascript_include_tag("application")
    => "<script src=\"/assets/application-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js\"></script>"
    >> require "open-uri"
    >> URI.open(helper.javascript_path("application", host: "http://localhost:3000")).read
    => "var GLOB;\n"
    
    >> Rails.root.join("app/assets/javascripts/application.js").delete
    >> reload!
    >> URI.open(helper.javascript_path("application", host: "http://localhost:3000")).read
    => "import \"@hotwired/turbo-rails\";\nimport \"controllers\";\n"
    

    Name the file something else:

    // app/assets/javascripts/globals.js
    
    const GLOBAL = "setting";
    

    Add it to manifest:

    // app/assets/config/manifest.js
    
    //= globals.js
    // or add the whole directory
    //= link_directory ../javascripts .js
    

    Load it:

    <%= javascript_include_tag "globals" %>
    

    To do this in javascript modules, add globals to window:

    // app/javascript/globals.js
    
    window.GLOBAL = "setting";
    
    // app/javascript/application.js
    
    // you have to do this in a separate file, because imports will run first
    window.LOADED = "after imports";
    
    import "./globals";
    import "./components"; // <= can use GLOBAL now; LOADED is not assigned yet
    

    However, you should just import what you need where you need it, instead of relying on global variables.