Stub out the vite manifest when stabilising template renders

This commit is contained in:
Olivier 'reivilibre
2025-10-29 17:01:50 +00:00
parent b0e2a8f4cd
commit a33605b388
8 changed files with 95 additions and 31 deletions

View File

@@ -166,6 +166,8 @@ impl Options {
&url_builder,
// Don't use strict mode in production yet
false,
// Don't stabilise in production
false,
)
.await?;
shutdown.register_reloadable(&templates);

View File

@@ -90,8 +90,10 @@ impl Options {
let templates = templates_from_config(
&template_config,
&site_config,
&url_builder, // Use strict mode in template checks
&url_builder,
// Use strict mode in template checks
true,
stabilise,
)
.await?;
let all_renders = templates.check_render(now, &rng)?;

View File

@@ -58,6 +58,8 @@ impl Options {
&url_builder,
// Don't use strict mode on task workers for now
false,
// Don't stabilise in production
false,
)
.await?;

View File

@@ -233,11 +233,12 @@ pub async fn templates_from_config(
site_config: &SiteConfig,
url_builder: &UrlBuilder,
strict: bool,
stabilise: bool,
) -> Result<Templates, anyhow::Error> {
Templates::load(
config.path.clone(),
url_builder.clone(),
config.assets_manifest.clone(),
(!stabilise).then(|| config.assets_manifest.clone()),
config.translations_path.clone(),
site_config.templates_branding(),
site_config.templates_features(),

View File

@@ -172,7 +172,7 @@ impl TestState {
let templates = Templates::load(
workspace_root.join("templates"),
url_builder.clone(),
workspace_root.join("frontend/dist/manifest.json"),
Some(workspace_root.join("frontend/dist/manifest.json")),
workspace_root.join("translations"),
site_config.templates_branding(),
site_config.templates_features(),

View File

@@ -47,6 +47,48 @@ pub struct Manifest {
inner: HashMap<Utf8PathBuf, ManifestEntry>,
}
impl Manifest {
/// Produce a sample manifest for use in reproducible sample renders.
#[must_use]
#[allow(clippy::missing_panics_doc)]
pub fn sample() -> Self {
let mut inner = HashMap::new();
for name in &[
"src/shared.css",
"src/templates.css",
"src/main.tsx",
"src/swagger.ts",
] {
inner.insert(
name.parse().unwrap(),
ManifestEntry {
name: None,
names: None,
src: None,
// Construct a fake but slightly plausible dummy asset name.
file: name
.replace('/', "__")
.replace('.', "-XXXXX.")
.replace(".tsx", ".js")
.replace(".ts", ".js")
.parse()
.unwrap(),
css: None,
assets: None,
is_entry: None,
is_dynamic_entry: None,
imports: None,
dynamic_imports: None,
integrity: None,
},
);
}
Manifest { inner }
}
}
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
enum FileType {
Script,

View File

@@ -434,10 +434,10 @@ impl Object for IncludeAsset {
let path: &Utf8Path = path.into();
let (main, imported) = self.vite_manifest.find_assets(path).map_err(|_e| {
let (main, imported) = self.vite_manifest.find_assets(path).map_err(|e| {
Error::new(
ErrorKind::InvalidOperation,
"Invalid assets manifest while calling function `include_asset`",
format!("Invalid assets manifest while calling function `include_asset` with path = {path:?}: {e}"),
)
})?;

View File

@@ -72,7 +72,7 @@ pub struct Templates {
url_builder: UrlBuilder,
branding: SiteBranding,
features: SiteFeatures,
vite_manifest_path: Utf8PathBuf,
vite_manifest_path: Option<Utf8PathBuf>,
translations_path: Utf8PathBuf,
path: Utf8PathBuf,
/// Whether template rendering is in strict mode (for testing,
@@ -143,6 +143,11 @@ fn is_hidden(entry: &DirEntry) -> bool {
impl Templates {
/// Load the templates from the given config
///
/// # Parameters
///
/// - `vite_manifest_path`: None if we are rendering resources for
/// reproducibility, in which case a dummy Vite manifest will be used.
///
/// # Errors
///
/// Returns an error if the templates could not be loaded from disk.
@@ -154,7 +159,7 @@ impl Templates {
pub async fn load(
path: Utf8PathBuf,
url_builder: UrlBuilder,
vite_manifest_path: Utf8PathBuf,
vite_manifest_path: Option<Utf8PathBuf>,
translations_path: Utf8PathBuf,
branding: SiteBranding,
features: SiteFeatures,
@@ -163,7 +168,7 @@ impl Templates {
let (translator, environment) = Self::load_(
&path,
url_builder.clone(),
&vite_manifest_path,
vite_manifest_path.as_deref(),
&translations_path,
branding.clone(),
features,
@@ -186,7 +191,7 @@ impl Templates {
async fn load_(
path: &Utf8Path,
url_builder: UrlBuilder,
vite_manifest_path: &Utf8Path,
vite_manifest_path: Option<&Utf8Path>,
translations_path: &Utf8Path,
branding: SiteBranding,
features: SiteFeatures,
@@ -196,13 +201,18 @@ impl Templates {
let span = tracing::Span::current();
// Read the assets manifest from disk
let vite_manifest = tokio::fs::read(vite_manifest_path)
.await
.map_err(TemplateLoadingError::ViteManifestIO)?;
let vite_manifest = if let Some(vite_manifest_path) = vite_manifest_path {
let raw_vite_manifest = tokio::fs::read(vite_manifest_path)
.await
.map_err(TemplateLoadingError::ViteManifestIO)?;
serde_json::from_slice::<ViteManifest>(&raw_vite_manifest)
.map_err(TemplateLoadingError::ViteManifest)?
} else {
ViteManifest::sample()
};
// Parse it
let vite_manifest: ViteManifest =
serde_json::from_slice(&vite_manifest).map_err(TemplateLoadingError::ViteManifest)?;
let translations_path = translations_path.to_owned();
let translator =
@@ -291,7 +301,7 @@ impl Templates {
let (translator, environment) = Self::load_(
&self.path,
self.url_builder.clone(),
&self.vite_manifest_path,
self.vite_manifest_path.as_deref(),
&self.translations_path,
self.branding.clone(),
self.features,
@@ -506,23 +516,28 @@ mod tests {
Utf8Path::new(env!("CARGO_MANIFEST_DIR")).join("../../frontend/dist/manifest.json");
let translations_path =
Utf8Path::new(env!("CARGO_MANIFEST_DIR")).join("../../translations");
let templates = Templates::load(
path,
url_builder,
vite_manifest_path,
translations_path,
branding,
features,
// Use strict mode in tests
true,
)
.await
.unwrap();
// Check the renders are deterministic, when given the same rng
let render1 = templates.check_render(now, &rng).unwrap();
let render2 = templates.check_render(now, &rng).unwrap();
for use_real_vite_manifest in [true, false] {
let templates = Templates::load(
path.clone(),
url_builder.clone(),
// Check both renders against the real vite manifest and the 'dummy' vite manifest
// used for reproducible renders.
use_real_vite_manifest.then_some(vite_manifest_path.clone()),
translations_path.clone(),
branding.clone(),
features,
// Use strict mode in tests
true,
)
.await
.unwrap();
assert_eq!(render1, render2);
// Check the renders are deterministic, when given the same rng
let render1 = templates.check_render(now, &rng).unwrap();
let render2 = templates.check_render(now, &rng).unwrap();
assert_eq!(render1, render2);
}
}
}