about summary refs log tree commit diff
path: root/src/librustdoc/html
diff options
context:
space:
mode:
authorJacob Hoffman-Andrews <github@hoffman-andrews.com>2021-06-08 17:19:03 -0700
committerJacob Hoffman-Andrews <github@hoffman-andrews.com>2021-06-20 23:07:28 -0700
commitcd0f93193c84ddc6698f9b65909da71c084dcb74 (patch)
tree3f0b0152ed00f9f65dfdf6e0de2fb4d8443b4e4a /src/librustdoc/html
parent50a407200b970d8a48e4e58de37c94df355f5472 (diff)
downloadrust-cd0f93193c84ddc6698f9b65909da71c084dcb74.tar.gz
rust-cd0f93193c84ddc6698f9b65909da71c084dcb74.zip
Use Tera templates for rustdoc.
Replaces a format!() call in layout::render with a template
expansion. Introduces a `templates` field in SharedContext so parts
of rustdoc can share pre-rendered templates.

This currently builds in a copy of the single template available, like
with static files. However, future work can make this live-loadable with
a perma-unstable flag, to make rustdoc developers' work easier.
Diffstat (limited to 'src/librustdoc/html')
-rw-r--r--src/librustdoc/html/layout.rs214
-rw-r--r--src/librustdoc/html/render/context.rs14
-rw-r--r--src/librustdoc/html/render/write_shared.rs9
-rw-r--r--src/librustdoc/html/sources.rs1
-rw-r--r--src/librustdoc/html/static_files.rs2
-rw-r--r--src/librustdoc/html/templates/STYLE.md37
-rw-r--r--src/librustdoc/html/templates/page.html119
7 files changed, 221 insertions, 175 deletions
diff --git a/src/librustdoc/html/layout.rs b/src/librustdoc/html/layout.rs
index d2d1757b900..6ed603c96bb 100644
--- a/src/librustdoc/html/layout.rs
+++ b/src/librustdoc/html/layout.rs
@@ -7,7 +7,9 @@ use crate::html::escape::Escape;
 use crate::html::format::{Buffer, Print};
 use crate::html::render::{ensure_trailing_slash, StylePath};
 
-#[derive(Clone)]
+use serde::Serialize;
+
+#[derive(Clone, Serialize)]
 crate struct Layout {
     crate logo: String,
     crate favicon: String,
@@ -22,6 +24,7 @@ crate struct Layout {
     crate generate_search_filter: bool,
 }
 
+#[derive(Serialize)]
 crate struct Page<'a> {
     crate title: &'a str,
     crate css_class: &'a str,
@@ -40,7 +43,19 @@ impl<'a> Page<'a> {
     }
 }
 
+#[derive(Serialize)]
+struct PageLayout<'a> {
+    static_root_path: &'a str,
+    page: &'a Page<'a>,
+    layout: &'a Layout,
+    style_files: String,
+    sidebar: String,
+    content: String,
+    krate_with_trailing_slash: String,
+}
+
 crate fn render<T: Print, S: Print>(
+    templates: &tera::Tera,
     layout: &Layout,
     page: &Page<'_>,
     sidebar: S,
@@ -48,184 +63,35 @@ crate fn render<T: Print, S: Print>(
     style_files: &[StylePath],
 ) -> String {
     let static_root_path = page.get_static_root_path();
-    format!(
-        "<!DOCTYPE html>\
-<html lang=\"en\">\
-<head>\
-    <meta charset=\"utf-8\">\
-    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
-    <meta name=\"generator\" content=\"rustdoc\">\
-    <meta name=\"description\" content=\"{description}\">\
-    <meta name=\"keywords\" content=\"{keywords}\">\
-    <title>{title}</title>\
-    <link rel=\"stylesheet\" type=\"text/css\" href=\"{static_root_path}normalize{suffix}.css\">\
-    <link rel=\"stylesheet\" type=\"text/css\" href=\"{static_root_path}rustdoc{suffix}.css\" \
-          id=\"mainThemeStyle\">\
-    {style_files}\
-    <script id=\"default-settings\"{default_settings}></script>\
-    <script src=\"{static_root_path}storage{suffix}.js\"></script>\
-    <script src=\"{root_path}crates{suffix}.js\"></script>\
-    <noscript><link rel=\"stylesheet\" href=\"{static_root_path}noscript{suffix}.css\"></noscript>\
-    {css_extension}\
-    {favicon}\
-    {in_header}\
-    <style type=\"text/css\">\
-    #crate-search{{background-image:url(\"{static_root_path}down-arrow{suffix}.svg\");}}\
-    </style>\
-</head>\
-<body class=\"rustdoc {css_class}\">\
-    <!--[if lte IE 11]>\
-    <div class=\"warning\">\
-        This old browser is unsupported and will most likely display funky \
-        things.\
-    </div>\
-    <![endif]-->\
-    {before_content}\
-    <nav class=\"sidebar\">\
-        <div class=\"sidebar-menu\" role=\"button\">&#9776;</div>\
-        {logo}\
-        {sidebar}\
-    </nav>\
-    <div class=\"theme-picker\">\
-        <button id=\"theme-picker\" aria-label=\"Pick another theme!\" aria-haspopup=\"menu\" title=\"themes\">\
-            <img src=\"{static_root_path}brush{suffix}.svg\" \
-                 width=\"18\" height=\"18\" \
-                 alt=\"Pick another theme!\">\
-        </button>\
-        <div id=\"theme-choices\" role=\"menu\"></div>\
-    </div>\
-    <nav class=\"sub\">\
-        <form class=\"search-form\">\
-            <div class=\"search-container\">\
-                <div>{filter_crates}\
-                    <input class=\"search-input\" name=\"search\" \
-                           disabled \
-                           autocomplete=\"off\" \
-                           spellcheck=\"false\" \
-                           placeholder=\"Click or press ‘S’ to search, ‘?’ for more options…\" \
-                           type=\"search\">\
-                </div>\
-                <button type=\"button\" id=\"help-button\" title=\"help\">?</button>\
-                <a id=\"settings-menu\" href=\"{root_path}settings.html\" title=\"settings\">\
-                    <img src=\"{static_root_path}wheel{suffix}.svg\" \
-                         width=\"18\" height=\"18\" \
-                         alt=\"Change settings\">\
-                </a>\
-            </div>\
-        </form>\
-    </nav>\
-    <section id=\"main\" class=\"content\">{content}</section>\
-    <section id=\"search\" class=\"content hidden\"></section>\
-    {after_content}\
-    <div id=\"rustdoc-vars\" data-root-path=\"{root_path}\" data-current-crate=\"{krate}\" \
-       data-search-index-js=\"{root_path}search-index{suffix}.js\" \
-       data-search-js=\"{static_root_path}search{suffix}.js\"></div>\
-    <script src=\"{static_root_path}main{suffix}.js\"></script>\
-    {extra_scripts}\
-</body>\
-</html>",
-        css_extension = if layout.css_file_extension.is_some() {
+    let krate_with_trailing_slash = ensure_trailing_slash(&layout.krate).to_string();
+    let style_files = style_files
+        .iter()
+        .filter_map(|t| {
+            if let Some(stem) = t.path.file_stem() { Some((stem, t.disabled)) } else { None }
+        })
+        .filter_map(|t| if let Some(path) = t.0.to_str() { Some((path, t.1)) } else { None })
+        .map(|t| {
             format!(
-                "<link rel=\"stylesheet\" \
-                       type=\"text/css\" \
-                       href=\"{static_root_path}theme{suffix}.css\">",
-                static_root_path = static_root_path,
-                suffix = page.resource_suffix
-            )
-        } else {
-            String::new()
-        },
-        content = Buffer::html().to_display(t),
-        static_root_path = static_root_path,
-        root_path = page.root_path,
-        css_class = page.css_class,
-        logo = {
-            if layout.logo.is_empty() {
-                format!(
-                    "<a href='{root}{path}index.html'>\
-                     <div class='logo-container rust-logo'>\
-                     <img src='{static_root_path}rust-logo{suffix}.png' alt='logo'></div></a>",
-                    root = page.root_path,
-                    path = ensure_trailing_slash(&layout.krate),
-                    static_root_path = static_root_path,
-                    suffix = page.resource_suffix
-                )
-            } else {
-                format!(
-                    "<a href='{root}{path}index.html'>\
-                     <div class='logo-container'><img src='{logo}' alt='logo'></div></a>",
-                    root = page.root_path,
-                    path = ensure_trailing_slash(&layout.krate),
-                    logo = layout.logo
-                )
-            }
-        },
-        title = page.title,
-        description = Escape(page.description),
-        keywords = page.keywords,
-        favicon = if layout.favicon.is_empty() {
-            format!(
-                r##"<link rel="icon" type="image/svg+xml" href="{static_root_path}favicon{suffix}.svg">
-<link rel="alternate icon" type="image/png" href="{static_root_path}favicon-16x16{suffix}.png">
-<link rel="alternate icon" type="image/png" href="{static_root_path}favicon-32x32{suffix}.png">"##,
-                static_root_path = static_root_path,
-                suffix = page.resource_suffix
-            )
-        } else {
-            format!(r#"<link rel="shortcut icon" href="{}">"#, layout.favicon)
-        },
-        in_header = layout.external_html.in_header,
-        before_content = layout.external_html.before_content,
-        after_content = layout.external_html.after_content,
-        sidebar = Buffer::html().to_display(sidebar),
-        krate = layout.krate,
-        default_settings = layout
-            .default_settings
-            .iter()
-            .map(|(k, v)| format!(r#" data-{}="{}""#, k.replace('-', "_"), Escape(v)))
-            .collect::<String>(),
-        style_files = style_files
-            .iter()
-            .filter_map(|t| {
-                if let Some(stem) = t.path.file_stem() { Some((stem, t.disabled)) } else { None }
-            })
-            .filter_map(|t| {
-                if let Some(path) = t.0.to_str() { Some((path, t.1)) } else { None }
-            })
-            .map(|t| format!(
                 r#"<link rel="stylesheet" type="text/css" href="{}.css" {} {}>"#,
                 Escape(&format!("{}{}{}", static_root_path, t.0, page.resource_suffix)),
                 if t.1 { "disabled" } else { "" },
                 if t.0 == "light" { "id=\"themeStyle\"" } else { "" }
-            ))
-            .collect::<String>(),
-        suffix = page.resource_suffix,
-        extra_scripts = page
-            .static_extra_scripts
-            .iter()
-            .map(|e| {
-                format!(
-                    "<script src=\"{static_root_path}{extra_script}.js\"></script>",
-                    static_root_path = static_root_path,
-                    extra_script = e
-                )
-            })
-            .chain(page.extra_scripts.iter().map(|e| {
-                format!(
-                    "<script src=\"{root_path}{extra_script}.js\"></script>",
-                    root_path = page.root_path,
-                    extra_script = e
-                )
-            }))
-            .collect::<String>(),
-        filter_crates = if layout.generate_search_filter {
-            "<select id=\"crate-search\">\
-                 <option value=\"All crates\">All crates</option>\
-             </select>"
-        } else {
-            ""
-        },
-    )
+            )
+        })
+        .collect::<String>();
+    let content = Buffer::html().to_display(t); // Note: This must happen before making the sidebar.
+    let sidebar = Buffer::html().to_display(sidebar);
+    let teractx = tera::Context::from_serialize(PageLayout {
+        static_root_path,
+        page,
+        layout,
+        style_files,
+        sidebar,
+        content,
+        krate_with_trailing_slash,
+    })
+    .unwrap();
+    templates.render("page.html", &teractx).unwrap()
 }
 
 crate fn redirect(url: &str) -> String {
diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs
index 1898f5feed2..2085739fc46 100644
--- a/src/librustdoc/html/render/context.rs
+++ b/src/librustdoc/html/render/context.rs
@@ -1,5 +1,6 @@
 use std::cell::RefCell;
 use std::collections::BTreeMap;
+use std::error::Error as StdError;
 use std::io;
 use std::path::{Path, PathBuf};
 use std::rc::Rc;
@@ -29,6 +30,7 @@ use crate::formats::FormatRenderer;
 use crate::html::escape::Escape;
 use crate::html::format::Buffer;
 use crate::html::markdown::{self, plain_text_summary, ErrorCodes, IdMap};
+use crate::html::static_files::PAGE;
 use crate::html::{layout, sources};
 
 /// Major driving force in all rustdoc rendering. This contains information
@@ -121,6 +123,8 @@ crate struct SharedContext<'tcx> {
     /// to `Some(...)`, it'll store redirections and then generate a JSON file at the top level of
     /// the crate.
     redirections: Option<RefCell<FxHashMap<String, String>>>,
+
+    pub(crate) templates: tera::Tera,
 }
 
 impl SharedContext<'_> {
@@ -218,6 +222,7 @@ impl<'tcx> Context<'tcx> {
 
         if !self.render_redirect_pages {
             layout::render(
+                &self.shared.templates,
                 &self.shared.layout,
                 &page,
                 |buf: &mut _| print_sidebar(self, it, buf),
@@ -408,6 +413,12 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
         let mut issue_tracker_base_url = None;
         let mut include_sources = true;
 
+        let mut templates = tera::Tera::default();
+        templates.add_raw_template("page.html", PAGE).map_err(|e| Error {
+            file: "page.html".into(),
+            error: format!("{}: {}", e, e.source().map(|e| e.to_string()).unwrap_or_default()),
+        })?;
+
         // Crawl the crate attributes looking for attributes which control how we're
         // going to emit HTML
         for attr in krate.module.attrs.lists(sym::doc) {
@@ -454,6 +465,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
             errors: receiver,
             redirections: if generate_redirect_map { Some(Default::default()) } else { None },
             show_type_layout,
+            templates,
         };
 
         // Add the default themes to the `Vec` of stylepaths
@@ -540,6 +552,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
         };
         let all = self.shared.all.replace(AllTypes::new());
         let v = layout::render(
+            &self.shared.templates,
             &self.shared.layout,
             &page,
             sidebar,
@@ -557,6 +570,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
         let sidebar = "<p class=\"location\">Settings</p><div class=\"sidebar-elems\"></div>";
         style_files.push(StylePath { path: PathBuf::from("settings.css"), disabled: false });
         let v = layout::render(
+            &self.shared.templates,
             &self.shared.layout,
             &page,
             sidebar,
diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs
index a4188e6b203..840566731d7 100644
--- a/src/librustdoc/html/render/write_shared.rs
+++ b/src/librustdoc/html/render/write_shared.rs
@@ -460,7 +460,14 @@ pub(super) fn write_shared(
                     })
                     .collect::<String>()
             );
-            let v = layout::render(&cx.shared.layout, &page, "", content, &cx.shared.style_files);
+            let v = layout::render(
+                &cx.shared.templates,
+                &cx.shared.layout,
+                &page,
+                "",
+                content,
+                &cx.shared.style_files,
+            );
             cx.shared.fs.write(&dst, v.as_bytes())?;
         }
     }
diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs
index 5e2a94fe684..80dd7a7a952 100644
--- a/src/librustdoc/html/sources.rs
+++ b/src/librustdoc/html/sources.rs
@@ -136,6 +136,7 @@ impl SourceCollector<'_, 'tcx> {
             static_extra_scripts: &[&format!("source-script{}", self.scx.resource_suffix)],
         };
         let v = layout::render(
+            &self.scx.templates,
             &self.scx.layout,
             &page,
             "",
diff --git a/src/librustdoc/html/static_files.rs b/src/librustdoc/html/static_files.rs
index ca7e5ef8150..00e13d4ec1a 100644
--- a/src/librustdoc/html/static_files.rs
+++ b/src/librustdoc/html/static_files.rs
@@ -64,6 +64,8 @@ crate static RUST_FAVICON_SVG: &[u8] = include_bytes!("static/favicon.svg");
 crate static RUST_FAVICON_PNG_16: &[u8] = include_bytes!("static/favicon-16x16.png");
 crate static RUST_FAVICON_PNG_32: &[u8] = include_bytes!("static/favicon-32x32.png");
 
+crate static PAGE: &str = include_str!("templates/page.html");
+
 /// The built-in themes given to every documentation site.
 crate mod themes {
     /// The "light" theme, selected by default when no setting is available. Used as the basis for
diff --git a/src/librustdoc/html/templates/STYLE.md b/src/librustdoc/html/templates/STYLE.md
new file mode 100644
index 00000000000..fff65e3b5ff
--- /dev/null
+++ b/src/librustdoc/html/templates/STYLE.md
@@ -0,0 +1,37 @@
+# Style for Templates
+
+This directory has templates in the [Tera templating language](teradoc), which is very
+similar to [Jinja2](jinjadoc) and [Django](djangodoc) templates, and also to [Askama](askamadoc).
+
+[teradoc]: https://tera.netlify.app/docs/#templates
+[jinjadoc]: https://jinja.palletsprojects.com/en/3.0.x/templates/
+[djangodoc]: https://docs.djangoproject.com/en/3.2/topics/templates/
+[askamadoc]: https://docs.rs/askama/0.10.5/askama/
+
+We want our rendered output to have as little unnecessary whitespace as
+possible, so that pages load quickly. To achieve that we use Tera's
+[whitespace control] features. At the end of most lines, we put an empty comment
+tag with the whitespace control characters: `{#- -#}`. This causes all
+whitespace between the end of the line and the beginning of the next, including
+indentation, to be omitted on render. Sometimes we want to preserve a single
+space. In those cases we put the space at the end of the line, followed by
+`{# -#}`, which is a directive to remove following whitespace but not preceding.
+We also use the whitespace control characters in most instances of tags with
+control flow, for example `{%- if foo -%}`.
+
+[whitespace control]: https://tera.netlify.app/docs/#whitespace-control
+
+We want our templates to be readable, so we use indentation and newlines
+liberally. We indent by four spaces after opening an HTML tag _or_ a Tera
+tag. In most cases an HTML tag should be followed by a newline, but if the
+tag has simple contents and fits with its close tag on a single line, the
+contents don't necessarily need a new line.
+
+Tera templates support quite sophisticated control flow. To keep our templates
+simple and understandable, we use only a subset: `if` and `for`. In particular
+we avoid [assignments in the template logic](assignments) and [Tera
+macros](macros). This also may make things easier if we switch to a different
+Jinja-style template system, like Askama, in the future.
+
+[assignments]: https://tera.netlify.app/docs/#assignments
+[macros]: https://tera.netlify.app/docs/#macros
diff --git a/src/librustdoc/html/templates/page.html b/src/librustdoc/html/templates/page.html
new file mode 100644
index 00000000000..9b1bef5e447
--- /dev/null
+++ b/src/librustdoc/html/templates/page.html
@@ -0,0 +1,119 @@
+<!DOCTYPE html> {#- -#}
+<html lang="en"> {#- -#}
+<head> {#- -#}
+    <meta charset="utf-8"> {#- -#}
+    <meta name="viewport" content="width=device-width, initial-scale=1.0"> {#- -#}
+    <meta name="generator" content="rustdoc"> {#- -#}
+    <meta name="description" content="{{page.description}}"> {#- -#}
+    <meta name="keywords" content="{{page.keywords}}"> {#- -#}
+    <title>{{page.title}}</title> {#- -#}
+    <link rel="stylesheet" type="text/css" {# -#}
+          href="{{static_root_path | safe}}normalize{{page.resource_suffix}}.css"> {#- -#}
+    <link rel="stylesheet" type="text/css" {# -#}
+          href="{{static_root_path | safe}}rustdoc{{page.resource_suffix}}.css" {# -#}
+          id="mainThemeStyle"> {#- -#}
+    {{- style_files | safe -}}
+    <script id="default-settings" {# -#}
+      {% for k, v in layout.default_settings %}
+        data-{{k}}="{{v}}"
+      {%- endfor -%}
+    ></script> {#- -#}
+    <script src="{{static_root_path | safe}}storage{{page.resource_suffix}}.js"></script> {#- -#}
+    <script src="{{page.root_path | safe}}crates{{page.resource_suffix}}.js"></script> {#- -#}
+    <noscript> {#- -#}
+        <link rel="stylesheet" {# -#}
+           href="{{static_root_path | safe}}noscript{{page.resource_suffix}}.css"> {#- -#}
+    </noscript> {#- -#}
+    {%- if layout.css_file_extension -%}
+        <link rel="stylesheet" type="text/css" {# -#}
+            href="{{static_root_path | safe}}theme{{page.resource_suffix}}.css"> {#- -#}
+    {%- endif -%}
+    {%- if layout.favicon -%}
+        <link rel="shortcut icon" href="{{layout.favicon}}"> {#- -#}
+    {%- else -%}
+        <link rel="icon" type="image/svg+xml" {# -#}
+            href="{{static_root_path | safe}}favicon{{page.resource_suffix}}.svg"> {#- -#}
+        <link rel="alternate icon" type="image/png" {# -#}
+            href="{{static_root_path | safe}}favicon-16x16{{page.resource_suffix}}.png"> {#- -#}
+        <link rel="alternate icon" type="image/png" {# -#}
+            href="{{static_root_path | safe}}favicon-32x32{{page.resource_suffix}}.png"> {#- -#}
+    {%- endif -%}
+    {{- layout.external_html.in_header | safe -}}
+    <style type="text/css"> {#- -#}
+    #crate-search{ {#- -#}
+        background-image:url("{{static_root_path | safe}}down-arrow{{page.resource_suffix}}.svg"); {#- -#}
+    } {#- -#}
+    </style> {#- -#}
+</head> {#- -#}
+<body class="rustdoc {{page.css_class}}"> {#- -#}
+    <!--[if lte IE 11]> {#- -#}
+    <div class="warning"> {#- -#}
+        This old browser is unsupported and will most likely display funky things. {#- -#}
+    </div> {#- -#}
+    <![endif]--> {#- -#}
+    {{- layout.external_html.before_content | safe -}}
+    <nav class="sidebar"> {#- -#}
+        <div class="sidebar-menu" role="button">&#9776;</div> {#- -#}
+        <a href='{{page.root_path | safe}}{{krate_with_trailing_slash | safe}}index.html'> {#- -#}
+            <div class='logo-container rust-logo'> {#- -#}
+            <img src='
+                {%- if layout.logo -%}
+                {{layout.logo}}
+                {%- else -%}
+                {{static_root_path | safe}}rust-logo{{page.resource_suffix}}.png
+                {%- endif -%}
+                ' alt='logo'> {#- -#}
+            </div> {#- -#}
+        </a> {#- -#}
+        {{- sidebar | safe -}}
+    </nav> {#- -#}
+    <div class="theme-picker"> {#- -#}
+        <button id="theme-picker" aria-label="Pick another theme!" aria-haspopup="menu" title="themes"> {#- -#}
+            <img width="18" height="18" alt="Pick another theme!" {# -#}
+             src="{{static_root_path | safe}}brush{{page.resource_suffix}}.svg"> {#- -#}
+        </button> {#- -#}
+        <div id="theme-choices" role="menu"></div> {#- -#}
+    </div> {#- -#}
+    <nav class="sub"> {#- -#}
+        <form class="search-form"> {#- -#}
+            <div class="search-container"> {#- -#}
+                <div>{%- if layout.generate_search_filter -%}
+                    <select id="crate-search"> {#- -#}
+                        <option value="All crates">All crates</option> {#- -#}
+                    </select> {#- -#}
+                    {%- endif -%}
+                    <input {# -#}
+                        class="search-input"{# -#}
+                        name="search" {# -#}
+                        disabled {# -#}
+                        autocomplete="off" {# -#}
+                        spellcheck="false" {# -#}
+                        placeholder="Click or press ‘S’ to search, ‘?’ for more options…" {# -#}
+                        type="search"> {#- -#}
+                </div> {#- -#}
+                <button type="button" id="help-button" title="help">?</button> {#- -#}
+                <a id="settings-menu" href="{{page.root_path | safe}}settings.html" title="settings"> {#- -#}
+                    <img width="18" height="18" alt="Change settings" {# -#}
+                         src="{{static_root_path | safe}}wheel{{page.resource_suffix}}.svg"> {#- -#}
+                </a> {#- -#}
+            </div> {#- -#}
+        </form> {#- -#}
+    </nav> {#- -#}
+    <section id="main" class="content">{{- content | safe -}}</section> {#- -#}
+    <section id="search" class="content hidden"></section> {#- -#}
+    {{- layout.external_html.after_content | safe -}}
+    <div id="rustdoc-vars" {# -#}
+         data-root-path="{{page.root_path | safe}}" {# -#}
+         data-current-crate="{{layout.krate}}" {# -#}
+         data-search-index-js="{{page.root_path | safe}}search-index{{page.resource_suffix}}.js" {# -#}
+         data-search-js="{{static_root_path | safe}}search{{page.resource_suffix}}.js"> {#- -#}
+    </div>
+    <script src="{{static_root_path | safe}}main{{page.resource_suffix}}.js"></script> {#- -#}
+    {%- for script in page.static_extra_scripts -%}
+    <script src="{{static_root_path | safe}}{{script}}.js"></script> {#- -#}
+    {% endfor %}
+    {%- for script in page.extra_scripts -%}
+    <script src="{{page.root_path | safe}}{{script}}.js"></script> {#- -#}
+    {% endfor %}
+</body> {#- -#}
+</html> {#- -#}