0

I'm switching a Ruby on Rails application from sprockets to webpacker. I'm getting an error for inline javascript in my existing slim templates, JQuery is not defined. I tried adding a require for jquery in the application.html.slim but that doesn't help.

application.html.slim

doctype html
html
  head
    title My Application
    meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"
    meta name="have-i-been-pwned-verification" value="..." /

    meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"
    = include_gon(watch: true)
    = stylesheet_link_tag 'application', media: 'all'
    = javascript_pack_tag 'application'
    = javascript_include_tag 'jquery'
    = csrf_meta_tags
body
  = render 'layouts/navigation/main_nav', disable_search: true
  = flash_messages
  main.application
    = yield

error

Uncaught ReferenceError: $ is not defined

_comments.slim

.comments-wrapper.collapse.show.comments-collapse#comments
  .row.comments
    .col-sm-12
      .row.comment-post
        .col-sm-12.parent-comment-form
          - if user_signed_in?
            = render 'we_vote/comments/form', commentable: commentable
      - unless local_assigns[:limit_comments].present?
        .row.filter-row
          .col-sm-12.col-lg-2.offset-md-1.offset-sm-0.text-right
            .sort-label sort by
          .col-lg-4.col-sm-12.mb-2
            select.sort-select
              option Popular
              option Reply Number
              option Upvote Number
              option Newest
          .col-lg-4.offset-lg-1.col-sm-12.offset-md-0
            select.sort-select
              option Today
              option Last Week
              option This Week
      .row
        .col-sm-12.comments-container class="#{'empty' unless commentable.comments.any?}"
          ul.comments-list
            - if local_assigns[:limit_comments]
              - commentable.comments.top_level.by_popularity.limit(limit_comments).each do |comment|
                = render 'we_vote/comments/comment', comment: comment, commentable: commentable, limited: true
            - else
              - commentable.comments.top_level.by_popularity.each do |comment|
                = render 'we_vote/comments/comment', comment: comment, commentable: commentable

= render 'we_vote/comments/report_comment_modal'

javascript:

  $(function(){
    var commentId = "#{ params[:comment_id] }";
    if(commentId) {
      var $comment = $('#' + commentId);
      $comment.get(0).scrollIntoView();
      $comment.addClass('notified');
    }
  });

environment.js

const { environment } = require("@rails/webpacker");
const erb = require("./loaders/erb");
const jquery = require("./plugins/jquery");
const webpack = require("webpack");
environment.plugins.append(
  "Provide",
  new webpack.ProvidePlugin({
    "window.Tether": "tether",
    Popper: ["popper.js", "default"],
  })
);

const aliasConfig = {
  jquery: "jquery-ui-dist/external/jquery/jquery.js",
  "jquery-ui": "jquery-ui-dist/jquery-ui.js",
};

environment.config.set("resolve.alias", aliasConfig);

environment.plugins.prepend("jquery", jquery);
environment.loaders.prepend("erb", erb);
module.exports = environment;

config/webpack/plugins/jquery.js

const webpack = require("webpack");

module.exports = new webpack.ProvidePlugin({
  $: "jquery",
  jQuery: "jquery",
  "window.jQuery": "jquery",
});

application.js

import $ from "jquery";
import autosize from "autosize";
global.$ = $;
global.jQuery = $;
import tether from "tether";
global.Tether = tether;

require("@rails/ujs").start();
require("@rails/activestorage").start();
require("channels");

require("../src/google_analytics");
require("jquery");
require("jquery-ui");
require("../src/autocomplete-rails");
require("tether");
require("popper.js/dist/umd/popper");
require("bootstrap/dist/js/bootstrap");
require("bootstrap-notify/bootstrap-notify");
require("jquery-mask-plugin");
require("select2/dist/js/select2.full.min");
require("jquery-textcomplete");
require("jquery-match-height");
require("jquery-jscroll");
require("../src/facebook");

require("../src/components/comment");
require("../src/components/discussion");
require("../src/components/follow");
require("../src/components/hashtaggable");
require("../src/components/news_feed");
require("../src/components/preview-img");
require("../src/components/question");
require("../src/components/remote_buttons");
require("../src/components/report");
require("../src/components/search");
require("../src/components/sidebar");
require("../src/components/upvote");
require("../src/components/vote");
require("../src/components/verification");

jQuery.railsAutocomplete.options.delay = 300;
jQuery.railsAutocomplete.options.autoFocus = true;
Antarr Byrd
  • 24,863
  • 33
  • 100
  • 188

2 Answers2

1

The problem is that your inline script gets executed before jquery. The reason for that, is because it's inlined in the html, so it's available right away. On the other hand, jquery is not inline so the browser has to make a request before executing it.

There are several options.

Option: wait for jQuery to be loaded

One option is to wrap all you inline code in a function that waits for jQuery to be loaded.

javascript:
  document.addEventListener('DOMContentLoaded', function () {
    var commentId = "#{ params[:comment_id] }";
    if(commentId) {
      var comment = document.getElementById(commentId);
      commment.classList.add('notified');
    }
  }, false);

Option: stub jQuery

Another option would be to create a stub (fake) $ function, store all the calls to $, wait for jQuery to be loaded and replay those calls. More details here

  • I've updated my question with the code above. – Antarr Byrd Jul 11 '21 at 05:56
  • It seems that jQuery is not loaded when the inline script is executed. You can find more information here https://stackoverflow.com/questions/21013554/defer-loading-of-javascript-uncaught-referenceerror-is-not-defined – Léonard Henriquez Jul 11 '21 at 11:15
  • Looks like your are suggestion I add `= javascript_include_tag jquery` to the slim template where the inline script is. I've already tried that – Antarr Byrd Jul 11 '21 at 17:53
  • 1
    That's not what I meant. The problem is that your inline script gets executed before jquery. The reason for that, is because it's inlined in the html, it available right away. On the other hand, jquery is not inline so the browser has to make a request before executing it. So what you need to do is to wrap all you inline code in a function that waits for jQuery to be loaded. – Léonard Henriquez Jul 11 '21 at 18:41
  • That's what I ended up doing but ended up converting to vanilla js as well. If you want to update your answer to reflect that I will give you the check. You can see it [here](https://gist.github.com/antarr/545ebbfee028b0268687c0ddb90c0e6f) Thanks – Antarr Byrd Jul 11 '21 at 18:50
1

In your JavaScript pack file (e.g. app/javascript/packs/application.js), do the following:

import jQuery from 'jquery';

global.$ = global.jQuery = jQuery;
Summer
  • 141
  • 3
  • I already have this. Jquery works fine in my compile script that are included in the pack. Just not the inline scripts. – Antarr Byrd Jul 11 '21 at 05:43