Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Packaging JS and CSS with Encore

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 we installed Webpack Encore, its recipe gave us this new assets/ directory. Check out the app.js file. Interesting. Notice how it imports this bootstrap file. That's actually bootstrap.js: this file right here. The .js extension is optional.

JavaScript Imports

This is one of the most important things that Webpack gives us: the ability to import one JavaScript file from another. We can import functions, objects... really anything from another file. We're going to talk more about this bootstrap.js file in a little bit.

This also imports a CSS file!? If you haven't seen this before, it might look... weird: JavaScript importing CSS?

To see how this all works, in app.js, add a console.log().

15 lines assets/app.js
... lines 1 - 12
console.log('Hi! My name is app.js!');

And app.css already has a body background... but add an !important so that we can definitely see if this is being loaded.

body {
background-color: lightgray !important;
}

Ok... so who reads these files? Because... they don't live in the public/ directory... so we can't create script or link tags that point directly to them.

webpack.config.js

To answer that, open webpack.config.js. Webpack Encore is an executable binary: we're going to run it in a minute. When we do, it will load this file to get its config.

And while there are a lot of features inside of Webpack, the only thing we need to focus on right now is this one: addEntry(). This app could be anything... like dinosaur, it doesn't matter. I'll show you how that's used in a minute. The important thing is that it points to the assets/app.js file. Because of this, app.js will be the first and only file that Webpack will parse.

It's pretty cool: Webpack will reads the app.js file and then follow all of the import statements recursively until it finally has a giant collection of all the JavaScript and CSS our app needs. Then, it will write that into the public/ directory.

Running Webpack Encore

Let's see it in action. Find your terminal and run:

yarn watch

Tip

If you're using NPM run:

npm run watch

This is, as it says, a shortcut for running encore dev --watch. If you look at your package.json file, it came with a script section with some shortcuts.

Anyways, yarn watch does two things. First, it created a new public/build/ directory and, inside, app.css and app.js files! But don't let the names fool you: app.js contains a lot more that just what's inside assets/app.js: it contains all the JavaScript from all the imports it finds. app.css contains all the CSS from all the imports.

The reason these files are called app.css and app.js is because of the entry name.

So the takeaway is that, thanks to Encore, we suddenly have new files in a public/build/ directory that contain all the JavaScript and CSS our app needs!

The Encore Twig Functions

And if you move over to your homepage and refresh... woh! It instantly worked!? The background changed... and in my inspector... there's the console log! How the heck did that happen?

Open your base layout: templates/base.html.twig. The secret is in the encore_entry_link_tags() and encore_entry_script_tags() functions. I bet you can guess what these do: add the link tag to build/app.css and the script tag to build/app.js.

You can see this in your browser. View the source for the page and... yup! The link tag for /build/app.css... and script tag for /build/app.js. Oh, but it also rendered two other script tags. That's because Webpack is really smart. For performance purposes, instead of dumping one gigantic app.js file, sometimes Webpack will split it into multiple, smaller files. Fortunately, these Encore Twig functions are smart enough to handle that: it will include all the link or script tags needed.

The most important thing is that the code that we have in our assets/app.js file - including anything it imports - is now functioning and showing up on our page!

Watching for Changes

Oh, and because we ran yarn watch, Encore is still running in the background watching for changes. Check it out: go into app.css... and change the background color. Save, move over and refresh.

body {
background-color: maroon !important;
}

It instantly updated! That's because Encore noticed the change and recompiled the built file really quickly.

Next: let's move our existing CSS into the new system and learn how we can install and import third party libraries - look Bootstrap or FontAwesome - right into our Encore setup.

Leave a comment!

39
Login or Register to join the conversation

Wanted to let you know :

Stimulus (Bootstrap.js) was removed around version 2.0 of symfony/webpack-encore-bundle.
From the changelog, it look like it was moved to symfony/stimulus-bundle.

Looked into the recipe for symfony/stimulus-bundle and installing it add the files that would be missing if we now only installed Webpack Encore .

2 Reply

Hey @Arnaud-G,

Yeah you are totally right, however to follow course we recommend to install symfony/webpack-encore-bundle:1.14 with version constraint.

anyway thanks for the tip ;)

Cheers!

1 Reply
Cesar-S Avatar
Cesar-S Avatar Cesar-S | posted 18 days ago

Is there an option to embed the app.js (or {entry}.js for that matter) on the HTML head?

Reasoning:
In the enterprise context I would like to maintain sensitive data protected by password instead of hosting is as static. I don't care to expose the pkgs used thou. An example of exposing too much is when you use React Admin. Even if you protects your API documentation it will expose it in the build. Although embedding RA entirely might be a bad idea it illustrates the concern with exposing sensitive enterprise data models.

Reply

Hey Cesar-S,

Actually, it's the best practice to include your JS / CSS files in HTML's head tag now but also use the defer attribute on it, and Webpack can add that tag automatically for you.

Cheers!

1 Reply
Cesar-S Avatar
Cesar-S Avatar Cesar-S | VictorTest | posted 15 days ago | edited

Thanks VictorTest,

That is the best practice for public sites and for performance indeed. But in our enterprise reality we can't expose sensitive data contained in the source unless logged in. There are some infra alternatives like VPN, but it would be nice if we could solve the problem in the app. Would make the infra much simpler for us and the UX much simpler since using a VPN is mostly a pain for the user.

To be clear we would no care to expose simple stuff like "there is a button that when clicked calls x" but rather the fact the that url returns data that is hydrated in a model named X containing the fields Y, Z, etc.. X, Y and Z cannot be exposed, and even in minifyed and transpiled code some info will transpire or in general we don't want to be proofing the transpiled code.

Reply

Hey Cesar-S,

Hm, I understand in general your problem, but not sure what are you looking for fairly speaking. In the first message, you asked for:

Is there an option to embed the app.js (or {entry}.js for that matter) on the HTML head?

Unless I misunderstand what you're trying to do, you can embed JS files in your HTML head. The defer tag will help you with it in case those tags were in the body before.

You probably may conditionally require those JS files, i.e. require them only if the user is authorized. But if you don't want to pass them through the compiling process - probably you should stop using Webpack then. Or at least stop using it for your sensitive files maybe.

Unfortunately, I have never tried something similar before, so it's difficult to give you any advice on it.

Cheers!

1 Reply
Wim-T Avatar
Wim-T Avatar Wim-T | posted 4 months ago | edited

Hi,
I've Been watching quite a few of your tutorials now and in the past and think they are great. Even more important they surely helped me update my knowledge on using Symfony to build an app.
I'm not a frequent "user" anymore and I'm a bit rusty (comes with the age). I used to work in IT for more than 35 years now and the last time I have been really using Symfony (it was Symfony2 I think) dates back to 2013.
When I retired from IT and stepped into business at my own home (bookkeeping and accountancy) together with my wife, I wanted to start improve our own IT and make a few of our own apps in an "online" environment running locally on a self-hosted internal webserver from home. I decided to use Symfony and started to refresh my skills. I watched the videos, typed along all of the code and almost all worked fine for me. Up untill...and the same thing happens now...Encore Webpack and Node JS and/or Yarn becomes the topic.
Not so much the Symfony-part of Encore but the Node and/or Yarn part of the story which really sents me into the woods (so to speak).

Now I learned Yarn is part of NPM and because of that it could be used both (syntax differs I understand) but I can't get it to work.

Especially in this tutorial you mention you use Yarn (watch) instead of NPM. You follow along using Yarn and skip the alternative when using NPM. Also in the script of the videos nor in the conversations I could find a resolution.

To me it feels the NPM and/or Yarn stuff is quite a jungle (with version maintained and abadoned and you using a version that is not the latest version).

I'm sorry for this lengthy story behind my "problem", As experienced as I think I am in IT related problems, I'm lost.
Can you please help me in the right direction for getting this "watch-thing" up and working?

How can I get something simple (as it seems) as "yarn watch" to work without using Yarn, since Yarn is part of NPM?

Kind Regards,
Wim.

Reply

Hey @Wim-T

Welcome back to Symfony! It has changed a lot since then but in a good way. I get what you say. It can be challenging to set up NodeJs with everything it needs. I'd recommend using Yarn instead of NPM, but if you don't want to invest time in learning Yarn, you can replicate the yarn watch functionality by using node-watch. I found this guide you can follow to set it up https://medium.com/this-code/the-easy-way-to-watch-npm-builds-without-reloading-every-damn-time-cd1fd14393cb

I hope it helps. Cheers!

Reply
Wim-T Avatar
Wim-T Avatar Wim-T | MolloKhan | posted 4 months ago | edited

Hi @MolloKhan,
Thanks for pointing me in the direction of using nodejs's npm.

I read the article you linked and after reading it, I decided to stick to yarn. This because yarn seems to be running out-of-the-box without having to add any code to existing scripts. Writing my own code also means maintaining my own code and if I can prevent that I'd rather prefer that and that's exactly what using yarn looks like (so far).

Many thanks never the less for your quick answer and helping me out.

Kind Regards,
Wim

Reply

I'm not sure what version you are using, but in mine (Symfony 6.3 / @symfony/webpack-encore 4.3.0), watch is just a npm script ("watch": "encore dev --watch").

So I can simply use npm run watch to run it.

Kind regards!

1 Reply

You're welcome @Wim-T I agree with what you say, less custom code, more fun :)
Cheers!

1 Reply
Yangzhi Avatar
Yangzhi Avatar Yangzhi | posted 7 months ago | edited

hi,if i add function or const on assets/app.js:

const demo='ok'

function test(){
    alert('this is test')
}
yarn run build

then i want call the test() function on Twig:

<button onclick='test()'>Test</button>

i got error, how to use it?

Reply

Hey Yangzhi,

It's difficult to say because we don't see the actual error. Could you share with us what exactly error you see?

Cheers!

Reply
Yangzhi Avatar
Yangzhi Avatar Yangzhi | Victor | posted 7 months ago | edited

hi, the error is:

test if not defined

if i change test function like this:

window.test=()=>{alert('ok')}

then all work fine. but i want kown is this is only and best way or has other best way?

Reply

Hey Yangzhi,

Ah, I see. Yeah, that's on purpose. Probably you found the best workaround for this with setting that function on global window object. The idea behind webpack encore is different, it makes all the code you're writing in JS files encapsulated (not global) - and that's awesome actually! No more mess in your JS code :)

So, you need to change the way you write your JS code. I'd recommend you to find the element (tag) in your JS code and register a listener for "click" event on that element instead of trying to call the method directly with onclick attribute - that's a legacy way of doing things now. Or, you can take a look into direction of Stimulus, we have a tutorial about it here: https://symfonycasts.com/screencast/stimulus - it has similar (but much cooler) way of declaring actions for special events like click on an element, etc.

Cheers!

Reply
red_smeg Avatar
red_smeg Avatar red_smeg | posted 9 months ago

Using Symfony 6.1.6 and yarn 1.22.19.

running yarn Watch I get

getting. Error: Cannot find module 'webpack/bin/webpack'

I also received "Error: Cannot find module '@babel/core" but for that I could do yarn add @babel/core

How do I solve this ?

Reply

Hi,

Try to run yarn install and then yarn watch again

Cheers!

Reply
red_smeg Avatar

didn't work its complaining about a file that exists in the node_modules tree

Reply

Can you please share your package.json file? That's pretty strange behaviour.

Reply
red_smeg Avatar
red_smeg Avatar red_smeg | sadikoff | posted 8 months ago | edited
{
    "devDependencies": {
        "@hotwired/stimulus": "^3.0.0", 
        "@popperjs/core": "^2.10.2", 
        "@symfony/stimulus-bridge": "^3.0.0", 
        "@symfony/webpack-encore": "^4.1.1", 
        "axios": "^0.24.0", 
        "bootstrap": "^5.0.0", 
        "core-js": "^3.0.0", 
        "jquery": "^3.6.0", 
        "regenerator-runtime": "^0.13.2", 
        "webpack-notifier": "^1.6.0" 
        },
        "license": "UNLICENSED",
        "private": true, 
        "scripts": { 
        "dev-server": "encore dev-server", 
        "dev": "encore dev", 
        "watch": "encore dev --watch", 
        "build": "encore production --progress" 
    }
}
Reply

Hm ok, let's think differently. Is your code related to this chapter? I'm asking because I see some packages which should be added later in this tutorial.

Also I'd like you to test one thing, can you tweak your package.json modify webpack-encore version to

"@symfony/webpack-encore": "^1.7.0",

then try again yarn install and then yarn watch

Cheers!

Reply
red_smeg Avatar
red_smeg Avatar red_smeg | sadikoff | posted 8 months ago | edited

let me see. I changed it to 1.7.0 and it all works.

Reply

Yo! that's nice 🤩

Reply
Eric Avatar

Hi,
my usecase is the following:
I have 3rd-party page template and organized the js-files in a separate folder in /assets. I imported them in a separate template.js. All compiles flawlessly. I use the entry_tags in my twig template. All good.

But when I load the page it tries to load some of the imported .js-files directly with https://localhost:port1234/path/like/in/assets/folder Obviously that path doesn't exist on the local dev server as the compiled files are in /build so it fails and the respective js-plugins don't work.

I have no clue were the GET to the files on that path is coming from and why it tries to load them like that at all. Can anyone help?

Reply

Hey @Eric!

Apologies for the slow reply - we're just getting back from Symfony conference week!

This sounds like very strange behavior. A few questions:

A) You said:

But when I load the page it tries to load some of the imported .js-files directly with...

When you look at the HTML source, do you see the physical <script tags with the wrong src="" attribute? Or do all the script paths look ok... but then something inside of those script files is then trying to download those strange paths?

B) When you look in the public/build/ directory, what do you see? Does anything look strange in there?

C) Do you have any special config in webpack.config.js?

D) You mentioned:

I have 3rd-party page template and organized the js-files in a separate folder in /assets. I imported them in a separate template.js.

Can you share some of that code? What does that template.js look like and what imports that file?

Cheers!

1 Reply
Steve-D Avatar

Hi Everyone

I've come to this part of the tutorial for some help, hopefully. I've recently update a webapp to Symfony v6.1 and all appeared well until I ran yarn watch. Everything was fine in v6.0

I got this error in terminal
Error: Cannot find module '@babel/core'
I ran yarn add @babel/core
Sorted.

Running yarn watch now gives me:
Error: Cannot find module 'webpack/bin/webpack'
Require stack:

  • /Users/steve/Sites/symfony/v6/webapps/node_modules/@symfony/webpack-encore/bin/encore.js

And now I'm stuck. Is this related in anyway to you guys now using Stimulus or is it something else i'm missing? I've not got as far as using Stimulus yet.

I've tried removing and re-adding Webpack encore (this did initially remove my package.json file too, not sure if that's a problem)

Thank you

Steve

Reply
Steve-D Avatar

and by coming back to this tutorial, in particular the previous video I've found the answer...

I removed webpack encore and installed as instructed encore, copied everything over from the previous package.json, this file is removed regardless of how it was added automatically, and then updated the webpack config file. All is super great again...few.

One question why does the main Symfony site say to use: composer require symfony/webpack-encore-bundle? What's the big difference?

Cheers

Steve

Reply
red_smeg Avatar

what exact steps did you take I have the same problem !!

Reply
Jesse-Rushlow Avatar
Jesse-Rushlow Avatar Jesse-Rushlow | SFCASTS | Steve-D | posted 10 months ago

Howdy Steve,

symfony/webpack-encore is the JavaScript library for Symfony Webpack Encore. Whereas symfony/webpack-encore-bundle is the PHP library, in the form of a Symfony Bundle, that configures a Symfony Application to use Webpack Encore - including using Symfony's Flex recipes to configure and add symfony/webpack-encore to the projects JavaScript packages.

Basically webpack-encore goes in the package.json file. webpack-encore-bundle is for composer.json if that makes sense.

Reply
Steve-D Avatar

Hey Jesse

That makes perfect sense, thank you for answering and clearing up my confusion.

Have a great day

Steve

1 Reply
Rufnex Avatar

Just a short question about the javascript embedding. Should it embed at the end oft the html instead of the head areas?

Reply
Victor Avatar Victor | SFCASTS | Rufnex | posted 11 months ago | HIGHLIGHTED

Hey Rufnex,

Good question! For many years it was recommended to include your JS at the end of your body tag so that them do not block the page rendering and page loads faster. But lately things changed, and now it's recommended to include all your JS/CSS in the head of the page again... but adding the defer attribute to it. Check a few screencasts to learn more about it:

As you can see, Webpack Encore already knows how to handle it, you just need to configure it.

Cheers!

3 Reply
Rufnex Avatar

Thank you for your great Input. Now i've got it ;o)

Reply

Hey Rufnex,

Perfect! I'm glad to hear it ;)

Cheers!

1 Reply

although the site works correctly but they show me these errors in the console

Reply

Hello, i have JS error in console,

Uncaught (in promise) TypeError: Cannot destructure property 'eligibleURLPatterns' of 'Jt' as it is undefined companion-bubble.js:1436
at companion-bubble.js:1436:11909
at Generator.next (<anonymous>)
at tn (companion-bubble.js:1436:11431)

Uncaught TypeError: Cannot read properties of null (reading 'style') (index):212
at renderAjaxRequests ((index):212:10065)
at finishAjaxRequest ((index):212:15943)
at (index):212:21834
at xhr.onreadystatechange ((index):212:7896)

Uncaught (in promise) TypeError: Cannot read properties of null (reading 'style') (index):212
at renderAjaxRequests ((index):212:10065)
at finishAjaxRequest ((index):212:15943)
at (index):212:18218

Reply
Petr L. Avatar
Petr L. Avatar Petr L. | posted 1 year ago

Hello, I feel like there's something I'm doing wrong, but I simply cannot figure out what it is; the background color of the page won't change regardless of what I do, it always stays "lightgrey" and keeps overwriting the custom app.css that I copied earlier from the tutorial folder.
The issue I'm facing specifically is that even when I update assets/styles/app.css to a different color (let's say "maroon"), it stay lightgrey, even though the "maroon" is stated in app.css in the /build directory. According to the developer tools, the file that overwrites the color of <body> stems from webpack///assets/styles/app.css. I also noticed that the console log specified in app.js will never go through either, the console stays empty.
Everything else works as intended, yarn apparently works, but I've had this default lightgrey color there from the very beginning of the whole course and I can't figure out how to rewrite it.

Reply

Hi Petr L.!

Apologies for the slow reply! This is super weird... it's as if your code is "stuck" with the original code. That's especially true since you said the console.log in app.js also doesn't seem to work.

First:

> According to the developer tools, the file that overwrites the color of <body> stems from webpack///assets/styles/app.css.

Encore publishes a sourcemap, which helps map the styles to the "source" filename. So in case you were wondering why it doesn't say "build/app.css", that's why. It's loading it from build/app.css... but then the sourcemap tells it that the true source is assets/styles/app.css. But this doesn't answer what's wrong at all - just mentioning this.

Ok, so let's do some debugging! You mentioned that:

> even though the "maroon" is stated in app.css in the /build directory

That's a good detail to check into. To be absolutely sure, stop Encore, completely empty the public/build directory, then re-run Encore. NOW look inside there again. Does the public/build/app.css file have maroon inside of it or lightgrey?

What happens if you make a JavaScript syntax error in assets/app.js? You should get an error in your console from Encore. Do you? Or not?

Finally, triple check that you browser isn't loading out-of-date content by doing a force refresh (or opening the site in incognito mode).

Let me know what you find out - something is definitely wrong, but I bet it's a minor thing (the minor things are the hard ones to track down!).

Cheers!

Reply
Otacon Avatar

Hello , got the same issue with the app.js. cleaned my browser cache and finaly got the console.log :)

Reply
Cat in space

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

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.0.2",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "symfony/asset": "6.0.*", // v6.0.3
        "symfony/console": "6.0.*", // v6.0.3
        "symfony/dotenv": "6.0.*", // v6.0.3
        "symfony/flex": "^2", // v2.1.5
        "symfony/framework-bundle": "6.0.*", // v6.0.4
        "symfony/monolog-bundle": "^3.0", // v3.7.1
        "symfony/runtime": "6.0.*", // v6.0.3
        "symfony/twig-bundle": "6.0.*", // v6.0.3
        "symfony/ux-turbo": "^2.0", // v2.0.1
        "symfony/webpack-encore-bundle": "^1.13", // v1.13.2
        "symfony/yaml": "6.0.*", // v6.0.3
        "twig/extra-bundle": "^2.12|^3.0", // v3.3.8
        "twig/twig": "^2.12|^3.0" // v3.3.8
    },
    "require-dev": {
        "symfony/debug-bundle": "6.0.*", // v6.0.3
        "symfony/stopwatch": "6.0.*", // v6.0.3
        "symfony/web-profiler-bundle": "6.0.*" // v6.0.3
    }
}
userVoice