Using Mix versioning outside Laravel

Published by at 21st September 2019 10:30 am

Laravel Mix is a really convenient front end scaffold, and not just in the context of a Laravel application. Last year, I added it to a legacy application I maintain, with positive results, and I'm including it in a CMS I'm working on.

However, I've always had issues trying to implement versioning outside a Laravel application. I've used the timestamp technique described here a lot in the past, but nowadays I do most of my work in a Lando container, and I've had a lot of issues with timestamp changes not being picked up, forcing me to restart my container regularly when working on front-end assets. Switching to using Mix versioning seemed like a good way to resolve that issue, but of course the mix() helper isn't available elsewhere.

Fortunately, its not all that hard to implement your own solution. Under the bonnet, Mix versioning works as follows:

  • The build generates an array of compiled static assets, with the key being the path to the asset, and the value being the path with a query string appended, and then saves it as mix-manifest.json
  • The mix() helper loads the mix-manifest.json file, converts it to JSON, fetches the array entry by path, and then returns the appropriate value for passing back from the helper

With that in mind, I wrote the following Twig filter to handle assets versioned with Mix:

1<?php declare(strict_types=1);
2
3namespace Project\Core\Views\Filters;
4
5use Exception;
6
7final class Mix
8{
9 public function __invoke(string $path): string
10 {
11 $manifest = json_decode(file_get_contents('mix-manifest.json'), true);
12 if (! array_key_exists("/" . $path, $manifest)) {
13 throw new Exception(
14 "Unable to locate Mix file: {$path}"
15 );
16 }
17 if (!file_exists($path)) {
18 throw new Exception('Included file does not exist');
19 }
20 return $manifest["/" . $path];
21 }
22}

This works on the basis that the web root is set in the public/ folder, and that the compiled CSS and Javascript files are placed there - if that's not the case you may need to adapt this accordingly.

You also need to add the version() call to your webpack.mix.js:

1const mix = require('laravel-mix');
2
3/*
4 |--------------------------------------------------------------------------
5 | Mix Asset Management
6 |--------------------------------------------------------------------------
7 |
8 | Mix provides a clean, fluent API for defining some Webpack build steps
9 | for your Laravel application. By default, we are compiling the Sass
10 | file for the application as well as bundling up all the JS files.
11 |
12 */
13
14mix
15 .setPublicPath('public/')
16 .js('resources/js/app.js', 'public/js')
17 .sass('resources/sass/app.scss', 'public/css')
18 .version();

Then, when you instantiate Twig, you can add the new filter using something like this:

1$twig = new Environment($container->get('Twig\Loader\FilesystemLoader'), $config);
2$mix = $container->get('Project\Core\Views\Filters\Mix');
3$twig->addFilter(new TwigFilter('mix', $mix));

Now, the filter should be usable in your Twig views as shown:

1<!doctype html>
2<html lang="en">
3 <head>
4 <meta charset="utf-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
6
7 <link rel="stylesheet" href="{{ 'css/app.css'| mix }}" />
8
9 <title>{{ title }}</title>
10 </head>
11 <body>
12 {% include 'header.html' %}
13 {% block body %}
14 {% endblock %}
15
16 <script src="{{ 'js/app.js'| mix }}"></script>
17 </body>
18</html>

If you're using a different framework or templating system, there should be a way to create helpers, and it should be possible to implement this technique fairly easily. I was able to do so in the context of a legacy Zend application, so it should be possible with other legacy frameworks like CodeIgniter.