Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Importing External Libraries & Global Variables

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.

We already added the app entry files to our base layout: the <script> tag and the <link> tag both live here:

<!doctype html>
<html lang="en">
<head>
... lines 5 - 8
{% block stylesheets %}
{{ encore_entry_link_tags('app') }}
... lines 11 - 14
{% endblock %}
</head>
<body>
... lines 19 - 90
{% block javascripts %}
{{ encore_entry_script_tags('app') }}
... lines 93 - 105
{% endblock %}
</body>
</html>

This means that any time we have some CSS or JavaScript that should be included on every page, we can put it in app.js.

Look down at the bottom:

<!doctype html>
<html lang="en">
... lines 3 - 17
<body>
... lines 19 - 90
{% block javascripts %}
{{ encore_entry_script_tags('app') }}
<script src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js" integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script>
<script>
$('.dropdown-toggle').dropdown();
$('.custom-file-input').on('change', function(event) {
var inputFile = event.currentTarget;
$(inputFile).parent()
.find('.custom-file-label')
.html(inputFile.files[0].name);
});
</script>
{% endblock %}
</body>
</html>

Ah... we have a few script tags for external files and some inline JavaScript. Shame on me! Let's refactor all of this into our new Encore-powered system.

The first thing we include is jQuery... which makes sense because we're using it below. Great! Get rid of it. Not surprisingly... this gives us a nice, big error:

$ is not defined

Installing a Library (jQuery)

No worries! One of the most wondrous things about modern JavaScript is that we can install third-party libraries properly. I mean, with a package manager. Find your terminal and run:

yarn add jquery --dev

The --dev part isn't important. Technically we only need these files during the "build" process... they don't need to be included on production... which is why the --dev makes sense. But in 99% of the cases, it doesn't matter. We'll talk about production builds and deploying at the end of the tutorial.

And... that was painless! We now have jQuery in our app.

Importing a Third-Party Library

We already know how to import a file that lives in a directory next to us. To import a third party library, we can say import $ from, and then the name of the package: jquery:

15 lines assets/js/app.js
... lines 1 - 7
// any CSS you require will output into a single css file (app.css in this case)
import '../css/app.css';
import $ from 'jquery';
... lines 12 - 15

The critical thing is that there is no . or ./ at the start. If the path starts with a ., Webpack knows to look for that file relative to this one. If there is no ., it knows to look for it inside the node_modules/ directory.

Check it out: open node_modules/ and ... there's it is! A jquery directory! But how does it know exactly which file in here to import? I'm so glad you asked! Open jQuery's package.json file. Every JavaScript package you install... unless it's seriously ancient, will have a main key that tells Webpack exactly which file it should import. We just say import 'jquery', but it really imports this specific file.

Global Variables inside Webpack

Cool! We've imported jQuery in app.js and set it to a $ variable. And because that <script> tag is included above our inline code in base.html.twig:

<!doctype html>
<html lang="en">
... lines 3 - 17
<body>
... lines 19 - 90
{% block javascripts %}
{{ encore_entry_script_tags('app') }}
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js" integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script>
<script>
$('.dropdown-toggle').dropdown();
$('.custom-file-input').on('change', function(event) {
var inputFile = event.currentTarget;
$(inputFile).parent()
.find('.custom-file-label')
.html(inputFile.files[0].name);
});
</script>
{% endblock %}
</body>
</html>

The $ variable should be available down here, right?

Nope! $ is still not defined! Wait, the second error is more clear. Yep, $ is not defined, coming from our code in base.html.twig.

This uncovers a super important detail. When you import a file from a 3rd party library, that file behaves differently than if you add a <script> tag on your page that points to the exact same file! Yea!

That's because a well-written library will contain code that detects how it's being used and then changes its behavior.

Check it out: open jquery.js. It's not super easy to read, but look at this: if typeof module.exports === "object". That's key. This is jQuery detecting if it's being used from within an environment like Webpack. If it is, it exports the jQuery object in the same way that we're exporting a function from the get_nice_message.js file:

export default function(exclamationCount) {
... line 2
};

But if we are not in a module-friendly environment like Webpack... specifically, if jQuery is being loaded via a script tag in our browser, it's not too obvious, but this code is creating a global variable.

So, if jQuery is in a script tag, we get a global $ variable. But if you import it like we're doing here:

15 lines assets/js/app.js
... lines 1 - 10
import $ from 'jquery';
... lines 12 - 15

It does not create a global variable. It returns the jQuery object, which is then set on this local variable. Also, all modules... or "files", in Webpack live in "isolation": if you set a variable in one file, it won't be available in any other file, regardless of what order they're loaded.

That is probably the biggest thing to re-learn in Webpack. Global variables are dead. That's awesome. But it also changes everything.

Forcing a Global jQuery Variable

The ultimate solution is to refactor all of your code from your templates and un-Webpack-ified JavaScript files into Encore. But... if you're upgrading an existing site, phew! You probably have a ton of JavaScript that expects there to be global $ or jQuery variables. Moving all of that into Encore all at once... it's, uh... not very realistic.

So, if you really want a global variable, you can add one with global.$ = $:

16 lines assets/js/app.js
... lines 1 - 10
import $ from 'jquery';
global.$ = $;
... lines 13 - 16

That global keyword is special to Webpack. Try it now: refresh! It works!

But... don't do this unless you have to. I'll remove it and add some comments to explain that this is useful for legacy code:

17 lines assets/js/app.js
... lines 1 - 10
import $ from 'jquery';
// uncomment if you have legacy code that needs global variables
//global.$ = $;
... lines 14 - 17

Let's properly finish this next by refactoring all our code into app.js, which will include installing two more libraries and our first jQuery plugin... It turns out that jQuery plugins are a special beast.

Leave a comment!

18
Login or Register to join the conversation
Krzysztof K. Avatar
Krzysztof K. Avatar Krzysztof K. | posted 4 years ago

seems that IE11 still cannot see jQuery :/

Update: ok works, IE is sometimes is very confusing, something else was failing but it was not obvious for me ;)

1 Reply

Hey Krzysztof,

Thanks for updating on it, good to know it works :)

Cheers!

Reply
Pascal Avatar

Hey friends, i'm getting desperate, i'm trying to load anime.js via app.js. but i always get after the compilation in the browser that anime is not defined.
I have tried import ,require and global nothing helps. Does anyone have any idea how i can load anime.js with encore, via CDN it works great ....

Greets Pascal

Reply

Hey Pascal!

Ah, that's no fun! Ok, let's do some debugging! My guess is that you have something like this:


// app.js

// I'm not sure why you need this exact path - it must be due to how the library is set up
// I got this from their docs
import anime from 'animejs/lib/anime.es.js';

global.anime = anime;

Is that about right? If so, that should work. So check your HTML source on your page. Do your script tags have a "defer" attribute on them? If so, that's the problem. To fix it, set this line to false: https://github.com/symfony/recipes/blob/master/symfony/webpack-encore-bundle/1.9/config/packages/webpack_encore.yaml#L9

If I'm correct, here is some more info about the change: https://symfony.com/blog/moving-script-inside-head-and-the-defer-attribute

If I'm not correct... let me know ;).

Cheers!

Reply
Pascal Avatar
Pascal Avatar Pascal | weaverryan | posted 2 years ago | edited

Hey weaverryan , thanks for your help. It works now . :D

Reply
shulambert Avatar
shulambert Avatar shulambert | posted 2 years ago

Hello,
I have a script nioapp.js. But when importing it inside another script (script.js) using parameter NioApp, this error happens

Uncaught TypeError: Cannot set property 'name' of undefined

But when copying the content of nioapp.js inside script.js, it works. Any idea on how to solve this issue ?

Thanks.

Reply

Hi @Shu!

Hmm. It looks like the nioapp.js script isn't setup to work well with "modules". It doesn't export a value, which is why your import doesn't work. For these old scripts, it's pretty common for them to at least set a global variable, but I can't tell if that is true in this case or not. If it IS, you should be able to do this:


import './vendors/nioapp/nioapp.min';

// now immediately start referencing NioApp because nioapp.min.js set it as a global variable

But I'm guessing that NioApp is set as a global variable - I'm not sure. It many only be set as a "local" variable in nioapp.min.js, which is why you can't access it from script.js. And... if that's the case, there's nothing you can really do but copy nioapp.min.js into your code or fix nioapp.min.js to export a value.

Cheers!

Reply
fd Avatar
fd Avatar fd | posted 3 years ago | edited

When i try to yarn install it give me the following error:

`error Couldn't find package "@xtuc/long@4.2.2" required by "@webassemblyjs/wast-parser@1.9.0" on the "npm" registry.
info Visit https://yarnpkg.com/en/docs/cli/install for documentation about this command.
/code>

Reply
weaverryan Avatar weaverryan | SFCASTS | fd | posted 3 years ago | edited

Hey fd!

Hmm. Is this when you run yarn add jquery or just when you run yarn install? Are you using the "start" code in the project?

The interesting thing is that the package - @xtuc/long DOES exist... and version 4.2.2 exists - https://www.npmjs.com/package/@xtuc/long/v/4.2.2 - so the error doesn't make sense. From some quick searching, I've seen a few users that needed to their .npmrc file - https://github.com/facebook/create-react-app/issues/7466#issuecomment-519922774 - which is an npm config file that can live in a few places - https://docs.npmjs.com/configuring-npm/npmrc.html#files

That is a total guess though - something is somehow wrong with the communication to the npm registry... or with yarn... or something else. Sorry I can't be more helpful!

Cheers!

Reply
Kevin S. Avatar
Kevin S. Avatar Kevin S. | posted 3 years ago

I have this error :
Cannot find module 'jquery'
But jquery folder exists in node_modules.
What can I do about this?
Thanks !

Reply

Hey Kevin,

Try to completely remove node_modules/ directory, install it again with yarn install, and try again. If the problem is still persist - please, give us a bit more context about what exactly you're trying to do when you see this error :)

Cheers!

Reply
Bertin Avatar

Hi!.
Still not getting the --dev part. It will add the package to devDependencies. I though this was used for dev only (something like composer require-dev).

Like said in the video the --dev isn't important, technically only need these files during the "build process" and don't need to be included on production.
Is build process not the same as production?

Could you explain further?

Reply

Hey Bertin,

Yeah, it might be confusing some times... but really, don't think too much about it, because it's not really important. And yes, you totally can put all your node deps into the "dependencies" section and it will be totally ok. However, let me try to explain the point of you behind of this. First of all, Webpack Encore just build all the files you need on production, and after you got all the files in the public/build/ directory generated by Encore you can forget about package.json at all. Well, this mean that you can totally remove your node_modules/ folder along with the package.json file, because you don't care, you don't need it. Everything, really *everything* you need on production was put into that public/build/ directory. This means that you do NOT use Node packages on production at all. And if so - all your dependencies in package.json are just dev dependencies and therefore should be in devDependencies section. The exception is when you do really use a Node library on production, like calling a Node script via PHP process for do something - in this case, that library should be in "dependencies" section, agree.

So, to recap, as long as you use only Webpack Encore that builds all the assets for you, you even don't need a NodeJS installed on your production server. You can totally build the assets locally, in your *dev* environment, then commit them to the repo and then use it on production even without having node_modules/ and package.json there. That's it. And actually that's exactly what devs are doing in Symfony Demo: https://github.com/symfony/... - when someone changes assets, then assets are rebuild with Webpack Encore and commit directly to the repo. And this allows other devs running Symfony Demo application without even having NodeJS on his laptops because assets are already built and committed to the repository

I hope this helps to understand ;)

Cheers!

1 Reply
Beis Avatar

Hi! Importing jQuery, generates me another file called: vendor~app.js . Why this? I have seen thats it's jQuery inside and not inside app.js.. Should I remove it to follow next videos? Thanks!

Reply

Hey Beis

That file is generated because splitEntryChunks() feature is enabled, what it basically does is to split your assets into smaller files based on algorithm. You can learn more about it in this chapter: https://symfonycasts.com/screencast/webpack-encore/code-splitting

Cheers!

Reply

import $ from 'jquery' doesn't work for me i get the error while compiling:
import $ from 'jquery';
^

SyntaxError: Unexpected identifier
at new Script (vm.js:80:7)
at NativeCompileCache._moduleCompile (C:\Users\Giaco\Desktop\TribunaleMinori\TribunaleMinori\node_modules\v8-compile-cache\v8-compile-cache.js:240:18)
at Module._compile (C:\Users\Giaco\Desktop\TribunaleMinori\TribunaleMinori\node_modules\v8-compile-cache\v8-compile-cache.js:186:36)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:712:10)
at Module.load (internal/modules/cjs/loader.js:600:32)
at tryModuleLoad (internal/modules/cjs/loader.js:539:12)
at Function.Module._load (internal/modules/cjs/loader.js:531:3)
at Module.require (internal/modules/cjs/loader.js:637:17)
at require (C:\Users\Giaco\Desktop\TribunaleMinori\TribunaleMinori\node_modules\v8-compile-cache\v8-compile-cache.js:161:20)
at WEBPACK_OPTIONS (C:\Users\Giaco\Desktop\TribunaleMinori\TribunaleMinori\node_modules\webpack-cli\bin\utils\convert-argv.js:115:13)
at requireConfig (C:\Users\Giaco\Desktop\TribunaleMinori\TribunaleMinori\node_modules\webpack-cli\bin\utils\convert-argv.js:117:6)
at C:\Users\Giaco\Desktop\TribunaleMinori\TribunaleMinori\node_modules\webpack-cli\bin\utils\convert-argv.js:124:17
at Array.forEach (<anonymous>)
at module.exports (C:\Users\Giaco\Desktop\TribunaleMinori\TribunaleMinori\node_modules\webpack-cli\bin\utils\convert-argv.js:122:15)
at yargs.parse (C:\Users\Giaco\Desktop\TribunaleMinori\TribunaleMinori\node_modules\webpack-cli\bin\cli.js:71:45)
at Object.parse (C:\Users\Giaco\Desktop\TribunaleMinori\TribunaleMinori\node_modules\yargs\yargs.js:567:18)
at C:\Users\Giaco\Desktop\TribunaleMinori\TribunaleMinori\node_modules\webpack-cli\bin\cli.js:49:8
at Object.<anonymous> (C:\Users\Giaco\Desktop\TribunaleMinori\TribunaleMinori\node_modules\webpack-cli\bin\cli.js:375:3)
at Module._compile (internal/modules/cjs/loader.js:701:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:712:10)
at Module.load (internal/modules/cjs/loader.js:600:32)
at tryModuleLoad (internal/modules/cjs/loader.js:539:12)
at Function.Module._load (internal/modules/cjs/loader.js:531:3)
at Module.require (internal/modules/cjs/loader.js:637:17)
at require (internal/modules/cjs/helpers.js:22:18)
at Object.<anonymous> (C:\Users\Giaco\Desktop\TribunaleMinori\TribunaleMinori\node_modules\webpack\bin\webpack.js:156:2)
at Module._compile (internal/modules/cjs/loader.js:701:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:712:10)
at Module.load (internal/modules/cjs/loader.js:600:32)
at tryModuleLoad (internal/modules/cjs/loader.js:539:12)

Reply

Hey Giacomo,

Could you explain a bit more what exactly you're doing when see this error? Because "import" syntax works only on server side. But if you try to do this import on client side, i.e. just point to this source file (without processing it with Webpack) in your HTML - it will fail.

Cheers!

Reply
Cat in space

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

This tutorial works great with Symfony5!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-iconv": "*",
        "aws/aws-sdk-php": "^3.87", // 3.91.4
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.7", // 1.7.1
        "knplabs/knp-paginator-bundle": "^2.7", // v2.8.0
        "knplabs/knp-time-bundle": "^1.8", // 1.9.0
        "league/flysystem-aws-s3-v3": "^1.0", // 1.0.22
        "league/flysystem-cached-adapter": "^1.0", // 1.0.9
        "liip/imagine-bundle": "^2.1", // 2.1.0
        "nexylan/slack-bundle": "^2.0,<2.2.0", // v2.1.0
        "oneup/flysystem-bundle": "^3.0", // 3.0.3
        "php-http/guzzle6-adapter": "^1.1", // v1.1.1
        "sensio/framework-extra-bundle": "^5.1", // v5.3.1
        "stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
        "symfony/asset": "^4.0", // v4.2.5
        "symfony/console": "^4.0", // v4.2.5
        "symfony/flex": "^1.9", // v1.17.6
        "symfony/form": "^4.0", // v4.2.5
        "symfony/framework-bundle": "^4.0", // v4.2.5
        "symfony/orm-pack": "^1.0", // v1.0.6
        "symfony/security-bundle": "^4.0", // v4.2.5
        "symfony/serializer-pack": "^1.0", // v1.0.2
        "symfony/twig-bundle": "^4.0", // v4.2.5
        "symfony/validator": "^4.0", // v4.2.5
        "symfony/web-server-bundle": "^4.0", // v4.2.5
        "symfony/webpack-encore-bundle": "^1.4", // v1.5.0
        "symfony/yaml": "^4.0", // v4.2.5
        "twig/extensions": "^1.5" // v1.5.4
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.1.0
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.7
        "fzaninotto/faker": "^1.7", // v1.8.0
        "symfony/debug-bundle": "^3.3|^4.0", // v4.2.5
        "symfony/dotenv": "^4.0", // v4.2.5
        "symfony/maker-bundle": "^1.0", // v1.11.5
        "symfony/monolog-bundle": "^3.0", // v3.3.1
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.2.5
        "symfony/profiler-pack": "^1.0", // v1.0.4
        "symfony/var-dumper": "^3.3|^4.0" // v4.2.5
    }
}

What JavaScript libraries does this tutorial use?

// package.json
{
    "devDependencies": {
        "@symfony/webpack-encore": "^0.27.0", // 0.27.0
        "autocomplete.js": "^0.36.0",
        "autoprefixer": "^9.5.1", // 9.5.1
        "bootstrap": "^4.3.1", // 4.3.1
        "core-js": "^3.0.0", // 3.0.1
        "dropzone": "^5.5.1", // 5.5.1
        "font-awesome": "^4.7.0", // 4.7.0
        "jquery": "^3.4.0", // 3.4.0
        "popper.js": "^1.15.0",
        "postcss-loader": "^3.0.0", // 3.0.0
        "sass": "^1.29.0", // 1.29.0
        "sass-loader": "^7.0.1", // 7.3.1
        "sortablejs": "^1.8.4", // 1.8.4
        "webpack-notifier": "^1.6.0" // 1.7.0
    }
}
userVoice