Asset Compilation
Introduction
Winter CMS includes a server-side Asset Compiler that makes use of the Assetic Framework to compile and combine assets like CSS and JavaScript serverside, through PHP, negating the need for complex build workflows. The Asset Compiler provides on-the-fly server-side compilation of SASS and LESS stylesheets as well as run-once manual compilation of assets without requiring additional workflow tools like Node or NPM. It is also able to combine and minify CSS and JS files.
Additionally, you can define variables in the theme.yaml file that can be modified in the Theme Settings area of the backend which are then injected into the compiled files, creating flexibility for theming and branding.
NOTE: Looking for a way to compile or build Node-based assets in your project? Check out the Winter Mix functionality which can handle this for you.
Injecting Page Assets
Asset injection into the generated HTML of Winter CMS powered sites is handled by the System\Traits\AssetMaker
trait. This trait is used in several places in Winter CMS to interact with the Asset Compiler from directly within a class (usually one that interacts with the generated HTML) and is the primary method of interacting with the Asset Compiler. Classes that currently implement the trait include the following:
-
ControllerBehavior
-
Widget
-
Component
- Backend Controllers
- Frontend Controller (through the
| theme
filter when provided an array)
This trait provides the following methods:
-
addJs($name, $attributes = [])
-
addCss($name, $attributes = [])
// Add a publicly accessible asset to the injected page assets
$this->addJs('/plugins/acme/blog/assets/javascript/blog-controls.js');
If the path specified in the addCss
and addJs
method argument begins with a slash (/) then it will be relative to the website root. If the asset path does not begin with a slash then it is relative to the directory referenced by the $assetPath
property on the class currently implementing the System\Traits\AssetMaker
.
The addCss
and addJs
methods provide a second argument that defines the attributes of your injected asset as an array. A special attribute - build
- is available, that will suffix your injected assets with the current version of the plugin specified. This can be used to refresh cached assets when a plugin is upgraded.
$this->addJs('/plugins/acme/blog/assets/javascript/blog-controls.js', [
'build' => 'Acme.Test',
'defer' => true
]);
You may also use a string as the second argument, which then defaults to using the string value as the value for the build
option:
$this->addJs('/plugins/acme/blog/assets/javascript/blog-controls.js', 'Acme.Test');
Triggering Asset Compilation
In order to trigger asset compilation or combination, asset paths must be passed as an array of paths to the above methods, even if there is just a single asset file to compile that itself loads other files. Asset Compilation is currently supported for JS files using the =require path/to/other.js
syntax as well as LESS and SASS/SCSS files.
Example:
$this->addCss(['assets/less/base.less']);
Combining CSS or JavaScript Files
The Asset Compiler can also be used to combine assets of the same type by passing an array of files.
$this->addCss([
'assets/css/styles1.css',
'assets/css/styles2.css'
]);
NOTE: You can enable the assets minification with the
cms.enableAssetMinify
configuration value in theconfig/cms.php
file. By default minification is disabled.
Path Symbols
In some cases you may wish to combine a file outside of the current context (AssetMaker trait container or the current theme), this can be achieved by prefixing the path with a symbol to create a dynamic path. For example, a path beginning with ~/
will create a path relative to the application:
<script src="{{ ['~/modules/system/assets/js/framework.js'] | theme }}"></script>
The following symbols are supported for creating dynamic paths:
Symbol | Description |
---|---|
~ |
Relative to the application directory |
$ |
Relative to the plugins directory |
# |
Relative to the themes directory |
Combiner Aliases
The asset combiner supports common aliases that substitute file paths, these will begin with the @
symbol. For example the AJAX framework assets can be included in the combiner:
<script src="{{ [
'@jquery',
'@framework',
'@framework.extras',
'assets/javascript/app.js'
] | theme }}"></script>
The following aliases are supported:
Alias | Description |
---|---|
@jquery |
Reference to the jQuery library (v3.4.0) used in the backend. (JavaScript) |
@framework |
AJAX framework extras, subsitute for {% framework %} tag. (JavaScript) |
@framework.extras |
AJAX framework extras, subsitute for {% framework extras %} tag. (JavaScript, CSS) |
@framework.extras.js |
AJAX framework extras, (JavaScript) |
@framework.extras.css |
AJAX framework extras, (CSS) |
The same alias can be used for JavaScript or CSS, for example @framework.extras
. At least one explicit reference with a file extension is needed in the array to determine which is used.
Rendering Injected Assets
In order to output the injected assets on the frontend you need to use the {% styles %} and {% scripts %} tags.
Example:
<head>
...
{% styles %}
</head>
<body>
...
{% scripts %}
</body>
If you are wanting to render the injected assets in any other context, you can call $this->makeAssets()
on the object that implements the System\Traits\AssetMaker
trait to rener the injected assets for that object instance.
NOTE: Injected assets are rendered by default in the backend through the
<?= $this->makeAssets() ?>
call inmodules/backend/layouts/_head.htm
, so if you are using a custom layout for your backend controllers you will need to ensure that it includes that call.
Compiler Bundles
While the majority of the time dynamic asset compilation through addJs()
, addCss()
, or the | theme
filter should be sufficient for your needs, you may occassionally have a complex asset compilation that you would like to just generate a static file on command instead of dynamically.
The Winter CMS core registers several such bundles for internal usage that are compiled whenever the artisan winter:util compile assets
command is run.
Extending the Asset Compiler
The System\Classes\AssetCombiner
class provides the registerCallback(callable $callback)
static method to extend it's default behaviour. Additionally, the system.assets.beforeAddAsset
event is available for extending any calls to addJs()
or addCss()
Register Custom Aliases
It is possible to register your own custom aliases through the registerCallback
method of the System\Classes\CombineAssets
class:
/*
* Register custom asset compiler aliases
*/
CombineAssets::registerCallback(function ($combiner) {
$this->registerAlias('jquery', '~/modules/backend/assets/js/vendor/jquery-and-migrate.min.js');
});
Register Custom Asset Bundles
It is possible to register your own custom asset bundles through the registerCallback
method of the System\Classes\CombineAssets
:
/*
* Register custom asset compiler bundles
*/
CombineAssets::registerCallback(function ($combiner) {
$combiner->registerBundle('~/modules/system/assets/less/styles.less', '$/myauthor/myplugin/assets/css/generate-styles.css');
});