dart-lang / dartdoc

API documentation tool for Dart.
https://pub.dev/packages/dartdoc
BSD 3-Clause "New" or "Revised" License
473 stars 118 forks source link

Single Page App design with static files #3079

Closed srawlins closed 4 months ago

srawlins commented 2 years ago

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:

A library with a class with n members will generate O(n) files, each of which has a sidebar of length O(n), resulting in a total of O(n2) bytes.

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:

  1. a header
  2. a left sidebar
  3. a right sidebar
  4. a main body
  5. a footer

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:

  1. a header
  2. a main body
  3. (probably not the footer; but removing it can be done separately)

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:

<html>
    <head>...</head>
    <body>
        <header>...</header>
        <main>...</main>
        <sidebar-left><!-- intentionally empty --></sidebar-left>
        <sidebar-right><!-- intentionally empty --></sidebar-right>
        <footer><!-- maybe empty --></footer>
    </body>
</html>

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, ...):

  1. the JS intercepts it and instead requests the URL (the same HTML file destination) asynchronously,
  2. parses the HTML response,
  3. pushes the new location via the JS Navigation API
  4. replaces the current <header> section with that of the response
  5. replaces the current <main> section with that of the response
  6. replaces the <title> tag (other stuff in <head>?)
  7. requests the new page's left sidebar and right sidebar
  8. replaces the left sidebar with the new one, and the right sidebar with the new one
Levi-Lesches commented 2 years ago

Some questions:

  1. Would an <iframe> of the sidebar be simpler? The link to the iframe's src can be generated as part of the regular process.
  2. 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.
  3. Does this make #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.

If embedded HTML is the way to go, not a complete JSON overhaul, I can look into this.

srawlins commented 2 years ago
  1. Would an