Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Multiple Entries / Pages

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Our simple site actually has two pages: this main "lift stuff" page, and also a login page... which has just a little bit of its own JavaScript: a toggleable message and an error if you get crazy and type a super-long username.

Open up the template: app/Resources/FOSUserBundle/views/Security/login.html.twig. Yep, it has a script tag for assets/js/login.js: a traditional, non-webpacked file:

... lines 1 - 10
{% block javascripts %}
... lines 12 - 13
<script src="{{ asset('assets/js/login.js') }}"></script>
{% endblock %}
... lines 16 - 72

And... that's a bummer! I want Webpack to also process this file so I can remove the self-executing function and require jQuery instead of hoping it's included on the page:

... lines 1 - 2
(function(window, $) {
... lines 4 - 21
})(window, jQuery);

Every Page is an App

Ok, let me paint you a picture. I want you to start thinking about every page that has its own JavaScript as its own mini-application. Yep, from a JavaScript perspective, we have a "login" application and also a "rep log" application. These two apps run completely independent of each other, and each deserves its own, webpack-ified, built file. Unless you're creating a true single-page app, this is your reality.

So let's Webpackify login.js! Start by removing the self-executing function:

... lines 1 - 4
$(document).ready(function() {
... lines 6 - 21
});

Then, since we're relying on the $ variable, add const $ = require('jquery'):

... lines 1 - 2
const $ = require('jquery');
$(document).ready(function() {
... lines 6 - 21
});

This file is ready!

webpack.config.js & Multiple entry

Next, open webpack.config.js. We have a problem: we really only have the ability to configure one entry file... which creates one output file:

... lines 1 - 2
module.exports = {
entry: './web/assets/js/rep_log.js',
output: {
path: path.resolve(__dirname, 'web', 'build'),
filename: 'rep_log.js',
}
};

How can we tell Webpack to also look at login.js and build a second file? The secret is that entry can actually contain multiple things.

Copy the original path and set entry to {}:

... lines 1 - 2
module.exports = {
entry: {
... lines 5 - 6
},
... lines 8 - 11
};

For the first entry, put rep_log as the key set to the copied path:

... lines 1 - 2
module.exports = {
entry: {
rep_log: './web/assets/js/rep_log.js',
... line 6
},
... lines 8 - 11
};

Then, repeat that for a new entry called login that points to our source login.js file:

... lines 1 - 2
module.exports = {
entry: {
rep_log: './web/assets/js/rep_log.js',
login: './web/assets/js/login.js',
},
... lines 8 - 11
};

Woohoo! Webpack will now read these two entry files. To have it output two built files, we need to change the filename. In place of rep_log, use a special placeholder: [name].js:

... lines 1 - 2
module.exports = {
entry: {
rep_log: './web/assets/js/rep_log.js',
login: './web/assets/js/login.js',
},
output: {
... line 9
filename: '[name].js',
}
};

The name is referring to the keys I created under entry: rep_log and login. These could have been anything. For consistency, they match the source filenames, but really, you can choose whatever you want. The only significance of these keys is that they become the name of the final, dumped file.

You guys know the drill: we just made a change to webpack.config.js. And that means it's time to stop the watch script with Control+C and restart it:

./node_modules/.bin/webpack --watch

Ah... yes! It dumped login.js. There it is, sitting beautifully inside build/.

In login.html.twig, make sure to update the script tag to point to build/login.js:

... lines 1 - 10
{% block javascripts %}
... lines 12 - 13
<script src="{{ asset('build/login.js') }}"></script>
{% endblock %}
... lines 16 - 72

Then, head over to your browser and refresh the rep log page first. It still works. Go to /login... and it works!

This was another huge step! We now have the power to create as many different distinct JavaScript applications as we need.

Adding the Layout Entry

In fact, surprise! We already have a third app. Inside web/assets/js/, in addition to login.js and rep_log.js, we have a file called layout.js:

'use strict';
(function(window, $) {
$(document).ready(function() {
$('[data-toggle="tooltip"]').tooltip();
});
})(window, jQuery);

It's pretty simple: it just activates Bootstrap tooltips across the site... which is why we see some tool tips when we hover over parts of the header.

So yes, you can even think of the layout itself as a separate JavaScript application that deserves its own entry.

Leave the self-executing function for now and skip straight to webpack.config.js. Add a third entry called layout:

... lines 1 - 2
module.exports = {
entry: {
... lines 5 - 6
layout: './web/assets/js/layout.js',
},
... lines 9 - 12
};

Then, inside our base layout - app/Resources/views/base.html.twig, I'll change the script tag to point to build/layout.js:

... lines 1 - 98
{% block javascripts %}
... lines 100 - 104
<script src="{{ asset('build/layout.js') }}"></script>
{% endblock %}
... lines 107 - 110

Let's go make that: Control+C the webpack script and restart it:

./node_modules/.bin/webpack --watch

Yes! It dumped layout.js. Refresh the page. Those tooltips are still kicking.

This is wonderful. But, we still have a few issues. Like, both login.js and rep_log.js contain their own copies of jQuery. You can see the file sizes for these are huge. We're actually making our user download jQuery three times: once in each of these two files and a third time still via the script tag in our layout. Geez.

That's something we will fix. I promise.

But first... we have a more immediate problem. When we refactor layout.js to use proper require statements... we're in for a surprise.

Leave a comment!

0
Login or Register to join the conversation
Cat in space

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

This tutorial explains the concepts of an old version of Webpack using an old version of Symfony. The most important concepts are still the same, but you should expect significant differences in new versions.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.2.0",
        "symfony/symfony": "3.3.*", // v3.3.16
        "twig/twig": "2.10.*", // v2.10.0
        "doctrine/orm": "^2.5", // v2.7.0
        "doctrine/doctrine-bundle": "^1.6", // 1.10.3
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.5
        "symfony/swiftmailer-bundle": "^2.3", // v2.6.3
        "symfony/monolog-bundle": "^2.8", // v2.12.1
        "symfony/polyfill-apcu": "^1.0", // v1.4.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.26
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "friendsofsymfony/user-bundle": "^2.0", // v2.1.2
        "doctrine/doctrine-fixtures-bundle": "~2.3", // v2.4.1
        "doctrine/doctrine-migrations-bundle": "^1.2", // v1.3.2
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "friendsofsymfony/jsrouting-bundle": "^1.6" // 1.6.0
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.6
        "symfony/phpunit-bridge": "^3.0" // v3.3.5
    }
}

What JavaScript libraries does this tutorial use?

// package.json
{
    "dependencies": [],
    "devDependencies": {
        "babel-core": "^6.25.0", // 6.25.0
        "babel-loader": "^7.1.1", // 7.1.1
        "babel-plugin-syntax-dynamic-import": "^6.18.0", // 6.18.0
        "babel-preset-env": "^1.6.0", // 1.6.0
        "bootstrap-sass": "^3.3.7", // 3.3.7
        "clean-webpack-plugin": "^0.1.16", // 0.1.16
        "copy-webpack-plugin": "^4.0.1", // 4.0.1
        "core-js": "^2.4.1", // 2.4.1
        "css-loader": "^0.28.4", // 0.28.4
        "extract-text-webpack-plugin": "^3.0.0", // 3.0.0
        "file-loader": "^0.11.2", // 0.11.2
        "font-awesome": "^4.7.0", // 4.7.0
        "jquery": "^3.2.1", // 3.2.1
        "lodash": "^4.17.4", // 4.17.4
        "node-sass": "^4.5.3", // 4.5.3
        "resolve-url-loader": "^2.1.0", // 2.1.0
        "sass-loader": "^6.0.6", // 6.0.6
        "style-loader": "^0.18.2", // 0.18.2
        "sweetalert2": "^6.6.6", // 6.6.6
        "webpack": "^3.4.1", // 3.4.1
        "webpack-chunk-hash": "^0.4.0", // 0.4.0
        "webpack-dev-server": "^2.6.1", // 2.6.1
        "webpack-manifest-plugin": "^1.2.1" // 1.2.1
    }
}
userVoice