Skip to content

Assets: allow passing direct HTML content into asset kwarg (for Pluto support) #2726

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

fonsp
Copy link

@fonsp fonsp commented May 19, 2025

Hi! 👋

I am working on adding easy Pluto support to Documenter via a plugin 😊 I am not yet sure about the user-facing API, but I got an implementation working where Pluto is embedded inside Documeter pages (alongside existing content).

Demo

Here is a WIP demo of the Pluto notebook embedded in Documenter.jl. Note that you can "Edit or run this notebook" with binder!

https://dashing-custard-e9c5cc.netlify.app/

The code for this demo is here: https://github.com/fonsp/Documenter.jl-Pluto-embed-test

No <iframe>

I currently do the embedding without <iframe>, but with a Custom Element. This is also used in other Pluto-embedding web sites like Computational Thinking. The benefit is better performance, better scrolling, accessibility and other UA features, and your notebook content can be indexed by search engines.

For this embedding to work, I need to place content in the <head> of the Documenter.jl output HTML. I saw that there is an existing HTMLAsset system, but unfortunately this is hard to use in our case: the <head> content of Pluto notebooks contains more asset types than just JS, CSS and ICO. We also don't have the <head> content in a Julia format: the <head> is generated dynamically by our bundler. So to use HTMLAsset, we would first need a DOM parser to parse the head content, then convert it into HTMLAsset objects...

Changes

This PR adds the ability to add raw HTML content as an element of the assets kwarg, using the new type RawHTMLHeadContent. This gives plugins more flexibility in loading <head> content.

Take a look at the code changes for the implemented new types.

I'm also happy to hear other ideas for implementing this goal!

Existing solutions

There are some existing solutions for Pluto in Documenter:

  • PlutoStaticHTML.jl (maintained by another Pluto developer). The idea here is different: output as-simple-as-possible static HTML, to make the content match the Documenter.jl style. In some cases, this is exactly what users wants. In other cases, having a true Pluto embed would add consistent styling (WYSIWYG publishing), interactivity, run-with-binder, and more.
  • DemoCards.jl uses PlutoStaticHTML.jl
  • Iframe embeds: I believe @adrhill once made a website that embeds Pluto notebooks using iframes. The goal of the new plugin is to have the direct embedding technical benefits described above, plus an easier workflow for adding notebooks to documentation.

@fonsp fonsp marked this pull request as draft May 20, 2025 09:53
@fonsp
Copy link
Author

fonsp commented May 20, 2025

Marking this as draft since it still needs docs and tests :) I'll wait to hear what you think before I continue

Plugin idea

I'd also love your input on what a good user-facing API would be, and how to make this an easy plugin to use. My idea right now (but I have not looked into the plugin system much, so I'm not fully aware of what is possible):

Adding notebooks to your site

You store your Pluto .jl files in a folder in docs/ (or somewhere else?), eg:

MyProject/
  docs/
    index.md
    notebooks/
      notebook1.jl
      ...
    demo.md # example page that uses a notebook
    Project.toml # containing Documenter and PlutoDocumenterPlugin
    make.jl

Then, to include notebook content in a Documenter page, I was thinking to use HTML Custom Elements.

E.g.

# (this is demo.md)

Hello from Documenter! Take a look at the demo notebook

```@raw
<pluto-documenter path="docs/notebooks/notebook1.jl" ></pluto-documenter>
```

Setup

And your make.jl file looks like:

# ... usual imports

pluto_assets = PlutoDocumenter.generate()

makedocs(;
	assets=[pluto_assets..., more...],
	...
)

@mortenpi
Copy link
Member

mortenpi commented Jun 1, 2025

Allowing raw HTML head content seems fine to me. Regardless of the result of the discussion below, I think it's worth having this -- this seems like a useful thing other people can use to hack stuff together.

For the Pluto case, I have one question though -- do you want it to be included in every page? I guess ideally it would get added into the <head> "on-demand"?

I think the at-raw block is a good MVP. In the longer term, my two cents would be to try to turning this into a plugin package (DocumenterPluto?) that then defines a custom block. Something like this:

```@pluto
Path="docs/notebooks/notebook1.jl"
```

That might take care of the unnecessary duplication and the need to manually pass the asset -- the at-pluto block is present, then we add the Pluto stuff into <head>.

We don't have a really great plugin API (contributions very welcome!), but in DocumenterMermaid I hacked something similar together in this way (here: https://github.com/JuliaDocs/DocumenterMermaid.jl/blob/main/src/DocumenterMermaid.jl):

  1. There's an at-mermaid block (MermaidBlock) that inserts the diagram itself.
  2. It also adds a build step that loops over all pages and pushes a MermaidScriptBlock to the beginning of each page, which then loads the JS dependency.

In the Pluto case, you want this to be in the HEAD. What might work is, instead of pushing a block to the page AST, you just push the RawHTMLHeadContent into the HTML's .assets field in a build step.

@fonsp
Copy link
Author

fonsp commented Jun 2, 2025

Thanks! I added docs, but I'm not sure how to write the test. I thought to check if Documenter.HTML(assets = [RawHTMLHeadContent("<!-- hello -->")]) writes the comment to the output HTML, but I need a Document to test it on. Any suggestion?

@fonsp fonsp marked this pull request as ready for review June 2, 2025 11:35
@fonsp
Copy link
Author

fonsp commented Jun 2, 2025

Thanks for the ideas! The @pluto block looks good :)

In the Pluto case, you want this to be in the HEAD. What might work is, instead of pushing a block to the page AST, you just push the RawHTMLHeadContent into the HTML's .assets field in a build step.

This sounds like a good approach! How can I do this selectively for different pages, so that only the pages with Pluto notebooks load the assets?

@asinghvi17
Copy link
Collaborator

but I need a Document to test it on.

debug = true on makedocs should return a document....

do this selectively for different pages

I don't think HTMLWriter allows for per-page assets? It seems like this rendering happens here:

# Custom user-provided assets.
asset_links(src, ctx.settings.assets),

and the nav node doesn't actually hold the page struct, only its key, so it's not possible to do it that way either. What solution were you thinking of for dynamic HEAD injection @mortenpi?

Of course the way you select the page is a bit simpler: see e.g here, but you can replace filter with findfirst. If the result is nothing, then no block met that criterion and you can ignore that particular page.

@fonsp
Copy link
Author

fonsp commented Jun 5, 2025

debug = true on makedocs should return a document....

I mean specifically for writing tests for this PR: I'm wondering what to write in test/htmlwriter.jl so that I can check the output HTML contents.

@mortenpi
Copy link
Member

mortenpi commented Jun 6, 2025

Thanks! I added docs, but I'm not sure how to write the test.

The best option would probably to test this as part of the examples tests.

I'd add it to one example build, and then grep-check if it appears in the generated HTML.

do this selectively for different pages

I don't think HTMLWriter allows for per-page assets?

No. We'd have to add support for that 🙂

Just thinking out loud: Page should be writer-independent, so it might be better to do this somehow in the writer directly. E.g. add some facility for extensions to return extra assets, but in a way that they can return different stuff for different pages.

@fonsp
Copy link
Author

fonsp commented Jun 12, 2025

This PR should be ready now 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants