Closed srawlins closed 4 months ago
Some questions:
<iframe>
of the sidebar be simpler? The link to the iframe's src
can be generated as part of the regular process.O(n)
files, but an extra two files for every container is still a lot. On the other hand, it produces shorter files. If embedded HTML is the way to go, not a complete JSON overhaul, I can look into this.
- Would an
Definitely. I think it reduces or removes any ability to do JS in there.
- Would this affect SEO? I suppose not, since the class/enum/etc's main page will still have all its members, and each member gets its page to link to.
I don't think so.
- Does this make https://github.com/dart-lang/dartdoc/issues/1272 unacceptably worse? It's still O(n) files, but an extra two files for every container is still a lot. On the other hand, it produces shorter files.
I don't think so. Just a little worse.
CC @sigurdm @jonasfj regarding the idea of serving each page as 3 files: header/main/footer + left sidebar + right sidebar
CC @jcollins-g we talked about this design yesterday.
Other similar options might be:
<iframe>
with CSS to make it appear as embedded HTML, and <base target="_parent">
within the iframe.
(I'm not sure if this can be made to feel nice, and whether or not loading will be janky)<!--#include file="sidebar.html" -->
To be clear, I think the idea in #3384 is solid, and if we maintain the ability for the javascript navigation to be easy to disable, it wouldn't be hard for pub.dev to adopt.
For pub.dev, we would probably prefer some variant of (3). In fact, I think we would love it if the HTML file was something like:
<html>
<head>
<!--#include file="static/template_head.html" -->
</head>
<body>
<header><!--#include file="header.html" --></header>
<main><!--#include file="content.html" --></main>
<aside><!--#include file="sidebar_left.html" --></aside>
<aside><!--#include file="sidebar_right.html" --></aside>
<div><!--#include file="footer.html" --></div>
</body>
</html>
(maybe having header and content on two files is overkill).
But the point is that we would like to load the HTML, sanitize it and put it into a template we maintain.
We could also do that with the SPA solution, if the javascript doing the client navigation is placed in a separate file, such that we can exclude it and concatenate the files serverside before serving to the user.
Our new code path for serving dartdoc is here (we haven't migrated traffic to this code path yet):
DartDocPage.parse
DartDocPage
DartDocPage.render
sanitize_html
The aim with this is to ensure that if the server running dartdoc
is compromised, it will not be able to mess up our template structure, inject HTML, CSS or javascript.
If the VM running dartdoc
is compromised it would be able to mess with the contents of the generated HTML pages sure. It could make things look weird, or provide documentation that is incorrect. But it wouldn't be able to inject harmful scripts/html into pages.
Someday, this may enable us to run {@tool
s when generated documentation on pub.dev. For now the primary motivation is that we could be able to embed dartdoc generated documentation on the package page, such that generated dartdoc doesn't feel like a separate website.
Linking
The JS needs to also add click handlers for all links destined for other pages within the same doc site.
Why does the JS need to handle links destined for other pages? Is this an optimization?
As discussed offline, I think this aligns well with Reducing dartdoc generated file footprint. The impacts of the two problems are multiplicative and dartdoc output could get quite small if we implement both of them.
Why does the JS need to handle links destined for other pages? Is this an optimization?
I need to correct that. I realized that's a bad design because it wouldn't support things like "Right click -> Copy link destination." and in implementing, it was easier to just make links point to the right place, statically, and every click is a full reload.
I realized that's a bad design because it wouldn't support things like "Right click -> Copy link destination."
It could be made to work. But it's a nin-trivial pain to do -- and testing it is difficult.
Same with history management, it's also doable, just mildly painful.
Serving plain HTML has a lot going for it.
@srawlins I'm really impressed by the sidebars PR #3384, but it broke my docs (in version 6.3.0
) :(
Here's what the sidebars should look like
And here's what a method/property/constructor page actually looks like:
Clearly the ProtoSocket-class-sidebar.html
file exists and loads fine, but I noticed the constructor/method/property pages don't even request it!
Here are the network requests for the ProtoSocket
page:
And here are the requests for the init
method:
Here's the code for the main-content
on the ProtoSocket
page:
<div id="dartdoc-main-content" class="main-content" data-above-sidebar="burt_network/burt_network-library-sidebar.html" data-below-sidebar="burt_network/ProtoSocket-class-sidebar.html">
And here's the code for the init
method:
<div id="dartdoc-main-content" class="main-content" data-above-sidebar="burt_network/ProtoSocket-class-sidebar.html" data-below-sidebar="">
When I edit the HTML to put the sidebar on the right as well, it loads... but only on the right side. My repository is here, feel free to ask for more info.
Thanks for the report, @Levi-Lesches could you open an issue for what you're seeing?
Filed #3467
Forgot to close this when I shipped it :D
In order to solve output size issues (https://github.com/dart-lang/dartdoc/issues/2995), performance issues (https://github.com/dart-lang/dartdoc/issues/2799, https://github.com/dart-lang/dartdoc/issues/2998), and in order to enable some desired front-end features ([citation needed]), we can convert generated docs into a rough Single Page App design.
Typically a single page app would have a web server at the backend which is generating JSON responses to update the contents of the page, but we can carry out the minimum amount of work by just dumping slightly different HTML files, and incorporating some JS to request them and insert HTML snippets into the DOM.
Background
The long and short of our performance and output size problems come from this fact:
Design
In a green field design, we could re-imagine dartdoc to instead dump out one or many JSON files which would be served to a JS app and drawn into the page. But in this design, I take all of our existing code which generates one HTML file for every element, and make only small changes.
Generation
Today, for each element, we generate an HTML file with the following 5 sections in its body:
The blocks with incredible amounts of duplication are the two sidebars. The footer may also be perfectly duplicated across every file, but probably doesn't represent a lot of bytes.
In this design, for each element, we instead generate an HTML file with the following 2-3 filled sections:
The other sections will be present but empty (or near enough; there are some breadcrumbs for mobile at the top of some sidebars)
The HTML will look something like:
Additionally, we generate a few more files (apologies, https://github.com/dart-lang/dartdoc/issues/1272):
Initial page load
This new design requires that docs be served from an HTTP file server. The dartdoc package uses the dhttpd package in it's grinder scripts. Python's httpd module works fine as well. Whatever currently serves docs for pub.dev, api.dart.dev, and api.flutter.dev, will also work fine.
When a page is loaded initially, the browser will fill in the DOM just as it does today, except the sidebars will be empty. On load, some JS will read a data property of some tag (body?) for the left sidebar, build a URL for the HTML file for the desired sidebar, and request it. It then inserts the sidebar into its proper place in the DOM. The JS will do the same for the right sidebar.
Linking
As an optimization, to reduce page load (typical SPA optimization): we can add click handlers in the JS for all links destined for other pages within the same doc site.
When a link to another page in the same site is clicked (like a doc comment reference, or a type in a signature, or a member with a class, ...):
<header>
section with that of the response<main>
section with that of the response<title>
tag (other stuff in<head>
?)