Rollup merge of #82310 - jsha:rustdoc-search-onfocus, r=GuillaumeGomez

Load rustdoc's JS search index on-demand.

Instead of being loaded on every page, the JS search index is now loaded when either (a) there is a `?search=` param, or (b) the search input is focused.

This saves both CPU and bandwidth. As of Feb 2021, https://doc.rust-lang.org/search-index1.50.0.js is 273,838 bytes gzipped or 2,544,939 bytes uncompressed. Evaluating it takes 445 ms of CPU time in Chrome 88 on a i7-10710U CPU (out of a total ~2,100 ms page reload).

Tested on Firefox and Chrome.

New:
https://jacob.hoffman-andrews.com/rust/search-on-demand/std/primitive.slice.html
https://jacob.hoffman-andrews.com/rust/search-on-demand/std/primitive.slice.html?search=fn

Old:
https://jacob.hoffman-andrews.com/rust/search-on-load/std/primitive.slice.html
https://jacob.hoffman-andrews.com/rust/search-on-load/std/primitive.slice.html?search=fn
This commit is contained in:
Yuki Okushi
2021-03-04 20:01:03 +09:00
committed by GitHub
3 changed files with 69 additions and 51 deletions

View File

@@ -58,6 +58,7 @@ crate fn render<T: Print, S: Print>(
{style_files}\ {style_files}\
<script id=\"default-settings\"{default_settings}></script>\ <script id=\"default-settings\"{default_settings}></script>\
<script src=\"{static_root_path}storage{suffix}.js\"></script>\ <script src=\"{static_root_path}storage{suffix}.js\"></script>\
<script src=\"{static_root_path}crates{suffix}.js\"></script>\
<noscript><link rel=\"stylesheet\" href=\"{static_root_path}noscript{suffix}.css\"></noscript>\ <noscript><link rel=\"stylesheet\" href=\"{static_root_path}noscript{suffix}.css\"></noscript>\
{css_extension}\ {css_extension}\
{favicon}\ {favicon}\
@@ -112,10 +113,10 @@ crate fn render<T: Print, S: Print>(
<section id=\"search\" class=\"content hidden\"></section>\ <section id=\"search\" class=\"content hidden\"></section>\
<section class=\"footer\"></section>\ <section class=\"footer\"></section>\
{after_content}\ {after_content}\
<div id=\"rustdoc-vars\" data-root-path=\"{root_path}\" data-current-crate=\"{krate}\"></div> <div id=\"rustdoc-vars\" data-root-path=\"{root_path}\" data-current-crate=\"{krate}\" \
data-search-js=\"{root_path}search-index{suffix}.js\"></div>
<script src=\"{static_root_path}main{suffix}.js\"></script>\ <script src=\"{static_root_path}main{suffix}.js\"></script>\
{extra_scripts}\ {extra_scripts}\
<script defer src=\"{root_path}search-index{suffix}.js\"></script>\
</body>\ </body>\
</html>", </html>",
css_extension = if layout.css_file_extension.is_some() { css_extension = if layout.css_file_extension.is_some() {

View File

@@ -1061,10 +1061,12 @@ themePicker.onblur = handleThemeButtonsBlur;
cx.shared.fs.write(&dst, v.as_bytes())?; cx.shared.fs.write(&dst, v.as_bytes())?;
} }
// Update the search index // Update the search index and crate list.
let dst = cx.dst.join(&format!("search-index{}.js", cx.shared.resource_suffix)); let dst = cx.dst.join(&format!("search-index{}.js", cx.shared.resource_suffix));
let (mut all_indexes, mut krates) = try_err!(collect_json(&dst, &krate.name.as_str()), &dst); let (mut all_indexes, mut krates) = try_err!(collect_json(&dst, &krate.name.as_str()), &dst);
all_indexes.push(search_index); all_indexes.push(search_index);
krates.push(krate.name.to_string());
krates.sort();
// Sort the indexes by crate so the file will be generated identically even // Sort the indexes by crate so the file will be generated identically even
// with rustdoc running in parallel. // with rustdoc running in parallel.
@@ -1072,11 +1074,15 @@ themePicker.onblur = handleThemeButtonsBlur;
{ {
let mut v = String::from("var searchIndex = JSON.parse('{\\\n"); let mut v = String::from("var searchIndex = JSON.parse('{\\\n");
v.push_str(&all_indexes.join(",\\\n")); v.push_str(&all_indexes.join(",\\\n"));
// "addSearchOptions" has to be called first so the crate filtering can be set before the v.push_str("\\\n}');\ninitSearch(searchIndex);");
// search might start (if it's set into the URL for example).
v.push_str("\\\n}');\naddSearchOptions(searchIndex);initSearch(searchIndex);");
cx.shared.fs.write(&dst, &v)?; cx.shared.fs.write(&dst, &v)?;
} }
let crate_list_dst = cx.dst.join(&format!("crates{}.js", cx.shared.resource_suffix));
let crate_list =
format!("window.ALL_CRATES = [{}];", krates.iter().map(|k| format!("\"{}\"", k)).join(","));
cx.shared.fs.write(&crate_list_dst, &crate_list)?;
if options.enable_index_page { if options.enable_index_page {
if let Some(index_page) = options.index_page.clone() { if let Some(index_page) = options.index_page.clone() {
let mut md_opts = options.clone(); let mut md_opts = options.clone();
@@ -1098,9 +1104,6 @@ themePicker.onblur = handleThemeButtonsBlur;
extra_scripts: &[], extra_scripts: &[],
static_extra_scripts: &[], static_extra_scripts: &[],
}; };
krates.push(krate.name.to_string());
krates.sort();
krates.dedup();
let content = format!( let content = format!(
"<h1 class=\"fqn\">\ "<h1 class=\"fqn\">\

View File

@@ -42,6 +42,7 @@ if (!DOMTokenList.prototype.remove) {
if (rustdocVars) { if (rustdocVars) {
window.rootPath = rustdocVars.attributes["data-root-path"].value; window.rootPath = rustdocVars.attributes["data-root-path"].value;
window.currentCrate = rustdocVars.attributes["data-current-crate"].value; window.currentCrate = rustdocVars.attributes["data-current-crate"].value;
window.searchJS = rustdocVars.attributes["data-search-js"].value;
} }
var sidebarVars = document.getElementById("sidebar-vars"); var sidebarVars = document.getElementById("sidebar-vars");
if (sidebarVars) { if (sidebarVars) {
@@ -1922,8 +1923,8 @@ function defocusSearchBar() {
return searchWords; return searchWords;
} }
function startSearch() { function registerSearchEvents() {
var callback = function() { var searchAfter500ms = function() {
clearInputTimeout(); clearInputTimeout();
if (search_input.value.length === 0) { if (search_input.value.length === 0) {
if (browserSupportsHistoryApi()) { if (browserSupportsHistoryApi()) {
@@ -1935,8 +1936,8 @@ function defocusSearchBar() {
searchTimeout = setTimeout(search, 500); searchTimeout = setTimeout(search, 500);
} }
}; };
search_input.onkeyup = callback; search_input.onkeyup = searchAfter500ms;
search_input.oninput = callback; search_input.oninput = searchAfter500ms;
document.getElementsByClassName("search-form")[0].onsubmit = function(e) { document.getElementsByClassName("search-form")[0].onsubmit = function(e) {
e.preventDefault(); e.preventDefault();
clearInputTimeout(); clearInputTimeout();
@@ -1999,7 +2000,6 @@ function defocusSearchBar() {
} }
}); });
} }
search();
// This is required in firefox to avoid this problem: Navigating to a search result // This is required in firefox to avoid this problem: Navigating to a search result
// with the keyboard, hitting enter, and then hitting back would take you back to // with the keyboard, hitting enter, and then hitting back would take you back to
@@ -2017,8 +2017,14 @@ function defocusSearchBar() {
} }
index = buildIndex(rawSearchIndex); index = buildIndex(rawSearchIndex);
startSearch(); registerSearchEvents();
// If there's a search term in the URL, execute the search now.
if (getQueryStringParams().search) {
search();
}
};
function addSidebarCrates(crates) {
// Draw a convenient sidebar of known crates if we have a listing // Draw a convenient sidebar of known crates if we have a listing
if (window.rootPath === "../" || window.rootPath === "./") { if (window.rootPath === "../" || window.rootPath === "./") {
var sidebar = document.getElementsByClassName("sidebar-elems")[0]; var sidebar = document.getElementsByClassName("sidebar-elems")[0];
@@ -2029,14 +2035,6 @@ function defocusSearchBar() {
var ul = document.createElement("ul"); var ul = document.createElement("ul");
div.appendChild(ul); div.appendChild(ul);
var crates = [];
for (var crate in rawSearchIndex) {
if (!hasOwnProperty(rawSearchIndex, crate)) {
continue;
}
crates.push(crate);
}
crates.sort();
for (var i = 0; i < crates.length; ++i) { for (var i = 0; i < crates.length; ++i) {
var klass = "crate"; var klass = "crate";
if (window.rootPath !== "./" && crates[i] === window.currentCrate) { if (window.rootPath !== "./" && crates[i] === window.currentCrate) {
@@ -2044,9 +2042,6 @@ function defocusSearchBar() {
} }
var link = document.createElement("a"); var link = document.createElement("a");
link.href = window.rootPath + crates[i] + "/index.html"; link.href = window.rootPath + crates[i] + "/index.html";
// The summary in the search index has HTML, so we need to
// dynamically render it as plaintext.
link.title = convertHTMLToPlaintext(rawSearchIndex[crates[i]].doc);
link.className = klass; link.className = klass;
link.textContent = crates[i]; link.textContent = crates[i];
@@ -2057,7 +2052,7 @@ function defocusSearchBar() {
sidebar.appendChild(div); sidebar.appendChild(div);
} }
} }
}; }
/** /**
* Convert HTML to plaintext: * Convert HTML to plaintext:
@@ -2862,37 +2857,18 @@ function defocusSearchBar() {
} }
} }
window.addSearchOptions = function(crates) { function addSearchOptions(crates) {
var elem = document.getElementById("crate-search"); var elem = document.getElementById("crate-search");
if (!elem) { if (!elem) {
enableSearchInput(); enableSearchInput();
return; return;
} }
var crates_text = [];
if (Object.keys(crates).length > 1) {
for (var crate in crates) {
if (hasOwnProperty(crates, crate)) {
crates_text.push(crate);
}
}
}
crates_text.sort(function(a, b) {
var lower_a = a.toLowerCase();
var lower_b = b.toLowerCase();
if (lower_a < lower_b) {
return -1;
} else if (lower_a > lower_b) {
return 1;
}
return 0;
});
var savedCrate = getSettingValue("saved-filter-crate"); var savedCrate = getSettingValue("saved-filter-crate");
for (var i = 0, len = crates_text.length; i < len; ++i) { for (var i = 0, len = crates.length; i < len; ++i) {
var option = document.createElement("option"); var option = document.createElement("option");
option.value = crates_text[i]; option.value = crates[i];
option.innerText = crates_text[i]; option.innerText = crates[i];
elem.appendChild(option); elem.appendChild(option);
// Set the crate filter from saved storage, if the current page has the saved crate // Set the crate filter from saved storage, if the current page has the saved crate
// filter. // filter.
@@ -2900,7 +2876,7 @@ function defocusSearchBar() {
// If not, ignore the crate filter -- we want to support filtering for crates on sites // If not, ignore the crate filter -- we want to support filtering for crates on sites
// like doc.rust-lang.org where the crates may differ from page to page while on the // like doc.rust-lang.org where the crates may differ from page to page while on the
// same domain. // same domain.
if (crates_text[i] === savedCrate) { if (crates[i] === savedCrate) {
elem.value = savedCrate; elem.value = savedCrate;
} }
} }
@@ -2969,6 +2945,44 @@ function defocusSearchBar() {
buildHelperPopup = function() {}; buildHelperPopup = function() {};
} }
function loadScript(url) {
var script = document.createElement('script');
script.src = url;
document.head.append(script);
}
function setupSearchLoader() {
var searchLoaded = false;
function loadSearch() {
if (!searchLoaded) {
searchLoaded = true;
loadScript(window.searchJS);
}
}
// `crates{version}.js` should always be loaded before this script, so we can use it safely.
addSearchOptions(window.ALL_CRATES);
addSidebarCrates(window.ALL_CRATES);
search_input.addEventListener("focus", function() {
search_input.origPlaceholder = search_input.placeholder;
search_input.placeholder = "Type your search here.";
loadSearch();
});
search_input.addEventListener("blur", function() {
search_input.placeholder = search_input.origPlaceholder;
});
enableSearchInput();
var crateSearchDropDown = document.getElementById("crate-search");
crateSearchDropDown.addEventListener("focus", loadSearch);
var params = getQueryStringParams();
if (params.search !== undefined) {
loadSearch();
}
}
onHashChange(null); onHashChange(null);
window.onhashchange = onHashChange; window.onhashchange = onHashChange;
setupSearchLoader();
}()); }());