From f410c0d175233f346ebc71d6c5178a88480e082c Mon Sep 17 00:00:00 2001
From: Benjamin Franzke <bfr@qbus.de>
Date: Mon, 19 Sep 2022 14:49:29 +0200
Subject: [PATCH] [TASK] Deprecate support for RequireJS modules

The RequireJS project has been discontinued [1] and was therefore
replaced by native ECMAScript v6/v11 modules in TYPO3 with #96510.

The infrastructure for configuration and loading of RequireJS
modules is now deprecated and will be removed in TYPO3 v13.

[1] https://github.com/requirejs/requirejs/issues/1816

Releases: main
Resolves: #97057
Resolves: #97067
Related: #96510
Change-Id: I75a45cdb9a107f01ec2bb72bb9a73b1b0f158031
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/75781
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Georg Ringer <georg.ringer@gmail.com>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
---
 .../Form/Element/AbstractFormElement.php      |  5 +-
 .../Controller/RequireJsController.php        |  2 +
 .../Page/JavaScriptModuleInstruction.php      | 10 ++-
 .../sysext/core/Classes/Page/PageRenderer.php |  8 +-
 .../core/Configuration/Backend/AjaxRoutes.php |  1 +
 ...cation-97057-DeprecateRequireJSSupport.rst | 75 +++++++++++++++++++
 .../Page/JavaScriptRendererTest.php           |  6 +-
 .../Controller/DashboardController.php        |  6 +-
 .../Be/Menus/ActionMenuViewHelper.php         | 20 ++---
 .../Controller/FormEditorController.php       |  2 +-
 .../Controller/SetupModuleController.php      |  6 +-
 .../Event/AddJavaScriptModulesEvent.php       | 28 +++++++
 12 files changed, 143 insertions(+), 26 deletions(-)
 create mode 100644 typo3/sysext/core/Documentation/Changelog/12.0/Deprecation-97057-DeprecateRequireJSSupport.rst

diff --git a/typo3/sysext/backend/Classes/Form/Element/AbstractFormElement.php b/typo3/sysext/backend/Classes/Form/Element/AbstractFormElement.php
index d709cec52259..7045c25efcd5 100644
--- a/typo3/sysext/backend/Classes/Form/Element/AbstractFormElement.php
+++ b/typo3/sysext/backend/Classes/Form/Element/AbstractFormElement.php
@@ -334,9 +334,12 @@ abstract class AbstractFormElement extends AbstractNode
         if ($javaScriptEvaluation instanceof JavaScriptModuleInstruction) {
             if ($javaScriptEvaluation->shallLoadRequireJs()) {
                 // just use the module name and export-name
+                // @deprecated will be removed in TYPO3 v13.0
                 $resultArray['javaScriptModules'][] = JavaScriptModuleInstruction::forRequireJS(
                     $javaScriptEvaluation->getName(),
-                    $javaScriptEvaluation->getExportName()
+                    $javaScriptEvaluation->getExportName(),
+                    // silence deprecation error, has already been triggered by the original JavaScriptModuleInstruction instance
+                    true
                 )->invoke('registerCustomEvaluation', $name);
             } else {
                 // just use the module name and export-name
diff --git a/typo3/sysext/core/Classes/Controller/RequireJsController.php b/typo3/sysext/core/Classes/Controller/RequireJsController.php
index 40c41c9bd377..c6d626b10929 100644
--- a/typo3/sysext/core/Classes/Controller/RequireJsController.php
+++ b/typo3/sysext/core/Classes/Controller/RequireJsController.php
@@ -25,6 +25,8 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Handling requirejs client requests.
+ *
+ * @deprecated will be removed in TYPO3 v13.0.
  */
 class RequireJsController
 {
diff --git a/typo3/sysext/core/Classes/Page/JavaScriptModuleInstruction.php b/typo3/sysext/core/Classes/Page/JavaScriptModuleInstruction.php
index 409c067d6273..ed4cf4cd8b25 100644
--- a/typo3/sysext/core/Classes/Page/JavaScriptModuleInstruction.php
+++ b/typo3/sysext/core/Classes/Page/JavaScriptModuleInstruction.php
@@ -23,6 +23,7 @@ class JavaScriptModuleInstruction implements \JsonSerializable
 {
     /**
      * Indicates a requireJS module shall be loaded.
+     * @deprecated will be removed in TYPO3 v13.0
      */
     public const FLAG_LOAD_REQUIRE_JS = 1;
 
@@ -61,11 +62,15 @@ class JavaScriptModuleInstruction implements \JsonSerializable
      * @param string $name RequireJS module name
      * @param string|null $exportName (optional) name used internally to export the module
      * @return self
+     * @deprecated will be removed in TYPO3 v13.0. Use JavaScriptModuleInstruction::create() instead.
      */
-    public static function forRequireJS(string $name, string $exportName = null): self
+    public static function forRequireJS(string $name, string $exportName = null, bool $internalCall = false): self
     {
         $target = GeneralUtility::makeInstance(static::class, $name, self::FLAG_LOAD_REQUIRE_JS);
         $target->exportName = $exportName;
+        if (!$internalCall) {
+            trigger_error('JavaScriptModuleInstruction::forRequireJS() is deprecated in favor of native ES6 modules, use JavaScriptModuleInstruction::create() instead. Support for RequireJS module loading will be removed in TYPO3 v13.0.', E_USER_DEPRECATED);
+        }
         return $target;
     }
 
@@ -183,6 +188,9 @@ class JavaScriptModuleInstruction implements \JsonSerializable
         return $this;
     }
 
+    /**
+     * @deprecated will be removed in TYPO3 v13.0
+     */
     public function shallLoadRequireJs(): bool
     {
         return ($this->flags & self::FLAG_LOAD_REQUIRE_JS) === self::FLAG_LOAD_REQUIRE_JS;
diff --git a/typo3/sysext/core/Classes/Page/PageRenderer.php b/typo3/sysext/core/Classes/Page/PageRenderer.php
index 7d851b1c087e..dbf0512f5f5e 100644
--- a/typo3/sysext/core/Classes/Page/PageRenderer.php
+++ b/typo3/sysext/core/Classes/Page/PageRenderer.php
@@ -1578,9 +1578,13 @@ class PageRenderer implements SingletonInterface
      *
      * @param string $mainModuleName Must be in the form of "TYPO3/CMS/PackageName/ModuleName" e.g. "TYPO3/CMS/Backend/FormEngine"
      * @param string $callBackFunction loaded right after the requireJS loading, must be wrapped in function() {}
+     * @deprecated will be removed in TYPO3 v13.0. Use loadJavaScriptModule() instead, available since TYPO3 v12.0.
      */
-    public function loadRequireJsModule($mainModuleName, $callBackFunction = null)
+    public function loadRequireJsModule($mainModuleName, $callBackFunction = null, bool $internal = false)
     {
+        if (!$internal) {
+            trigger_error('PageRenderer->loadRequireJsModule is deprecated in favor of native ES6 modules, use loadJavaScriptModule() instead. Support for RequireJS module loading will be removed in TYPO3 v13.0.', E_USER_DEPRECATED);
+        }
         $inlineCodeKey = $mainModuleName;
         // make sure requireJS is initialized
         $this->loadRequireJs();
@@ -1593,7 +1597,7 @@ class PageRenderer implements SingletonInterface
         }
         if ($callBackFunction === null && $this->getApplicationType() === 'BE') {
             $this->javaScriptRenderer->addJavaScriptModuleInstruction(
-                JavaScriptModuleInstruction::forRequireJS($mainModuleName)
+                JavaScriptModuleInstruction::forRequireJS($mainModuleName, null, true)
             );
             return;
         }
diff --git a/typo3/sysext/core/Configuration/Backend/AjaxRoutes.php b/typo3/sysext/core/Configuration/Backend/AjaxRoutes.php
index 60a3b8a1d8a4..f75a7dd3424a 100644
--- a/typo3/sysext/core/Configuration/Backend/AjaxRoutes.php
+++ b/typo3/sysext/core/Configuration/Backend/AjaxRoutes.php
@@ -7,6 +7,7 @@ use TYPO3\CMS\Core\Controller\RequireJsController;
  */
 return [
     // dynamically load requirejs module definitions
+    // @deprecated will be removed in TYPO3 v13.0.
     'core_requirejs' => [
         'path' => '/core/requirejs',
         'access' => 'public',
diff --git a/typo3/sysext/core/Documentation/Changelog/12.0/Deprecation-97057-DeprecateRequireJSSupport.rst b/typo3/sysext/core/Documentation/Changelog/12.0/Deprecation-97057-DeprecateRequireJSSupport.rst
new file mode 100644
index 000000000000..3748b36b43a6
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/12.0/Deprecation-97057-DeprecateRequireJSSupport.rst
@@ -0,0 +1,75 @@
+.. include:: /Includes.rst.txt
+
+.. _deprecation-97057-1664653704:
+
+=================================================
+Deprecation: #97057 - Deprecate RequireJS support
+=================================================
+
+See :issue:`97057`
+
+Description
+===========
+
+The RequireJS project has been discontinued_ and was therefore
+replaced by native ECMAScript v6/v11 modules in TYPO3 in :issue:`96510`.
+
+The infrastructure for configuration and loading of RequireJS
+modules is now deprecated and will be removed in TYPO3 v13.
+
+
+Impact
+======
+
+Registering modules via :php:`'requireJsModules'` will still work.
+These modules will be loaded after modules registered via :php:`'javaScriptModules'`. Extensions that
+use :php:`'requireJsModules` will work as before but trigger a PHP :php:`E_USER_DEPRECATED` error.
+
+
+Affected installations
+======================
+
+Installations that register custom JavaScript modules for the TYPO3 backend.
+
+
+Migration
+=========
+
+Migrate your JavaScript from the AMD module format to native ES6 modules and register your configuration in :php:`Configuration/JavaScriptModules.php`, also see :issue:`96510` for more information:
+
+.. code-block:: php
+
+    # Configuration/JavaScriptModules.php
+    <?php
+
+    return [
+        'dependencies' => ['core', 'backend'],
+        'imports' => [
+            '@vendor/my-extension/' => 'EXT:my_extension/Resources/Public/JavaScript/',
+        ],
+    ];
+
+Then use :php:`TYPO3\CMS\Core\Page\PageRenderer::loadJavaScriptModules()` instead of :php:`TYPO3\CMS\Core\Page\PageRenderer::loadRequireJsModule()` to load the ES6 module:
+
+.. code-block:: php
+
+    // via PageRenderer
+    $this->packageRenderer->loadJavaScriptModule('@vendor/my-extension/example.js');
+
+
+In Fluid templates `includeJavaScriptModules` is to be used instead of `includeRequireJsModules`:
+
+In Fluid template the `includeJavaScriptModules` property of the
+:html:`<f:be.pageRenderer>` ViewHelper may be used:
+
+.. code-block:: xml
+
+   <f:be.pageRenderer
+      includeJavaScriptModules="{
+         0: '@vendor/my-extension/example.js'
+      }"
+   />
+
+.. _discontinued: https://github.com/requirejs/requirejs/issues/1816
+
+.. index:: Backend, JavaScript, NotScanned, ext:backend
diff --git a/typo3/sysext/core/Tests/Functional/Page/JavaScriptRendererTest.php b/typo3/sysext/core/Tests/Functional/Page/JavaScriptRendererTest.php
index 716abb281e91..0aba10395ebd 100644
--- a/typo3/sysext/core/Tests/Functional/Page/JavaScriptRendererTest.php
+++ b/typo3/sysext/core/Tests/Functional/Page/JavaScriptRendererTest.php
@@ -32,15 +32,15 @@ class JavaScriptRendererTest extends FunctionalTestCase
     {
         $subject = JavaScriptRenderer::create('anything.js');
         $subject->addJavaScriptModuleInstruction(
-            JavaScriptModuleInstruction::forRequireJS('TYPO3/CMS/Test*/')
+            JavaScriptModuleInstruction::create('@typo3/test/module.js')
                 ->invoke('test*/', 'arg*/')
         );
         $subject->addGlobalAssignment(['section*/' => ['key*/' => 'value*/']]);
         self::assertSame(
             '<script src="anything.js" async="async">'
                 . '/* [{"type":"globalAssignment","payload":{"section*\/":{"key*\/":"value*\/"}}},'
-                . '{"type":"javaScriptModuleInstruction","payload":{"name":"TYPO3\/CMS\/Test*\/","exportName":null,'
-                . '"flags":1,"items":[{"type":"invoke","method":"test*\/","args":["arg*\/"]}]}}] */</script>',
+                . '{"type":"javaScriptModuleInstruction","payload":{"name":"@typo3\/test\/module.js","exportName":null,'
+                . '"flags":2,"items":[{"type":"invoke","method":"test*\/","args":["arg*\/"]}]}}] */</script>',
             trim($subject->render())
         );
     }
diff --git a/typo3/sysext/dashboard/Classes/Controller/DashboardController.php b/typo3/sysext/dashboard/Classes/Controller/DashboardController.php
index da34c372a8a2..2ba53029af92 100644
--- a/typo3/sysext/dashboard/Classes/Controller/DashboardController.php
+++ b/typo3/sysext/dashboard/Classes/Controller/DashboardController.php
@@ -168,10 +168,12 @@ class DashboardController
         }
         foreach ($this->dashboardInitializationService->getRequireJsModules() as $requireJsModule) {
             if (is_array($requireJsModule)) {
-                $this->pageRenderer->loadRequireJsModule($requireJsModule[0], $requireJsModule[1]);
+                // Deprecation message is triggered by DashboardInitializationService::defineResourcesOfWidgets, and therefore silenced here.
+                $this->pageRenderer->loadRequireJsModule($requireJsModule[0], $requireJsModule[1], true);
             } else {
+                // Deprecation message is triggered by DashboardInitializationService::defineResourcesOfWidgets, and therefore silenced here.
                 $javaScriptRenderer->addJavaScriptModuleInstruction(
-                    JavaScriptModuleInstruction::forRequireJS($requireJsModule)
+                    JavaScriptModuleInstruction::forRequireJS($requireJsModule, null, true)
                 );
             }
         }
diff --git a/typo3/sysext/fluid/Classes/ViewHelpers/Be/Menus/ActionMenuViewHelper.php b/typo3/sysext/fluid/Classes/ViewHelpers/Be/Menus/ActionMenuViewHelper.php
index 84127af2c7ac..7101cfd06e0d 100644
--- a/typo3/sysext/fluid/Classes/ViewHelpers/Be/Menus/ActionMenuViewHelper.php
+++ b/typo3/sysext/fluid/Classes/ViewHelpers/Be/Menus/ActionMenuViewHelper.php
@@ -17,9 +17,8 @@ declare(strict_types=1);
 
 namespace TYPO3\CMS\Fluid\ViewHelpers\Be\Menus;
 
-use TYPO3\CMS\Core\Page\JavaScriptModuleInstruction;
-use TYPO3\CMS\Core\Page\JavaScriptRenderer;
 use TYPO3\CMS\Core\Page\PageRenderer;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3Fluid\Fluid\Core\Compiler\TemplateCompiler;
 use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\ViewHelperNode;
 use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;
@@ -79,8 +78,8 @@ final class ActionMenuViewHelper extends AbstractTagBasedViewHelper
             'data-action-navigate' => '$value',
         ]);
         $this->tag->setContent($options);
-        return $this->loadRequireJsModule('TYPO3/CMS/Backend/GlobalEventHandler')
-            . '<div class="docheader-funcmenu">' . $this->tag->render() . '</div>';
+        $this->getPageRenderer()->loadJavaScriptModule('@typo3/backend/global-event-handler.js');
+        return '<div class="docheader-funcmenu">' . $this->tag->render() . '</div>';
     }
 
     /**
@@ -95,17 +94,8 @@ final class ActionMenuViewHelper extends AbstractTagBasedViewHelper
         return null;
     }
 
-    /**
-     * Renders `<script src="JavaScriptItemHandler.js">...</script>` for loading
-     * corresponding module. Using `JavaScriptRenderer` makes this independent
-     * from `PageRenderer` and its current application state.
-     */
-    protected function loadRequireJsModule(string $name): string
+    protected static function getPageRenderer(): PageRenderer
     {
-        $javaScriptRenderer = JavaScriptRenderer::create();
-        $javaScriptRenderer->addJavaScriptModuleInstruction(
-            JavaScriptModuleInstruction::forRequireJS($name)
-        );
-        return $javaScriptRenderer->render();
+        return GeneralUtility::makeInstance(PageRenderer::class);
     }
 }
diff --git a/typo3/sysext/form/Classes/Controller/FormEditorController.php b/typo3/sysext/form/Classes/Controller/FormEditorController.php
index 6cf23319e112..463732f84c49 100644
--- a/typo3/sysext/form/Classes/Controller/FormEditorController.php
+++ b/typo3/sysext/form/Classes/Controller/FormEditorController.php
@@ -108,7 +108,7 @@ class FormEditorController extends AbstractBackendController
             $this->prototypeConfiguration['formEditor']['dynamicJavaScriptModules']['additionalViewModelModules'] ?? []
         );
         $additionalViewModelRequireJsModules = array_map(
-            static fn (string $name) => JavaScriptModuleInstruction::forRequireJS($name),
+            static fn (string $name) => JavaScriptModuleInstruction::forRequireJS($name, null, true),
             $this->prototypeConfiguration['formEditor']['dynamicRequireJsModules']['additionalViewModelModules'] ?? []
         );
         if (count($additionalViewModelRequireJsModules) > 0) {
diff --git a/typo3/sysext/setup/Classes/Controller/SetupModuleController.php b/typo3/sysext/setup/Classes/Controller/SetupModuleController.php
index 3aa246a757bb..6f2e4cc63f70 100644
--- a/typo3/sysext/setup/Classes/Controller/SetupModuleController.php
+++ b/typo3/sysext/setup/Classes/Controller/SetupModuleController.php
@@ -152,8 +152,12 @@ class SetupModuleController
         $event = new AddJavaScriptModulesEvent();
         /** @var AddJavaScriptModulesEvent $event */
         $event = $this->eventDispatcher->dispatch($event);
+        foreach ($event->getJavaScriptModules() as $specifier) {
+            $this->pageRenderer->loadJavaScriptModule($specifier);
+        }
         foreach ($event->getModules() as $moduleName) {
-            $this->pageRenderer->loadRequireJsModule($moduleName);
+            // The deprecation is added in AddJavaScriptModulesEvent::addModule, and therefore silenced here.
+            $this->pageRenderer->loadRequireJsModule($moduleName, null, true);
         }
     }
 
diff --git a/typo3/sysext/setup/Classes/Event/AddJavaScriptModulesEvent.php b/typo3/sysext/setup/Classes/Event/AddJavaScriptModulesEvent.php
index 63ba207f9ed5..440d8c2145c1 100644
--- a/typo3/sysext/setup/Classes/Event/AddJavaScriptModulesEvent.php
+++ b/typo3/sysext/setup/Classes/Event/AddJavaScriptModulesEvent.php
@@ -22,19 +22,47 @@ namespace TYPO3\CMS\Setup\Event;
  */
 final class AddJavaScriptModulesEvent
 {
+    /**
+     * @var string[]
+     */
+    private array $javaScriptModules = [];
+
     /**
      * @var string[]
      */
     private array $modules = [];
 
+    /**
+     * @param string $specifier Bare module identifier like @my/package/filename.js
+     */
+    public function addJavaScriptModule(string $specifier): void
+    {
+        if (in_array($specifier, $this->javaScriptModules, true)) {
+            return;
+        }
+        $this->javaScriptModules[] = $specifier;
+    }
+
+    /**
+     * @deprecated will be removed in TYPO3 v13.0. Use addJavaScriptModule() instead, available since TYPO3 v12.0.
+     */
     public function addModule(string $moduleName): void
     {
+        trigger_error('AddJavaScriptModulesEvent->addModule is deprecated in favor of native ES6 modules and will be removed in TYPO3 v13.0. Use an ES6 module via addJavaScriptModule instead.', E_USER_DEPRECATED);
         if (in_array($moduleName, $this->modules, true)) {
             return;
         }
         $this->modules[] = $moduleName;
     }
 
+    /**
+     * @return string[]
+     */
+    public function getJavaScriptModules(): array
+    {
+        return $this->javaScriptModules;
+    }
+
     /**
      * @return string[]
      */
-- 
GitLab