Skip to content

Commit 2d0cd15

Browse files
committed
fix(template): add import map for JS module entry points
Currently apps are broken if they have exports in the JS entry point, because they then will import from the entry point but because they do not know about the Nextcloud cache buster they will import without cache buster. This results in two problem: 1. The module might be outdated (old cached) 2. The module is duplicated, so the module will be loaded twice and will have two different - out of sync - states. This also means it will re-run sideeffects of the entry point. To fix this we generate an import map which basically maps the plain entry point script to the script with cache buster added. For example: ```js // entry.mjs console.error('called') async function onClick() { await import('./chunk.mjs') } export const name = 'foo' // chunk.mjs import { name } from './entry.mjs' console.error(name) ``` When calling `onClick` without this fix the output will be: > called > called > foo With this fix: > called > foo Signed-off-by: Ferdinand Thiessen <[email protected]>
1 parent 75edec9 commit 2d0cd15

File tree

1 file changed

+27
-2
lines changed

1 file changed

+27
-2
lines changed

lib/private/Template/functions.php

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,10 @@ function emit_script_tag(string $src, string $script_content = '', string $conte
6161
$defer_str = ' defer';
6262
$type = $content_type !== '' ? ' type="' . $content_type . '"' : '';
6363

64-
$s = '<script nonce="' . $nonceManager->getNonce() . '"';
64+
$s = '<script nonce="' . $nonceManager->getNonce() . '"' . $type;
6565
if (!empty($src)) {
6666
// emit script tag for deferred loading from $src
67-
$s .= $defer_str . ' src="' . $src . '"' . $type . '>';
67+
$s .= $defer_str . ' src="' . $src . '">';
6868
} elseif ($script_content !== '') {
6969
// emit script tag for inline script from $script_content without defer (see MDN)
7070
$s .= ">\n" . $script_content . "\n";
@@ -81,6 +81,8 @@ function emit_script_tag(string $src, string $script_content = '', string $conte
8181
* @param array $obj all the script information from template
8282
*/
8383
function emit_script_loading_tags($obj): void {
84+
emit_import_map($obj);
85+
8486
foreach ($obj['jsfiles'] as $jsfile) {
8587
$fileName = explode('?', $jsfile, 2)[0];
8688
$type = str_ends_with($fileName, '.mjs') ? 'module' : '';
@@ -91,6 +93,29 @@ function emit_script_loading_tags($obj): void {
9193
}
9294
}
9395

96+
/**
97+
* Print the import map for the current JS modules.
98+
* The import map is needed to ensure that an import of an entry point does not duplicate the state,
99+
* but reuses the already loaded module. This is needed because Nextcloud will append a cache buster
100+
* to the entry point URLs but the scripts does not know about that (both must match).
101+
*
102+
* @param array $obj all the script information from template
103+
*/
104+
function emit_import_map($obj): void {
105+
$modules = [];
106+
foreach ($obj['jsfiles'] as $jsfile) {
107+
$fileName = explode('?', $jsfile, 2)[0];
108+
if (str_ends_with($fileName, '.mjs') && $jsfile !== $fileName) {
109+
// its a module and we have a cache buster available
110+
$modules[$fileName] = $jsfile;
111+
}
112+
}
113+
if (!empty($modules)) {
114+
$json = json_encode(['imports' => $modules], JSON_UNESCAPED_SLASHES | JSON_FORCE_OBJECT);
115+
emit_script_tag('', $json, 'importmap');
116+
}
117+
}
118+
94119
/**
95120
* Prints an unsanitized string - usage of this function may result into XSS.
96121
* Consider using p() instead.

0 commit comments

Comments
 (0)