,
+ data: &'a [u8],
+ filename: &str,
+ ) -> Cow<'a, [u8]> {
+ RESOURCE.replace_all(data, move |captures: &Captures<'_>| {
+ let name = captures
+ .get(1)
+ .expect("capture 1 in resource regex")
+ .as_bytes();
+ let name = std::str::from_utf8(name).expect("resource name with invalid utf8");
+ let resource_filename = hash_map.get(name).map(|s| &s[..]).unwrap_or(name);
+ let path_to_root = utils::fs::path_to_root(filename);
+ format!("{}{}", path_to_root, resource_filename)
+ .as_bytes()
+ .to_owned()
+ })
+ }
+ for static_file in &self.static_files {
+ match static_file {
+ StaticFile::Builtin { filename, data } => {
+ debug!("Writing builtin -> {}", filename);
+ let data = if filename.ends_with(".css") || filename.ends_with(".js") {
+ replace_all(&self.hash_map, data, filename)
+ } else {
+ Cow::Borrowed(&data[..])
+ };
+ write_file(destination, filename, &data)?;
+ }
+ StaticFile::Additional {
+ ref input_location,
+ ref filename,
+ } => {
+ let output_location = destination.join(filename);
+ debug!(
+ "Copying {} -> {}",
+ input_location.display(),
+ output_location.display()
+ );
+ if let Some(parent) = output_location.parent() {
+ fs::create_dir_all(parent)
+ .with_context(|| format!("Unable to create {}", parent.display()))?;
+ }
+ if filename.ends_with(".css") || filename.ends_with(".js") {
+ let data = fs::read(input_location)?;
+ let data = replace_all(&self.hash_map, &data, filename);
+ write_file(destination, filename, &data)?;
+ } else {
+ fs::copy(input_location, &output_location).with_context(|| {
+ format!(
+ "Unable to copy {} to {}",
+ input_location.display(),
+ output_location.display()
+ )
+ })?;
+ }
+ }
+ }
+ }
+ let hash_map = self.hash_map;
+ Ok(ResourceHelper { hash_map })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::config::HtmlConfig;
+ use crate::theme::Theme;
+ use crate::utils::fs::write_file;
+ use tempfile::TempDir;
+
+ #[test]
+ fn test_write_directive() {
+ let theme = Theme {
+ index: Vec::new(),
+ head: Vec::new(),
+ redirect: Vec::new(),
+ header: Vec::new(),
+ chrome_css: Vec::new(),
+ general_css: Vec::new(),
+ print_css: Vec::new(),
+ variables_css: Vec::new(),
+ favicon_png: Some(Vec::new()),
+ favicon_svg: Some(Vec::new()),
+ js: Vec::new(),
+ highlight_css: Vec::new(),
+ tomorrow_night_css: Vec::new(),
+ ayu_highlight_css: Vec::new(),
+ highlight_js: Vec::new(),
+ clipboard_js: Vec::new(),
+ toc_js: Vec::new(),
+ toc_html: Vec::new(),
+ fonts_css: None,
+ font_files: Vec::new(),
+ };
+ let temp_dir = TempDir::with_prefix("mdbook-").unwrap();
+ let reference_js = Path::new("static-files-test-case-reference.js");
+ let mut html_config = HtmlConfig::default();
+ html_config.additional_js.push(reference_js.to_owned());
+ write_file(
+ temp_dir.path(),
+ reference_js,
+ br#"{{ resource "book.js" }}"#,
+ )
+ .unwrap();
+ let mut static_files = StaticFiles::new(&theme, &html_config, temp_dir.path()).unwrap();
+ static_files.hash_files().unwrap();
+ static_files.write_files(temp_dir.path()).unwrap();
+ // custom JS winds up referencing book.js
+ let reference_js_content = std::fs::read_to_string(
+ temp_dir
+ .path()
+ .join("static-files-test-case-reference-635c9cdc.js"),
+ )
+ .unwrap();
+ assert_eq!("book-e3b0c442.js", reference_js_content);
+ // book.js winds up empty
+ let book_js_content =
+ std::fs::read_to_string(temp_dir.path().join("book-e3b0c442.js")).unwrap();
+ assert_eq!("", book_js_content);
+ }
+}
diff --git a/src/theme/book.js b/src/theme/book.js
index a5c255500c..d78bd79651 100644
--- a/src/theme/book.js
+++ b/src/theme/book.js
@@ -294,9 +294,9 @@ function playground_text(playground, hidden = true) {
themeIds.push(el.id);
});
var stylesheets = {
- ayuHighlight: document.querySelector("[href$='ayu-highlight.css']"),
- tomorrowNight: document.querySelector("[href$='tomorrow-night.css']"),
- highlight: document.querySelector("[href$='highlight.css']"),
+ ayuHighlight: document.querySelector("#ayu-highlight-css"),
+ tomorrowNight: document.querySelector("#tomorrow-night-css"),
+ highlight: document.querySelector("#highlight-css"),
};
function showThemes() {
diff --git a/src/theme/fonts/fonts.css b/src/theme/fonts/fonts.css
index 858efa5980..a6b12b3bf6 100644
--- a/src/theme/fonts/fonts.css
+++ b/src/theme/fonts/fonts.css
@@ -7,7 +7,7 @@
font-style: normal;
font-weight: 300;
src: local('Open Sans Light'), local('OpenSans-Light'),
- url('open-sans-v17-all-charsets-300.woff2') format('woff2');
+ url('{{ resource "fonts/open-sans-v17-all-charsets-300.woff2" }}') format('woff2');
}
/* open-sans-300italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */
@@ -16,7 +16,7 @@
font-style: italic;
font-weight: 300;
src: local('Open Sans Light Italic'), local('OpenSans-LightItalic'),
- url('open-sans-v17-all-charsets-300italic.woff2') format('woff2');
+ url('{{ resource "fonts/open-sans-v17-all-charsets-300italic.woff2" }}') format('woff2');
}
/* open-sans-regular - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */
@@ -25,7 +25,7 @@
font-style: normal;
font-weight: 400;
src: local('Open Sans Regular'), local('OpenSans-Regular'),
- url('open-sans-v17-all-charsets-regular.woff2') format('woff2');
+ url('{{ resource "fonts/open-sans-v17-all-charsets-regular.woff2" }}') format('woff2');
}
/* open-sans-italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */
@@ -34,7 +34,7 @@
font-style: italic;
font-weight: 400;
src: local('Open Sans Italic'), local('OpenSans-Italic'),
- url('open-sans-v17-all-charsets-italic.woff2') format('woff2');
+ url('{{ resource "fonts/open-sans-v17-all-charsets-italic.woff2" }}') format('woff2');
}
/* open-sans-600 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */
@@ -43,7 +43,7 @@
font-style: normal;
font-weight: 600;
src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'),
- url('open-sans-v17-all-charsets-600.woff2') format('woff2');
+ url('{{ resource "fonts/open-sans-v17-all-charsets-600.woff2" }}') format('woff2');
}
/* open-sans-600italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */
@@ -52,7 +52,7 @@
font-style: italic;
font-weight: 600;
src: local('Open Sans SemiBold Italic'), local('OpenSans-SemiBoldItalic'),
- url('open-sans-v17-all-charsets-600italic.woff2') format('woff2');
+ url('{{ resource "fonts/open-sans-v17-all-charsets-600italic.woff2" }}') format('woff2');
}
/* open-sans-700 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */
@@ -61,7 +61,7 @@
font-style: normal;
font-weight: 700;
src: local('Open Sans Bold'), local('OpenSans-Bold'),
- url('open-sans-v17-all-charsets-700.woff2') format('woff2');
+ url('{{ resource "fonts/open-sans-v17-all-charsets-700.woff2" }}') format('woff2');
}
/* open-sans-700italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */
@@ -70,7 +70,7 @@
font-style: italic;
font-weight: 700;
src: local('Open Sans Bold Italic'), local('OpenSans-BoldItalic'),
- url('open-sans-v17-all-charsets-700italic.woff2') format('woff2');
+ url('{{ resource "fonts/open-sans-v17-all-charsets-700italic.woff2" }}') format('woff2');
}
/* open-sans-800 - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */
@@ -79,7 +79,7 @@
font-style: normal;
font-weight: 800;
src: local('Open Sans ExtraBold'), local('OpenSans-ExtraBold'),
- url('open-sans-v17-all-charsets-800.woff2') format('woff2');
+ url('{{ resource "fonts/open-sans-v17-all-charsets-800.woff2" }}') format('woff2');
}
/* open-sans-800italic - latin_vietnamese_latin-ext_greek-ext_greek_cyrillic-ext_cyrillic */
@@ -88,7 +88,7 @@
font-style: italic;
font-weight: 800;
src: local('Open Sans ExtraBold Italic'), local('OpenSans-ExtraBoldItalic'),
- url('open-sans-v17-all-charsets-800italic.woff2') format('woff2');
+ url('{{ resource "fonts/open-sans-v17-all-charsets-800italic.woff2" }}') format('woff2');
}
/* source-code-pro-500 - latin_vietnamese_latin-ext_greek_cyrillic-ext_cyrillic */
@@ -96,5 +96,5 @@
font-family: 'Source Code Pro';
font-style: normal;
font-weight: 500;
- src: url('source-code-pro-v11-all-charsets-500.woff2') format('woff2');
+ src: url('{{ resource "fonts/source-code-pro-v11-all-charsets-500.woff2" }}') format('woff2');
}
diff --git a/src/theme/index.hbs b/src/theme/index.hbs
index 7775f262d6..b9d37f3128 100644
--- a/src/theme/index.hbs
+++ b/src/theme/index.hbs
@@ -20,32 +20,32 @@
{{#if favicon_svg}}
-
+
{{/if}}
{{#if favicon_png}}
-
+
{{/if}}
-
-
-
+
+
+
{{#if print_enable}}
-
+
{{/if}}
-
+
{{#if copy_fonts}}
-
+
{{/if}}
-
-
-
+
+
+
{{#each additional_css}}
-
+
{{/each}}
{{#if mathjax_support}}
@@ -59,7 +59,7 @@
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
-
+
@@ -280,26 +280,26 @@
{{/if}}
{{#if playground_js}}
-
-
-
-
-
+
+
+
+
+
{{/if}}
{{#if search_js}}
-
-
-
+
+
+
{{/if}}
-
-
-
+
+
+
{{#each additional_js}}
-
+
{{/each}}
{{#if is_print}}
diff --git a/src/theme/searcher/searcher.js b/src/theme/searcher/searcher.js
index dc03e0a02d..a275f48e40 100644
--- a/src/theme/searcher/searcher.js
+++ b/src/theme/searcher/searcher.js
@@ -468,12 +468,12 @@ window.search = window.search || {};
showResults(true);
}
- fetch(path_to_root + 'searchindex.json')
+ fetch('{{ resource "searchindex.json" }}')
.then(response => response.json())
.then(json => init(json))
.catch(error => { // Try to load searchindex.js if fetch failed
var script = document.createElement('script');
- script.src = path_to_root + 'searchindex.js';
+ script.src = '{{ resource "searchindex.js" }}';
script.onload = () => init(window.search);
document.head.appendChild(script);
});
diff --git a/src/theme/toc.html.hbs b/src/theme/toc.html.hbs
index f8fca87353..93dea2569c 100644
--- a/src/theme/toc.html.hbs
+++ b/src/theme/toc.html.hbs
@@ -21,20 +21,20 @@
{{> head}}
-
-
-
+
+
+
{{#if print_enable}}
-
+
{{/if}}
-
+
{{#if copy_fonts}}
-
+
{{/if}}
{{#each additional_css}}
-
+
{{/each}}
diff --git a/test_book/book.toml b/test_book/book.toml
index a30500763c..c89a3e51c4 100644
--- a/test_book/book.toml
+++ b/test_book/book.toml
@@ -9,6 +9,7 @@ edition = "2018"
[output.html]
mathjax-support = true
+hash-files = true
[output.html.playground]
editable = true