7

I have a new Rails 7 application. I'm currently trying to learn all the new features since Rails 5. I want to use the following code in my javascript file, but so far I'm getting the following error: Uncaught ReferenceError: $ is not defined.

$(document).on("turbo:load", () => {
  console.log("turbo!");
});

Here are two other relevant files. If I need to post anything else please let me know.

importmap.rb

pin "application", preload: true
pin "jquery", to: "https://ga.jspm.io/npm:jquery@3.6.0/dist/jquery.js", 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 "el-transition", to: "https://ga.jspm.io/npm:el-transition@0.0.7/index.js"

pin_all_from "app/javascript/controllers", under: "controllers"

application.js

import "@hotwired/turbo-rails"
import "jquery"

$(document).on("turbo:load", () => {
  console.log("turbo!");
});
daveomcd
  • 6,367
  • 14
  • 83
  • 137

4 Answers4

15

Just switch to CDN other than jspm, jQuery will be global on import:

# config/importmap.rb

# NOTE: pin jquery to jsdelivr instead of jspm
pin "jquery", to: "https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.js"
// app/javascript/application.js

import "jquery"; // this import first
import "script"; // then your other imports that use `$`

// NOTE: don't use relative imports: `import "./script"`
//       add `pin "script"` to `importmap.rb`

console.log($); // ok
// app/javascript/script.js
console.log($)  // ok

Everything just works, one import, multiple imports, jquery plugins. No extra hoisting needed.


When using jspm, jQuery has to be explicitly imported, where you need it, like any other module:

// app/javascript/place_where_i_need_jquery.js

import $ from "jquery";

// NOTE: don't make jQuery global, this only kind of works.
// window.$ = window.jQuery = $;

console.log($); // ok

To clarify and put window thing to rest. It works with stimulus, because controllers are imported with a dynamic import() which usually runs after jquery is loaded and there is time to assign it to window, but this is not guaranteed. Which is why you have to do the hoisting thing to ensure $ becomes global before any other imports.


Keep in mind that importmaps are deferred by default, so you can't use $ in your inline scripts (not without twisting your arms backwards), because jquery is loaded after the page is loaded.

Alex
  • 16,409
  • 6
  • 40
  • 56
  • Do you mean that different CDNs deliver different versions of a given package? Like `./bin/importmap pin jquery@3.6.3` will give me a different version of jquery than `./bin/importmap pin jquery@3.6.3 --from jsdelivr`? I think I must be misunderstanding something, that doesn't seem right. I feel like I'm taking crazy pills. – Fred Willmore Jan 21 '23 at 03:55
  • Oh I see they're very different. How does this make sense to anyone – Fred Willmore Jan 21 '23 at 04:12
  • 2
    @FredWillmore `jspm` is for ESM modules. because of the way the module is built/initialized `$` and `jQuery` don't get [exposed](https://github.com/jquery/jquery/blob/3.6.3/src/exports/global.js#L30-L32) globally. I guess there is also some extra processing involved, because this [part](https://github.com/jquery/jquery/blob/3.6.3/src/wrapper.js#L18-L37) is [changed](https://jspm.dev/npm:jquery@3.6.3!cjs) and no more global jquery. (jsdelivr also has esm option, which does the same). – Alex Jan 24 '23 at 05:06
5

Doing what the other answers say will work, but it will stop working when you pin a new package.

The reason is the so-called javascript hoisted way of working, which means that it will reorder the code you have to reorganize the imports in blocks. This will break jQuery window assignation.

To avoid that, extract

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

in another file and call it in on single import.

In my case, I have:

application.js

import "./src/jquery"

$(function(){
  console.log("Hey!")
})

and src/jquery.js with above's code.

Juanse Cora
  • 755
  • 9
  • 19
  • 1
    This answer has less upvotes but it's the better answer. This works even with multiple pins. Thx for sharing! – Robert Reiz Jan 02 '23 at 08:00
  • 1
    I'm getting GET http://localhost:3000/assets/src/jquery net::ERR_ABORTED 404 (Not Found) when I try to follow this setup – Tamik Soziev Jul 01 '23 at 22:47
  • @TamikSoziev don't use relative imports. you have to `pin "src/jquery"` and `import "src/jquery"` – Alex Jul 11 '23 at 05:13
2

I needed to add a few lines to my application.js file.

import "@hotwired/turbo-rails"
import jquery from "jquery"
window.jQuery = jquery
window.$ = jquery
daveomcd
  • 6,367
  • 14
  • 83
  • 137
  • apparently we need to do some more setup in order for this to work? Use specific gem/js package? – knagode Apr 20 '23 at 14:23
1

I tried all of the other suggestions here and none of those worked.

Here is how you solve this:

  1. Pin jquery
    bin/importmap pin jquery

  2. Use jsdelivr.net or local file in importmap.rb
    jspm won't work for some unknown reason

    a) pin "jquery", to: "https://cdn.jsdelivr.net/npm/jquery/dist/jquery.js"

    b) pin "jquery", to: "jquery.js"
    if you use a local file, you need to download jquery.js to app/javascript/jquery.js

    You can also pin to a specific version if you want that
    c) pin "jquery", to: "https://cdn.jsdelivr.net/npm/jquery@3.7.0/dist/jquery.js"

  3. In application.js file you only need to add this
    import "jquery"

  4. When you want to use jquery in a view, you need use type="module" on the script tag

<script type="module">

 $(document).ready(function(){
   console.log($)
 })
</script>
Timothy Alexis Vass
  • 2,526
  • 2
  • 11
  • 30