Asset Compilation (Mix)

Introduction

Winter brings first-class support for handling Node-based compilation for frontend assets through the Mix commands. The commands use the Laravel Mix wrapper, a user-friendly and simple interface for setting up compilation of multiple types of frontend assets through Webpack and various libraries.

Requirements

To take advantage of Mix asset compilation, you must have Node and the Node package manager (NPM) installed in your development environment. This will be dependent on your operating system - please review the Download NodeJS page for more information on installing Node.

Laravel Mix should also be present in the package.json file for any packages that will be using it (either in dependencies or a devDependencies) but if it is not specified in the project's package.json file then it can be optionally automatically added when running the mix:install command.

Registering a package

Registering for asset compilation through Mix is very easy. Automatic registration should meet your needs most of the time, and if not there are several methods available to manually register Mix packages.

Automatic registration

By default, Winter will scan all available and enabled modules, plugins and themes for the presence of a winter.mix.js file under each extension's root folder (i.e. modules/system/winter.mix.js, plugins/myauthor/myplugin/winter.mix.js, or themes/mytheme/winter.mix.js).

If the winter.mix.js file is found, it will be automatically registered as a package with an automatically generated package name, and will show up when running the Mix commands. Most of the time, this should be all you need to do in order to get started with Laravel Mix asset compilation in Winter CMS.

Registering plugins

To register frontend assets to be compiled through Mix in your plugin, simply return an array with the package names as the keys and the package paths relative to the plugin's directory as the values to register from your Plugin.php registration file's registerMixPackages() method. See below example.

public function registerMixPackages()
{
    return [
        'custom-package-name' => 'assets/js/build.mix.js',
    ];
}

Registering themes

Registration of asset compilation of themes is even easier, and can be done by adding a mix definition to your theme information file (theme.yaml).

name: "Winter CMS Demo"
description: "Demonstrates the basic concepts of the frontend theming."
author: "Winter CMS"
homepage: "https://wintercms.com"
code: "demo"

mix:
    <name of package>: winter.mix.js

The mix definition takes any number of registered packages as a YAML object, with the key being the name of the package as a kebab-case string and the location of your winter.mix.js file relative to the theme's root directory.

For example, if you want to register two packages called demo-theme-style and demo-theme-shop located in the assets folder, you would use the following definition:

name: "Winter CMS Demo"
description: "Demonstrates the basic concepts of the frontend theming."
author: "Winter CMS"
homepage: "https://wintercms.com"
code: "demo"

mix:
    demo-theme-style: assets/style/winter.mix.js
    demo-theme-shop: assets/shop/winter.mix.js

Mix configuration

The Mix configuration file (winter.mix.js) is a configuration file that manages the configuration of Laravel Mix itself. In conjunction with the package.json file that defines your dependencies, this file defines how Laravel Mix will compile your assets.

You can review examples or the full Mix API at the Laravel Mix website.

Your winter.mix.js file must include Mix as a requirement, and must also define the public path to the current directory, as follows:

const mix = require('laravel-mix');

// For assets in the current directory
// mix.setPublicPath(__dirname);

// For assets in a /assets subdirectory
mix.setPublicPath(__dirname + '/assets');

// Your mix configuration below

Paths

When the winter.mix.js file is evaluated, regardless of where you ran mix:compile from, the working directory is set to the parent directory of the winter.mix.js file. That means that any relative paths used in the configuration will be relative to the current directory of the winter.mix.js file.

NOTE: Winter's path symbols are also supported in the winter.mix.js file.

Examples

Here are some examples of installing common frontend libraries for use with the asset compilation.

Tailwind CSS

For themes that wish to use Tailwind CSS, include the tailwindcss, postcss and autoprefixer dependencies in your package.json file.

# Inside the project root folder
npm install --save-dev tailwindcss postcss autoprefixer

# Run the Tailwind initialisation
npx tailwindcss init

This will create a Tailwind configuration file (tailwind.config.js) inside your theme that you may configure to your specific theme's needs.

Then, add a winter.mix.js configuration file that will compile Tailwind as needed:

const mix = require('laravel-mix');
mix.setPublicPath(__dirname);

// Render Tailwind style
mix.postCss('assets/css/base.css', 'assets/css/theme.css', [
  require('postcss-import'),
  require('tailwindcss'),
]);

In the example above, we have a base CSS file that contains the Tailwind styling - assets/css/base.css - that will compile to a final compiled CSS file in assets/css/theme.css.

Your theme will now be ready for Tailwind CSS development.

Alpine JS

For themes that wish to use Alpine JS, include the alpinejs dependency in your theme's package.json file.

# Run this inside the theme's directory (i.e. myproject/themes/mytheme)
npm install alpinejs

You will need to update your Mix assets by running php artisan mix:update from the project root folder after the installation is complete.

Import Alpine into your theme script bundle (for example, stored at themes/mytheme/assets/src/js/scripts.js), and initialize it like so:

import Alpine from 'alpinejs'
window.Alpine = Alpine
Alpine.start()

The window.Alpine = Alpine is optional, but is nice to have for flexibility by making Alpine globally accessible via JavaScript, for situations like tinkering with Alpine from the devtools or using Alpine inline in your theme files.

Then, update your winter.mix.js configuration file that will compile Alpine as needed:

const mix = require('laravel-mix');
mix.setPublicPath(__dirname);

mix
  // other mix commands
  .js('assets/src/js/scripts.js', 'assets/dist/js/app.js');

In the example above, we compile the theme script bundle that contains Alpine initialization - assets/src/js/scripts.js - into a final, compiled build at assets/dist/js/app.js.

Your theme will now be ready to use Alpine JS by simply including the build in your theme's HTML:

<script src="{{ 'assets/js/app.js' | theme }}"></script>

Vue

If you want to use Vue 3 in your project, either in the backend or in a component or theme, you can follow these steps.

First, define Vue as a dependency in your plugin's package.json:

    "name": "myauthor-myplugin",
    "private": true,
    "version": "1.0.0",
    "dependencies": {
        "vue": "^3.2.41"
    }

Run php artisan mix:install to install Vue and the dependencies that Vue requires.

Then, add a winter.mix.js configuration file to your plugin directory. This will build a specific entry point file, in this case assets/src/js/myplugin.js and create a built version of your Vue app as assets/dist/js/myplugin.js.

const mix = require('laravel-mix');
mix.setPublicPath(__dirname);
mix
    // compile javascript assets for plugin
    .js('assets/src/js/myplugin.js', 'assets/dist/js').vue({ version: 3 })

Next you can create your Vue source files in your plugin's assets directory. Mix supports rendering of single-file components, allowing you to define the template, scripting and styling all in one file.

// assets/src/js/myplugin.js

import { createApp } from 'vue'
import Welcome from './components/Welcome'

const myPlugin = createApp({})

myPlugin.component('welcome', Welcome)

myPlugin.mount('[data-vue-app="myPlugin"]')
// assets/src/js/components/Welcome.vue

<template>
    <h1>{{ title }}</h1>
</template>

<script>
export default {
    setup: () => ({
        title: 'Vue 3 in Laravel'
    })
}
</script>

Now if you comple your assets in the project root with php artisan mix:compile, Mix will create your compiled and built Vue component as a JS file.

Next in the your controller's template file (eg. controllers/myvuecontroller/index.php) you can include the generated myplugin.js file, and render the content in the div with the data-vue-app="myPlugin" attribute:

<div data-vue-app="myPlugin">
  <welcome/>
</div>

<script src="/plugins/foo/bar/assets/dist/js/myplugin.js"></script>

Commands

Install Node dependencies

php artisan mix:install [-p <package name>] [--npm <path to npm>]

The mix:install command will install Node dependencies for all registered Mix packages.

This command will add each registered package to the workspaces.packages property of your root package.json file and then run and display the results of npm install from your project root to install all of the dependencies for all of the registered packages at once.

You can optionally provide a -p or --package flag to install dependencies for one or more packages. To define multiple packages, simply add more -p flags to the end of the command.

If the command is run with a -p or --package flag and the provided package name is not already registered and the name matches a valid module, plugin, or theme package name (modules are prefixed with module-$moduleDirectory, themes are prefixed with theme-$themeDirectory, and plugins are simply Author.Plugin) then a winter.mix.js file will be automatically generated for that package and will be included in future runs of any mix commands through the automatic registration feature.

The --npm flag can also be provided if you have a custom path to the npm program. If this is not provided, the system will try to guess where npm is located.

Update Node dependencies

php artisan mix:update [-p <package name>] [--npm <path to npm>]

The mix:update command will update Node dependencies for all registered Mix packages.

This command operates very similar to mix:install, except that it only updates previously installed packages. This allows you to keep dependencies up to date, especially in the case of security fixes or breaking updates from your Node dependencies.

Please see the mix:install documentation for the available arguments and options.

List registered Mix packages

php artisan mix:list

The mix:list command will list all registered Mix packages found in the Winter installation. This is useful for determining if your plugin or theme is correctly registered.

The command will list all packages, as well as the directory for the asset and the configuration file that has been defined during registration.

Compile a Mix packages

php artisan mix:compile [-p <package name>] [-f|--production] [-- <extra build options>]

The mix:compile command compiles all registered Mix packages, running each package through Laravel Mix for compilation.

By specifying the -p flag, you can compile one or more selected packages. To define multiple packages, simply add more -p flags to the end of the command.

By default, all packages are built in "development" mode. If you wish to compile in "production" mode, which may include more optimisations for production sites, add the -f or --production flag to the command.

The command will generate a report of all compiled files and their final size once complete.

If you wish to pass extra options to the Webpack CLI, for special cases of compilation, you can add -- to the end of the command, followed by any parameters as per the Webpack CLI options.

Watch a Mix package

php artisan mix:watch <package> [-f|--production] [-- <extra build options>]

The mix:watch command is similar to the the mix:compile command, except that it remains active and watches for any changes made to files that would be affected by your compilation. When any changes are made, a compile is automatically executed. This is useful for development in allowing you to quickly make changes and review them in your browser.

With this command, only one package can be provided and watched at any one time.

Run a package script

php artisan mix:run <package> <script> [-f|--production] [-- <extra script args>]

The mix:run command allows you to quickly run scripts defined in the package.json file of a Mix package.

// package.json
{
    // ...
    "scripts": {
        "scriptName": "script to execute"
    }
    // ...
}

This can be useful for running arbitrary scripts that augment the capabilities of your plugin or theme, such as executing unit tests, making customised builds and much more. Note that scripts will run with the working directory set to the root folder of the package, not the root folder of the entire project that the artisan command normally executes within.

By default, all package scripts are run in "development" mode. If you wish to run a script in "production" mode, add the -f or --production flag to the command.

If you wish to pass extra arguments or options to your script, you can add -- to the end of the command. Any arguments or options added after the -- argument are interpreted as arguments and options to be sent to the script itself.

Copyright © 2024 Winter CMS