Multiple Pages / Entries
This is all really nice... but, so far... it kinda looks like Webpack only works for single-page apps! I mean, if this were the only page in our app, we could write all of our JavaScript in the one entry file, require what we need and... be done!
But even our small app has another page: /login
. And this page has its own custom JavaScript.... which right now, is done in this boring, old, non-Webpack-ified login.js
file:
; | |
(function(window, $) { | |
$(document).ready(function() { | |
$('.js-recommended-login').on('click', '.js-show-login', function(e) { | |
e.preventDefault(); | |
$('.js-recommended-login-details').toggle(); | |
}); | |
$('.js-login-field-username').on('keydown', function(e) { | |
const $usernameInput = $(e.currentTarget); | |
// remove any existing warnings | |
$('.login-long-username-warning').remove(); | |
if ($usernameInput.val().length >= 20) { | |
const $warning = $('<div class="login-long-username-warning">This is a really long username - are you sure that is right?</div>'); | |
$usernameInput.before($warning); | |
} | |
}); | |
}); | |
})(window, jQuery); |
So... how can we also process this file through Webpack? We could require it from rep_log.js
, but that's wasteful! We really only need this code on the login page.
Multiple Entries
The answer is... so simple: add a second entry called login
that will load the assets/js/login.js
file:
// ... lines 1 - 2 | |
Encore | |
// ... lines 4 - 9 | |
.addEntry('rep_log', './public/assets/js/rep_log.js') | |
.addEntry('login', './public/assets/js/login.js') | |
// ... lines 12 - 13 | |
; | |
// ... lines 15 - 18 |
I want you to think of each entry as a separate page on your site. Or, you can think of each entry as a separate JavaScript application that runs on your site. Like, we have our main "rep log" application and also our "login" application.
Because we just changed the webpack config, go back and restart Encore:
yarn run encore dev --watch
Webpack-Sponsored Cleanup
And now we can improve things! First, remove that self-executing function:
; | |
// ... lines 2 - 4 | |
$(document).ready(function() { | |
$('.js-recommended-login').on('click', '.js-show-login', function(e) { | |
e.preventDefault(); | |
$('.js-recommended-login-details').toggle(); | |
}); | |
$('.js-login-field-username').on('keydown', function(e) { | |
const $usernameInput = $(e.currentTarget); | |
// remove any existing warnings | |
$('.login-long-username-warning').remove(); | |
if ($usernameInput.val().length >= 20) { | |
const $warning = $('<div class="login-long-username-warning">This is a really long username - are you sure that is right?</div>'); | |
$usernameInput.before($warning); | |
} | |
}); | |
}); |
Then, more importantly, require the dependencies we need: in this case jQuery with const $ = require('jquery')
:
; | |
const $ = require('jquery'); | |
$(document).ready(function() { | |
$('.js-recommended-login').on('click', '.js-show-login', function(e) { | |
e.preventDefault(); | |
$('.js-recommended-login-details').toggle(); | |
}); | |
$('.js-login-field-username').on('keydown', function(e) { | |
const $usernameInput = $(e.currentTarget); | |
// remove any existing warnings | |
$('.login-long-username-warning').remove(); | |
if ($usernameInput.val().length >= 20) { | |
const $warning = $('<div class="login-long-username-warning">This is a really long username - are you sure that is right?</div>'); | |
$usernameInput.before($warning); | |
} | |
}); | |
}); |
That's it! Go back and... refresh! Bah:
require
is not defined
Boo! My bad - I forgot to use the new built file. Open templates/bundles/FOSUserBundle/Security/login.html.twig
. Point the script tag to build/login.js
:
// ... lines 1 - 10 | |
{% block javascripts %} | |
// ... lines 12 - 13 | |
<script src="{{ asset('build/login.js') }}"></script> | |
{% endblock %} | |
// ... lines 16 - 72 |
And now... it works! When I type a really long username, this message appears thanks to that JavaScript.
The "layout" Entry
But... there's one last problem. Open the base layout file: base.html.twig
. Yep, we also include one JavaScript file on every page:
// ... lines 1 - 98 | |
{% block javascripts %} | |
// ... lines 100 - 104 | |
<script src="{{ asset('assets/js/layout.js') }}"></script> | |
{% endblock %} | |
// ... lines 107 - 110 |
It doesn't do much... just adds a tooltip when you hover over your username:
; | |
(function(window, $) { | |
$(document).ready(function() { | |
$('[data-toggle="tooltip"]').tooltip(); | |
}); | |
})(window, jQuery); |
So... how do we handle this? How can we Webpackify this file? I mean, the layout is not its own page... so... can it be its own entry? The answer is... yes! Add another entry called layout
and point it to assets/js/layout.js
:
// ... lines 1 - 2 | |
Encore | |
// ... lines 4 - 11 | |
.addEntry('layout', './public/assets/js/layout.js') | |
// ... lines 13 - 14 | |
; | |
// ... lines 16 - 19 |
Here's the deal: usually you will include exactly one script tag for a built JavaScript file on each page - like rep_log.js
or login.js
. But, if you have some JavaScript that should be included on every page, you can think of that JavaScript as its own, mini JS application. In that case, you'll have two built files per page: your layout JavaScript and your page-specific JavaScript... if you have any for that page.
Go back and restart Webpack so it reads the new config.
yarn run encore dev --watch
But... let's not refactor this file yet: we'll do that next. In base.html.twig
, use the new file: build/layout.js
:
// ... lines 1 - 98 | |
{% block javascripts %} | |
// ... lines 100 - 104 | |
<script src="{{ asset('build/layout.js') }}"></script> | |
{% endblock %} | |
// ... lines 107 - 110 |
Boom! Try it! Refresh the page! Yes! It still works. Next, let's refactor layout.js
to remove the self-executing function and require its dependencies. But this time... there's a surprise!