Laravel Mix is great as an out-of-the-box build tool, it wraps a common setup for Webpack with zero configuration needed. However, it does not cater for serving assets from your CDN when compiling for production.
There are plenty of solutions out there for serving CDN assets that are called in your blade templates, whether it is a package that you install through composer or a helper method you implement yourself. But I could not find a solution that added your CDN URL to image references in your compiled CSS or JS.
My first thought was to add a loader to the Webpack config that would replace any image references with the CDN URL prepended as it was compiling. And while Laravel Mix does allow for this, it appears to not offer control over where the loader is merged into the configuration, meaning that the other pre-configured loaders overwrite the final output. The solution to this is to pull the Webpack config and customise it, but for me, that defeats the point of using Laravel Mix at all.
So I went back to basics and opted for a simple string replacement function in the mix file. Luckily, Laravel Mix offers a callback function that fires once Webpack is complete, which is an ideal spot to update the compiled output with your CDN URL.
First, you will need to add your CDN URL to your dotenv file:
CDN_DOMAIN=https://cdn.example.com
Then add the following function to your webpack.mix.js file:
mix.imgCDN = function (path, cdn) {
let file = new File(path);
// Replace all occurrences of /img/ with CDN URL prepended
let contents = file.read().replace(/\/img\//g, cdn+"/img/");
file.write(contents);
// Update version hash in manifest
Mix.manifest.hash(file.pathFromPublic()).refresh();
return this;
}.bind(mix);
This function will take a path to a compiled CSS/JS file and your CDN, it will then do a global replace of any image strings with your CDN prepended. It then refreshes the hash of the updated file for versioning in the mix.manifest.json file (comment out this line if you are not using versioning).
Note: The regular expression used is very basic and was adequate for my needs. You will probably need to amend the regular expression the function is using to find image strings based on your requirements to ensure it only replaces image strings.
Finally, let's call the function on each file we want to process and prepend the CDN URL to all of the image references:
if (mix.inProduction()) {
mix.then(function(){
let cdn = process.env.CDN_DOMAIN;
mix
.imgCDN('public/css/app.css', cdn)
.imgCDN('public/js/app.js', cdn);
});
}
This only fires when you're building for production and once Webpack has finished compiling. Once complete, you will have the same compiled CSS/JS files with your CDN prepended to all image references.
There are probably other, more robust ways of achieving this, but this was simple enough for my requirements and hopefully helps you if you're facing a similar issue.