Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

watch + Install jQuery with Yarn

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

The downside of using something like Webpack - or any tool that builds your assets - is that every time you make a change, you have to re-run webpack. That's lame.

Fortunately, like many tools, Webpack has a "watch mode", and it just works. Right now, if I click delete, it says, "Delete this log?"

Re-run webpack, but this time with a --watch flag:

./node_modules/.bin/webpack --watch

It still builds the assets... but then waits. It's now constantly watching all your files for changes. Creepy.

Put it to the test: in RepLogApp.js, add a few more question marks to the delete modal for emphasis and save:

... lines 1 - 4
(function(window, $, Routing, swal) {
... lines 6 - 8
class RepLogApp {
... lines 10 - 59
handleRepLogDelete(e) {
... lines 61 - 64
swal({
title: 'Delete this log???',
... lines 67 - 72
});
}
... lines 75 - 196
}
... lines 198 - 215
})(window, jQuery, Routing, swal);

Refresh the page and.... we instantly see the change.

This is nothing earth-shattering... it's just a tool we need... and Webpack provides it without any setup. In the terminal, you can even see that it re-dumped our file.

There is one caveat with watch: and we'll see it a few times. If you make a change to the webpack.config.js file itself, you will need to manually restart webpack. Webpack watch does not detect changes to itself.

The Classic Problem with JS Dependencies

Ok, it's time to do something completely different... and unlock a massively powerful new tool. I'm going to say that a lot in this tutorial... and I mean it every time!

Right now, RepLogApp only works because jQuery is available globally:

... lines 1 - 4
(function(window, $, Routing, swal) {
... lines 6 - 215
})(window, jQuery, Routing, swal);

We have a self-executing function, which expects jQuery to be a global variable by the time this file is loaded. That works because, in our base layout - app/Resources/views/base.html.twig - we include jQuery:

... lines 1 - 98
{% block javascripts %}
<script src="https://code.jquery.com/jquery-3.1.1.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
... lines 101 - 105
{% endblock %}
... lines 107 - 110

This gives us two global variables - $ and jQuery - which we use everywhere else.

And this is the big, huge, classic, horrifying problem we want to fix: when we write JavaScript, our code is not self-contained. Nope, we need to very carefully make sure to remember to include any dependent script tags before loading our JavaScript. If you forget to add a script tag or add it in the wrong order... bam! Your user sees an error. And you see your face hitting your palm.

This is a terrible and unprofessional way to live. But with Webpack, we can finally fix this. Instead of crossing your fingers and hoping that jQuery is included on this page, in RepLogApp.js, we will require it.

Installing jQuery via Yarn

In our terminal, we already used yarn earlier to install webpack. But we can also use it to install other, front-end libraries... like jQuery. Try it:

yarn add jquery --dev

And just like that! jQuery now lives inside node_modules/:

13 lines package.json
{
... lines 2 - 7
"devDependencies": {
"jquery": "^3.2.1",
... line 10
}
}

That's great! Because we can now require it like any other module.

At the top of RepLogApp.js, add const $ = require('jquery'):

... lines 1 - 2
const Helper = require('./RepLogHelper');
const $ = require('jquery');
... lines 5 - 218

For simplicity, we can remove the $ from the self-executing function and the jQuery argument at the bottom:

... lines 1 - 3
const $ = require('jquery');
(function(window, Routing, swal) {
... lines 7 - 216
})(window, Routing, swal);

Now, we require jQuery from node_modules and assign it to the $ variable. Whenever we reference $ in this file, that required value is used. We are no longer dependent on whether or not a global $ or jQuery variable exists.

What Happens when you Require a Module

There are two very important things I want to point out. First, we just said require('jquery'):

... lines 1 - 3
const $ = require('jquery');
... lines 5 - 218

We now know that because this does not start with ., Node knows to look for a core library - like it did with path - or to look inside node_modules/, which is what happens this time. That's perfect!

The second very, very, very important thing is that jQuery acts differently, depending on whether or not it's being included with a traditional script tag - like in our layout - or if it's being required by a module system. This is a very common thing you'll see.

Let me show you: I'll hold command to click into the jquery module itself. Yea, it's a bit hard to read. But, on top, it checks to see if typeof module === "object", and typeof module.exports === "object". Basically jQuery is checking whether or not it is being used inside of a module environment, called a commonjs environment.

If it is, it does not create global jQuery or $ variables. Nope, instead it uses module.exports to export the jQuery function. This is really important: it means that when you require jquery, it returns the jQuery function... but does not actually create a global variable. This is a pivotal difference between classic JavaScript and modern JavaScript: instead of creating and using global variables, we require modules and export things from these modules.

Phew! Let's try this out already! In my terminal, I'll check out my watch tab. Yep, it's still working, and should have already detected our new require() and dumped the new file.

Back in the browser, refresh! Yes! Everything still works! It's not super obvious yet, but RepLogApp is now using the required jQuery instead of the global script tag.

But, I can't remove that script tag from the base layout yet... because we have other pages that depend on it. And yes, that means that - until we fix this - our users are downloading jQuery twice: once in the base layout and again when they download rep_log.js.

Open up that file: web/build/rep_log.js. Yep, most of this file is now jQuery.

Now that we have this super power, let's repeat it with SweetAlert... and discover one lingering issue.

Leave a comment!

12
Login or Register to join the conversation
Deuklyoung K. Avatar
Deuklyoung K. Avatar Deuklyoung K. | posted 4 years ago

solution of watch no working in Windows Subsystem for Linux

https://github.com/webpack/...

Add this to your webpack configuration file:

watchOptions: {
poll: true
},

1 Reply

Hey Deuklyoung K. ,

Thanks for sharing this info for our Windows users! Might be useful

Cheers!

Reply

Why use --dev in yarn add jquery --dev? You are going to use jQuery in the final bundle, it should not be included as --dev.

Reply

Hey Yuri,

That's a controversial question. Technically, you won't use it from the node_modules directory/ on production :) Actually, you may not have this node_modules/ directory at all on your production server because *everything* is packed with Webpack Encore into public/build/ directory. This is the only files you need on production, and so node_modules/ including jQuery and other deps are just needed during the build stage that means it's just dev dependencies.

But it does not important at all, you can add those deps without `--dev` and it will still work. But separate your Node deps with --dev or not is only useful for node applications, we have a PHP application here :)

I hope this clearer for you know.

Cheers!

Reply

Thanks, Victor. Yep, I've already found your reply to the similar question under another video here. I see your point and agree. It looks like, really, it depends. Here is some useful conversation and info: https://github.com/webpack/...

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

But what if I want to use jQuery in every page? it sucks to require it in every script file, it is code repetition. How to solve this?

Reply

Hey Coder,

Actually, it's not code repetition, it's just only one line of code. And it's a good idea to explicitly show your dependencies, so if all your JS files requires jQuery, so it's OK to show it, i.e. require it in every file explicitly. You do the same with Symfony services, I bet you inject entity manager in many services - but that's a good practice :)

Cheers!

Reply

hello Ryan, for some strange reason I am not able to get webpack "watch" my files.

My setup is a Vagrant VM with Ubuntu 16.04 and PHPStorm in a MacBook Pro with High Sierra.
If I ssh into the VM and change the files with vim inside the guest machine, webpack detects the change I make.
If I change my file with PHPStorm (or any other editor in the host machine) it remains idle.
This seems weird, do you know how I could solve this rather annoying problem?

Thank you and, BTW, Happy New Year and thank you for your great tutorials!

carlo

Reply

Hey Carlo,

Hm, tricky question. It's always tricky when we're talking about virtualization :) Probably you need to wait a bit more time? Btw, where do you run webpack watch, on the guest or host machine? Are you sure you have 2 way sync of your files? I mean, as you said, vagrant syncs changes from guest machine to host machine if you do some changes on your *guest* machine. But probably changes do not sync when you do the reverse: do some changes on your *host* machine. So what type of sync do you have? I heard, NFS is probably the best 2 way syncing with a good performance, do you have a different one? See the list of available types here: https://www.vagrantup.com/d... .

Thanks! Happy New Year to you too!

Cheers!

Reply

Hi Victor,

thank you for your reply. no matter how long I wait, the watch option remains idle. I run webpack on the guest machine and yes, I have NFS and sync is bidirectional: if I change a file inside the guest, almost immediately I see the change in the host and vice versa.

I will keep investigate, thank you for your help.

Cheers

1 Reply

Hey chieroz!

Happy new year back to you! Check out this issue: https://github.com/symfony/webpack-encore/issues/194

Does it help? It's for Encore, but that ultimately, you just need to set the watchOptions key with poll: true.

Let us know if it helps!

Cheers!

Reply

hello Ryan,

YOU are the man. setting the watchOption poll:true now watch works also with VirtualBox and NFS.

thank you so much,

cheers,

carlo

7 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