Is there a standard way to scale Laravel Mix to support a build process of 50+ different frontend themes?
I'm simply running npm run dev
and npm run watch
currently with four different themes in my webpack.mix.js file, and that goes through and builds/watches all of those themes at once, but I fear performance is going to break down when we go to scale. Ideally I would like to be able to only build/watch themes one at a time, e.g. npm run dev --theme:some-site
or npm run watch --theme:another-site
Here's what my webpack.mix.js is eventually going to look like at this rate if I don't change anything:
const mix = require('laravel-mix');
// Parent Theme
mix.js('resources/[parent-theme-folder]/assets/js/app.js', 'public/[parent-theme-folder]/js/')
.sass('resources/[parent-theme-folder]/assets/scss/app.scss', 'public/[parent-theme-folder]/css/')
;
// Client 1
mix.js('resources/[child-theme-1-folder]/assets/js/app.js', 'public/[child-theme-1-folder]/js/')
.sass('resources/[child-theme-1-folder]/assets/scss/app.scss', 'public/[child-theme-1-folder]/css/')
;
// Client 2
mix.js('resources/[child-theme-2-folder]/assets/js/app.js', 'public/[child-theme-2-folder]/js/')
.sass('resources/[child-theme-2-folder]/assets/scss/app.scss', 'public/[child-theme-2-folder]/css/')
;
...etc...
// Client 47
mix.js('resources/[child-theme-47-folder]/assets/js/app.js', 'public/[child-theme-47-folder]/js/')
.sass('resources/[child-theme-47-folder]/assets/scss/app.scss', 'public/[child-theme-47-folder]/css/')
;
Any suggestions? Thanks!
--
Some more info on our setup, if it helps...
We're using a parent/child theme package - igaster/laravel-theme - to manage multiple frontends in one Laravel project. Essentially, we have 4 types of website products, and 20-30 clients that all have their own instance of up to 4 of those products. They each have their own child theme that extends the parent theme for that product to add any custom layouts, styles, views, etc. We felt one Laravel project was going to be easier to manage than setting up 20-30 different Laravel projects for each client, especially when it came to managing maintenance and updates.
According to the documentation, you can use environment variables to inject parameters into Laravel Mix. Combine this with a custom script in your package.json
and you get what you want.
The default package.json
comes with the following two scripts (and more):
"scripts": {
"dev": "npm run development",
"development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
}
Simply add your own script as a wrapper:
"scripts": {
"dev": "npm run development",
"dev:customer1": "cross-env MIX_CUSTOMER=customer1 npm run dev"
}
Surely this works also with yarn...
In your webpack.mix.js
, you can then simply query the environment variable to only build a single customer:
const mix = require('laravel-mix');
// Parent Theme
mix.js('resources/[parent-theme-folder]/assets/js/app.js', 'public/[parent-theme-folder]/js/')
.sass('resources/[parent-theme-folder]/assets/scss/app.scss', 'public/[parent-theme-folder]/css/');
if (process.env.MIX_CUSTOMER === 'customer1') {
mix.js('resources/[child-theme-1-folder]/assets/js/app.js', 'public/[child-theme-1-folder]/js/')
.sass('resources/[child-theme-1-folder]/assets/scss/app.scss', 'public/[child-theme-1-folder]/css/');
}
if (process.env.MIX_CUSTOMER === 'customer2') {
mix.js('resources/[child-theme-2-folder]/assets/js/app.js', 'public/[child-theme-2-folder]/js/')
.sass('resources/[child-theme-2-folder]/assets/scss/app.scss', 'public/[child-theme-2-folder]/css/');
}
To simplify your webpack.mix.js
, you can also do the following when using an environment variable:
const mix = require('laravel-mix');
// Parent Theme
mix.js('resources/[parent-theme-folder]/assets/js/app.js', 'public/[parent-theme-folder]/js/')
.sass('resources/[parent-theme-folder]/assets/scss/app.scss', 'public/[parent-theme-folder]/css/');
function buildCustomerAssets(customerFolder)
{
mix.js(`resources/${customerFolder}/assets/js/app.js', 'public/${customerFolder}/js/`)
.sass(`resources/${customerFolder}/assets/scss/app.scss', 'public/${customerFolder}/css/`);
}
var customers = {
'customer1': 'child-theme-1-folder',
'customer2': 'child-theme-2-folder',
};
if (process.env.MIX_CUSTOMER) {
var customerFolder = customers[process.env.MIX_CUSTOMER];
buildCustomerAssets(customerFolder);
} else {
for (const customerFolder of Object.values(customers)) {
buildCustomerAssets(folder);
}
}
Because your parent theme uses the same folder structure, you could even compile it using the function. But all of this makes only sense if your build config is exactly the same for all customers.
I ended up going with the technique outlined in lots of detail in the compulsivecoders article, but with a few personal tweaks. I won't rehash their entire article and explain each line, but I'll include every line of code you'll need to duplicate my setup below.
--
First, run npm install laravel-mix-merge-manifest
. This part is required if you want to use mix()
in your views to find your css/js assets and enable cache-busting. Otherwise, every time you run npm run dev/watch/etc --theme=theme-name
it's gonna overwrite the previous theme in your mix-manifest.json file and you'll get Laravel errors that it can't find your assets. You'll actually use this package in your theme's mix files at the end.
Then, delete everything in your webpack.mix.js file in your root and paste this:
// webpack.mix.js
try {
require(`${__dirname}/webpack/webpack.${process.env.npm_config_theme}.mix.js`)
} catch (ex) {
console.log(
'\x1b[41m%s\x1b[0m',
'Provide correct --theme argument to build, e.g.: `npm run watch --theme=theme1` or `npm run dev --theme=theme2`'
)
throw new Error('Provide correct --theme argument to build: `npm run watch --theme=theme1` or `npm run dev --theme=theme2`')
}
// ...that's it. Nothing is ever managed in this file.
I tweaked this from the example in the article. I didn't like using an if()
to search an array of all of your theme names because that was one more thing to have to update when you add/remove themes, so I'm just using try {}
which will output an error if it fails to find that theme's webpack.mix file. You could probably write something that would find and loop all your mix files and build everything at once, but that's not necessary in my use case, or performant when I have as many themes as I do.
Then create a /webpack/webpack.[theme].mix.js file for every theme. I put mine in a /webpack/ folder just because I didn't want to have 50+ of these files in my root. Note the /webpack/ folder is referenced in the 3rd line of the webpack.mix.js file above, so change that reference if you want it somewhere else. Here's an example of one of mine, but yours will obviously vary.
// /webpack/webpack.my-theme-1.mix.js
const mix = require('laravel-mix');
require('laravel-mix-merge-manifest');
mix.js('resources/my-theme-1/assets/js/app.js', 'public/my-theme-1/js/').sourceMaps().version()
.sass('resources/my-theme-1/assets/scss/app.scss', 'public/my-theme-1/css/').sourceMaps().version()
.mergeManifest();
Note the two references to the merge manifest. Those two things are required in each theme.
Finally, you can run all of your usual build commands by passing in a --theme=[theme-name] argument, e.g.:
npm run dev --theme=my-theme-1
npm run watch --theme=my-theme-2
npm run prod --theme=my-parent-theme-1
I personally split my terminal and run watch
on both my child and parent themes as I'm starting to build these out, but once my parent theme is stable I'll just have to watch my child themes.
Everything's working great so far, but I'm open to critique. Thanks to everyone for their help getting me on the right path.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With