Migration to typo3/composer-cms-installers version 4

/ updated on

With the availability of typo3/cms-composer-installers version 4 (compatible with TYPO3 v11+), saving extensions in the typo3conf folder has gone. The article TYPO3 and Composer — we've come a long way describes the changes. For more insight into the change, see another blog post "Composer Changes for TYPO3 v11 and v12". In this blog post I want to show the challenges I had to tackle to make my projects compatible with the new typo3/cms-composer-installers version.

The visible change is that assets are now embedded in the web page without the name of the extension being exposed. In typo3/cms-composer-installers before version 4 it looked like this:

<link rel="stylesheet" href="/typo3conf/ext/site/Resources/Public/Css/styles.css" media="all">

As with version 4, the typo3conf folder no longer contains the extensions. Instead, the extensions' Public/Resources folders are linked into a new _assets folder under a folder which contains a hash value instead of the extension name:

<link rel="stylesheet" href="/_assets/0304760f2d5b5a1f133ce43f8d460a02/Css/styles.css" media="all">

Important: This also means that every asset that is available on a website must be stored in the Resources/Public folder of an extension.

Therefore, every hard-coded asset path that uses typo3conf/ext needs to be adapted to be compatible with the new version. So i looked for typo3conf/ext paths in a project's custom extensions and substituted them over time with the solutions described below. Then I added the typo3/cms-composer-installers release candidate version as a requirement to the composer.json file of the project.

CSS files

In CSS files, it is common to reference background images with a absolute path, for example:

.selector {
  background-image: url("/typo3conf/ext/site/Resources/Public/Images/background.jpg");
}

This can be changed to a relative path. In this example it is assumed that the CSS folder and the Images folder both directly under Resources/Public of the same extension:

.selector {
  background-image: url("../Images/background.jpg");
}

If it is only a small image, like an icon, it can also be embedded inline with a data URI:

.selector {
  background-image: url("data:image/jpeg;base64,...");
}

Fluid templates

I often use SVG sprites in my projects with a path to a file in typo3conf/ext:

<svg><use xlink:href="/typo3conf/ext/site/Resources/Public/Images/icons.svg#symbol"></use></svg>

The solution now is to use the URI resource view helper:

<!-- If asset is stored in the same extension -->
<svg><use xlink:href="{f:uri.resource(path: 'Images/icons.svg#symbol')}"></use></svg>

<!-- If asset is stored in another extension -->
<svg><use xlink:href="{f:uri.resource(path: 'Images/icons.svg#symbol', extensionName: 'site')}"></use></svg>

Of couse, the URI resource view helper can also be used to refer to other files in extensions.

TypoScript configuration

In TypoScript I preload the relevant fonts to avoid FOUT, for instance:

page.headerData.10 = TEXT
page.headerData.10 {
  value = <link rel="preload" href="/typo3conf/ext/site/Resources/Public/Fonts/Roboto.woff2" as="font" type="font/woff2" crossorigin="anonymous">
}

With the help of the stdWrap function insertData it can be rewritten to:

page.headerData.10 = TEXT
page.headerData.10 {
  value = <link rel="preload" href="{path : EXT:site/Resources/Public/Fonts/Roboto.woff2}" as="font" type="font/woff2" crossorigin="anonymous">
  insertData = 1
}

Another example is adding the OpenGraph image as meta tag, which must be an absolute URL:

page.meta {
  og:image = https://example.com/typo3conf/ext/site/Resources/Public/Images/opengraph.png
}

The solution is to use the typolink function in combination with the content object IMG_RESOURCE:

page.meta {
  og:image.cObject = TEXT
  og:image.cObject {
    typolink {
      parameter.cObject = IMG_RESOURCE
      parameter.cObject.file = EXT:site/Resources/Public/Images/opengraph.png
      returnLast = url
      forceAbsoluteUrl = 1
    }
  }
}

TSconfig configuration

I organise the settings of the backend user groups into files and then reference the file in the TSconfig field of the corresponding backend user group record:

<INCLUDE_TYPOSCRIPT: source="FILE:typo3conf/ext/site/Configuration/TSconfig/User/editors.tsconfig">

This can easily be adapted to be compatible with the new version of typo3/cms-composer-installers:

<INCLUDE_TYPOSCRIPT: source="FILE:EXT:site/Configuration/TSconfig/User/editors.tsconfig">

RTE configuration

In the configuration for the rich text editor, I added a custom CSS file this way:

editor:
  config:
    contentCss: '/typo3conf/ext/site/Resources/Public/Css/rte.css'

As you've already guessed, using of the EXT: syntax is the way to go:

editor:
  config:
    contentCss: 'EXT:site/Resources/Public/Css/rte.css'

JavaScript

Data attributes

Sometimes JavaScript code contains references to files. The paths to the files were hard-coded into the JavaScript file. In this example it was a Leaflet application that defines custom marker icons for a map:

const icon = L.icon({
  iconUrl: '/typo3conf/ext/site/Resources/Public/Icons/Map/marker.svg',
  iconSize: [25, 41],
  iconAnchor: [12,41],
  popupAnchor: [1, -34],
  tooltipAnchor: [16, -28],
  shadowUrl: '/typo3conf/ext/site/Resources/Public/Icons/Map/shadow.svg',
  shadowSize: [41, 41],
});

We can solve this by defining the paths to the icons in data attributes in HTML using the Fluid URI resource view helper:

<div id="map"
  data-icon="{f:uri.resource(path: 'Icons/Map/marker.svg')}" 
  data-shadow="{f:uri.resource(path: 'Icons/Map/shadow.svg')}"
></div>

Now we can retrieve the paths to the icons in JavaScript:

const mapElement = document.getElementById('map');
const icon = L.icon({
  iconUrl: mapElement.dataset.icon,
  iconSize: [25, 41],
  iconAnchor: [12,41],
  popupAnchor: [1, -34],
  tooltipAnchor: [16, -28],
  shadowUrl: mapElement.dataset.shadow,
  shadowSize: [41, 41],
});

Path substitution

Assuming your script is located in EXT:site/Resources/Public/JavaScript/some-script.js you may use document.currentScript.src to create a proper (pseudo-relative) path:

const prefix = document.currentScript.src.replace(/\/JavaScript\/.*\.js.*/, '');
const image = `${prefix}/Images/some-image.png`;

Webpack Encore

Take a look at the documentation of the typo3_encore extension by Sebastian Schreiber to learn how to configure it to use typo3/cms-composer-installers version 4.

PHP

In a project I have a logo provider class that returns the URL to the company logo that is used in several places for embedding structured data:

<?php
declare(strict_types=1);

namespace Acme\Site\Provider;

use TYPO3\CMS\Core\Utility\GeneralUtility;

final class LogoProvider
{
    private const PATH_LOGO = 'typo3conf/ext/site/Resources/Public/Images/logo.png';

    public static function getLogo(): string
    {
        return GeneralUtility::getIndpEnv('TYPO3_SITE_URL') . self::PATH_LOGO;
    }
}

This can be rewritten as:

<?php
declare(strict_types=1);

namespace Acme\Site\Provider;

use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;

final class LogoProvider
{
    private const PATH_LOGO = 'EXT:site/Resources/Public/Images/logo.png';

    public static function getLogo(): string
    {
        return rtrim(GeneralUtility::getIndpEnv('TYPO3_SITE_URL'), '/')
            . PathUtility::getAbsoluteWebPath(GeneralUtility::getFileAbsFileName(self::PATH_LOGO));
    }
}

Summary

Version 4 of typo3/cms-composer-installers stores core and custom extensions in the vendor folder. The Resources/Public folders are linked into the _assets folder of the web root, with a hash as the folder name to disguise the extension name. Some migrations need to be done to be compatible with the new version. As long as a stable version is not available (as of writing of this blog post only a release candidate was released) it can be explicitly installed via:

composer req typo3/cms-composer-installers:^4.0-rc

To avoid installing version 4 after the stable version has been release, you can explicitly pin the Composer plugin to version 3 in your project:

composer req typo3/cms-composer-installers:^3.0

But keep in mind: TYPO3 v12 will require the use of version 4, so be prepared.