A neat little last minute feature made it into TYPO3 10 LTS: the asset collector. Inspired by a concept found at worpress, b13 released a public extension for TYPO3 9 called assetcollector (find EXT:assetcollector on GitHub). Shortly before the feature freeze of version 10, the asset collector has been integrated into the TYPO3 core (Patch, Feature RST). In this blog post we will have a look at a common use case and how to use it. Let's start with the use case.

Use Cases

The main use case for the asset collector might be "content blocks" or components that provide their CSS and JavaScript in the fluid template. With this approach the stylesheets and scripts included on every page can be reduced to the requirements of the page layout and every content element adds its own styles and JavaScript on top. This has the advantage that no superfluous assets are served. The CSS and JavaScript of e.g. a slider element is only added to a page if there is actually a slider element on that page. Of course styles and scripts must not be added twice if a second slider element is added.

This allows for truly modular and reusable content while keeping transferred assets as lightweight as possible.

Sounds like something we want, so how do we do it?

Top

Usage

The asset collector has been integrated as a singleton PHP class with an API for adding to the output of CSS and JavaScript. During run time it collects all assets and adds them to the output. Depending on where and how we want the assets to be rendered, we can use different API methods of the asset collector:

public function addJavaScript(string $identifier, string $source, array $attributes = [], array $options = [])

public function addInlineJavaScript(string $identifier, string $source, array $attributes = [], array $options = [])

public function addStyleSheet(string $identifier, string $source, array $attributes = [], array $options = [])

public function addInlineStyleSheet(string $identifier, string $source, array $attributes = [], array $options = [])

The $identifier should be unique for each asset. If an asset is registered multiple times with the same identifier it will only be stored once in the asset collector.

With $source the actual asset is passed. This is expected to be inline CSS resp. JavaScript, or the path to a file depending on what API method we are using.

The $attributes array holds additional attributes for the rendered tag. Either <script> for JavaScript or <link>, resp. <styles> for CSS.

All the API methods support a priority flag (passed in the $options array with the key 'priority') to put the asset either in <head> (with priority = true) or near the end of the <body> tag (default position).

With that being said we can instantiate the AssetCollecor and add assets e.g. from a DataProcessor.

However the most common way of adding assets to the asset collector is probably via ViewHelper directly from a fluid template. For this purpose two ViewHelpers have been added to the core:

  • f:asset.css
  • f:asset.script

Let's have a quick look at an example:

<f:asset.css identifier="uniquie-identifier-1" href="EXT:my_ext/Resources/Public/Css/styles.css" />
<f:asset.css identifier="uniquie-identifier-1">
  body { background-color: blue; }
</f:asset.css>

<f:asset.script identifier="uniquie-identifier-2" src="EXT:my_ext/Resources/Public/JavaScript/script.js" priority="1" />
<f:asset.script identifier="uniquie-identifier-2" priority="1" >
  alert('hello world');
</f:asset.script>

The ViewHelpers also allow to set all the tag attributes. All available arguments of the CssViewHelper look like this:

parent::registerUniversalTagAttributes();
$this->registerTagAttribute('as', 'string', '', false);
$this->registerTagAttribute('crossorigin', 'string', '', false);
$this->registerTagAttribute('disabled', 'bool', '', false);
$this->registerTagAttribute('href', 'string', '', false);
$this->registerTagAttribute('hreflang', 'string', '', false);
$this->registerTagAttribute('importance', 'string', '', false);
$this->registerTagAttribute('integrity', 'string', '', false);
$this->registerTagAttribute('media', 'string', '', false);
$this->registerTagAttribute('referrerpolicy', 'string', '', false);
$this->registerTagAttribute('rel', 'string', '', false);
$this->registerTagAttribute('sizes', 'string', '', false);
$this->registerTagAttribute('type', 'string', '', false);
$this->registerTagAttribute('nonce', 'string', '', false);
$this->registerArgument(
    'identifier',
    'string',
    'Use this identifier within templates to only inject your CSS once, even though it is added multiple times',
    true
);
$this->registerArgument(
    'priority',
    'boolean',
    'Define whether the css should be put in the <head> tag above-the-fold or somewhere in the body part.',
    false,
    false
);

We learned how to add assets to the asset collector with PHP and through ViewHelpers, it's quite simple after all. Cool thing is, that this works obviously with uncached content but with cached content as well.

There are also events in place to further process and manipulate assets before rendering:

Top

Events

If you are interested in the basics of PSR-14 events in TYPO3 10 LTS, please read my in depth article about it here.

 To allow for processing of assets before rendering two events have been introduced in TYPO3 10 LTS (Patch, Feature RST):

  • BeforeStylesheetsRenderingEvent
  • BeforeJavaScriptsRenderingEvent

Both events are dispatched from inside the AssetRenderer, a class that functions as a bridge between the AssetCollector and the PageRenderer. The AssetRenderer is called twice for each above mentioned API method. Once with priority flag and once without priority. This means that each event is been fired multiple times. In total, we have calls for

  • JS with priority
  • JS without priority
  • Inline JS with priority
  • Inline JS without priority
  • CSS with priority
  • CSS without priority
  • Inline CSS with priority
  • Inline CSS without priority

To expose what combination of inline and priority is currently rendered, each event offers the corresponding API methods:

public function isInline(): bool
{
    return $this->inline;
}

public function isPriority(): bool
{
    return $this->priority;
}

In addition, each event contains the AssetCollector that can be manipulated (use the getAssetCollector() method for this) to influence the final rendering.

The structured content initiative published an extension (see EXT:asset_rendering on GitHub) for showcasing the usage of the asset renderer events.

With all of this we now should be able to provide content components and reduce our shipped CSS and JavaScript at least in some instances.

Thanks for reading!

Top

Further Information