Feature: #90522 - Introduce AssetCollector

See Issue #90522

Description

AssetCollector is a concept to allow custom CSS/JS code, inline or external, to be added multiple times in e.g. a Fluid template (via <f:asset.script> or <f:asset.css> ViewHelpers) but only rendered once in the output.

It supports best practices for optimizing page performance by having a “priority” flag to either move the asset to the <head> section (useful for CSS in above-the-fold concepts) or to the bottom of the <body> tag.

AssetCollector helps to work with content elements as components, effectively reducing the amount of CSS to be loaded. It also incorporates the HTTP/2 power where it is not relevant to have all files compressed and concatenated in one file (although this could be added later-on).

AssetCollector is implemented as singleton and should slowly replace the various existing options in TypoScript.

AssetCollector also collects information about “imagesOnPage”, effectively taking off pressure from PageRenderer and TSFE to store common data in FE - as this is now handled in AssetCollector, which can be used in cached and non-cached components.

The new API

  • \TYPO3\CMS\Core\Page\AssetCollector::addJavaScript(string $identifier, string $source, array $attributes, array $options = []): self
  • \TYPO3\CMS\Core\Page\AssetCollector::addInlineJavaScript(string $identifier, string $source, array $attributes, array $options = []): self
  • \TYPO3\CMS\Core\Page\AssetCollector::addStyleSheet(string $identifier, string $source, array $attributes, array $options = []): self
  • \TYPO3\CMS\Core\Page\AssetCollector::addInlineStyleSheet(string $identifier, string $source, array $attributes, array $options = []): self
  • \TYPO3\CMS\Core\Page\AssetCollector::addMedia(string $fileName, array $additionalInformation): self
  • \TYPO3\CMS\Core\Page\AssetCollector::removeJavaScript(string $identifier): self
  • \TYPO3\CMS\Core\Page\AssetCollector::removeInlineJavaScript(string $identifier): self
  • \TYPO3\CMS\Core\Page\AssetCollector::removeStyleSheet(string $identifier): self
  • \TYPO3\CMS\Core\Page\AssetCollector::removeInlineStyleSheet(string $identifier): self
  • \TYPO3\CMS\Core\Page\AssetCollector::removeMedia(string $identifier): self
  • \TYPO3\CMS\Core\Page\AssetCollector::getJavaScripts(?bool $priority = null): array
  • \TYPO3\CMS\Core\Page\AssetCollector::getInlineJavaScripts(?bool $priority = null): array
  • \TYPO3\CMS\Core\Page\AssetCollector::getStyleSheets(?bool $priority = null): array
  • \TYPO3\CMS\Core\Page\AssetCollector::getInlineStyleSheets(?bool $priority = null): array
  • \TYPO3\CMS\Core\Page\AssetCollector::getMedia(): array

New ViewHelpers

There are also two new ViewHelpers, the <f:asset.css> and the - <f:asset.script> ViewHelper which use the AssetCollector API.

<f:asset.css identifier="identifier123" href="EXT:my_ext/Resources/Public/Css/foo.css" />
<f:asset.css identifier="identifier123">
   .foo { color: black; }
</f:asset.css>

<f:asset.script identifier="identifier123" src="EXT:my_ext/Resources/Public/JavaScript/foo.js" />
<f:asset.script identifier="identifier123">
   alert('hello world');
</f:asset.script>

Considerations

Currently, assets registered with the AssetCollector are not included in callbacks of these hooks:

  • $GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['cssCompressHandler']
  • $GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['jsCompressHandler']
  • $GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['cssConcatenateHandler']
  • $GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['jsConcatenateHandler']

New in version 10.4: Events for the new API have been introduced in Feature: #90899 - Introduce AssetRenderer pre-rendering events

Currently, CSS and JavaScript registered with the AssetCollector will be rendered after their PageRenderer counterparts. The order is:

  • <head>
  • page.includeJSLibs.forceOnTop
  • page.includeJSLibs
  • page.includeJS.forceOnTop
  • page.includeJS
  • AssetCollector::addJavaScript() with ‘priority’
  • page.jsInline
  • AssetCollector::addInlineJavaScript() with ‘priority’
  • </head>
  • page.includeJSFooterlibs.forceOnTop
  • page.includeJSFooterlibs
  • page.includeJSFooter.forceOnTop
  • page.includeJSFooter
  • AssetCollector::addJavaScript()
  • page.jsFooterInline
  • AssetCollector::addInlineJavaScript()

Currently, JavaScript registered with AssetCollector is not affected by config.moveJsFromHeaderToFooter.

Examples

Add a JavaScript file to the collector with script attribute data-foo=”bar”:

GeneralUtility::makeInstance(AssetCollector::class)
   ->addJavaScript('my_ext_foo', 'EXT:my_ext/Resources/Public/JavaScript/foo.js', ['data-foo' => 'bar']);

Add a JavaScript file to the collector with script attribute data-foo="bar" and priority which means rendering before other script tags:

GeneralUtility::makeInstance(AssetCollector::class)
   ->addJavaScript('my_ext_foo', 'EXT:my_ext/Resources/Public/JavaScript/foo.js', ['data-foo' => 'bar'], ['priority' => true]);

Add a JavaScript file to the collector with type="module" (by default, no type= is output for JavaScript):

GeneralUtility::makeInstance(AssetCollector::class)
   ->addJavaScript('my_ext_foo', 'EXT:my_ext/Resources/Public/JavaScript/foo.js', ['type' => 'module']);