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

Require Outside Libraries

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.

When you use Webpack, the hardest thing is that you need to start thinking about your JavaScript differently. You need to stop thinking about global variables, and start thinking about how you can code correctly. It's not as easy as it sounds: we've been using global variables in JavaScript... well... forever!

For example, in RepLogApp.js, we created this self-executing function to give our code a little bit of isolation:

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

That part isn't too important. But at the bottom, we are relying on there to be a global jQuery variable. It just must exist, or else everything will explode! On top, this becomes a $ variable in the function.

Open the base layout file - base.html.twig. The only reason our code works is that, at the bottom, yep! We have a script tag for jQuery, which adds a global jQuery variable:

... 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

And this is the process we've used for years: add a script tag for a JS library, then reference its global variable everywhere else.

I hate this! In RepLogApp.js, I just have to hope that jQuery was included correctly. That's madness, and it needs to stop. So, from now on, we have a new philosophy: if we need a variable in a file - like $ - then we need to require it in the same way that we are requiring Helper.

The only difference is that jQuery is a third-party library. Well... in PHP, we would use Composer to install third-party libraries. And... yea! In JavaScript, we can use Yarn to do the same thing!

Installing jQuery via Yarn

Check this out: open a third terminal tab - we're getting greedy! Then run:

yarn add jquery --dev

Yep! We can use yarn to download front-end libraries! Oh, and you can search for package names on npmjs.com or npms.io.

This downloads jquery into the node_modules/ directory and adds it to package.json:

8 lines package.json
{
"devDependencies": {
... line 3
"jquery": "^3.3.1",
... line 5
}
}

Requiring jQuery

So... how do we require it? Oh, it's awesome: const $ = require('jquery'):

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

That's it! When a require path does not start with a ., Webpack knows to look for a package in node_modules/ with that name.

And now that we are properly importing the $ variable - yay us - remove $ and jQuery from the self-executing function:

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

Yep, when we use the $ variable below, it is no longer dependent on any global jQuery variable! Responsible coding for the win!

But... does it work? Try it! Go back to our site and refresh! It does! That's because, back on the terminal, if you run:

ls -la public/build

... yep! Our rep_log.js file now has jQuery inside of it - you know because it's now 300kb! Don't worry, we'll talk about optimizations later.

But the point is this: all we need to do is require the libraries we need, and Webpack takes care of the rest!

Installing & Using SweetAlert2

Let's require one more outside package. Search for "swal". We're using a really cool library called SweetAlert to bring up the delete dialog. But... the only reason this works is that, in the template, we're including a script tag for it:

... lines 1 - 47
{% block stylesheets %}
... lines 49 - 50
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@7.11.0/dist/sweetalert2.min.css" />
{% endblock %}
... line 53
{% block javascripts %}
... lines 55 - 56
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@7.11.0/dist/sweetalert2.all.min.js"></script>
... lines 58 - 65
{% endblock %}

Boo! Let's refactor this to require that library properly.

If you search for this package, you'll find out that it's called sweetalert2. Let's install it:

yarn add sweetalert2@^7.11.0 --dev

This time, delete the script tag entirely. We can't remove the jQuery script tag yet because we're still using the global variable in a few places. But, we'll fix that soon.

Then, in RepLogApp.js, remove the argument from the self-executing function: that global variable doesn't even exist anymore!

... lines 1 - 6
(function(window, Routing) {
... lines 8 - 215
})(window, Routing);

To prove it, refresh! Awesome!

swal is not defined

To get it back, add const swal = require('sweetalert2');:

... lines 1 - 4
const swal = require('sweetalert2');
(function(window, Routing) {
... lines 8 - 215
})(window, Routing);

As soon as we save this, Webpack recompiles, we refresh and... it works! Yes! We can use any outside library by running one command and adding one require line.

Let's use our new unstoppable skills to refactor our code into re-usable components.

Leave a comment!

14
Login or Register to join the conversation
Tac-Tacelosky Avatar
Tac-Tacelosky Avatar Tac-Tacelosky | posted 4 years ago | edited

there Copy is now available: https://symfony.com/blog/en...

1 Reply
Default user avatar
Default user avatar JAVroegop | posted 5 years ago

If you ever get a warning from Yarn like this:

An unexpected error occurred: "EPERM: operation not permitted, unlink '.....\\node_modules\\.bin\\encore'"

Before you try to install jquery, please stop encore. I spent quite a while figuring out why yarn couldn't delete node_modules/.bin/encore... on my Windows 10 machine. *Doh!*

1 Reply

Hey JAVroegop

Nice tip, thanks for sharing it :)
Cheers!

Reply
Daniel W. Avatar
Daniel W. Avatar Daniel W. | posted 2 years ago | edited

I need some help.

I want to add an external js lib in the right way. This js lib needs to be in the same scope as my code in my entry.
All I have is the link the link: <script src="https://sdk.example.api.js"></script>

What I tried:

  1. Adding the scripttag in to my HTML don't work because everything in scripttags lands in globalspace and is not able see my code in my WP entry.
  2. downloading the script and importing it : import '../api.js' does not work the script throws an errormessage that it's not allowed to run the script like this and that I should load it like this: <script src="https://sdk.example.api.js"></script>

I have no idea how the script knows how its loaded but is there anything I can do?

Reply

Hey Daniel W.

Your approach of downloading the JS file and then importing it is the right way but I believe the library you're trying to use was written a way that's not compatible with Webpack. As an example, there are some old jQuery plugins that cannot be used with Webpack

Cheers!

1 Reply
Default user avatar

Hi! everyone, after installing the sweetalert2 plugin, i'm having this error in the browser. After followed along all the steps.
- Installation (yarn add sweetalert2 --dev)
- Removing the script tag from index.html.twig
- Removing the swal global variable from the function argments at the top and the bottom
- Adding the require line (const swal = require('sweetalert2');)

In chrome, i'm having this error:

Uncaught TypeError: Cannot read property 'constructor' of undefined
at SweetAlert (sweetalert2.all.js:2524)
at RepLogApp.handleRepLogDelete (RepLogApp.js:83)
at HTMLDivElement.dispatch (jquery-3.1.1.min.js:3)
at HTMLDivElement.q.handle (jquery-3.1.1.min.js:3)

In Firefox developer edition, i'm having this one:

TypeError: this is undefined[En savoir plus] rep_log.js line 21 > eval:2524:7
SweetAlert webpack:///./node_modules/sweetalert2/dist/sweetalert2.all.js?:2524
handleRepLogDelete webpack:///./public/assets/js/RepLogApp.js?:83
handleRepLogDelete self-hosted:974
jQuery 2

I've already checked on google, but I've just found something about a ".fire" function which is now available inside the "swal" object. Some said that it worked for them, but i'm so new to javascript, i don't even know where to put it in ryan's code. I've even try to install the same version on "jquery" and "sweetalert2" as him, but it doesn't change anything. I'm a symfony back-end developper (trying to become full-stack) and I'm just trying to start working with webpack since i know it's a great tool.

Don't know what to do. May i have a help please.

Thanks guys

Reply
Default user avatar

Well, i eventually found out it was just a version matter. The version above 8 seems (Cause mine installed version "sweetalert2@8.2.4") to not be working properly. So i installed the exact same version as Ryan ("sweetalert2@7.12.0"), and it does.

You can also execute "yarn add sweetalert2@^7.11.0 --dev".

Thanks

1 Reply
Victor Avatar Victor | SFCASTS | crgbe | posted 4 years ago | edited

Hey crgbe,

Thank you for getting back on this problem, it might be helpful for others.

Cheers!

Reply
Tac-Tacelosky Avatar
Tac-Tacelosky Avatar Tac-Tacelosky | posted 4 years ago

Why is sweetalert2 installed with --dev? In Symfony, using composer the --dev makes sense for debug-pack, webserver, fixtures, etc. But SweetAlert (and jQuery, etc.) are using the final app, not just during development. Obviously, it works, but I'm uncertain about when something should be installed using --dev.

Reply

Hey Michael,

Good question! Actually, all your Node dependencies are dev because nothing of them is publicly available, right? Because node_modules/ as also assets/ folders are behind the server document root, i.e. outside of public/ folder. What to expose is up to you, and you do this with Webpack Encore that moves all the *prod* assets into the public/build/ folder that is available for everyone. So, technically ALL your Node packages that you install with npm/yarn are for *dev* only. But really, it is not important at all, you can move them all to the "dependencies" section and nothing will change. Really, you need to care about "devDependencies" and "dependencies" if you write a Node JS server application.

I hope it clarify things for you.

Cheers!

Reply
Bertin Avatar
Bertin Avatar Bertin | posted 5 years ago | edited

I'am trying to install a yarn plugin called: <a target="_blank" href="https://yarnpkg.com/en/package/autoprefixer&quot;&gt; autoprefixer </a>.
Autoprefixer auto fills css for different browsers.

In my webpack.config.js i've added:


    #webpack.config.js
    .addLoader({
        test: /\.css$/,
        use: ["style-loader", "css-loader", "postcss-loader"]
    })

In my backend.js i've added


    #assets/js/backend/backend.js
'use strict';

import $ from 'jquery';
import 'bootstrap';
import 'autoprefixer';

// rest of js code here
$(document).ready(function(){

});

But when i run yarn run encore dev --watch i get errors. Hope someone could help me out.


Webpack is watching the files…

 ERROR  Failed to compile with 2 errors                                                                                                                                                19:58:37

 error  in ./assets/scss/backend/backend.scss

Module build failed: 
'use strict';
^
      Invalid CSS after "'": expected 1 selector or at-rule, was "'use strict';"
      in /node_modules/autoprefixer/lib/autoprefixer.js (line 1, column 1)

 @ ./assets/scss/backend/backend.scss 4:14-211
 @ multi ./assets/scss/backend/backend.scss

 error  in ./assets/scss/backend/backend.scss

Module build failed: ModuleBuildError: Module build failed: 
'use strict';
^
      Invalid CSS after "'": expected 1 selector or at-rule, was "'use strict';"
      in /node_modules/autoprefixer/lib/autoprefixer.js (line 1, column 1)
    at runLoaders (/node_modules/webpack/lib/NormalModule.js:195:19)
    at newwayhoogeveen/node_modules/loader-runner/lib/LoaderRunner.js:364:11
    at /node_modules/loader-runner/lib/LoaderRunner.js:230:18
    at context.callback (/node_modules/loader-runner/lib/LoaderRunner.js:111:13)
    at Object.asyncSassJobQueue.push [as callback] (/node_modules/sass-loader/lib/loader.js:76:13)
    at Object.done [as callback] node_modules/neo-async/async.js:7974:18)
    at options.error (node_modules/node-sass/lib/index.js:294:32)
Reply

Hey Bertin!

... not to ignore your question... but I might have a better solution for doing this. Encore already supports PostCss (just call enablePostCss) - I see you added this manually in your code, but this isn't necessary. PostCss (as I think you know) can be used to integrate with autoprefixer. In fact, if I remember correctly, when you call enablePostCss(), Encore will give you an error that you need to create a config file, and the config contents it suggests include the code needed to enable autoprefixer (you likely already have this config file). So basically, you add one line to webpack.config.js, add one config file, install a few packages (which Encore will tell you) and... that should be it!

Let me know if this helps!

Reply
Bertin Avatar
Bertin Avatar Bertin | weaverryan | posted 5 years ago | edited

Hey weaverryan
It works.
I just enabled enablePostCss() in webpack.config.js
When running yarn run encore dev --watch, i get the message package 'postcss-loader' missing.

After adding the package and run the comment again, it says: FIX create a postcss.config.js file.

I've created this file at the root


# at config, postcss.config.js 
module.exports = {
    plugins: {
        'autoprefixer': {},
    }
};

Now it works

And when i look in my compiled css
position: sticky; has now also position: -webkit-sticky; (created by autoPrefixer).

But i've some questions:

  1. Now i need to add the postcss.config.js file to the root but is there a way to change the path, eq to /assets/configs/

I've seen some options on git, <a href="https://github.com/symfony/webpack-encore/pull/130&quot;&gt;webpack-encore pull 130</a>

But don't now how to use it within webpack.encore.js.
See my webpack.config.js below with an example but not working


var Encore = require('@symfony/webpack-encore');
const CopyWebpackPlugin = require('copy-webpack-plugin');

Encore
    .setOutputPath('public/build/')
    .setPublicPath('/build')
    .cleanupOutputBeforeBuild()
    .autoProvidejQuery()
    .enableSourceMaps(!Encore.isProduction())
    .enableVersioning()

    .createSharedEntry('js/common', ['jquery'])
    .addEntry('js/backend/backend', './assets/js/backend/backend.js')
    .addStyleEntry('css/backend/backend', ['./assets/scss/backend/backend.scss'])

    .addEntry('js/frontend/app', './assets/js/frontend/app.js')
    .addStyleEntry('css/frontend/app', ['./assets/scss/frontend/app.scss'])
    .addStyleEntry('css/frontend/login', './assets/scss/frontend/login.scss')

    .enableSassLoader()
    .enablePostCssLoader()

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

// example but not working
// Encore.enablePostCssLoader((options) => {
//     options.config = {
//         path: 'assets/config/'
//     };
// });

module.exports = Encore.getWebpackConfig();

Some other question, in the video about Copy image you mentioned that encore would have an copy feature in the future. Is this already being implemented?

Reply

Hey Bertin!

Woohoo! I'm so happy it's working! This is exactly the "workflow" I was hoping people would have to enable this feature :).

> 1. Now i need to add the postcss.config.js file to the root but is there a way to change the path, eq to /assets/configs/

Hmm. The commented-out config you have looks perfect. When you try this, do you get an error? Or does it just not see the file in the new location? If you completely move the file to the new location (and use that config), is there an error or does it just not work correctly? I'm not sure what's going wrong here.

> 2. Some other question, in the video about Copy image you mentioned that encore would have an copy feature in the future. Is this already being implemented?

Booo. No, not yet. Here's the PR: https://github.com/symfony/.... It's blocked due to some problems in an upstream library. I'm not sure if we'll be able to work around that, or if we'll just need to implement something ourself.

Cheers!

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
    }
}
userVoice