ES6 in the TYPO3 backend
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
Page or
Java.
Exposing all module configurations is possible via
Java, 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/ folder to an import prefix @vendor/:
<?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/',
],
];
Complex configuration example containing recursive-lookup exclusions, third-party library definitions and overwrites:
<?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',
],
];
Loading ES6
A module can be added to the current page response either via
Page or as
Java via
Java:
<?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'),
);
}
}
In a Fluid template the include property of the
<f: ViewHelper may be used:
Importing language labels
New in version 14.2 Feature: #108941 - Provide language labels as virtual JavaScript modules
Language labels are available as virtual JavaScript modules.
A JavaScript module imports the labels of a
translation domain directly, by using the
virtual module prefix ~labels/ followed by the domain name. The imported
object provides a get method that resolves a label by its key:
import { html } from 'lit';
// Import the labels of the language domain "my_extension.messages"
import labels from '~labels/my_extension.messages';
html`<p>${labels.get('comment.saved')}</p>`
Placeholders are substituted following the ICU message format. They are passed as the second argument, and a callback may be used to render a pseudo XML tag contained in the label:
// Label: File "{filename}" deleted
html`<p>${labels.get('file.deleted', { filename: 'my-file.txt' })}</p>`
// Label: File <bold>{filename}</bold> deleted
html`<p>${labels.get('file.deleted', {
filename: 'my-file.txt',
bold: chunks => html`<strong>${chunks}</strong>`,
})}</p>`
Labels imported this way do not have to be pushed into the global
TYPO3. object by a controller beforehand, which makes it
possible to write generic web components. The generated URLs are version and
locale specific, so the labels can be cached by the user agent without
explicit cache invalidation.
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
Changed in version 15.0 Breaking: #109998 - Removed jQuery
jQuery and the jquery entry in the import map of the TYPO3 Core
were removed.
TYPO3 does not ship jQuery. Use native JavaScript instead.
If your extension still depends on jQuery, ship your own copy of the library and register it in the import map of your extension:
<?php
return [
'dependencies' => ['core'],
'imports' => [
'jquery' => 'EXT:my_extension/Resources/Public/JavaScript/Contrib/jquery.js',
],
];
Then import it in your modules:
import $ from 'jquery';
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. 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.