diff --git a/lib/private/Template/functions.php b/lib/private/Template/functions.php index 402a7491e035d..326b5e578e7ee 100644 --- a/lib/private/Template/functions.php +++ b/lib/private/Template/functions.php @@ -54,23 +54,22 @@ function emit_css_loading_tags($obj): void { * @param string $src the source URL, ignored when empty * @param string $script_content the inline script content, ignored when empty * @param string $content_type the type of the source (e.g. 'module') + * + * @since 27.0.0 added the $content_type parameter */ function emit_script_tag(string $src, string $script_content = '', string $content_type = ''): void { $nonceManager = Server::get(ContentSecurityPolicyNonceManager::class); - $defer_str = ' defer'; + $defer_str = $content_type === '' ? ' defer' : ''; // "defer" only works with classic scripts $type = $content_type !== '' ? ' type="' . $content_type . '"' : ''; - $s = ''; print_unescaped($s . "\n"); @@ -81,6 +80,8 @@ function emit_script_tag(string $src, string $script_content = '', string $conte * @param array $obj all the script information from template */ function emit_script_loading_tags($obj): void { + emit_import_map($obj); + foreach ($obj['jsfiles'] as $jsfile) { $fileName = explode('?', $jsfile, 2)[0]; $type = str_ends_with($fileName, '.mjs') ? 'module' : ''; @@ -91,6 +92,29 @@ function emit_script_loading_tags($obj): void { } } +/** + * Print the import map for the current JS modules. + * The import map is needed to ensure that an import of an entry point does not duplicate the state, + * but reuses the already loaded module. This is needed because Nextcloud will append a cache buster + * to the entry point URLs but the scripts does not know about that (both must match). + * + * @param $obj all the script information from template + */ +function emit_import_map(array $obj): void { + $modules = []; + foreach ($obj['jsfiles'] as $jsfile) { + $fileName = explode('?', $jsfile, 2)[0]; + if (str_ends_with($fileName, '.mjs') && $jsfile !== $fileName) { + // its a module and we have a cache buster available + $modules[$fileName] = $jsfile; + } + } + if (!empty($modules)) { + $json = json_encode(['imports' => $modules], JSON_UNESCAPED_SLASHES | JSON_FORCE_OBJECT); + emit_script_tag('', $json, 'importmap'); + } +} + /** * Prints an unsanitized string - usage of this function may result into XSS. * Consider using p() instead. diff --git a/tests/lib/TemplateFunctionsTest.php b/tests/lib/TemplateFunctionsTest.php index 8c1523628ab51..3afb1e4e34355 100644 --- a/tests/lib/TemplateFunctionsTest.php +++ b/tests/lib/TemplateFunctionsTest.php @@ -54,13 +54,34 @@ public function testEmitScriptTagWithSource(): void { } public function testEmitScriptTagWithModuleSource(): void { - $this->expectOutputRegex('/