add site-wide JS caching through import maps
This commit is contained in:
parent
f3aee8f41a
commit
10ccb250c1
15 changed files with 169 additions and 48 deletions
|
@ -25,6 +25,8 @@ use crate::{
|
|||
navmap::{build_navigation_map, NavigationMap},
|
||||
tree::branches_to_html,
|
||||
},
|
||||
import_map::ImportMap,
|
||||
include_static::IncludeStatic,
|
||||
state::Source,
|
||||
static_urls::StaticUrls,
|
||||
tree::SemaRoots,
|
||||
|
@ -216,7 +218,8 @@ impl Generator {
|
|||
let mut config_derived_data = ConfigDerivedData {
|
||||
image_sizes: Default::default(),
|
||||
static_urls: StaticUrls::new(
|
||||
paths.static_dir.to_owned(),
|
||||
// NOTE: Allow referring to generated static assets here.
|
||||
paths.target_dir.join("static"),
|
||||
format!("{}/static", config.site),
|
||||
),
|
||||
};
|
||||
|
@ -227,10 +230,19 @@ impl Generator {
|
|||
handlebars.register_helper(
|
||||
"asset",
|
||||
Box::new(StaticUrls::new(
|
||||
paths.static_dir.to_owned(),
|
||||
paths.target_dir.join("static"),
|
||||
format!("{}/static", config.site),
|
||||
)),
|
||||
);
|
||||
handlebars.register_helper(
|
||||
"include_static",
|
||||
Box::new(IncludeStatic {
|
||||
// NOTE: Again, allow referring to generated static assets.
|
||||
// This is necessary for import maps, for whom the <src> attribute is not
|
||||
// currently supported.
|
||||
base_dir: paths.target_dir.join("static"),
|
||||
}),
|
||||
);
|
||||
|
||||
let mut template_file_ids = HashMap::new();
|
||||
for entry in WalkDir::new(paths.template_dir) {
|
||||
|
@ -389,11 +401,18 @@ pub fn generate(paths: &Paths<'_>) -> anyhow::Result<(Config, Treehouse)> {
|
|||
info!("copying static directory to target directory");
|
||||
copy_dir(paths.static_dir, paths.target_dir.join("static"))?;
|
||||
|
||||
info!("creating static/generated directory");
|
||||
std::fs::create_dir_all(paths.target_dir.join("static/generated"))?;
|
||||
|
||||
info!("parsing tree");
|
||||
let mut generator = Generator::default();
|
||||
generator.add_directory_rec(paths.content_dir)?;
|
||||
let (mut treehouse, parsed_trees) = generator.parse_trees(&config, paths)?;
|
||||
|
||||
// NOTE: The navigation map is a legacy feature that is lazy-loaded when fragment-based
|
||||
// navigation is used.
|
||||
// I couldn't be bothered with adding it to the import map since fragment-based navigation is
|
||||
// only used on very old links. Adding caching to the navigation map is probably not worth it.
|
||||
info!("generating navigation map");
|
||||
let navigation_map = build_navigation_map(&treehouse, "index");
|
||||
std::fs::write(
|
||||
|
@ -401,6 +420,13 @@ pub fn generate(paths: &Paths<'_>) -> anyhow::Result<(Config, Treehouse)> {
|
|||
navigation_map.to_javascript(),
|
||||
)?;
|
||||
|
||||
info!("generating import map");
|
||||
let import_map = ImportMap::generate(config.site.clone(), &config.javascript.import_roots);
|
||||
std::fs::write(
|
||||
paths.target_dir.join("static/generated/import-map.json"),
|
||||
serde_json::to_string_pretty(&import_map).context("could not serialize import map")?,
|
||||
)?;
|
||||
|
||||
info!("generating standalone pages");
|
||||
generator.generate_all_files(
|
||||
&mut treehouse,
|
||||
|
|
|
@ -11,6 +11,7 @@ use crate::{
|
|||
compiled::{compile_syntax, CompiledSyntax},
|
||||
Syntax,
|
||||
},
|
||||
import_map::ImportRoot,
|
||||
static_urls::StaticUrls,
|
||||
};
|
||||
|
||||
|
@ -47,6 +48,9 @@ pub struct Config {
|
|||
/// ```
|
||||
pub redirects: Redirects,
|
||||
|
||||
/// JavaScript configuration.
|
||||
pub javascript: JavaScript,
|
||||
|
||||
/// Overrides for emoji filenames. Useful for setting up aliases.
|
||||
///
|
||||
/// On top of this, emojis are autodiscovered by walking the `static/emoji` directory.
|
||||
|
@ -74,6 +78,12 @@ pub struct Redirects {
|
|||
pub page: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct JavaScript {
|
||||
/// Import roots to generate in the project's import map.
|
||||
pub import_roots: Vec<ImportRoot>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn load(path: &Path) -> anyhow::Result<Self> {
|
||||
let string = std::fs::read_to_string(path).context("cannot read config file")?;
|
||||
|
|
64
crates/treehouse/src/import_map.rs
Normal file
64
crates/treehouse/src/import_map.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
use std::{collections::HashMap, ffi::OsStr, path::PathBuf};
|
||||
|
||||
use log::warn;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::static_urls::StaticUrls;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct ImportMap {
|
||||
pub imports: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct ImportRoot {
|
||||
pub name: String,
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
impl ImportMap {
|
||||
pub fn generate(base_url: String, import_roots: &[ImportRoot]) -> Self {
|
||||
let mut import_map = ImportMap {
|
||||
imports: HashMap::new(),
|
||||
};
|
||||
|
||||
for root in import_roots {
|
||||
let static_urls = StaticUrls::new(
|
||||
PathBuf::from(&root.path),
|
||||
format!("{base_url}/{}", root.path),
|
||||
);
|
||||
for entry in WalkDir::new(&root.path) {
|
||||
let entry = match entry {
|
||||
Ok(entry) => entry,
|
||||
Err(error) => {
|
||||
warn!("directory walk failed: {error}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if !entry.file_type().is_dir() && entry.path().extension() == Some(OsStr::new("js"))
|
||||
{
|
||||
let normalized_path = entry
|
||||
.path()
|
||||
.strip_prefix(&root.path)
|
||||
.unwrap_or(entry.path())
|
||||
.to_string_lossy()
|
||||
.replace('\\', "/");
|
||||
match static_urls.get(&normalized_path) {
|
||||
Ok(url) => {
|
||||
import_map
|
||||
.imports
|
||||
.insert(format!("{}/{normalized_path}", root.name), url);
|
||||
}
|
||||
Err(error) => {
|
||||
warn!("could not get static url for {normalized_path}: {error}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
import_map
|
||||
}
|
||||
}
|
28
crates/treehouse/src/include_static.rs
Normal file
28
crates/treehouse/src/include_static.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use handlebars::{Context, Handlebars, Helper, HelperDef, RenderContext, RenderError, ScopedJson};
|
||||
use serde_json::Value;
|
||||
|
||||
pub struct IncludeStatic {
|
||||
pub base_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl HelperDef for IncludeStatic {
|
||||
fn call_inner<'reg: 'rc, 'rc>(
|
||||
&self,
|
||||
helper: &Helper<'reg, 'rc>,
|
||||
_: &'reg Handlebars<'reg>,
|
||||
_: &'rc Context,
|
||||
_: &mut RenderContext<'reg, 'rc>,
|
||||
) -> Result<ScopedJson<'reg, 'rc>, RenderError> {
|
||||
if let Some(param) = helper.param(0).and_then(|v| v.value().as_str()) {
|
||||
return Ok(ScopedJson::Derived(Value::String(
|
||||
std::fs::read_to_string(self.base_dir.join(param)).map_err(|error| {
|
||||
RenderError::new(format!("cannot read static asset {param}: {error}"))
|
||||
})?,
|
||||
)));
|
||||
}
|
||||
|
||||
Err(RenderError::new("asset path must be provided"))
|
||||
}
|
||||
}
|
|
@ -14,6 +14,8 @@ mod cli;
|
|||
mod config;
|
||||
mod fun;
|
||||
mod html;
|
||||
mod import_map;
|
||||
mod include_static;
|
||||
mod paths;
|
||||
mod state;
|
||||
mod static_urls;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue