gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
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.
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:
<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.
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:
; | |
(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
:
; | |
(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:
; | |
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
:
; | |
class Helper { | |
... lines 4 - 31 | |
} | |
module.exports = Helper; |
Now, in RepLogApp
, at the top, add const Helper = require('./RepLogHelper');
:
; | |
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.
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:
{ | |
"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/*
:
/node_modules/* | |
... lines 2 - 18 |
Brilliant! Encore is installed. Let's do some webpacking!
// 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
}
}
// 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
}
}
In case it's helpful for other users - or the SymfonyCasts team can do some updates - after downloading the code and trying to run `composer install` as per the instructions, I got the following error:
```
[ErrorException]
Declaration of Symfony\Flex\ParallelDownloader::getRemoteContents($originUrl, $fileUrl, $context) should be compat
ible with Composer\Util\RemoteFilesystem::getRemoteContents($originUrl, $fileUrl, $context, ?array &$responseHeade
rs = NULL)
```
I googled it and found a related thread and solution (https://github.com/symfony/..., which is to run `composer update symfony/flex --no-plugins` and then `composer install`.