Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails 7 (7.0.2.3) Importmap jQuery is not defined in view

I've looked around for as much help as possible regarding installing jQuery in Rails 7 (7.0.2.3). I want to use it in script tags in my views, but I can't seem to get it exported to where it is globally available or anywhere for that matter.

importmaps is easy to manipulate as far as installing and mapping packages, however after that the documentation is unclear.

I am trying to figure out how to add something similar to this:

import jquery from "jquery"
window.jQuery = jquery;
window.$ = jquery;

to application.js to get global functions, like $, to work, as I'd like $ to be available in all my views.

As for what I've done:

./bin/importmap pin jquery --download

Gives me the importmap line:

pin "jquery" # @3.6.0

Then looking at the importmap JSON:

{
  "imports": {
    "application": "/assets/application-37a24e4747cc3cde854cbbd628efbdf8f909f7b031a9ec5d22c5052b06207eb8.js",
    "@hotwired/turbo-rails": "/assets/turbo.min-96cbf52c71021ba210235aaeec4720012d2c1df7d2dab3770cfa49eea3bb09da.js",
    "@hotwired/stimulus": "/assets/stimulus.min-900648768bd96f3faeba359cf33c1bd01ca424ca4d2d05f36a5d8345112ae93c.js",
    "@hotwired/stimulus-loading": "/assets/stimulus-loading-1fc59770fb1654500044afd3f5f6d7d00800e5be36746d55b94a2963a7a228aa.js",
    "jquery": "/assets/jquery-498b35766beec7b412bab57a5acbe41761daa65aa7090857db4e973fa88a5623.js",
    "controllers/application": "/assets/controllers/application-368d98631bccbf2349e0d4f8269afb3fe9625118341966de054759d96ea86c7e.js",
    "controllers/hello_controller": "/assets/controllers/hello_controller-549135e8e7c683a538c3d6d517339ba470fcfb79d62f738a0a089ba41851a554.js",
    "controllers": "/assets/controllers/index-7a8fc081f7e391bd7b6fba95a75e36f88ba813da2c4c8787adad248afb9a0a06.js"
  }
}

It appears jQuery is there but a simple script tag in application.html.erb:

<script type="text/javascript" charset="utf-8">
        $(document).ready(function (){
            console.log('jQuery working.');
        })
</script>

Fails with the error:

(index):41 Uncaught ReferenceError: $ is not defined

This really seems basic, yet docs are very sparse on these things.

Can someone please explain why this occurs and how to correct it?

like image 478
user1572597 Avatar asked Jan 18 '26 03:01

user1572597


1 Answers

Discussion

As of now (Apr-2022) There are two things to consider: inline script loading and browser importmap support. Both together can make inlining scripts which refer to variables defined through importmap counter-intuivite and error-prone.

Inline Script Loading

Inline scripts are executed first. That's before the importmap JS scripts are loaded. See MDN script docs.

Browser Importmap Support

Importmap is still very new and support varies. This complicates things.

  • For firefox without importmap support the es-module-shim is used which can load the importmap after 'load' + 'DomContentLoaded' events.
  • For Chrome with importmap support the importmap is loaded before 'load' and 'DomContentLoaded'.
  • Additionally DOM might be loaded twice for various reasons.

Conclusion

The variable- and function- hoisting mechanism takes care of script loading sequence problems, but in this case because the script is defined inline and importmap scripts have not yet been loaded, the variable is undeclared and it will definitively result in a ReferenceError: ... is not defined.

Solutions

Ensure the variable is defined before accessing, by checking if scripts from the importmap have been loaded within the inline script and before running code accessing them.

Most reliable is placing a variable into application.js and checking for its declaration safely in the inline script. If it exists importmap has been loaded and everything in application.js exists in the inline script context.

The document.DomContentLoaded event or the window.load event or both can be used for this in conjunction with the in keyword. Alternatively a custom event can be thrown at the end of application.js to enforce running the inline code only after the importmap code has been loaded.

Example:

  1. After pinning jquery in importmap.rb
  2. In app/javascript/application.js
// jquery does not export 'default' but defines window.$ and
// window.jQuery when loaded:
// - import 'jquery'; will not work
// - namespace does not matter here (jq)
// - no need to redefine it again w/ window.$ = jq.$
import * as jq from 'jquery';

// Define a variable to check in inlined HTML script
window.importmapScriptsLoaded = true;
  1. In the .html / .erb.html

NOTE: Depending on injected shims/polyfills the load order might still be undefined. In this case more work is needed (like throwing a custom event). Also the DOM might be loaded twice which needs to be taken into account when code shan't be double executed.

<h1 id="hello">Hello</h1>

<script type="text/javascript">

// Guard against double DOM loads
var codeExecuted = false;

document.addEventListener('DOMContentLoaded', function(e) {

  // Check if importmap stuff exisits without throwing an error.
  // Then run main code w/ guard against multiple executions.
  if ("importmapScriptsLoaded" in window) { 

    if (!codeExecuted) {
      // Main code here
      console.log($('#hello'));

      // Don't forget to bump guard for one-time only JS execution !!
      codeExecuted = true; 
    };  
  };

});
</script>
  • Alternatively inline the respective script directly, either by using the asset pipeline or straight the public folder. This is not recommended for two reasons: Inline scripts will be loaded aside importmap scripts. It is likely that inlining one script will lead to inline all its dependencies. (importmap scripts are not loaded here yet).

Example:

<script type="text/javascript" src="/jquery.js"></script> 
like image 156
count0 Avatar answered Jan 19 '26 19:01

count0



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!