If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeNow that layout.js
is being processed through webpack, we can remove our self-executing function and replace the $
with const $ = require('jquery')
:
; | |
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!
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.
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.
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!
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!
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.
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!
// 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
}
}
// 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
}
}
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