Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Legacy Libraries & global jQuery

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Now that layout.js is being processed through webpack, we can remove our self-executing function and replace the $ with const $ = require('jquery'):

'use strict';
const $ = require('jquery');
$(document).ready(function() {
$('[data-toggle="tooltip"]').tooltip();
});

We rock! But this time... there's a surprise. When we refresh, we do not get warm, fuzzy feelings: we get a cold and not-so-fuzzy error:

$.tooltip() is not a function

Interesting!

... lines 1 - 4
$(document).ready(function() {
$('[data-toggle="tooltip"]').tooltip();
});

This $.tooltip() function should be coming from Bootstrap... which is included in our base layout:

... lines 1 - 98
{% block javascripts %}
... line 100
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
... lines 102 - 105
{% endblock %}
... lines 107 - 110

So... why isn't this working? Well... think about it: Bootstrap's job is to modify jQuery and add more functions to it, like "tooltip". But, in layout.js, we require a different, fresh, unmodified jQuery and then try to call .tooltip() on it!

... lines 1 - 2
const $ = require('jquery');
$(document).ready(function() {
$('[data-toggle="tooltip"]').tooltip();
});

That doesn't work because - when the Bootstrap code is loaded - it modifies the jQuery that's loaded in our layout: it modifies the global jQuery variable. In layout.js, we're not using the global jQuery variable: we're requiring our own jQuery... which has not been modified by Bootstrap.

The fix? We already know it: we need to once again handle all our dependencies inside layout.js. I mean, if we need bootstrap functions inside of this file, then we need to require bootstrap here.

Let's do it!

Installing and Requiring Bootstrap

First, install Bootstrap:

Tip

Hey, Bootstrap 4 is out! But this site is designed for Bootstrap 3 CSS, so don't forget to change your dependency to version 3: yarn add bootstrap@3 --dev.

yarn add bootstrap@3 --dev

Then, in layout.js, this is interesting: just say require('bootstrap'):

... lines 1 - 2
const $ = require('jquery');
require('bootstrap');
... lines 5 - 9

Nope, I'm not setting this to a variable. This is really common with jQuery plugins, like Bootstrap. These files don't actually return anything. Instead, we require them so that they modify the jQuery function and add new functions to it.

We should be good, right? Try it! Ah!!! The same cold, unfuzzy error! This is confusing.

Modules that Rely on Global Variables

To see what's really going on, open the base layout and remove the script tags for jQuery and bootstrap:

... lines 1 - 98
{% block javascripts %}
<script src="{{ asset('bundles/fosjsrouting/js/router.js') }}"></script>
<script src="{{ path('fos_js_routing_js', { callback: 'fos.Router.setData' }) }}"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-promise/4.0.5/es6-promise.auto.min.js"></script>
<script src="{{ asset('build/layout.js') }}"></script>
{% endblock %}
... lines 105 - 108

We don't need these anymore: we've refactored all of our code to properly require what we need instead of relying on global variables.

And when we refresh now, the new error is a bit more helpful:

jQuery is not defined

The source of this isn't clear at first, but this is coming from Bootstrap. This is where Webpack gets tricky.

The bootstrap module relies on there to be a global jQuery variable. Yep, it's behaving poorly.

Look, in a perfect world, when we require bootstrap, internally, it would use the require() function to require jQuery and then modify it. See, if multiple files require the same module - like jQuery, they are all returned the same object. This means that Bootstrap would add the tooltip() function to the same jquery that we use in layout.js.

But.... that doesn't happen. Instead, Bootstrap looks for a global jQuery variable to modify. If there is no global variable - like in our nice system - it explodes.

Fixing Poorly-Behaving Modules

There are a few ways to fix this. First, we could say window.jQuery = $ before requiring bootstrap. If we try that and refresh, no more errors! Yep, we require the jquery module and then make it a global variable just in time for Bootstrap to look for it.

But there are a few problems with this... the biggest being that I don't want to keep relying on global variables! And second... we're probably going to face this issue again and again... with other jQuery plugins. I'd rather have a more global way of fixing this.

How? Through the power of Webpack!

Leave a comment!

5
Login or Register to join the conversation
Default user avatar
Default user avatar Radoje Albijanic | posted 5 years ago | edited

Hey folks,

It is I again :). Latest bootstrap version requires Popper.js, so in order not to break webpack watch when adding bootstrap you should also

yarn add popper.js```


Cheers.
1 Reply

Hey Radoje Albijanic!

Ah yes, thanks for the note - Bootstrap 4 is out! We're going to add a note about this :). Bootstrap version 3 might work better... because the site's HTML is designed for the Bootstrap 3 CSS.

Cheers!

2 Reply
Default user avatar
Default user avatar Radoje Albijanic | weaverryan | posted 5 years ago

Yepp, you are right, bootstrap 4 is not behaving nicely, thanks :)

1 Reply
Lijana Z. Avatar
Lijana Z. Avatar Lijana Z. | posted 4 years ago

I imagine how much time people have wasted getting those errors without big benefit. WHen you waste time for such thign, you start thinking - whats the point of this, it worked nicely old way and was not wasting time.
But bootstrap could update then their code and fix this.

Reply

Hey Lijana Z.!

You're 100% right, and I think we see things like this due to how young and fast-moving the JavaScript world still is. These new tools are *amazing*, but they are still developing and we're in this "middle time" where it's still painful at times to use the new tools.

By the way, Bootstrap 4 DID fix this :) - they require jQuery (and another library called popper) correctly. Wooh! I see this jQuery plugin problem less and less in general.

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial explains the concepts of an old version of Webpack using an old version of Symfony. The most important concepts are still the same, but you should expect significant differences in new versions.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.2.0",
        "symfony/symfony": "3.3.*", // v3.3.16
        "twig/twig": "2.10.*", // v2.10.0
        "doctrine/orm": "^2.5", // v2.7.0
        "doctrine/doctrine-bundle": "^1.6", // 1.10.3
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.5
        "symfony/swiftmailer-bundle": "^2.3", // v2.6.3
        "symfony/monolog-bundle": "^2.8", // v2.12.1
        "symfony/polyfill-apcu": "^1.0", // v1.4.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.26
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "friendsofsymfony/user-bundle": "^2.0", // v2.1.2
        "doctrine/doctrine-fixtures-bundle": "~2.3", // v2.4.1
        "doctrine/doctrine-migrations-bundle": "^1.2", // v1.3.2
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "friendsofsymfony/jsrouting-bundle": "^1.6" // 1.6.0
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.6
        "symfony/phpunit-bridge": "^3.0" // v3.3.5
    }
}

What JavaScript libraries does this tutorial use?

// package.json
{
    "dependencies": [],
    "devDependencies": {
        "babel-core": "^6.25.0", // 6.25.0
        "babel-loader": "^7.1.1", // 7.1.1
        "babel-plugin-syntax-dynamic-import": "^6.18.0", // 6.18.0
        "babel-preset-env": "^1.6.0", // 1.6.0
        "bootstrap-sass": "^3.3.7", // 3.3.7
        "clean-webpack-plugin": "^0.1.16", // 0.1.16
        "copy-webpack-plugin": "^4.0.1", // 4.0.1
        "core-js": "^2.4.1", // 2.4.1
        "css-loader": "^0.28.4", // 0.28.4
        "extract-text-webpack-plugin": "^3.0.0", // 3.0.0
        "file-loader": "^0.11.2", // 0.11.2
        "font-awesome": "^4.7.0", // 4.7.0
        "jquery": "^3.2.1", // 3.2.1
        "lodash": "^4.17.4", // 4.17.4
        "node-sass": "^4.5.3", // 4.5.3
        "resolve-url-loader": "^2.1.0", // 2.1.0
        "sass-loader": "^6.0.6", // 6.0.6
        "style-loader": "^0.18.2", // 0.18.2
        "sweetalert2": "^6.6.6", // 6.6.6
        "webpack": "^3.4.1", // 3.4.1
        "webpack-chunk-hash": "^0.4.0", // 0.4.0
        "webpack-dev-server": "^2.6.1", // 2.6.1
        "webpack-manifest-plugin": "^1.2.1" // 1.2.1
    }
}
userVoice