This tutorial has a new version, check it out!

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

Hiya guys! And welcome to our tutorial on Webpack Encore! I have to admit, this tutorial is near and dear to my heart, because I helped write Webpack Encore. But also because I think you're going to love working with it and I know that it's going to drastically improve the way you write JavaScript.

Basically, Encore is a wrapper around Webpack... because honestly, Webpack - while amazing - is a pain to setup. And what does Webpack do? We'll get to that.

Setting up the Project

And when we do.... you're definitely going to want to code along with me. Because, we're going to code JavaScript... dramatic pause... correctly!

Download the course code from this page. After you unzip it, you'll find a start/ directory that has the same code you see here. Follow the README.md file to get setup details and, of course, a Haiku about Webpack.

The last step will be to find a terminal, move into the project, and run:

php bin/console server:run

to start the built-in web server. Find your most favorite browser and go to: http://localhost:8000.

Aw yea, it's Lift Stuff! Our startup for keeping track of all of the stuff... we lift! Login with username ron_furgandy password pumpup.

This is a two-page app: the login page and this incredible page: where we can record that - while programming today - we lifted our cat 10 times. I love exercise! Everything on this page works via AJAX and JavaScript... but the JavaScript is pretty traditional. If you watched our Webpack tutorial, we've actually reset this project back to before we introduced Webpack. Yep, in the public/ directory, there are some normal CSS and JavaScript files. Nothing special.

Oh, and this is a Symfony 4 application... but that doesn't matter much. For you Symfony users out there, the only special setup I've done is to install the Asset component so that we can use the Twig asset() function:

<!DOCTYPE html>
<html lang="en">
<head>
... lines 4 - 10
{% block stylesheets %}
... lines 12 - 13
<link href="{{ asset('assets/css/main.css') }}" rel="stylesheet" />
{% endblock %}
... lines 16 - 17
</head>
... lines 19 - 108
</html>

On a fresh Symfony 4 project, run:

composer require asset

to get it.

The Magical require Statement

Ok... so what's all the fuss about with Webpack anyways? Well, the JavaScript file that runs the main page is called RepLogApp.js. Look inside: it holds two classes:

'use strict';
(function(window, $, Routing, swal) {
let HelperInstances = new WeakMap();
class RepLogApp {
... lines 8 - 192
}
/**
* A "private" object
*/
class Helper {
... lines 199 - 226
}
const rowTemplate = (repLog) => `
<tr data-weight="${repLog.totalWeightLifted}">
<td>${repLog.itemLabel}</td>
<td>${repLog.reps}</td>
<td>${repLog.totalWeightLifted}</td>
<td>
<a href="#"
class="js-delete-rep-log"
data-url="${repLog.links._self}"
>
<span class="fa fa-trash"></span>
</a>
</td>
</tr>
`;
window.RepLogApp = RepLogApp;
})(window, jQuery, Routing, swal);

If you haven't see the class syntax in JavaScript, go back and watch episode 2 of our JavaScript series. It's cool stuff.

Anyways, we have a class RepLogApp and then.... way down below, we have Helper:

'use strict';
(function(window, $, Routing, swal) {
... lines 4 - 6
class RepLogApp {
... lines 8 - 192
}
/**
* A "private" object
*/
class Helper {
... lines 199 - 226
}
... lines 228 - 245
})(window, jQuery, Routing, swal);

In PHP, we would never do this: we would organize each class into a different file. But in JavaScript, that's a pain! Because, if I move this Helper code to another file, then in my template, I need to remember to include a second script tag:

... lines 1 - 53
{% block javascripts %}
... lines 55 - 57
<script src="{{ asset('assets/js/RepLogApp.js') }}"></script>
... lines 59 - 65
{% endblock %}

This is why we can't have nice things.

But... what if we could require files in JavaScript... just like we can in PHP? Um... let's try it! Copy the Helper class, remove it, then - in the js/ directory, add a new file: RepLogHelper.js. Paste the class here - I'll remove the comment on top:

'use strict';
class Helper {
constructor(repLogs) {
this.repLogs = repLogs;
}
calculateTotalWeight() {
return Helper._calculateWeights(
this.repLogs
);
}
getTotalWeightString(maxWeight = 500) {
let weight = this.calculateTotalWeight();
if (weight > maxWeight) {
weight = maxWeight + '+';
}
return weight + ' lbs';
}
static _calculateWeights(repLogs) {
let totalWeight = 0;
for (let repLog of repLogs) {
totalWeight += repLog.totalWeightLifted;
}
return totalWeight;
}
}
... lines 33 - 35

You see, in Node - which is server-side JavaScript, they have this idea of modules. Each file is a "module"... which doesn't mean much except that each file can export a value from itself. Then, other files, um, modules, can require that file to get that value.

In RepLogHelper.js, we really to make this Helper class available to other files. To export it, at the bottom, add module.exports = Helper:

'use strict';
class Helper {
... lines 4 - 31
}
module.exports = Helper;

Now, in RepLogApp, at the top, add const Helper = require('./RepLogHelper');:

'use strict';
const Helper = require('./RepLogHelper');
(function(window, $, Routing, swal) {
... lines 6 - 213
})(window, jQuery, Routing, swal);

I want to highlight two things. First, you do not need the .js at the end of the filename. You can add it... but you don't need it - it's assumed. Second, the ./ is important: this tells the require function to look relative to this file. Later, we'll find out what it means to not start with ./.

So here's the reality: if we ran this code on the server via Node... it would work! Yea! This require() thing is real! But... does it work in a browser?

Let's find out! Move over, open the debugging console and... refresh! Oh man!

require is not defined

Booo! So... the require() function is not something that works in a browser... in any browser. And, the thing is, it can't work. PHP and Node are server-side languages, so Node can instantly read this file from the filesystem. But in a browser, in order to get this RepLogHelper.js file, it would need to make an AJAX request... and of course that's far from instant.

The point is: the require() function just doesn't make sense in a browser. And this is the problem that Webpack solves. Webpack is a command that can read this file, parse through all of the require calls and create one final JavaScript file packed with all the code we need.

But, we're not going to install Webpack directly. Google for "Webpack Encore" to find its documentation on Symfony.com.

Installing Webpack Encore

Click into the Installation page and copy the yarn add line. And, some background: Webpack is a Node executable, so you'll need to make sure it's installed. And second... Node has two package managers: yarn and npm. You can use either - I'll use Yarn. So make sure you have that installed too.

Then, find your terminal, open a fresh new tab, lift your cat, and then run:

yarn add @symfony/webpack-encore --dev

Tip

Encore version 0.21.0 contains a few cool changes. Don't worry, we'll tell you in this tutorial where anything is now different.

If you're a Symfony user, there is also a composer line you can use. Actually, all it really does is install a Flex recipe that creates a few files for you to get you started faster. We'll do everything manually so that we can see how it works.

Move back and... it's done! If you're new to Yarn, this did two things. First, it created a package.json file:

6 lines package.json
{
"devDependencies": {
"@symfony/webpack-encore": "^0.19.0"
}
}

That's just like composer.json for Node - and also a yarn.lock file - that's like composer.lock. Second, it downloaded everything into a node_modules/ directory: that's the vendor/ directory for Node.

And just like in PHP, we do not want to commit all those vendor files. Open your .gitignore file and ignore /node_modules/*:

18 lines .gitignore
/node_modules/*
... lines 2 - 18

Brilliant! Encore is installed. Let's do some webpacking!

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.2.0",
        "ext-iconv": "*",
        "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.6.2
        "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
    }
}