@@ -54,23 +54,22 @@ function emit_css_loading_tags($obj): void {
5454 * @param string $src the source URL, ignored when empty
5555 * @param string $script_content the inline script content, ignored when empty
5656 * @param string $content_type the type of the source (e.g. 'module')
57+ *
58+ * @since 27.0.0 added the $content_type parameter
5759 */
5860function emit_script_tag (string $ src , string $ script_content = '' , string $ content_type = '' ): void {
5961 $ nonceManager = Server::get (ContentSecurityPolicyNonceManager::class);
6062
61- $ defer_str = ' defer ';
63+ $ defer_str = $ content_type === '' ? ' defer ' : '' ; // "defer" only works with classic scripts
6264 $ type = $ content_type !== '' ? ' type=" ' . $ content_type . '" ' : '' ;
6365
64- $ s = '<script nonce=" ' . $ nonceManager ->getNonce () . '" ' ;
66+ $ s = '<script nonce=" ' . $ nonceManager ->getNonce () . '" ' . $ type ;
6567 if (!empty ($ src )) {
6668 // emit script tag for deferred loading from $src
67- $ s .= $ defer_str . ' src=" ' . $ src . '" ' . $ type . ' > ' ;
68- } elseif ( $ script_content !== '' ) {
69+ $ s .= $ defer_str . ' src=" ' . $ src . '"> ' ;
70+ } else {
6971 // emit script tag for inline script from $script_content without defer (see MDN)
7072 $ s .= "> \n" . $ script_content . "\n" ;
71- } else {
72- // no $src nor $src_content, really useless empty tag
73- $ s .= '> ' ;
7473 }
7574 $ s .= '</script> ' ;
7675 print_unescaped ($ s . "\n" );
@@ -81,6 +80,8 @@ function emit_script_tag(string $src, string $script_content = '', string $conte
8180 * @param array $obj all the script information from template
8281 */
8382function emit_script_loading_tags ($ obj ): void {
83+ emit_import_map ($ obj );
84+
8485 foreach ($ obj ['jsfiles ' ] as $ jsfile ) {
8586 $ fileName = explode ('? ' , $ jsfile , 2 )[0 ];
8687 $ type = str_ends_with ($ fileName , '.mjs ' ) ? 'module ' : '' ;
@@ -91,6 +92,29 @@ function emit_script_loading_tags($obj): void {
9192 }
9293}
9394
95+ /**
96+ * Print the import map for the current JS modules.
97+ * The import map is needed to ensure that an import of an entry point does not duplicate the state,
98+ * but reuses the already loaded module. This is needed because Nextcloud will append a cache buster
99+ * to the entry point URLs but the scripts does not know about that (both must match).
100+ *
101+ * @param $obj all the script information from template
102+ */
103+ function emit_import_map (array $ obj ): void {
104+ $ modules = [];
105+ foreach ($ obj ['jsfiles ' ] as $ jsfile ) {
106+ $ fileName = explode ('? ' , $ jsfile , 2 )[0 ];
107+ if (str_ends_with ($ fileName , '.mjs ' ) && $ jsfile !== $ fileName ) {
108+ // its a module and we have a cache buster available
109+ $ modules [$ fileName ] = $ jsfile ;
110+ }
111+ }
112+ if (!empty ($ modules )) {
113+ $ json = json_encode (['imports ' => $ modules ], JSON_UNESCAPED_SLASHES | JSON_FORCE_OBJECT );
114+ emit_script_tag ('' , $ json , 'importmap ' );
115+ }
116+ }
117+
94118/**
95119 * Prints an unsanitized string - usage of this function may result into XSS.
96120 * Consider using p() instead.
0 commit comments