Skip to content

Commit 7a12c03

Browse files
committed
Rollup merge of rust-lang#23060 - lifthrasiir:rustdoc-sidebar-in-js, r=alexcrichton
It had been a source of huge bloat in rustdoc outputs. Of course, we can simply disable compiler docs (as `rustc` generates over 90M of HTML) but this approach fares better even after such decision. Each directory now has `sidebar-items.js`, which immediately calls `initSidebarItems` with a JSON sidebar data. This file is shared throughout every item in the sidebar. The current item is highlighted via a separate JS snippet (`window.sidebarCurrent`). The JS file is designed to be loaded asynchronously, as the sidebar is rendered before the content and slow sidebar loading blocks the entire rendering. For the minimal accessibility without JS, links to the parent items are left in HTML. In the future, it might also be possible to integrate crates data with the same fashion: `sidebar-items.js` at the root path will do that. (Currently rustdoc skips writing JS in that case.) This has a huge impact on the size of rustdoc outputs. Originally it was 326MB uncompressed (37.7MB gzipped, 6.1MB xz compressed); it is 169MB uncompressed (11.9MB gzipped, 5.9MB xz compressed) now. The sidebar JS only takes 10MB uncompressed & 0.3MB gzipped.
2 parents e99b00f + 4a6fb45 commit 7a12c03

File tree

2 files changed

+106
-72
lines changed

2 files changed

+106
-72
lines changed

src/librustdoc/html/render.rs

Lines changed: 41 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,10 @@ use html::item_type::ItemType;
6767
use html::layout;
6868
use html::markdown::Markdown;
6969
use html::markdown;
70-
use html::escape::Escape;
7170
use stability_summary;
7271

7372
/// A pair of name and its optional document.
74-
#[derive(Clone, Eq, Ord, PartialEq, PartialOrd)]
75-
pub struct NameDoc(String, Option<String>);
73+
pub type NameDoc = (String, Option<String>);
7674

7775
/// Major driving force in all rustdoc rendering. This contains information
7876
/// about where in the tree-like hierarchy rendering is occurring and controls
@@ -98,12 +96,6 @@ pub struct Context {
9896
/// This describes the layout of each page, and is not modified after
9997
/// creation of the context (contains info like the favicon and added html).
10098
pub layout: layout::Layout,
101-
/// This map is a list of what should be displayed on the sidebar of the
102-
/// current page. The key is the section header (traits, modules,
103-
/// functions), and the value is the list of containers belonging to this
104-
/// header. This map will change depending on the surrounding context of the
105-
/// page.
106-
pub sidebar: HashMap<String, Vec<NameDoc>>,
10799
/// This flag indicates whether [src] links should be generated or not. If
108100
/// the source files are present in the html rendering, then this will be
109101
/// `true`.
@@ -271,7 +263,6 @@ pub fn run(mut krate: clean::Crate,
271263
passes: passes,
272264
current: Vec::new(),
273265
root_path: String::new(),
274-
sidebar: HashMap::new(),
275266
layout: layout::Layout {
276267
logo: "".to_string(),
277268
favicon: "".to_string(),
@@ -1232,7 +1223,16 @@ impl Context {
12321223
clean::ModuleItem(m) => m,
12331224
_ => unreachable!()
12341225
};
1235-
this.sidebar = this.build_sidebar(&m);
1226+
1227+
// render sidebar-items.js used throughout this module
1228+
{
1229+
let items = this.build_sidebar_items(&m);
1230+
let js_dst = this.dst.join("sidebar-items.js");
1231+
let mut js_out = BufferedWriter::new(try!(File::create(&js_dst)));
1232+
try!(write!(&mut js_out, "initSidebarItems({});",
1233+
json::as_json(&items)));
1234+
}
1235+
12361236
for item in m.items {
12371237
f(this,item);
12381238
}
@@ -1252,15 +1252,11 @@ impl Context {
12521252
}
12531253
}
12541254

1255-
fn build_sidebar(&self, m: &clean::Module) -> HashMap<String, Vec<NameDoc>> {
1255+
fn build_sidebar_items(&self, m: &clean::Module) -> HashMap<String, Vec<NameDoc>> {
12561256
let mut map = HashMap::new();
12571257
for item in &m.items {
12581258
if self.ignore_private_item(item) { continue }
12591259

1260-
// avoid putting foreign items to the sidebar.
1261-
if let &clean::ForeignFunctionItem(..) = &item.inner { continue }
1262-
if let &clean::ForeignStaticItem(..) = &item.inner { continue }
1263-
12641260
let short = shortty(item).to_static_str();
12651261
let myname = match item.name {
12661262
None => continue,
@@ -1269,7 +1265,7 @@ impl Context {
12691265
let short = short.to_string();
12701266
let v = map.entry(short).get().unwrap_or_else(
12711267
|vacant_entry| vacant_entry.insert(Vec::with_capacity(1)));
1272-
v.push(NameDoc(myname, Some(shorter_line(item.doc_value()))));
1268+
v.push((myname, Some(shorter_line(item.doc_value()))));
12731269
}
12741270

12751271
for (_, items) in &mut map {
@@ -2216,9 +2212,18 @@ impl<'a> fmt::Display for Sidebar<'a> {
22162212
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
22172213
let cx = self.cx;
22182214
let it = self.item;
2215+
let parentlen = cx.current.len() - if it.is_mod() {1} else {0};
2216+
2217+
// the sidebar is designed to display sibling functions, modules and
2218+
// other miscellaneous informations. since there are lots of sibling
2219+
// items (and that causes quadratic growth in large modules),
2220+
// we refactor common parts into a shared JavaScript file per module.
2221+
// still, we don't move everything into JS because we want to preserve
2222+
// as much HTML as possible in order to allow non-JS-enabled browsers
2223+
// to navigate the documentation (though slightly inefficiently).
2224+
22192225
try!(write!(fmt, "<p class='location'>"));
2220-
let len = cx.current.len() - if it.is_mod() {1} else {0};
2221-
for (i, name) in cx.current.iter().take(len).enumerate() {
2226+
for (i, name) in cx.current.iter().take(parentlen).enumerate() {
22222227
if i > 0 {
22232228
try!(write!(fmt, "::<wbr>"));
22242229
}
@@ -2228,40 +2233,25 @@ impl<'a> fmt::Display for Sidebar<'a> {
22282233
}
22292234
try!(write!(fmt, "</p>"));
22302235

2231-
fn block(w: &mut fmt::Formatter, short: &str, longty: &str,
2232-
cur: &clean::Item, cx: &Context) -> fmt::Result {
2233-
let items = match cx.sidebar.get(short) {
2234-
Some(items) => items,
2235-
None => return Ok(())
2236-
};
2237-
try!(write!(w, "<div class='block {}'><h2>{}</h2>", short, longty));
2238-
for &NameDoc(ref name, ref doc) in items {
2239-
let curty = shortty(cur).to_static_str();
2240-
let class = if cur.name.as_ref().unwrap() == name &&
2241-
short == curty { "current" } else { "" };
2242-
try!(write!(w, "<a class='{ty} {class}' href='{href}{path}' \
2243-
title='{title}'>{name}</a>",
2244-
ty = short,
2245-
class = class,
2246-
href = if curty == "mod" {"../"} else {""},
2247-
path = if short == "mod" {
2248-
format!("{}/index.html", name)
2249-
} else {
2250-
format!("{}.{}.html", short, name)
2251-
},
2252-
title = Escape(doc.as_ref().unwrap()),
2253-
name = name));
2254-
}
2255-
try!(write!(w, "</div>"));
2256-
Ok(())
2236+
// sidebar refers to the enclosing module, not this module
2237+
let relpath = if shortty(it) == ItemType::Module { "../" } else { "" };
2238+
try!(write!(fmt,
2239+
"<script>window.sidebarCurrent = {{\
2240+
name: '{name}', \
2241+
ty: '{ty}', \
2242+
relpath: '{path}'\
2243+
}};</script>",
2244+
name = it.name.as_ref().map(|x| &x[..]).unwrap_or(""),
2245+
ty = shortty(it).to_static_str(),
2246+
path = relpath));
2247+
if parentlen == 0 {
2248+
// there is no sidebar-items.js beyond the crate root path
2249+
// FIXME maybe dynamic crate loading can be merged here
2250+
} else {
2251+
try!(write!(fmt, "<script async src=\"{path}sidebar-items.js\"></script>",
2252+
path = relpath));
22572253
}
22582254

2259-
try!(block(fmt, "mod", "Modules", it, cx));
2260-
try!(block(fmt, "struct", "Structs", it, cx));
2261-
try!(block(fmt, "enum", "Enums", it, cx));
2262-
try!(block(fmt, "trait", "Traits", it, cx));
2263-
try!(block(fmt, "fn", "Functions", it, cx));
2264-
try!(block(fmt, "macro", "Macros", it, cx));
22652255
Ok(())
22662256
}
22672257
}

src/librustdoc/html/static/main.js

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,27 @@
1515
"use strict";
1616
var resizeTimeout, interval;
1717

18+
// This mapping table should match the discriminants of
19+
// `rustdoc::html::item_type::ItemType` type in Rust.
20+
var itemTypes = ["mod",
21+
"externcrate",
22+
"import",
23+
"struct",
24+
"enum",
25+
"fn",
26+
"type",
27+
"static",
28+
"trait",
29+
"impl",
30+
"tymethod",
31+
"method",
32+
"structfield",
33+
"variant",
34+
"macro",
35+
"primitive",
36+
"associatedtype",
37+
"constant"];
38+
1839
$('.js-only').removeClass('js-only');
1940

2041
function getQueryStringParams() {
@@ -552,27 +573,6 @@
552573
showResults(results);
553574
}
554575

555-
// This mapping table should match the discriminants of
556-
// `rustdoc::html::item_type::ItemType` type in Rust.
557-
var itemTypes = ["mod",
558-
"externcrate",
559-
"import",
560-
"struct",
561-
"enum",
562-
"fn",
563-
"type",
564-
"static",
565-
"trait",
566-
"impl",
567-
"tymethod",
568-
"method",
569-
"structfield",
570-
"variant",
571-
"macro",
572-
"primitive",
573-
"associatedtype",
574-
"constant"];
575-
576576
function itemTypeFromName(typename) {
577577
for (var i = 0; i < itemTypes.length; ++i) {
578578
if (itemTypes[i] === typename) return i;
@@ -708,6 +708,50 @@
708708

709709
window.initSearch = initSearch;
710710

711+
// delayed sidebar rendering.
712+
function initSidebarItems(items) {
713+
var sidebar = $('.sidebar');
714+
var current = window.sidebarCurrent;
715+
716+
function block(shortty, longty) {
717+
var filtered = items[shortty];
718+
if (!filtered) return;
719+
720+
var div = $('<div>').attr('class', 'block ' + shortty);
721+
div.append($('<h2>').text(longty));
722+
723+
for (var i = 0; i < filtered.length; ++i) {
724+
var item = filtered[i];
725+
var name = item[0];
726+
var desc = item[1]; // can be null
727+
728+
var klass = shortty;
729+
if (name === current.name && shortty == current.ty) {
730+
klass += ' current';
731+
}
732+
var path;
733+
if (shortty === 'mod') {
734+
path = name + '/index.html';
735+
} else {
736+
path = shortty + '.' + name + '.html';
737+
}
738+
div.append($('<a>', {'href': current.relpath + path,
739+
'title': desc,
740+
'class': klass}).text(name));
741+
}
742+
sidebar.append(div);
743+
}
744+
745+
block("mod", "Modules");
746+
block("struct", "Structs");
747+
block("enum", "Enums");
748+
block("trait", "Traits");
749+
block("fn", "Functions");
750+
block("macro", "Macros");
751+
}
752+
753+
window.initSidebarItems = initSidebarItems;
754+
711755
window.register_implementors = function(imp) {
712756
var list = $('#implementors-list');
713757
var libs = Object.getOwnPropertyNames(imp);

0 commit comments

Comments
 (0)