ES6 in the TYPO3 Backend

Changed in version 12.0

Starting with TYPO3 v12.0 JavaScript ES6 modules may be used instead of AMD modules, both in backend and frontend context.

JavaScript node-js style path resolutions are managed by import maps, which allow web pages to control the behavior of JavaScript imports.

In November 2022 import maps are supported natively by Google Chrome, a polyfill is available for Firefox and Safari and included by TYPO3 Core and applied whenever an import map is emitted.

For security reasons, import map configuration is only emitted when the modules are actually used, that means when a module has been added to the current page response via PageRenderer->loadJavaScriptModule() or JavaScriptRenderer->addJavaScriptModuleInstruction(). Exposing all module configurations is possible via JavaScriptRenderer->includeAllImports(), but that should only be done in backend context for logged-in users to avoid disclosing installed extensions to anonymous visitors.

Configuration

A simple configuration example for an extension that maps the Public/JavaScript folder to an import prefix @vendor/my-extensions:

EXT:my_extension/Configuration/JavaScriptModules.php
<?php

return [
    // required import configurations of other extensions,
    // in case a module imports from another package
    'dependencies' => ['backend'],
    'imports' => [
        // recursive definiton, all *.js files in this folder are import-mapped
        // trailing slash is required per importmap-specification
        '@vendor/my-extension/' => 'EXT:my_extension/Resources/Public/JavaScript/',
    ],
];
Copied!

Complex configuration example containing recursive-lookup exclusions, third-party library definitions and overwrites:

EXT:my_extension/Configuration/JavaScriptModules.php
<?php

return [
    'dependencies' => ['core', 'backend'],
    'imports' => [
        '@vendor/my-extension/' => [
            'path' => 'EXT:my_extension/Resources/Public/JavaScript/',
            // Exclude files of the following folders from being import-mapped
            'exclude' => [
                'EXT:my_extension/Resources/Public/JavaScript/Contrib/',
                'EXT:my_extension/Resources/Public/JavaScript/Overrides/',
            ],
        ],
        // Adding a third party package
        'thirdpartypkg' => 'EXT:my_extension/Resources/Public/JavaScript/Contrib/thidpartypkg/index.js',
        'thidpartypkg/' => 'EXT:my_extension/Resources/Public/JavaScript/Contrib/thirdpartypkg/',
        // Overriding a file from another package
        '@typo3/backend/modal.js' => 'EXT:my_extension/Resources/Public/JavaScript/Overrides/BackendModal.js',
    ],
];
Copied!

Loading ES6

A module can be added to the current page response either via PageRenderer or as JavaScriptModuleInstruction via JavaScriptRenderer:

EXT:my_extension/Classes/SomeNamespace/SomeClass.php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\SomeNamespace;

use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction;
use TYPO3\CMS\Core\Page\PageRenderer;

final class SomeClass
{
    public function __construct(
        // Inject the page renderer dependency
        private readonly PageRenderer $pageRenderer,
    ) {}

    public function someFunction()
    {
        // Load JavaScript via PageRenderer
        $this->pageRenderer->loadJavaScriptModule('@vendor/my-extension/example.js');

        // Load JavaScript via JavaScriptRenderer
        $this->pageRenderer->getJavaScriptRenderer()->addJavaScriptModuleInstruction(
            JavaScriptModuleInstruction::create('@vendor/my-extension/example.js'),
        );
    }
}
Copied!

In a Fluid template the includeJavaScriptModules property of the <f:be.pageRenderer> ViewHelper may be used:

EXT:my_extension/Resources/Private/Backend/Templates/SomeTemplate.html

<f:be.pageRenderer
    includeJavaScriptModules="{
         0: '@myvendor/my-extension/example.js'
      }"
/>
Copied!

Some tips on ES6

No ES6 JavaScript files are created directly in the TYPO3 Core. JavaScript is created as TypeScript module which is then converted to ES6 JavaScript during the build process. However, TypeScript and ES6 are quite similar, you can therefore look into those files for reference. The TypeScript files can be found on GitHub at Build/Sources/TypeScript.

For examples of an ES6 JavaScript file have a look at the JavaScript example in the LinkHandler Tutorial or the example in the Notification API.

For a practical example on how to introduce ES6 modules into a large extension see this commit for EXT:news: [TASK] Add support for TYPO3 v12 ES6 modules.

Using JQuery

In the TYPO3 Core usage of jQuery is eliminated step-by-step as the necessary functionality is provided by native JavaScript nowadays.

If you still have to use jQuery in your third-party extension, include it with the following statement:

import $ from 'jquery';
Copied!

Add JavaScript modules to import map in backend form

The JavaScript module import map is static and only generated and loaded in the first request to a document. All possible future modules requested in later Ajax calls need to be registered already in the first initial request.

The tag backend.form is used to identify JavaScript modules that can be used within backend forms. This ensures that the import maps are available for these modules even if the element is not displayed directly.

A typical use case for this is an InlineRelationRecord where the CKEditor is not part of the main record but needs to be loaded for the child record.

EXT:my_extension/Configuration/JavaScriptModules.php
<?php

return [
    'dependencies' => [
        'backend',
    ],
    'tags' => [
        'backend.form',
    ],
    'imports' => [
        '@typo3/rte-ckeditor/' => 'EXT:rte_ckeditor/Resources/Public/JavaScript/',
        '@typo3/ckeditor5-bundle.js' => 'EXT:rte_ckeditor/Resources/Public/Contrib/ckeditor5-bundle.js',
    ],
];
Copied!

Migration from RequireJS

RequireJS is shimmed to prefer ES6 modules if available, allowing any extension to ship ES6 modules by providing an import map configuration in Configuration/JavaScriptModules.php while providing full backward compatibility support for extensions that load modules via RequireJS.

Existing RequireJS modules can load new ES6 modules via a bridge that prefers ES6 modules over traditional RequireJS AMD modules. This allows extensions authors to migrate to ES6 without breaking dependencies that previously loaded a module of that extension via RequireJS.

Registering modules via $pageRenderer->requireJsModules will still work in TYPO3 v12. These modules will be loaded after modules registered via $pageRenderer->javaScriptModules. Extensions that use $pageRenderer->requireJsModules will work as before but trigger a PHP E_USER_DEPRECATED error.

If your extension wants to support both TYPO3 v11 and v12 you can keep the RequireJS version and remove it upon switching to TYPO3 v13.

If you want to prevent deprecation warnings you can also implement both RequireJS (with a version switch) and native ECMAScript v6/v11 (ES6) modules. This approach is recommended if you are working with TypeScript and the JavaScript will be generated anyway.

Migrate your JavaScript from the AMD module format to native ES6 modules and register your configuration in EXT:my_extension/Configuration/JavaScriptModules.php, also see Loading ES6 for more information:

EXT:my_extension/Configuration/JavaScriptModules.php
<?php

return [
    'dependencies' => ['core', 'backend'],
    'imports' => [
        '@vendor/my-extension/' => 'EXT:my_extension/Resources/Public/JavaScript/',
    ],
];
Copied!

Then use TYPO3\CMS\Core\Page\PageRenderer::loadJavaScriptModules() instead of TYPO3\CMS\Core\Page\PageRenderer::loadRequireJsModule() to load the ES6 module:

EXT:my_extension/Classes/SomeClass.php
<?php

use TYPO3\CMS\Core\Information\Typo3Version;

// ...

$typo3Version = new Typo3Version();
if ($typo3Version->getMajorVersion() > 11) {
    $this->pageRenderer->loadJavaScriptModule(
        '@vendor/my-extension/my-example.js',
    );
} else {
    // keep RequireJs for TYPO3 below v12.0
    $this->pageRenderer->loadRequireJsModule(
        'TYPO3/CMS/MyExtension/MyExample',
    );
}
Copied!

In Fluid templates includeJavaScriptModules is to be used instead of includeRequireJsModules.

In Fluid template the includeJavaScriptModules attribute of the <f:be.pageRenderer> ViewHelper may be used:

Fluid template for TYPO3 v12 and above

<f:be.pageRenderer
    includeJavaScriptModules="{
         0: '@myvendor/my-extension/example.js'
      }"
/>
Copied!
Fluid template for TYPO3 v11 and below
<f:be.pageRenderer
    includeRequireJsModules="{
        0: '@vendor/my-extension/my-example.js'
    }"
/>
Copied!