Webpack Encore: JavaScript Greatness

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.

Okay: here's how this whole thing works. The recipe added a new assets/ directory with a couple of example CSS and JS files. The app.js file basically just console.log()'s something:

15 lines assets/js/app.js
/*
* Welcome to your app's main JavaScript file!
*
* We recommend including the built version of this JavaScript file
* (and its CSS file) in your base layout (base.html.twig).
*/
// any CSS you import will output into a single css file (app.css in this case)
import '../css/app.css';
// Need jQuery? Install it with "yarn add jquery", then uncomment to import it.
// import $ from 'jquery';
console.log('Hello Webpack Encore! Edit me in assets/js/app.js');

The app.css changes the background color to light gray:

body {
background-color: lightgray;
}

Webpack Encore is entirely configured by one file: webpack.config.js:

var Encore = require('@symfony/webpack-encore');
// Manually configure the runtime environment if not already configured yet by the "encore" command.
// It's useful when you use tools that rely on webpack.config.js file.
if (!Encore.isRuntimeEnvironmentConfigured()) {
Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev');
}
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 your JavaScript imports CSS.
*/
.addEntry('app', './assets/js/app.js')
//.addEntry('page1', './assets/js/page1.js')
//.addEntry('page2', './assets/js/page2.js')
// When enabled, Webpack "splits" your files into smaller pieces for greater optimization.
.splitEntryChunks()
// 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 @babel/preset-env polyfills
.configureBabelPresetEnv((config) => {
config.useBuiltIns = 'usage';
config.corejs = 3;
})
// enables Sass/SCSS support
//.enableSassLoader()
// uncomment if you use TypeScript
//.enableTypeScriptLoader()
// uncomment to get integrity="..." attributes on your script & link tags
// requires WebpackEncoreBundle 1.4 or higher
//.enableIntegrityHashes(Encore.isProduction())
// uncomment if you're having problems with a jQuery plugin
//.autoProvidejQuery()
// uncomment if you use API Platform Admin (composer req api-admin)
//.enableReactPreset()
//.addEntry('admin', './assets/js/admin.js')
;
module.exports = Encore.getWebpackConfig();

We won't talk much about this file - we'll save that for the Encore tutorial - but it's already configured to point at the app.js and app.css files: it knows that it needs to process them.

Running Encore

To execute Encore, find your terminal and run:

yarn watch

This is a shortcut for running yarn run encore dev --watch. What does this do? It reads those 2 files in assets/, does some processing on them, and outputs a built version of each inside a new public/build/ directory. Here is the "built" app.css file... and the built app.js file. If we ran Encore in production mode - which is just a different command - it would minimize the contents of each file.

Including the Built CSS and JS Files

There's a lot more cool stuff going on, but this is the basic idea: we code in the assets/ directory, but point to the built files in our templates.

For example, in base.html.twig, instead of pointing at the old app.css file, we want to point at the one in the build/ directory. That's simple enough, but the WebpackEncoreBundle has a shortcut to make it even easier: {{ encore_entry_link_tags() }} and pass this app, because that's the name of the source files - called an "entry" in Webpack land.

... line 1
<html>
<head>
... lines 4 - 5
{% block stylesheets %}
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Spartan&display=swap">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/css/all.min.css" integrity="sha256-mmgLkCYLUQbXn0B1SRqzHar6dCnv9oZFPEC1g1cwlkk=" crossorigin="anonymous" />
{{ encore_entry_link_tags('app') }}
{% endblock %}
</head>
... lines 13 - 33
</html>

At the bottom, render the script tag with {{ encore_entry_script_tags('app') }}.

... line 1
<html>
... lines 3 - 12
<body>
... lines 14 - 25
{% block javascripts %}
<script
src="https://code.jquery.com/jquery-3.4.1.min.js"
integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
crossorigin="anonymous"></script>
{{ encore_entry_script_tags('app') }}
{% endblock %}
</body>
</html>

Let's try it! Move over and refresh. Did it work? It did! The background color is gray... and if I bring up the console, there's the log:

Hello Webpack Encore!

If you look at the HTML source, there's nothing special going on: we have a normal link tag pointing to /build/app.css.

Moving our Code into Encore

Now that this is working, let's move our CSS into the new system. Open public/css/app.css, copy all of this, then right click and delete the file. Now open the new app.css inside assets/ and paste.

127 lines assets/css/app.css
body {
font-family: spartan;
color: #444;
}
.jumbotron-img {
background: rgb(237,116,88);
background: linear-gradient(302deg, rgba(237,116,88,1) 16%, rgba(51,61,81,1) 97%);
color: #fff;
}
.q-container {
border-top-right-radius: .25rem;
border-top-left-radius: .25rem;
background-color: #efefee;
}
.q-container-show {
border-top-right-radius: .25rem;
border-top-left-radius: .25rem;
background-color: #ED7458 ;
}
.q-container img, .q-container-show img {
border: 2px solid #fff;
border-radius: 50%;
}
.q-display {
background: #fff;
border-radius: .25rem;
}
.q-title-show {
text-transform: uppercase;
font-size: 1.3rem;
color: #fff;
}
.q-title {
text-transform: uppercase;
color: #444;
}
.q-title:hover {
color: #2B2B2B;
}
.q-title h2 {
font-size: 1.3rem;
}
.q-display-response {
background: #333D51;
color: #fff;
}
.answer-link:hover .magic-wand {
transform: rotate(20deg);
}
.vote-arrows {
font-size: 1.5rem;
}
.vote-arrows span {
font-size: 1rem;
}
.vote-arrows a {
color: #444;
}
.vote-up:hover {
color: #3D9970;
}
.vote-down:hover {
color: #FF4136;
}
.btn-question {
color: #FFFFFF;
background-color: #ED7458;
border-color: #D45B3F;
}
.btn-question:hover,
.btn-question:focus,
.btn-question:active,
.btn-question.active,
.open .dropdown-toggle.btn-question {
color: #FFFFFF;
background-color: #D45B3F;
border-color: #D45B3F;
}
.btn-question:active,
.btn-question.active,
.open .dropdown-toggle.btn-question {
background-image: none;
}
.btn-question.disabled,
.btn-question[disabled],
fieldset[disabled] .btn-question,
.btn-question.disabled:hover,
.btn-question[disabled]:hover,
fieldset[disabled] .btn-question:hover,
.btn-question.disabled:focus,
.btn-question[disabled]:focus,
fieldset[disabled] .btn-question:focus,
.btn-question.disabled:active,
.btn-question[disabled]:active,
fieldset[disabled] .btn-question:active,
.btn-question.disabled.active,
.btn-question[disabled].active,
fieldset[disabled] .btn-question.active {
background-color: #ED7458;
border-color: #D45B3F;
}
.btn-question .badge {
color: #ED7458;
background-color: #FFFFFF;
}
footer {
background-color: #efefee;
}

As soon as I do that, when I refresh... it works! Our CSS is back! The reason is that - if you check your terminal - yarn watch is watching our files for changes. As soon as we modified the app.css file, it re-read that file and dumped a new version into the public/build directory. That's why we keep this running in the background.

Let's do the same thing for our custom JavaScript. Open question_show.js and, instead of having a page-specific JavaScript file - where we only include this on our "show" page - to keep things simple, I'm going to put this into the new app.js, which is loaded on every page.

29 lines assets/js/app.js
... lines 1 - 13
/**
* Simple (ugly) code to handle the comment vote up/down
*/
var $container = $('.js-vote-arrows');
$container.find('a').on('click', function(e) {
e.preventDefault();
var $link = $(e.currentTarget);
$.ajax({
url: '/comments/10/vote/'+$link.data('direction'),
method: 'POST'
}).then(function(data) {
$container.find('.js-vote-total').text(data.votes);
});
});

Then go delete the public/js/ directory entirely... and public/css/. Also open up templates/question/show.html.twig and, at the bottom, remove the old script tag.

{% extends 'base.html.twig' %}
{% block title %}Question: {{ question }}{% endblock %}
{% block body %}
... lines 6 - 57
{% endblock %}

With any luck, Encore already rebuilt my app.js. So if we click to view a question - I'll refresh just to be safe - and... click the vote icons. Yes! This still works.

Installing & Importing Outside Libraries (jQuery)

Now that we're using Encore, there are some really cool things we can do. Here's one: instead of linking to a CDN or downloading jQuery directly into our project and committing it, we can require jQuery and install it into our node_modules/ directory... which is exactly how we're used to doing things in PHP: we install third-party libraries into vendor/ instead of downloading them manually.

To do that, open a new terminal tab and run:

yarn add jquery --dev

This is equivalent to the composer require command: it adds jquery to the package.json file and downloads it into node_modules/. The --dev part is not important.

Next, inside base.html.twig, remove jQuery entirely from the layout.

... line 1
<html>
... lines 3 - 12
<body>
... lines 14 - 25
{% block javascripts %}
{{ encore_entry_script_tags('app') }}
{% endblock %}
</body>
</html>

If you go back to your browser and refresh the page now... it's totally broken:

$ is not defined

...coming from app.js. That makes sense: we did just download jQuery into our node_modules/ directory - you can find a directory here called jquery - but we're not using it yet.

How do we use it? Inside app.js, uncomment this import line: import $ from 'jquery'.

29 lines assets/js/app.js
... lines 1 - 9
// Need jQuery? Install it with "yarn add jquery", then uncomment to import it.
import $ from 'jquery';
... lines 14 - 29

This "loads" the jquery package we installed and assigns it to a $ variable. All these $ variables below are referencing the value we imported.

Here's the really cool part: without making any other changes, when we refresh, it works! Webpack noticed that we're importing jquery and automatically packaged it inside of the built app.js file. We import the stuff we need, and Webpack takes care of... packaging it all together.

Tip

Actually, Webpack splits the final files into multiple for efficiency. jQuery actually lives inside a different file in public/build/, though that doesn't matter!

Importing the Bootstrap CSS

We can do the same thing for the Bootstrap CSS. On the top of base.html.twig, delete the link tag for Bootstrap:

... line 1
<html>
<head>
... lines 4 - 5
{% block stylesheets %}
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Spartan&display=swap">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.12.1/css/all.min.css" integrity="sha256-mmgLkCYLUQbXn0B1SRqzHar6dCnv9oZFPEC1g1cwlkk=" crossorigin="anonymous" />
{{ encore_entry_link_tags('app') }}
{% endblock %}
</head>
... lines 12 - 28
</html>

No surprise, when we refresh, our site looks terrible.

To fix it, find your terminal and run:

yarn add bootstrap --dev

This downloads the bootstrap package into node_modules/. This package contains both JavaScript and CSS. We want to bring in the CSS.

To do that, open app.css and, at the top, use the good-old-fashioned @import syntax. Inside double quotes, say ~bootstrap:

129 lines assets/css/app.css
@import "~bootstrap";
... lines 3 - 129

In CSS, this ~ is a special way to say that you want to load the CSS from a bootstrap package inside node_modules/.

Move over, refresh and... we are back! Webpack saw this import, grabbed the CSS from the bootstrap package, and included it in the final app.css file. How cool is that?

What Else can Encore Do?

This is just the start of what Webpack Encore can do. It can also minimize your files for production, supports Sass or LESS compiling, comes with React and Vue.js support, has automatic filename versioning and more. To go further, check out our free Webpack Encore tutorial.

And... that's it for this tutorial! Congratulations for sticking with me to the end! You already understand the most important parts of Symfony. In the next tutorial, we're going unlock even more of your Symfony potential by uncovering the secrets of services. You'll be unstoppable.

As always, if you have questions, problems or have a really funny story - especially if it involves your cat - we would love to hear from you in the comments.

Alright friends - seeya next time!

Leave a comment!

  • 2020-07-06 weaverryan

    Hey Peter Dickins !

    Hmm, it could be one simply thing. You *do* have that corejs stuff in webpack.config.js, but you also need to make sure it's installed. Try running: yarn add core-js.

    That might be all you need. Let me know :).

    Cheers!

  • 2020-07-04 Peter Dickins

    I'm trying to use a bootstrap theme in my Symfony project. I have imported the CSS and JS files from the theme but when I run encore I get the following error:

    $ yarn run encore dev
    yarn run v1.22.4
    $ /Users/shaunthornburgh/Development/devfeed/node_modules/.bin/encore dev
    Running webpack ...

    ERROR Failed to compile with 7 errors 12:04:22

    These dependencies were not found:

    * core-js/modules/es.array.filter in ./assets/js/theme.min.js
    * core-js/modules/es.array.for-each in ./assets/js/dashkit.min.js
    * core-js/modules/es.array.map in ./assets/js/theme.min.js
    * core-js/modules/es.array.slice in ./assets/js/theme.min.js
    * core-js/modules/es.object.assign in ./assets/js/theme.min.js
    * core-js/modules/es.parse-int in ./assets/js/theme.min.js
    * core-js/modules/web.dom-collections.for-each in ./assets/js/theme.min.js

    To install them, you can run: npm install --save core-js/modules/es.array.filter core-js/modules/es.array.for-each core-js/modules/es.array.map core-js/modules/es.array.slice core-js/modules/es.object.assign core-js/modules/es.parse-int core-js/modules/web.dom-collections.for-each
    Entrypoint js/app [big] = runtime.js vendors~js/app.js js/app.js
    Entrypoint css/app [big] = runtime.js vendors~css/app.css vendors~css/app.js css/app.css
    error Command failed with exit code 2.
    info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

    I am confused as core-js seems to be included in my webpack.config.js file


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

    if (!Encore.isRuntimeEnvironmentConfigured()) {
    Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev');
    }

    Encore
    .setOutputPath('public/build/')
    .setPublicPath('/build')
    .addEntry('js/app', [
    './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/holderjs/holder.min.js',
    './assets/js/dashkit.min.js',
    './assets/js/theme.min.js'
    ])
    .addStyleEntry('css/app', [
    './node_modules/bootstrap/dist/css/bootstrap.min.css',
    './assets/css/theme.min.css'
    ])
    .splitEntryChunks()
    .enableSingleRuntimeChunk()
    .cleanupOutputBeforeBuild()
    .enableBuildNotifications()
    .enableSourceMaps(!Encore.isProduction())
    .enableVersioning(Encore.isProduction())

    // enables @babel/preset-env polyfills
    .configureBabelPresetEnv((config) => {
    config.useBuiltIns = 'usage';
    config.corejs = 3;
    })
    ;

    module.exports = Encore.getWebpackConfig();
  • 2020-05-31 weaverryan

    Nice job debugging Jose Camacho!

  • 2020-05-31 Jose Camacho

    nvm! I was using the built file instead the assets/

  • 2020-05-31 Jose Camacho

    Hey! I followed this course and everything was great until uncomment jQuery import. As soon I did that, a console error was showed up "Uncaught SyntaxError: Cannot use import statement outside a module" any cue?

  • 2020-05-04 weaverryan

    Hey Martin!

    Really happy you enjoyed the tutorial. And you noticed an excellent thing! Yes, when I originally created question_show.js, the idea was "Hey! This is JavaScript that's only needed on the question show page. So, instead of putting that into a JavaScript file that will be included on every page - and thus making people download that extra code even if they never go to the question show page - let's put it in its own file". So, indeed, the idea is (a) organization but also (b) not making users download JavaScript they won't need. This isn't something you should go crazy trying to optimize, but this is why I did it.

    So, when I refactored to Encore, why did I suddenly put it in app.js? Because, you are 100% correct: a user that goes to *any* page will now download this code. The reason was just simplicity :). Encore has a concept of "multiple entries", which allow you to have page-specific code. For example, I would have an "app" entry (we do have this) that includes "global" JavaScript that I want on every page. The app.js file would be included on every page (i.e. in the layout). Then I would create a "question_show" entry, put the question show JavaScript there, and only include the question_show.js script tag on that ONE PAGE. That would be a more optimal solution - I just skipped it here because i didn't want to get too deep into Encore.

    Cheers!

  • 2020-05-01 Martin

    Hi, I followed all the tutorial and it was great. Just a question... When you created the file "question_show.js" and only imported it in "show.html.twig" I assumed it was to reduce space usage or something related to component re-utilization like every framework. But now, that you moved that script to "app.js" file, it will be imported in every file. Is this right?

  • 2020-04-23 Victor Bocharsky

    Hey Fernando,

    Great, I'm glad you found it! It was easy to miss, and the most obvious problems are usually hardest.

    Cheers!

  • 2020-04-23 Fernando Gómez Gil

    Found I did a mistake

    I copy paste twice
    {{ encore_entry_link_tags('app') }}

    Instead
    {{ encore_entry_script_tags('app') }}
    for the script.
    Thank you

  • 2020-04-22 Victor Bocharsky

    Hey Fernando,

    Sometimes if you make any changes to the JS file but don't see any changes in the browser - it might be browser cache. To exclude it, try to open the same page in Chrome Incognito mode for example. If you still don't see changes - most probably it's the problem in your code - you would need to double check it. First of all, make sure you ran the webpack encore with "--watch" mode, otherwise you would need to re-run encore again to recompile all your assets after any change you made. Also, if you have any errors - Chrome Dev Tools should show it, find "Console" tab there, reload the page and watch for any errors.

    Those were a few tips that should help you to find some problems in your project. Usually, it's enough to find some obvious problems you missed. If you still have some problems - you would need to debug your code more carefully to find what exactly is missing.

    Cheers!

  • 2020-04-20 Fernando Gómez Gil

    At the moment I deleted the code in the file show.html.twig of the old javascript, doesn't work Voting Up and Down don't work. I don't know how to solve it.
    Also, I didn't give to much importance but when in the previous video you did the checking the console was not appearing the text to me.

  • 2020-03-27 Diego Aguiar

    Hey Pleaseenteryourname

    You can require CSS files in your JS scripts and let Webpack handle it. Webpack will build a CSS and a JS file, you need to include both in your HTML template so everything works as expected.

    Cheers!

  • 2020-03-26 Pleaseenteryourname

    Hi, the moment I put my js in the assets/js directory my css doesn't get loaded anymore

    IIRC this has something to do with the trans piling of webpack. So you have to link your css in your js file

  • 2020-03-23 Diego Aguiar

    Aha! Playing Civilization was the key! You know now what to do next time you get a weird problem :D

  • 2020-03-23 Micka Bup

    Hi @Diego Aguiar

    Honnestly, I stopped for a few moment trying to understand why it didn't work and I started to play Civilization. After my game, I came back on my project and... everything work ! I did nothing and I don't know why now it work. By the way thanks for your response ;)

  • 2020-03-23 Diego Aguiar

    Hey Micka Bup

    Are you running in the dev environment?
    What happens if you hard refresh your browser?

  • 2020-03-23 Micka Bup

    I followed all the tutorial but when I do yarn run dev, only my CSS is load. My jquery isn't load and I have still the console.log despite I delete it. I don't know what happened

  • 2020-03-23 Luis

    Cool. Thanks Victor. :)

  • 2020-03-23 Victor Bocharsky

    Hey Luis,

    Good question :) Because basically all the dependancies are just dev deps that are need only for generating final assets in public/build/ directory. You're not using any of those installed JS packages on production, you use only compiled version of them from public/build/. That's why literally they are dev dependencies. But it's really not too much important unless you have a node JS application :)

    Cheers!

  • 2020-03-22 Luis

    When adding jquery via yarn, you said that the "--dev" part was not important. So why did you put it there then? :D