Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Component Organization

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.

With our new-found super-power to require files, we can really start to clean things up! First, remove the self-executing function that's around everything:

... lines 1 - 6
let HelperInstances = new WeakMap();
class RepLogApp {
... lines 10 - 194
}
const rowTemplate = (repLog) => `
... lines 198 - 210
`;
window.RepLogApp = RepLogApp;

We originally added this because it gave our code a little bit of isolation. It helped us to, for example, avoid accidentally overriding global variables, but... now that RepLogApp is being processed by Webpack, it is itself a module! And Webpack automatically wraps it - behind the scenes - so that it's isolated. Basically, we don't need to worry about silly things like self-executing functions.

Creating a Skinny "entry" File

Next, look in the template: index.html.twig. We include the rep_log.js file... but we also have a little bit of JavaScript that is responsible for using that object and initializing it:

... lines 1 - 53
{% block javascripts %}
... lines 55 - 56
<script src="{{ asset('build/rep_log.js') }}"></script>
<script>
$(document).ready(function() {
var $wrapper = $('.js-rep-log-table');
var repLogApp = new RepLogApp($wrapper);
});
</script>
{% endblock %}

This is.... kind of a bummer: it relies on the RepLogApp variable to be global... and that only works because, at the bottom, we're purposely creating a global variable with window.RepLogApp = RepLogApp:

... lines 1 - 212
window.RepLogApp = RepLogApp;

Also, to fully Webpackify our app, we will eventually want to remove all JavaScript from our templates. Yep, you'll just include the one JS file and... that's it!

Skinny Entries

And this brings us to an important point about organization. Usually, the entry file - so the file that we list in webpack.config.js:

... lines 1 - 2
Encore
... lines 4 - 9
.addEntry('rep_log', './public/assets/js/RepLogApp.js')
... lines 11 - 12
;
... lines 14 - 17

Should contain a small amount of logic that calls out to other modules. It's kind of like a controller in Symfony: it's supposed to have just a few lines of code that call out to other parts of our app.

Actually, the code in index.html.twig is a pretty good example of what I'd expect in an entry file:

... lines 1 - 53
{% block javascripts %}
... lines 55 - 58
<script>
$(document).ready(function() {
var $wrapper = $('.js-rep-log-table');
var repLogApp = new RepLogApp($wrapper);
});
</script>
{% endblock %}

Let me show you what I mean: in the js/ directory, create a new file: called rep_log.js.

Next, open webpack.config.js: let's use this as the entry file instead:

... lines 1 - 2
Encore
... lines 4 - 9
.addEntry('rep_log', './public/assets/js/rep_log.js')
... lines 11 - 12
;
... lines 14 - 17

And since I just made a change, find your terminal and restart Encore:

yarn run encore dev --watch

Copy the code from index.html.twig, remove it, and paste it here:

... lines 1 - 5
$(document).ready(function() {
var $wrapper = $('.js-rep-log-table');
var repLogApp = new RepLogApp($wrapper);
});

Perfect!

And now that we are responsible JavaScript developers... finally... we need to require any dependencies. Oh, but first, add 'use strict'; on top - that's optional, but I like it:

'use strict';
... lines 2 - 5
$(document).ready(function() {
var $wrapper = $('.js-rep-log-table');
var repLogApp = new RepLogApp($wrapper);
});

Now add const $ = require('jquery') and, to get RepLogApp, const RepLogApp = require('./RepLogApp');:

'use strict';
const $ = require('jquery');
const RepLogApp = require('./RepLogApp');
$(document).ready(function() {
var $wrapper = $('.js-rep-log-table');
var repLogApp = new RepLogApp($wrapper);
});

I love it! Does it work? Move of it and... refresh! Bah!

RepLogApp is not a constructor

Ooof. This is a technical way of saying:

Hey! You're using RepLogApp like a class... but it's not!

Open RepLogApp.js, and scroll to the bottom:

... lines 1 - 8
class RepLogApp {
... lines 10 - 194
}
const rowTemplate = (repLog) => `
... lines 198 - 210
`;
window.RepLogApp = RepLogApp;

Aha! We forgot to export a value from this module. Replace the global variable with module.exports = RepLogApp:

... lines 1 - 8
class RepLogApp {
... lines 10 - 194
}
const rowTemplate = (repLog) => `
... lines 198 - 210
`;
module.exports = RepLogApp;

Try it again! It works!

You can start to see the pattern: create a small entry file and organize everything else into reusable classes or functions.

Moving into a Components Directory

Let's take this a step further and organize into directories. Create a new directory in js/ called Components/. Let's move our re-usable stuff here: RepLogApp and RepLogHelper.

Build failure! Of course! In rep_log.js, update the path: ./Components/RepLogApp:

... lines 1 - 3
const RepLogApp = require('./Components/RepLogApp');
... lines 5 - 10

Build successful! Make sure it still works... it does!

Next! This is great! But can Encore handle apps that are not single-page apps? Like, what if I need a different JavaScript file for my login page? Encore has you covered.

Leave a comment!

2
Login or Register to join the conversation
Amy anuszewski Avatar
Amy anuszewski Avatar Amy anuszewski | posted 5 years ago

Since you're using PhpStorm, it might be fun to teach people about Refactor->Move. Doing it that way updates the necessary files and your build doesn't break.

Reply

Hey @Amy!

That's a great idea! Honestly, I don't use that enough myself 😇

Cheers!

Reply
Cat in space

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

What PHP libraries does this tutorial use?

// 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
    }
}

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
    }
}
userVoice