Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

jQuery Plugins / Bootstrap

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Now that Webpack is handling layout.js, let's simplify it! Remove the self-executing function. And, of course, add const $ = require('jquery'):

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

Perfect, right? Well... we're in for a surprise! Go back to the main page and... refresh! Bah!

tooltip is not a function

Uh oh! The tooltip function comes from Bootstrap... and if you look in our base layout, yea! We are including jQuery and then Bootstrap:

<!DOCTYPE html>
<html lang="en">
... lines 3 - 19
<body>
... lines 21 - 98
{% block javascripts %}
<script src="https://code.jquery.com/jquery-3.1.1.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
... lines 102 - 105
{% endblock %}
</body>
</html>

Which should add this function to jQuery!

Trouble with jQuery Plugins

But be careful: this is where Webpack can get tricky! Internally, the Bootstrap JavaScript expects there to be a global jQuery variable that it can add the tooltip() function to. And there is a global jQuery variable! It's this jQuery that's included in the layout. So, Bootstrap adds .tooltip() to that jQuery object.

But, in layout.js, when we require('jquery'):

... lines 1 - 2
const $ = require('jquery');
... lines 4 - 8

This imports an entirely different jQuery object... and this one does not have the tooltip function!

To say this in a different way, if you look at just this file, we are not requiring bootstrap... so it should be no surprise that bootstrap hasn't been able to add its tooltip() function! What's the fix? Require Bootstrap!

Find your open terminal and run:

yarn add bootstrap@3 --dev

Bootstrap 4 just came out, but our app is built on Bootstrap 3. Now that it's installed, go back and add: require('bootstrap'):

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

And... that's it! Well, there is one strange thing... and it's really common for jQuery plugins: when you require bootstrap, it doesn't return anything. Nope, its whole job is to modify jQuery... not return something.

Now that it's fixed, go back and... refresh! What! The same error!!! This is where things get really interesting.

At this point, we're no longer using the global jQuery variable or Bootstrap JavaScript anywhere: all of our code now uses proper require statements. To celebrate, remove the two script tags from the base layout:

<!DOCTYPE html>
<html lang="en">
... lines 3 - 19
<body>
... lines 21 - 98
{% block javascripts %}
<script src="https://code.jquery.com/jquery-3.1.1.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
... lines 102 - 105
{% endblock %}
</body>
</html>

And now... refresh!

Fascinating!

jQuery is not defined

And it's coming from inside of Bootstrap!

Ah, ha! When we require bootstrap, internally in that file, it looks for a global variable called jQuery and then modifies it. But when you require jquery, it does not create a global variable: it just returns a value. And now that there is no global jQuery variable available, it fails! This is a really common situation for jQuery plugins... and there's a great fix. Actually, there are two ways to fix it... but only one good one.

The ugly fix is to say window.jQuery = $:

... lines 1 - 2
const $ = require('jquery');
window.jQuery = $;
require('bootstrap');
... lines 6 - 10

Try it! Go back and refresh! All better. Yep, we just made a global variable... so that when we require bootstrap, it uses it. But... come on! We're trying to remove global variables from our code - not re-add them!

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

So here's the better solution: go to webpack.config.js and add autoProvidejQuery():

... lines 1 - 2
Encore
... lines 4 - 14
// fixes modules that expect jQuery to be global
.autoProvidejQuery()
;
... lines 18 - 21

That's it. Find your terminal and restart Webpack:

yarn run encore dev --watch

And... refresh! Yes! It works! But... what the heck just happened? You've just experienced a crazy super power of Webpack. Thanks to autoProvidejQuery(), whenever Webpack finds a module that references an uninitialized global jQuery variable - yep, Webpack is smart enough to know this:

// node_modules/bootstrap/.../bootstrap.js

function ($) {
	// ...
} (jQuery)

It rewrites that code to require('jquery'):

// node_modules/bootstrap/.../bootstrap.js

function ($) {
	// ...
} (require('jquery'))

Yea... it basically rewrites the code so that it's written correctly! And so suddenly, Bootstrap requires the same jquery instance that we're using! This makes jQuery plugins work beautifully.

Tip

Not all jQuery plugins have this problem: some do behave properly out-fo-the-box.

Handling Legacy Template Code

Oh, but there's one other jQuery legacy situation I want to mention. If you're upgrading an existing app to Webpack, then you might not be able to move all of your JavaScript out of your templates at once. And that JavaScript probably needs jQuery. Here's my recommendation: remove jQuery from the base layout like we've already done. But then, in your layout.js file, require jquery and add: global.$ = $.

// ...
const $ = require('jquery');
global.$ = $;
require('bootstrap');
// ...

This global variable is special to Webpack - well... it's technically a Node thing, but that's not important. The point is, when you do this, it creates a global $ variable, which means that any JavaScript in your templates will be able to use it - as long as you make sure your code is included after your layout.js script tag.

Later, you should totally remove this when your code is refactored. But, it's a nice helper for upgrading.

Next, let's talk about how CSS fits into all of this!

Leave a comment!

31
Login or Register to join the conversation
Alexey R. Avatar
Alexey R. Avatar Alexey R. | posted 3 years ago

Hello!
Thank you for the tutorial!

I've some error when tried to using jquery.masked input plugin. (https://yarn.pm/jquery.mask...

I did
> yarn add jquery.maskedinput

Then required it in app.js file

const $ = require('jquery');
require('jquery.maskedinput');

But when I tried to use it got error
Uncaught TypeError: $(...).mask is not a function

Is there any ideas how to fix it?

1 Reply
KevinC Avatar
KevinC Avatar KevinC | Alexey R. | posted 3 years ago | edited

Alexey R. ,
Similarly, also having issues with the jquery.maskedinput. However, my issue is: Cannot read property 'definitions' of undefined at the line: $.mask.definitions['~'] = '[+-]';
Clearly, "mask" is undefined here.

Now, this is inside a Javascript Class. Relevant code:

'use strict';

const $ = require('jquery');
require('jquery.maskedinput');

class Enrollment {
init(params){
$.mask.definitions['~'] = '[+-]';
}
}
module.exports = Enrollment;

Uses above class as, which is in application.js and registered with webpack.conf.js

const Enrollment = require('./Enrollment');
var enrollment = new Enrollment();

enrollment.init({});

Then called via twig template extension:

{{ encore_entry_script_tags('application') }}

I don't seem to be having a jQuery issue, it's more about the maskedinput plugin not really attaching itself to $ as $.mask

Thoughts on what to try?

Reply

Hey Kevin!

The short answer is that this package is broken, at least when using through yarn or npm. Here is the pull request to fix the issue: https://github.com/excellal...

As we explain in this section (https://symfonycasts.com/sc..., when you require a file, the "main" key is read from the package.json of that library to know which file to include. That library's main key is just plain wrong.

So, my guess is that you main run into other problems, but I would start with this:'


const $ = require('jquery');
require('jquery.maskedinput/src/jquery.maskedinput.js');

Actually, looking into that file quickly, I think this WILL be all you need to do. Let me know! I hate these old, broken jQuery plugins :(

Cheers!

1 Reply
KevinC Avatar
KevinC Avatar KevinC | weaverryan | posted 3 years ago | edited

weaverryan , Thanks Ryan, for the quick response and it did fix the maskedinput issue.
Since this is somewhat related and I hope it will help someone else too, would you give some insight into the jquery-validation plugin through yarn?

Complaint is that $(...).validate is not a function

I have tried two different ways to 'require' it:

require('jquery-validation');

and

require('jquery-validation/dist/jquery.validate');
require('jquery-validation/dist/additional-methods');

Their package.json "main" key points to the dist/jquery.validate.js. Is this another broken plugin for some other reason? And what might be the work around?
Thanks again!

Reply

Hey Kevin!

These jQuery plugins will be the death of me :). But at least I'm quite good at debugging them now. This one is broken, for a different reason. Here is the conversation and fix (add the resolve.alias for jquery): https://github.com/symfony/...

I'm pretty sure that's your problem. If you'd like a bit more background about "why" let me know, but the short answer is that the library has "jquery" as a "dependency" in package.json, and that is incorrect (should be a peerDependency).

Cheers!

Reply
KevinC Avatar
KevinC Avatar KevinC | weaverryan | posted 3 years ago | edited

Whoa weaverryan , yeah these plugins are a bit of a mess. Your suggestion was spot on! I also noticed that some internal jquery-ui widgets have issue with just a simple require('jquery-ui'). I had to do things like require('jquery-ui/ui/widgets/tooltip'), as an example, in order to get $(...).tooltip to work in existing code that I'm migrating.
Thanks again for being there for us.

Reply
Alexey R. Avatar

Thank you!
require('jquery.maskedinput/src/jquery.maskedinput.js');
Works good!

Reply
Alexey R. Avatar

Thank you.
require('jquery.maskedinput/src/jquery.maskedinput.js');
This solution for me works good.

Reply

Hey Alexey R.

Have you tried enabling "autoProvidejQuery"?
More info here: https://symfonycasts.com/sc...

Cheers!

Reply
Alexey R. Avatar

Yes, but it not helped.

Reply

Hmm, that's weird, probably is because of something internal of that plugin. Try doing the other option explained here: https://symfonycasts.com/sc...

Reply
Default user avatar
Default user avatar bharatesh regoudar | posted 4 years ago

Hi there!

Firstly great article, thanks to author.
As I am new to symfony 4.x with encore. From beginning of this tutorial all went smooth. But got struck in the following issue.


/**
* add js libs
*/
require('bootstrap');

/**
* add css files
*/
require('bootstrap/dist/css/bootstrap.css');
require('../css/carousel.css');

$(document).ready(function() {
console.log('Test loading...!');
});

in the file named home.js

yarn encore dev works fine build the files.

But when I include them in the twig file. It is not able to run the javascript.


{% block javascripts %}

{#
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://getbootstrap.com/docs/4.1/assets/js/vendor/popper.min.js" type="text/javascript"></script>
<script src="https://getbootstrap.com/docs/4.1/dist/js/bootstrap.min.js" type="text/javascript"></script>
#}

{#
#}
<script src="{{ asset('build/home.js') }}"></script>
{% endblock %}

in the base.html.twig

to resolve javascript issue, as mentioned in the tutorial, I changed webpack.config.js file as below.


var Encore = require('@symfony/webpack-encore');

Encore
// directory where compiled assets will be stored
.setOutputPath('public/build/')
// public path used by the web server to access the output path
.setPublicPath('/build')
// only needed for CDN's or sub-directory deploy
//.setManifestKeyPrefix('build/')

/*
* ENTRY CONFIG
*
* Add 1 entry for each "page" of your app
* (including one that's included on every page - e.g. "app")
*
* Each entry will result in one JavaScript file (e.g. app.js)
* and one CSS file (e.g. app.css) if you JavaScript imports CSS.
*/
.addEntry('home', './assets/js/home.js')
//.addEntry('page1', './assets/js/page1.js')
//.addEntry('page2', './assets/js/page2.js')

// will require an extra script tag for runtime.js
// but, you probably want this, unless you're building a single-page app
.enableSingleRuntimeChunk()

/*
* FEATURE CONFIG
*
* Enable & configure other features below. For a full
* list of features, see:
* https://symfony.com/doc/current/frontend.html#adding-more-features
*/
.cleanupOutputBeforeBuild()
.enableBuildNotifications()
.enableSourceMaps(!Encore.isProduction())
// enables hashed filenames (e.g. app.abc123.css)
.enableVersioning(Encore.isProduction())

// enables Sass/SCSS support
//.enableSassLoader()

// uncomment if you use TypeScript
//.enableTypeScriptLoader()

// uncomment if you're having problems with a jQuery plugin
.autoProvidejQuery()
;

module.exports = Encore.getWebpackConfig();

Any clue would be helpful.

Reply

Hi bharatesh regoudar!

Welcome to Encore :). There is a learning curve to the "new way of doing JavaScript", bit it is TOTALLY worth it!

So, I think I see the problem. Basically, you should *remove* the script tags in your template for jquery, pooper and bootstrap. Instead, you should require all of these from inside your home.js file! First, install everything:


yarn add --dev jquery bootstrap popper.js

Then, require things in home.js


// behind the scenes, bootstrap will require "jquery" and add all the new functions to it
// so, after this, you should be able to do things like $('.some-element').tooltip(), for example
require('bootstrap');

// make sure to require jQuery and set it to a $ variable so you can use it below
var $ = require('jquery');

require('bootstrap/dist/css/bootstrap.css');
require('../css/carousel.css');

$(document).ready(function() {
console.log('Test loading...!');
});

Also, there is one more thing that is new to Encore that may be causing you problem: while following this tutorial, comment-out the .enableSingleRuntimeChunk() line OR add a new script tag that points to a new runtime.js file:


<script src="{{ asset('build/runtime.js') }}"></script>
<script src="{{ asset('build/home.js') }}"></script>

You're coding through this tutorial using a newer version of Encore, which has a few improvements. You can also, if you want, use some new Twig functions to print these script tags for you. The above 2 script tags could be replaced with:


{{ encore_entry_script_tags('home') }}

If you have more questions, let us know!

Cheers!

Reply

That's a good one. Lol

Reply
Default user avatar
Default user avatar bharatesh regoudar | weaverryan | posted 4 years ago

Thank you very much. Inclusion of runtime.js helped in solving the issue.

Reply
Jose M. Avatar
Jose M. Avatar Jose M. | posted 4 years ago

Awesome tutorial.... I have one question, if I have two different files to be included in the same page, and each file use internally jquery, after webpack encore this files, each file will included jquery?

Reply

Hey Jose M.!

Very good! You are 100% correct! And so, we need to solve that :). We do that with createSharedEntry() - we talk about it a bit later - https://symfonycasts.com/sc...

In the next version of Encore (released about 5 hours ago), there will also be a different way to handle this called splitChunks(). Look for a blog post on Symfony.com tomorrow about it.

Cheers!

Reply
Shaun T. Avatar
Shaun T. Avatar Shaun T. | posted 4 years ago

Hey guys?

Is it possible to call a Javascript function from a twig template?

This is my twig template:

{% extends 'base.html.twig' %}

{% block body %}
<div class="row">
<div class="col-12 col-md-8 js-post-show" data-post-id="{{ id }}"></div>
<div class="col-12 col-md-4 js-post-sidebar"></div>
</div>
<script>
$(document).ready(function(){
loadPost();
});
</script>
{% endblock %}

{% block javascripts %}
{{ parent() }}
<script src="{{ asset('build/js/post_show.js') }}"></script>
<script src="{{ asset('build/js/following.js') }}"></script>
{% endblock %}

This is my webpack.config.js

// webpack.config.js
var Encore = require('@symfony/webpack-encore');

Encore
.setOutputPath('public/build/')
.setPublicPath('/build')
.cleanupOutputBeforeBuild()
.enableSourceMaps(!Encore.isProduction())
.autoProvidejQuery()
.addEntry('js/app', [
'./assets/js/app.js',
'./node_modules/jquery/dist/jquery.slim.js',
'./node_modules/popper.js/dist/popper.min.js',
'./node_modules/bootstrap/dist/js/bootstrap.min.js',
'./node_modules/underscore/underscore-min.js',
])
.addEntry('js/header', './assets/js/header.js')
.addEntry('js/login', './assets/js/login.js')
.addEntry('js/post_list', './assets/js/post_list.js')
.addEntry('js/post_show', './assets/js/post_show.js')
.addEntry('js/following', './assets/js/following.js')
.addEntry('js/home', './assets/js/home.js')
.addEntry('js/notification_list', './assets/js/notification_list.js')
.addStyleEntry('css/app', [
'./node_modules/bootstrap/dist/css/bootstrap.min.css',
'./assets/css/app.css',
])
;

module.exports = Encore.getWebpackConfig();

And this is my assets/js/app.js

require('../css/app.css');
require('@fortawesome/fontawesome-free/css/all.min.css');
require('@fortawesome/fontawesome-free/js/all.min.js');

const $ = require('jquery');

// create global $ and jQuery variables
global.$ = global.jQuery = $;

global.userId = '';

However I try to load the twig template, I get the following error message:

Uncaught ReferenceError: $ is not defined

Reply

Hi Shaun T.,

It could be possible if you move your javascripts entry's in `base.html.twig` to the <head> block.

Cheers!

Reply
Shaun T. Avatar

Thanks Vladimir Sadicov, I tried that but I now get:

jquery.js:3827 Uncaught ReferenceError: loadPost is not defined

Reply

Hey Shaun!

Vladimir was right about the first part. An alternative solution is to keep your script tag where it was originally, but move the code you’re trying to execute down into the javascripts block. Basically, you need to make sure your script tag is included before you run your code.

About the second issue. Basically, this global function doesn’t exist! When you create a function in the world of Webpack, it does not become a global variable. You could force it with something like global.myFunc = function()

But, there’s a better way :). If you want to run some JavaScript on page load of a specific page, just out that code inside that page’s entry file. There’s no reason to put this JavaScript inside Twig, and I think it’s making your life more difficult.

If you have any issues - let us know! Webpack is a different “way” of thinking - but it’s worth it :).

Reply
Shaun T. Avatar

Thanks Ryan :)

The reason I wanted to call the Javascript function from the twig template is that there are functions in the javascript file that are also required on another page (edited).

I have post.html.twig and profile.html.twig which both show posts and use common functions within post_show.js

The way I got around this was to add the following to the top of post_show.js

if (window.location.pathname.match("/app/post")) {
loadPost();
} else if(window.location.pathname.match("/app/profile")) {
loadPostsByUser();
}

Thanks again for you help!

Reply

Hey Shaun T.

A better way to do it would be to create a utility file where all the logic for loading posts live, and then, create an entry file per page where you can call directly the methods you need. at the end you would end up with 3 files (2 entry files, and 1 for the functionality).

Cheers!

Reply
Shaun T. Avatar

Thanks Diego :)

Reply
Authoritas Avatar
Authoritas Avatar Authoritas | posted 4 years ago

👍 on these tutorials. They're the best I've found on Symfony anywhere on the web.

However, I have this issue I was hoping you'd be able to help with, Ryan.

I hate to think how long I've spent on trying to fix an issue with another jQuery plugin - in this case, jqgrid (http://www.trirand.com/blog/ and http://www.trirand.com/jqgr.... Still no joy. 😣


#webpack.config.js

const Encore = require('@symfony/webpack-encore');

// const isProduction = process.env.NODE_ENV === 'production';
const CopyWebpackPlugin = require('copy-webpack-plugin');

Encore
// the project directory where all compiled assets will be stored
.setOutputPath('public/build/')

// the public path used by the web server to access the previous directory
.setPublicPath('/build')

.createSharedEntry('layout', './assets/js/layout.js')
.addEntry('app', './assets/js/app.js')
.addEntry('tables', './assets/js/tables.js')

.enableBuildNotifications()

// fixes modules that expect jQuery to be global
.autoProvidejQuery()

.addPlugin(new CopyWebpackPlugin([
// copies to {output}/static
{ from: './assets/static', to: 'static' }
]))

.autoProvideVariables({
$: "jquery",
jQuery: "jquery",
jqGrid: "jqGrid",
jqgrid: "jqgrid"
})

.enableSassLoader()

.enableSourceMaps(!Encore.isProduction())

.cleanupOutputBeforeBuild()

.enableVersioning()
;

// export the final configuration
module.exports = Encore.getWebpackConfig();


'use strict';

...

// $ = window.$ = window.jQuery = require("jquery");
global.$ = global.jQuery = require('jquery');
require('jquery-ui');
...

window.$.jqgrid = window.$.jqGrid = require('jqgrid/js/jquery.jqGrid.src.js');

In Chrome Dev Tools' console, I get the following:


$.jqGrid
ƒ (jQuery) {
//@add
(function ($) {
"use strict";
$.jgrid = $.jgrid || {};
$.extend($.jgrid,{
version : "4.6.0",
htmlDecode : function(value){
if(value && (value===' ' || value==='…
$.jqgrid
ƒ (jQuery) {
//@add
(function ($) {
"use strict";
$.jgrid = $.jgrid || {};
$.extend($.jgrid,{
version : "4.6.0",
htmlDecode : function(value){
if(value && (value===' ' || value==='…

However, when I then try to call jqGrid on the same page:


$("#table").jqGrid({....

I get:


Uncaught TypeError: jQuery(...).jqGrid is not a function

If I click through to the actual part of the jQuery library throwing the error, it appears to be coming from:


jQuery.readyException = function( error ) {
window.setTimeout( function() {
throw error;
} );
};

🤔

So it's saying jQuery isn't ready, but it certainly is:


> jQuery
ƒ ( selector, context ) {

// The jQuery object is actually just the init constructor 'enhanced'
// Need init if jQuery is called (just allow error to be thrown if not included)
return new jQuery…

So it's the same as if it returned 'undefined'?

Any idea what is going wrong here?

Presumably it's something to do with what you mentioned about jQuery plugins expecting a global jQuery object?

Pls help, if you can. This has been driving me nuts! 🤯

Reply

Hey Authoritas

I believe you ended in an edge case where some jQuery plugins are not managing their dependencies correctly. In your webpack config replace your module export line by these:


const config = Encore.getWebpackConfig();

config.resolve.alias.jquery = path.join(__dirname, 'node_modules/jquery/dist/jquery');

module.exports = config;

If you want to know more about that problem, check this thread: https://github.com/symfony/...

I hope this fix your problem, cheers!

Reply
Default user avatar
Default user avatar Paul Hodel | posted 4 years ago

A nice one! https://bossanova.uk/jexcel and more bootstrap-like https://bossanova.uk/jexcel...

Reply
Default user avatar

I just finished the tutorial and my encore it's working great in my symfony app. However, I haver returned to the chapter 6 because I was wondering if keeping the CDN link for bootstrap it's better for a faster page speed. In theory, a CDN helps with that. I would like to know your opinion only to be sure. What can you recommend me?

Reply

Hey Cesar!

GREAT question actually :). Like many things, it's just a trade-off. By using the normal link tag to the Bootstrap CDN, you have the advantages of using a really fast CDN and this file may already be cached by the user. On the con side, this is an extra web request to fetch this asset versus making the user download just ONE CSS file that contains everything they need. And yes, if you also use a CDN for your own assets, it helps *some* of this, but there is still some trade-off. I honestly don't know which would be faster, but are probably fast enough for most cases.

Btw, Webpack actually has a feature to support using external CDN URLs (this is most relevant for JS files). You can read about "externals": https://webpack.js.org/conf.... It's basically a way for you to, for example, require('jquery'), but make Webpack smart enough NOT to package that file, because you will have a script tag already in your layout.

Cheers!

Reply
Default user avatar

Got it. Thanks Ryan.

Reply
Cat in space

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

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.2.0",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/doctrine-bundle": "^1.6", // 1.8.1
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.2
        "doctrine/doctrine-fixtures-bundle": "~3.0", // 3.0.2
        "doctrine/doctrine-migrations-bundle": "^1.2", // v1.3.1
        "doctrine/orm": "^2.5", // v2.7.2
        "friendsofsymfony/jsrouting-bundle": "^2.2", // 2.2.0
        "friendsofsymfony/user-bundle": "dev-master", // dev-master
        "sensio/framework-extra-bundle": "^5.1", // v5.1.5
        "symfony/asset": "^4.0", // v4.0.4
        "symfony/console": "^4.0", // v4.0.4
        "symfony/flex": "^1.0", // v1.17.6
        "symfony/form": "^4.0", // v4.0.4
        "symfony/framework-bundle": "^4.0", // v4.0.4
        "symfony/lts": "^4@dev", // dev-master
        "symfony/monolog-bundle": "^3.1", // v3.1.2
        "symfony/polyfill-apcu": "^1.0", // v1.7.0
        "symfony/serializer-pack": "^1.0", // v1.0.1
        "symfony/swiftmailer-bundle": "^3.1", // v3.1.6
        "symfony/twig-bundle": "^4.0", // v4.0.4
        "symfony/validator": "^4.0", // v4.0.4
        "symfony/yaml": "^4.0", // v4.0.4
        "twig/twig": "2.10.*" // v2.10.0
    },
    "require-dev": {
        "symfony/debug-pack": "^1.0", // v1.0.4
        "symfony/dotenv": "^4.0", // v4.0.4
        "symfony/phpunit-bridge": "^4.0", // v4.0.4
        "symfony/web-server-bundle": "^4.0" // v4.0.4
    }
}

What JavaScript libraries does this tutorial use?

// package.json
{
    "devDependencies": {
        "@symfony/webpack-encore": "^0.19.0", // 0.19.0
        "bootstrap": "3", // 3.3.7
        "copy-webpack-plugin": "^4.4.1", // 4.4.1
        "font-awesome": "4", // 4.7.0
        "jquery": "^3.3.1", // 3.3.1
        "node-sass": "^4.7.2", // 4.7.2
        "sass-loader": "^6.0.6", // 6.0.6
        "sweetalert2": "^7.11.0", // 7.11.0
        "webpack-notifier": "^1.5.1" // 1.5.1
    }
}