diff --git a/src/Writers/HTMLWriter.jl b/src/Writers/HTMLWriter.jl
index 4f52d9b6a8..9a726c2706 100644
--- a/src/Writers/HTMLWriter.jl
+++ b/src/Writers/HTMLWriter.jl
@@ -388,6 +388,10 @@ struct HTML <: Documenter.Writer
highlights :: Vector{String}
mathengine :: Union{MathEngine,Nothing}
footer :: Union{Markdown.MD, Nothing}
+ prerender :: Bool
+ node :: Union{Cmd,String,Nothing}
+ highlightjs :: Union{String,Nothing}
+
lang :: String
warn_outdated :: Bool
@@ -403,12 +407,54 @@ struct HTML <: Documenter.Writer
highlights :: Vector{String} = String[],
mathengine :: Union{MathEngine,Nothing} = KaTeX(),
footer :: Union{String, Nothing} = "Powered by [Documenter.jl](https://github.com/JuliaDocs/Documenter.jl) and the [Julia Programming Language](https://julialang.org/).",
+ # Code block prerendering
+ prerender :: Bool = false,
+ node :: Union{Cmd,String,Nothing} = nothing,
+ highlightjs :: Union{String,Nothing} = nothing,
+
# deprecated keywords
edit_branch :: Union{String, Nothing, Default} = Default(nothing),
lang :: String = "en",
- warn_outdated :: Bool = true
+ warn_outdated :: Bool = true,
)
collapselevel >= 1 || throw(ArgumentError("collapselevel must be >= 1"))
+ if prerender
+ node = node === nothing ? Sys.which("node") : node
+ if node === nothing
+ @error "HTMLWriter: no node executable given or found on the system. Setting `prerender=false`."
+ prerender = false
+ else
+ if !success(`$node --version`)
+ @error "HTMLWriter: bad node executable at $node. Setting `prerender=false`."
+ prerender = false
+ end
+ end
+ end
+ if prerender && highlightjs === nothing
+ # Try to download
+ curl = Sys.which("curl")
+ if curl === nothing
+ @error "HTMLWriter: no highlight.js file given and no curl executable found " *
+ "on the system. Setting `prerender=false`."
+ prerender = false
+ else
+ @debug "HTMLWriter: downloading highlightjs"
+ r = Utilities.JSDependencies.RequireJS([])
+ RD.highlightjs!(r, highlights)
+ libs = sort!(collect(r.libraries); by = first) # puts highlight first
+ key = join((x.first for x in libs), ',')
+ highlightjs = get!(HLJSFILES, key) do
+ path, io = mktemp()
+ for lib in libs
+ println(io, "// $(lib.first)")
+ run(pipeline(`$(curl) -fsSL $(lib.second.url)`; stdout=io))
+ println(io)
+ end
+ close(io)
+ return path
+ end
+ end
+ end
assets = map(assets) do asset
isa(asset, HTMLAsset) && return asset
isa(asset, AbstractString) && return HTMLAsset(assetclass(asset), asset, true)
@@ -434,10 +480,14 @@ struct HTML <: Documenter.Writer
end
isa(edit_link, Default) && (edit_link = edit_link[])
new(prettyurls, disable_git, edit_link, canonical, assets, analytics,
- collapselevel, sidebar_sitename, highlights, mathengine, footer, lang, warn_outdated)
+ collapselevel, sidebar_sitename, highlights, mathengine, footer,
+ prerender, node, highlightjs, lang, warn_outdated)
end
end
+# Cache of downloaded highlight.js bundles
+const HLJSFILES = Dict{String,String}()
+
"Provides a namespace for remote dependencies."
module RD
using JSON
@@ -479,7 +529,7 @@ module RD
"highlight",
"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/$(hljs_version)/highlight.min.js"
))
- prepend!(languages, ["julia", "julia-repl"])
+ languages = ["julia", "julia-repl", languages...]
for language in languages
language = jsescape(language)
push!(r, RemoteLibrary(
@@ -657,7 +707,9 @@ function render(doc::Documents.Document, settings::HTML=HTML())
RD.jquery, RD.jqueryui, RD.headroom, RD.headroom_jquery,
])
RD.mathengine!(r, settings.mathengine)
- RD.highlightjs!(r, settings.highlights)
+ if !settings.prerender
+ RD.highlightjs!(r, settings.highlights)
+ end
for filename in readdir(joinpath(ASSETS, "js"))
path = joinpath(ASSETS, "js", filename)
endswith(filename, ".js") && isfile(path) || continue
@@ -1358,7 +1410,7 @@ end
function domify(ctx, navnode, node)
fixlinks!(ctx, navnode, node)
- mdconvert(node, Markdown.MD(); footnotes=ctx.footnotes)
+ mdconvert(node, Markdown.MD(); footnotes=ctx.footnotes, settings=ctx.settings)
end
function domify(ctx, navnode, anchor::Anchors.Anchor)
@@ -1656,14 +1708,39 @@ mdconvert(b::Markdown.BlockQuote, parent; kwargs...) = Tag(:blockquote)(mdconver
mdconvert(b::Markdown.Bold, parent; kwargs...) = Tag(:strong)(mdconvert(b.text, parent; kwargs...))
-function mdconvert(c::Markdown.Code, parent::MDBlockContext; kwargs...)
+function mdconvert(c::Markdown.Code, parent::MDBlockContext; settings::HTML=HTML(), kwargs...)
@tags pre code
language = Utilities.codelang(c.language)
class = isempty(language) ? "nohighlight" : "language-$(language)"
- pre(code[".$class"](c.code))
+ if settings.prerender && !(isempty(language) || language == "nohighlight")
+ r = hljs_prerender(c, settings)
+ r !== nothing && return r
+ end
+ return pre(code[".$class"](c.code))
end
mdconvert(c::Markdown.Code, parent; kwargs...) = Tag(:code)(c.code)
+function hljs_prerender(c::Markdown.Code, settings)
+ @tags pre code
+ lang = Utilities.codelang(c.language)
+ hljs = settings.highlightjs
+ js = """
+ const hljs = require('$(hljs)');
+ console.log(hljs.highlight($(repr(c.code)), {'language': "$(lang)"}).value);
+ """
+ out, err = IOBuffer(), IOBuffer()
+ try
+ run(pipeline(`$(settings.node) -e "$(js)"`; stdout=out, stderr=err))
+ str = String(take!(out))
+ # prepend nohighlight to stop runtime highlighting
+ # return pre(code[".nohighlight $(lang) .hljs"](Tag(Symbol("#RAW#"))(str)))
+ return pre(code[".language-$(lang) .hljs"](Tag(Symbol("#RAW#"))(str)))
+ catch e
+ @error "HTMLWriter: prerendering failed" exception=e stderr=String(take!(err))
+ end
+ return nothing
+end
+
mdconvert(h::Markdown.Header{N}, parent; kwargs...) where {N} = DOM.Tag(Symbol("h$N"))(mdconvert(h.text, h; kwargs...))
mdconvert(::Markdown.HorizontalRule, parent; kwargs...) = Tag(:hr)()