Methods like modal() not working if Bootstrap loaded with Webpack (Rails)
Asked Answered
A

1

2

In my Rails 5 app, if I load Bootstrap 4 using a header <script> tag from a CDN, everything works fine. But when I install Bootstrap (and its dependencies) to node_modules, and serve it with Webpack, it mostly still works... but the jQuery methods Bootstrap is supposed to add like $(foo).modal() or $(bar).popover() give errors in the JavaScript console:

Uncaught TypeError: $(...).modal is not a function
Uncaught TypeError: $(...).popover is not a function

There are several other StackOverflow questions about errors like that, but none of the solutions listed there work for me. The most common cause seems to be jQuery getting included twice, but I'm pretty sure I'm not doing that; when I remove the jQuery import from app/javascript/packs/application.js (see below), then all of jQuery stops working. jQuery is not in my Gemfile as a gem, and the word "jquery" only appears in the files I've shown below.

I'm loading in the correct order (see below; jQuery and Popper before Bootstrap). I'm using a document-ready wrapper around the method calls (see below), so I'm not calling them too soon. The Bootstrap CSS loads fine, and DOM elements using data-toggle="modal" display the modal correctly:

<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal">
  Show Modal
</button>
<div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <!- ... ->
    </div>
  </div>
</div>

...it's only when methods like $(foo).modal() etc. are called in JS code that I get the error:

<script>
  $(function () {
    $('#exampleModal').on('shown.bs.modal', function (e) {
      console.log('modal shown!');
    });
    $('#otherModalButton').click(function (e) {
      e.preventDefault();
      console.log('clicked!');
      $('#exampleModal').modal('show');  // <- "not a function" error
      return false;
    });
  });
</script>

My setup:

Node.js v10.20.1
ruby 2.5.8p224 (2020-03-31 revision 67882) [x86_64-linux]
Rails 5.2.4.2

package.json:    "@rails/webpacker": "4.2.2",
package.json:    "bootstrap": "4.4.1",
package.json:    "jquery": "3.4.1",
package.json:    "popper.js": "1.16.1"
package.json:    "webpack-dev-server": "^3.11.0"
// config/webpack/development.js
process.env.NODE_ENV = process.env.NODE_ENV || 'development'
const environment = require('./environment')
module.exports = environment.toWebpackConfig()
// config/webpack/environment.js
const { environment } = require('@rails/webpacker')
const webpack = require('webpack')
environment.plugins.append(
  'Provide',
  new webpack.ProvidePlugin({
    $: 'jquery',
    jQuery: 'jquery',  // taking this out doesn't fix it
    Popper: ['popper.js', 'default']
  })
)
module.exports = environment
<!DOCTYPE html>
<!-- app/views/layouts/application.html.erb -->
<html>
  <head>
    [...]
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
    <%= javascript_pack_tag    'application' %>
    <%# if I load with these two lines (instead of in app/javascript/packs/application.js), it all works: %>
    <%# javascript_include_tag 'https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.1/umd/popper.min.js' %>
    <%# javascript_include_tag 'https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/js/bootstrap.min.js' %>
  </head>
  <body>
    <%= yield %>
  </body>
</html>
// app/javascript/packs/application.js
import 'jquery/src/jquery'
import 'popper.js/dist/esm/popper'
import 'bootstrap/dist/js/bootstrap'
import '../stylesheets/application'
// app/javascript/stylesheets/application.scss
@import "~bootstrap/scss/bootstrap";

I've manually reviewed the JS pack that webpack-dev-server is producing; I can see Bootstrap in there. The Webpack output in the console shows the pack successfully compiled with no errors. I've tried this on both Chrome (ChromeOS and MacOS) and Firefox (MacOS).

Any ideas for what I can try?

Asset answered 9/5, 2020 at 23:42 Comment(0)
T
3

The problem is

new webpack.ProvidePlugin({
 $: 'jquery',
 jQuery: 'jquery',  // taking this out doesn't fix it
...

and require "jquery" caused jquery to be loaded twice. if you check the compiled application.js, you may see it required "./node_modules/jquery/dist/jquery.js", and then required all js files inside "./node_modules/jquery/" one by one, try it, search ./node_modules/jquery/

enter image description here

As you can see in the minimap, so many results!

My Solution

I solved it by removing this from enviroments.js

 $: 'jquery',
 jQuery: 'jquery',  

and in applictions.js change require "jquery" to

import JQuery from 'jquery';
window.$ = window.JQuery = JQuery;
Tyratyrannical answered 28/8, 2020 at 8:42 Comment(1)
For those reading this in the future: Another potential way to solve this problem "the webpack way" would be via a loader e.g. the expose-loader.Eugenol

© 2022 - 2024 — McMap. All rights reserved.