Symfony 4: using ParcelJs with Twig templates
Webpack's flexibility and power comes at the price of a fairly steep learning curve, however, and whilst the time required to learn its intricacies turned out to be a sound investment for my more complicated bundling requirements, it all started to feel a bit unnecessary when I just wanted to spin up something straight forward.
ParcelJs brings something slightly different to the table, and whilst it won't replace Webpack for complicated bundling, it more than makes up for it with speed and increased simplicity.
I've also created a Github repo for anyone that wants to dive straight into the code.
By the end of this article, you should be able to:
- set up a skeleton Symfony 4 project
- display the bundled assets using Symfony's Twig templates
- display certain assets inline
- OSX Mojave
- A local installation of the Composer dependency manager
- Basic knowledge of PHP and the Symfony framework
- A local installation of the latest stable NodeJs release (12.9.1 at the time of writing)
- Basic knowledge of asset bundling
Create a Symfony 4 skeleton project
If you're unsure about this process then please read my article that explains how to create a new Symfony project from scratch.
Create a new Controller
We'll need a controller to test out the results of our asset bundling.
The controller we've made is about as basic as it gets really. I've used the route annotations so that we don't have to be bothered with a separate routing file, but that's about it.
Create a Twig template
At the moment, our controller method will attempt to render a template that doesn't exist, so let's create a simple twig template to fix that.
Before we move on though, let's try to understand a bit more about what's going on here.
If you view the source of our page in your web browser, you should see something similar to the following:
Lines 8 - 10 contain the markup that we put in our template, but everything else comes from the base template.
If you take another look at our template code, you'll see that we actually extended base.html.twig, and added a block named body.
This tells the Twig templating system to first load the base.html.twig template, and then inject the markup that it finds between block and endblock tags (in index.html.twig in this case) into the corresponding references in base.html.twig.
Here we've named our block body, and if we open up the templates/base.html.twig file, we can see that it contains, amongst other things, a body block too (line 9):
We'll need to deal with some of those other blocks shortly, so let's get on with creating our assets.
Adding styles and scripts
Our assets aren't much good to us in their current location, but as we'll be using Parcel for our pipeline, it's time to get that installed.
Creating the asset bundles
At this point, we can see Parcel in action.
The above command tells Parcel to take the code in the file scripts/homePage/index.js, and bundle it along with the code contained in any of its associated import statements into a single file in Symfony's default public directory (public/assets/homePage.bundle.js). The code in the resulting file will be minified, as mentioned previously, and no source maps will be created thanks to the --no-source-maps flag.
We can now do the same for our css files:
Using the bundles
Isn't that beautiful. An inspiration for designers everywhere.
Well, maybe not, but it illustrates very clearly that our bundled styles have been successfully rendered.
Bundling assets during development
So far, we've seen how to create minified bundles using scripts that were run directly on the command line. This is all well and good, but we can make the process a little bit neater and set things up so that the bundles will be created automatically when changes are made to the associated source files.
What we've actually done here is run two commands that will start two simultaneous processes: one that watches for changes in .js files, and another that watches for changes in .css files.
The watch flag tells Parcel to watch for changes in the source files, and then the relevant bundles are recreated whenever a change is detected.
This is really handy during the development cycle, and it's far easier having the process triggered automatically rather than having to mess around in the terminal every time you make a change.
The only other thing to note is the --public-url flag. This accepts a value that specifies the location that the assets are being served from, and is used to provide a reference in the bundle to its associated source map file.
If you open up homePage.bundle.css, for example, you should see the following reference.
It's all very messy running big long commands in the terminal, so it would be nice if we could tidy things up a bit.
The development build / watch process can now be started by running the following command in the terminal:
The production build can now be started by running the following command in the terminal:
You may have noticed that there's a bit of additional jiggery-pokery at the beginning of the build_assets_dev and build_assets_prod scripts:
All this does is remove any pre-existing bundle files (the ones that have .bundle. in their name) so that the production build isn't affected by any artifacts that might remain from a previously run development build, or vice versa.
First, we find all the files in the public/assets directory:
We then pipe the resulting files names through to the grep command, which returns only the files that have .bundle. in their name:
Finally, we pipe the filtered results into xargs, which passes the identified file names on to the rm command.
At this point, our development process still involves having two terminal windows open. This might be fine, or even desirable for some people, but nevertheless we can simplify our process further using composer.json.
Now you should be able to start the Symfony project and start watching both .css and .js files from composer. Try stopping the development server, along with any watch processes, and run:
If you want to do the same thing with a production build (without watchers), you can run the following command:
The main reason to bundle assets is to reduce the number of HTTP requests required to render a web page. If you have two files referenced in the initial HTML response from a web server, for example, then a browser would need to make two additional requests to retrieve this files. Assuming that the files are small enough, combining them into a single file would then only require one additional HTTP request to retrieve it.
A potential downside to requiring additional files, however, is that those files will be downloaded after the initial HTML has been rendered in the browser, which can often cause the page to flash or jiggle while the browser updates everything.
Nowadays, it's popular practice to embed, or "inline", certain assets within the initial response from the web server in order to give the user the perception of a "visually ready" page as soon as possible, despite the resulting increase in the size of the initial response.
Typically these inlined assets (styles, scripts, images, fonts, etc) are essential to the page's fundamental structure, and provide the user with everything required for a pleasant experience, whilst less important functionality and styling is loaded in later on by the bundles.
The example I'm using in this tutorial is far too simple to be realistic, but we can still use it to take a look at how we might go about inlining assets that have been processed by Parcel.
Let's create some "essential" assets that we can inline.
The above code is pretty simple. All we're really doing is using PHP's file_get_contents function to return the contents of the specified files rather than the entire files themselves.
Here, we're taking the asset file contents that were returned from our helper methods, and making them available to our Twig templates in the essential_styles and essential_scripts variables.
Those variables aren't yet referenced in our Twig template, so let's fix that.
There are a couple of things to pay attention to here:
In both of those code snippets, we're referencing a variable and using the raw filter to ensure that the variable's content isn't HTML encoded before it's rendered. By default, Symfony provides automatic escaping, but we want the content of our asset files to appear in the source exactly as it appears in the files themselves. As you can see, the asset contents are injected between style and script tags respectively.
The checks on lines 7 and 16 are just defensive programming. We make sure that the essential_styles and essential_scripts variables exist before we try to use them, and also that they contain content. There's really no point in rendering an empty pair of tags.
You should see that the new Comic Sans MS font has been applied:
The page source should show that our essential scripts and styles have been added directly into the HTML response. If you started the project with composer run-local-dev, then the inline assets will not be minified, and will be automatically updated when you make changes to the source, whereas the composer run-local-prod should have minified the inline assets.
By this stage, you should hopefully have a functional Symfony 4 project containing examples of assets that were bundled and inlined using Parcel.js.
Be sure to do your own research around all the topics dicscussed here, as I'm fairly new to PHP myself and this is quite an early iteration of my Symfony / Parcel / Twig solution.
I hope that my article has been helpful to you, and please share a link around if you liked it.