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

Yo friends! It's Webpack time! Yeeeeeeah! Well, maybe not super "yeeeeeeah!" if you are the person responsible for installing and configuring Webpack... Cause, woh, yea, that can be tough! Unless... you're using Webpack Encore! More about that in a few minutes.

Why all the Webpack Buzz?

But first, I want you to know why you should care about Webpack... like super please-let-me-start-using-webpack-right-now-and-never-stop-using-it kind of care. Sure, technically, Webpack is just a tool to build & compile your JavaScript and CSS files. But, it will revolutionize the way you write JavaScript.

The reason is right on their homepage! In PHP, we organize our code into small files that work together. But then, traditionally, in JavaScript, we just smash all our code into one gigantic file. Or, if we do split them up, it's still a pain because we then need to remember to go add a new script tag to every page that needs it... and those script tags need to be in just the right order. If they're not, kaboom! And even if you do have a build system like Gulp, you still need to manage keeping all of the files listed there and in the right order. How can our code be so nicely organized in PHP, but such a disaster in JavaScript?

Webpack changes this. Suppose we have an index.js file but we want to organize a function into a different, bar.js file. Thanks to Webpack, you can "export" that function as a value from bar.js and then import and use it in index.js. Yes, we can organize our code into small pieces! Webpack's job is to read index.js, parse through all of the import statements it finds, and output one JavaScript file that has everything inside of it. Webpack is a huge over-achiever.

So let's get to it! To import or... Webpack the maximum amount of knowledge from this tutorial, download the course code from this page and code along with me. After you unzip the file, you'll find a start/ directory that has the same code I have here: a Symfony 4 app. Open up the README.md file for all the setup details.

The last step will be to open a terminal, move into the project and start a web server. I'm going to use the Symfony local web server, which you can get from https://symfony.com/download. Run it with:

symfony serve

Then, swing back over to your browser and open up https://localhost:8000 to see... The Space Bar! An app we've been working on throughout our Symfony series. And, we did write some JavaScript and CSS in that series... but we kept it super traditional: the JavaScript is pretty boring, and there are multiple files but each has its own script tag in my templates.

This is not the way I really code. So, let's do this correctly.

Installing WebpackEncoreBundle + Recipe

So even though both Webpack and Encore are Node libraries, if you're using Symfony, you'll install Encore via composer... well... sort of. Open a new terminal tab and run:

composer require encore

This downloads a small bundle called WebpackEncoreBundle. Actually, Encore itself can be used with any framework or any language! But, it works super well with Symfony, and this thin bundle is part of the reason.

This bundle also has a Flex recipe - oooooOOOOooo - which gives us some files to get started! If you want to use Webpack from outside of a Symfony app, you would just need these files in your app.

Back in the editor, check out package.json:

15 lines package.json
{
"devDependencies": {
"@symfony/webpack-encore": "^0.27.0",
"core-js": "^3.0.0",
"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"
}
}

This is the composer.json file of the Node world. It requires Encore itself plus two optional packages that we'll use:

15 lines package.json
{
"devDependencies": {
"@symfony/webpack-encore": "^0.27.0",
"core-js": "^3.0.0",
"webpack-notifier": "^1.6.0"
},
... lines 7 - 14
}

Installing Encore via Yarn

To actually download these, go back to your terminal and run:

yarn

Or... yarn install if you're less lazy than me - it's the same thing. Node has two package managers - "Yarn" and "npm" - I know, kinda weird - but you can install and use whichever you want. Anyways, this is downloading our 3 libraries and their dependencies into Node's version of the vendor/ directory: node_modules/.

And... done! Congrats! You now have a gigantic node_modules/ directory... because JavaScript has tons of dependencies. Oh, the recipe also updated our .gitignore file to ignore node_modules/:

31 lines .gitignore
... lines 1 - 22
###> symfony/webpack-encore-bundle ###
/node_modules/
/public/build/
... lines 26 - 27
###
... lines 29 - 31

Just like with Composer, there is no reason to commit this stuff. This also ignores public/build/, which is where Webpack will put our final, built files.

Hello webpack.config.js

In fact, I'll show you why. At the root of your app, the recipe added the most important file of all webpack.config.js:

var Encore = require('@symfony/webpack-encore');
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 you 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
.configureBabel(() => {}, {
useBuiltIns: 'usage',
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()
// 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();

This is the configuration file that Encore reads. Actually, if you use Webpack by itself, you would have this exact same file! Encore is basically a configuration generator: you tell it how you want Webpack to behave and then, all the way at the bottom, say:

Please give me the standard Webpack config that will give me that behavior.

Encore makes things easy, but it's still true Webpack under-the-hood.

Most of the stuff in this file is for configuring some optional features that we'll talk about along the way - so ignore it all for now. The three super important things that we need to talk about are output path, public path and this addEntry() thing:

var Encore = require('@symfony/webpack-encore');
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')
... lines 8 - 10
/*
* 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 you JavaScript imports CSS.
*/
.addEntry('app', './assets/js/app.js')
//.addEntry('page1', './assets/js/page1.js')
//.addEntry('page2', './assets/js/page2.js')
... lines 23 - 65
;
... lines 67 - 68

Let's do that next, build our first Webpack'ed files and include them on the page.

Leave a comment!

This tutorial works great with Symfony5!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-iconv": "*",
        "aws/aws-sdk-php": "^3.87", // 3.91.4
        "knplabs/knp-markdown-bundle": "^1.7", // 1.7.1
        "knplabs/knp-paginator-bundle": "^2.7", // v2.8.0
        "knplabs/knp-time-bundle": "^1.8", // 1.9.0
        "league/flysystem-aws-s3-v3": "^1.0", // 1.0.22
        "league/flysystem-cached-adapter": "^1.0", // 1.0.9
        "liip/imagine-bundle": "^2.1", // 2.1.0
        "nexylan/slack-bundle": "^2.0,<2.2.0", // v2.1.0
        "oneup/flysystem-bundle": "^3.0", // 3.0.3
        "php-http/guzzle6-adapter": "^1.1", // v1.1.1
        "sensio/framework-extra-bundle": "^5.1", // v5.3.1
        "stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
        "symfony/asset": "^4.0", // v4.2.5
        "symfony/console": "^4.0", // v4.2.5
        "symfony/flex": "^1.0", // v1.6.2
        "symfony/form": "^4.0", // v4.2.5
        "symfony/framework-bundle": "^4.0", // v4.2.5
        "symfony/orm-pack": "^1.0", // v1.0.6
        "symfony/security-bundle": "^4.0", // v4.2.5
        "symfony/serializer-pack": "^1.0", // v1.0.2
        "symfony/twig-bundle": "^4.0", // v4.2.5
        "symfony/validator": "^4.0", // v4.2.5
        "symfony/web-server-bundle": "^4.0", // v4.2.5
        "symfony/webpack-encore-bundle": "^1.4", // v1.5.0
        "symfony/yaml": "^4.0", // v4.2.5
        "twig/extensions": "^1.5" // v1.5.4
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.1.0
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.7
        "fzaninotto/faker": "^1.7", // v1.8.0
        "symfony/debug-bundle": "^3.3|^4.0", // v4.2.5
        "symfony/dotenv": "^4.0", // v4.2.5
        "symfony/maker-bundle": "^1.0", // v1.11.5
        "symfony/monolog-bundle": "^3.0", // v3.3.1
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.2.5
        "symfony/profiler-pack": "^1.0", // v1.0.4
        "symfony/var-dumper": "^3.3|^4.0" // v4.2.5
    }
}

What JavaScript libraries does this tutorial use?

// package.json
{
    "devDependencies": {
        "@symfony/webpack-encore": "^0.27.0", // 0.27.0
        "autocomplete.js": "^0.36.0",
        "autoprefixer": "^9.5.1", // 9.5.1
        "bootstrap": "^4.3.1", // 4.3.1
        "core-js": "^3.0.0", // 3.0.1
        "dropzone": "^5.5.1", // 5.5.1
        "font-awesome": "^4.7.0", // 4.7.0
        "jquery": "^3.4.0", // 3.4.0
        "node-sass": "^4.11.0", // 4.11.0
        "popper.js": "^1.15.0",
        "postcss-loader": "^3.0.0", // 3.0.0
        "sass-loader": "^7.0.1", // 7.1.0
        "sortablejs": "^1.8.4", // 1.8.4
        "webpack-notifier": "^1.6.0" // 1.7.0
    }
}