[FEATURE] Load merged JS files asynchronous 30/57130/7
authorRune Piper <kontakt@runepiper.de>
Wed, 6 Jun 2018 07:22:21 +0000 (09:22 +0200)
committerChristian Kuhn <lolli@schwarzbu.ch>
Fri, 15 Jun 2018 13:18:56 +0000 (15:18 +0200)
The async attribute is now assigned to the script tag of the concatenated
JS files if all files have the async attribute enabled in TypoScript.

Resolves: #83476
Releases: master
Change-Id: If4d5f03cac5920cf0bcccefb2e91cc229f9b9e77
Reviewed-on: https://review.typo3.org/57130
Reviewed-by: Sascha Egerer <sascha@sascha-egerer.de>
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Wouter Wolters <typo3@wouterwolters.nl>
Reviewed-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Tested-by: Andreas Fernandez <a.fernandez@scripting-base.de>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
typo3/sysext/core/Classes/Resource/ResourceCompressor.php
typo3/sysext/core/Documentation/Changelog/master/Feature-83476-LoadMergedJSFilesAsynchronous.rst [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Resource/ResourceCompressorTest.php

index 093adea..c24d4f5 100644 (file)
@@ -169,6 +169,8 @@ class ResourceCompressor
      */
     public function concatenateJsFiles(array $jsFiles)
     {
+        $concatenatedJsFileIsAsync = false;
+        $allFilesToConcatenateAreAsync = true;
         $filesToInclude = [];
         foreach ($jsFiles as $key => $fileOptions) {
             // invalid section found or no concatenation allowed, so continue
@@ -184,6 +186,11 @@ class ResourceCompressor
             } else {
                 $filesToInclude[$fileOptions['section']][] = $filenameFromMainDir;
             }
+            if (!empty($fileOptions['async']) && (bool)$fileOptions['async']) {
+                $concatenatedJsFileIsAsync = true;
+            } else {
+                $allFilesToConcatenateAreAsync = false;
+            }
             // remove the file from the incoming file array
             unset($jsFiles[$key]);
         }
@@ -197,7 +204,8 @@ class ResourceCompressor
                     'compress' => true,
                     'excludeFromConcatenation' => true,
                     'forceOnTop' => false,
-                    'allWrap' => ''
+                    'allWrap' => '',
+                    'async' => $concatenatedJsFileIsAsync && $allFilesToConcatenateAreAsync,
                 ];
                 // place the merged javascript on top of the JS files
                 $jsFiles = array_merge([$targetFile => $concatenatedOptions], $jsFiles);
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-83476-LoadMergedJSFilesAsynchronous.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-83476-LoadMergedJSFilesAsynchronous.rst
new file mode 100644 (file)
index 0000000..da24a12
--- /dev/null
@@ -0,0 +1,30 @@
+.. include:: ../../Includes.txt
+
+===================================================
+Feature: #83476 - Load merged JS files asynchronous
+===================================================
+
+See :issue:`83476`
+
+Description
+===========
+
+The async attribute is now assigned to the script tag of the concatenated JS files if all files have the async attribute enabled in TypoScript.
+
+Example:
+--------
+
+.. code-block:: typoscript
+
+   config.concatenateJs = 1
+
+   page = PAGE
+   page.includeJSFooter {
+       test = fileadmin/user_upload/test.js
+       test.async = 1
+
+       test2 = fileadmin/user_upload/test2.js
+       test2.async = 1
+   }
+
+.. index:: Frontend, TypoScript, ext:core
index 97f12a3..4c873a1 100644 (file)
@@ -287,7 +287,7 @@ class ResourceCompressorTest extends BaseTestCase
     /**
      * @test
      */
-    public function concatenatedJsFileIsFlaggedToNotConcatenateAgain(): void
+    public function concatenateJsFileIsFlaggedToNotConcatenateAgain(): void
     {
         $fileName = 'fooFile.js';
         $concatenatedFileName = 'merged_' . $fileName;
@@ -312,6 +312,133 @@ class ResourceCompressorTest extends BaseTestCase
     /**
      * @return array
      */
+    public function concatenateJsFileAsyncDataProvider(): array
+    {
+        return [
+            'all files have no async' => [
+                [
+                    [
+                        'file' => 'file1.js',
+                        'excludeFromConcatenation' => false,
+                        'section' => 'top',
+                    ],
+                    [
+                        'file' => 'file2.js',
+                        'excludeFromConcatenation' => false,
+                        'section' => 'top',
+                    ],
+                ],
+                false
+            ],
+            'all files have async false' => [
+                [
+                    [
+                        'file' => 'file1.js',
+                        'excludeFromConcatenation' => false,
+                        'section' => 'top',
+                        'async' => false,
+                    ],
+                    [
+                        'file' => 'file2.js',
+                        'excludeFromConcatenation' => false,
+                        'section' => 'top',
+                        'async' => false,
+                    ],
+                ],
+                false
+            ],
+            'all files have async true' => [
+                [
+                    [
+                        'file' => 'file1.js',
+                        'excludeFromConcatenation' => false,
+                        'section' => 'top',
+                        'async' => true,
+                    ],
+                    [
+                        'file' => 'file2.js',
+                        'excludeFromConcatenation' => false,
+                        'section' => 'top',
+                        'async' => true,
+                    ],
+                ],
+                true
+            ],
+            'one file async true and one file async false' => [
+                [
+                    [
+                        'file' => 'file1.js',
+                        'excludeFromConcatenation' => false,
+                        'section' => 'top',
+                        'async' => true,
+                    ],
+                    [
+                        'file' => 'file2.js',
+                        'excludeFromConcatenation' => false,
+                        'section' => 'top',
+                        'async' => false,
+                    ],
+                ],
+                false
+            ],
+            'one file async true and one file async false but is excluded form concatenation' => [
+                [
+                    [
+                        'file' => 'file1.js',
+                        'excludeFromConcatenation' => false,
+                        'section' => 'top',
+                        'async' => true,
+                    ],
+                    [
+                        'file' => 'file2.js',
+                        'excludeFromConcatenation' => true,
+                        'section' => 'top',
+                        'async' => false,
+                    ],
+                ],
+                true
+            ],
+            'one file async false and one file async true but is excluded form concatenation' => [
+                [
+                    [
+                        'file' => 'file1.js',
+                        'excludeFromConcatenation' => false,
+                        'section' => 'top',
+                        'async' => false,
+                    ],
+                    [
+                        'file' => 'file2.js',
+                        'excludeFromConcatenation' => true,
+                        'section' => 'top',
+                        'async' => true,
+                    ],
+                ],
+                false
+            ],
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider concatenateJsFileAsyncDataProvider
+     * @param string $input
+     * @param bool $expected
+     */
+    public function concatenateJsFileAddsAsyncPropertyIfAllFilesAreAsync(array $input, bool $expected): void
+    {
+        $concatenatedFileName = 'merged_foo.js';
+        $this->subject->expects($this->once())
+            ->method('createMergedJsFile')
+            ->will($this->returnValue($concatenatedFileName));
+
+        $result = $this->subject->concatenateJsFiles($input);
+
+        $this->assertSame($expected, $result[$concatenatedFileName]['async']);
+    }
+
+    /**
+     * @return array
+     */
     public function calcStatementsDataProvider(): array
     {
         return [