Emit a modulepreload for dynamically imported chunks in include_asset

Now that the translations are lazily imported JS chunks rather than plain JSON
assets, `include_asset('locales/<lang>.json')` resolves to a manifest entry
flagged `isDynamicEntry`. Those must not be evaluated eagerly with a `<script>`
tag, so emit the same `modulepreload` hint we already use for imported chunks,
integrity included, and drop the now-unreachable top-level JSON preload.
This commit is contained in:
Quentin Gliech
2026-08-07 17:59:27 +02:00
parent 5d7a66557a
commit cacc757cef
2 changed files with 26 additions and 13 deletions
+10 -1
View File
@@ -1,3 +1,4 @@
// Copyright 2025, 2026 Element Creations Ltd.
// Copyright 2024, 2025 New Vector Ltd.
// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
//
@@ -30,7 +31,6 @@ pub struct ManifestEntry {
#[expect(dead_code)]
is_entry: Option<bool>,
#[expect(dead_code)]
is_dynamic_entry: Option<bool>,
imports: Option<Vec<Utf8PathBuf>>,
@@ -90,6 +90,7 @@ pub struct Asset<'a> {
file_type: FileType,
name: &'a Utf8Path,
integrity: Option<&'a str>,
is_dynamic_entry: bool,
}
impl<'a> Asset<'a> {
@@ -101,6 +102,7 @@ impl<'a> Asset<'a> {
file_type,
name,
integrity,
is_dynamic_entry: entry.is_dynamic_entry.unwrap_or(false),
})
}
@@ -116,6 +118,13 @@ impl<'a> Asset<'a> {
self.file_type
}
/// Whether this asset is a chunk which is only reachable through a dynamic
/// `import()`, and therefore must not be evaluated eagerly
#[must_use]
pub fn is_dynamic_entry(&self) -> bool {
self.is_dynamic_entry
}
/// Get the integrity HTML tag attribute, with a leading space, if any
#[must_use]
pub fn integrity_attr(&self) -> String {
+16 -12
View File
@@ -485,6 +485,20 @@ impl Object for IncludeAsset {
// We'll accumulate the output in this string
let mut output = String::new();
match main.file_type() {
mas_spa::FileType::Script if main.is_dynamic_entry() => {
// Chunks which are only reachable through a dynamic `import()`
// (like the translation files) must not be evaluated eagerly,
// we only hint the browser to fetch them
let integrity = main.integrity_attr();
let src = main.src(assets_base);
if tracker.mark_preloaded(&src) {
writeln!(
output,
r#"<link rel="modulepreload" href="{src}" crossorigin="anonymous"{integrity} />"#
)
.unwrap();
}
}
mas_spa::FileType::Script => {
let integrity = main.integrity_attr();
let src = main.src(assets_base);
@@ -508,14 +522,6 @@ impl Object for IncludeAsset {
}
}
mas_spa::FileType::Json => {
// When a JSON is included at the top level (a translation), we preload it
let src = main.src(assets_base);
if tracker.mark_preloaded(&src) {
writeln!(output, r#"<link rel="preload" href="{src}" as="fetch" />"#).unwrap();
}
}
file_type => {
return Err(Error::new(
ErrorKind::InvalidOperation,
@@ -560,10 +566,8 @@ impl Object for IncludeAsset {
}
}
mas_spa::FileType::Woff | mas_spa::FileType::Woff2 | mas_spa::FileType::Json => {
// Skip pre-loading fonts and JSON (translations) as it will
// lead to many wasted preloads. For translations, we only
// include them as preload if they are included on the
// top-level
// Skip pre-loading fonts and raw JSON assets, as it will
// lead to many wasted preloads
}
}
}