Skip to content
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

Encapsulating Docsify #2135

Open
5 tasks
trusktr opened this issue Jul 16, 2023 · 6 comments
Open
5 tasks

Encapsulating Docsify #2135

trusktr opened this issue Jul 16, 2023 · 6 comments

Comments

@trusktr
Copy link
Member

trusktr commented Jul 16, 2023

We need allow the ability to fully encapsulate Docsify features and state.

With encapsulation, it will be possible later on to mount any number of Docsify instances anywhere in a web site, as well as become possible to wrap Docsify as a React/Vue/Svelte/etc component that can be used in any framework.

Encapsulation changes:

  • progress bar currently appends to body. There should be an option to provide the location where it should be appended. It can default to body for backwards compatibility.
  • Routing: it currently assumes control of the address bar. For backwards compatibility, this can be the default.
    • We need an option to make it route virtually, without touching the address bar, for cases when that is desirable
    • Perhaps add the ability to listen to a Docsify instance's route changes, so that they can be mapped to the address bar in a custom way. Also allow setting Docsify's route from outside, so that a website managing multiple instances can "paginate" Docsify manually.
  • Some Docsify state is at the module level (singleton), including the aforementioned progress bar, but also including state variables. We should bring state into the Docsify class. Some of this this in Simplify and modernize Docsify #2104 in the items where we're bringing some functions into the classes as private methods.
trusktr added a commit that referenced this issue Jul 16, 2023
…private methods and properties to start to encapsulate Docsify

Also some small tweaks:

- move ajax to utils folder
- fix some type definitions and improve content in some JSDoc comments
- use concise class field syntax
- consolidate duplicate docsify-ignore comment removal code
- move initGlobalAPI out of Docsify.js to start to encapsulate Docsify

This handles a task in [Simplify and modernize Docsify](#2104), as well as works towards [Encapsulating Docsify](#2135).
trusktr added a commit that referenced this issue Jul 17, 2023
…private methods and properties to start to encapsulate Docsify

Also some small tweaks:

- move ajax to utils folder
- fix some type definitions and improve content in some JSDoc comments
- use concise class field syntax
- consolidate duplicate docsify-ignore comment removal code
- move initGlobalAPI out of Docsify.js to start to encapsulate Docsify

This handles a task in [Simplify and modernize Docsify](#2104), as well as works towards [Encapsulating Docsify](#2135).
trusktr added a commit that referenced this issue Jul 17, 2023
…private methods and properties to start to encapsulate Docsify

Also some small tweaks:

- move initGlobalAPI out of Docsify.js to start to encapsulate Docsify
- move ajax to utils folder
- fix some type definitions and improve content in some JSDoc comments
- use concise class field syntax
- consolidate duplicate docsify-ignore comment removal code

This handles a task in [Simplify and modernize Docsify](#2104), as well as works towards [Encapsulating Docsify](#2135).
Koooooo-7 added a commit that referenced this issue Aug 9, 2023
* chore: add missing Vue support for Vercel builds

* refactor: move some functions and module-level state into classes as private methods and properties to start to encapsulate Docsify

Also some small tweaks:

- move initGlobalAPI out of Docsify.js to start to encapsulate Docsify
- move ajax to utils folder
- fix some type definitions and improve content in some JSDoc comments
- use concise class field syntax
- consolidate duplicate docsify-ignore comment removal code

This handles a task in [Simplify and modernize Docsify](#2104), as well as works towards [Encapsulating Docsify](#2135).

* chore: add prettier code format check to our lint script, and add a prettier script for manually formatting the whole code base

* chore: update issue/pr templates

* chore: apply our format to the whole code base


---------

Co-authored-by: Koy <[email protected]>
Co-authored-by: i544693 <[email protected]>
@jhildenbiddle
Copy link
Member

jhildenbiddle commented Apr 3, 2024

Offering Docsify as a web component would provide encapsulation. Below is a high-level POC of two Docsify web components: <docsify-site> and <docsify-element> (names TBD).

Docsify "Sites"

A single Docsify web component instance presented as a full-page site (similar to what Docsify renders today). The example below shows a Docisfy site configuration using the default theme and multiple plugins.

<!DOCTYPE html>
<html>
  <head>
    ...
  </head>
  <body>
    <docsify-site></docsify-site>

    <script type="module">
      import Docsify from "...";
      import plugin1 from "...";
      import plugin2 from "...";

      const root = new Docsify({
        plugins: [plugin1, plugin2],
      });      
    </script>
  </body>
</html>
  • Docsify available as ES6 module (no longer an IIFE)
  • Docsify instance data is stored on the component (instead of window)
  • Docsify "sites" handle the address bar as it does today with both hash and history options available.
  • Docsify ships with a single default theme CSS bundled in JS, reducing the default Docsify install to single JS file. This default theme will provide "auto" light/dark (default), "light", and "dark" variants.
  • Themes are scoped and assigned to components using a data-theme attribute or theme configuration property. If/when @scoped CSS is universally supported, we can consider switching to nested<link> and <style> elements inside each Docsify web component.
  • Theme customization is available using CSS custom properties (similar in concept to docsify-themeable, but significantly lighter weight). This allows themes to be extremely light weight, including our default theme which will include light, dark, and auto styles with minimal CSS. This approach also increases the likelihood that we can modify the underlying CSS without breaking third-party themes that rely only on CSS custom properties for styling (which will be our recommended method).

Docsify "Elements"

One or more Docsify web component instances rendered as page elements that co-exist with other non-Docsify page elements. I'm purposely avoiding the word "embed" here since Docsify has its own embeddable content. The examples below shows multiple Docsify elements, each using different themes, custom styles, and plugins.

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="path/to/custom-theme1.css" />
    <link rel="stylesheet" href="path/to/custom-theme2.css" />
  </head>
  <body>
    <!-- Theme and style set via config (see below) -->
    <docsify-element id="foo"></docsify-element >

    <!-- Theme and style set via attributes -->
    <docsify-element 
      id="bar" 
      data-theme="custom-theme2" 
      style="--theme-color: blue;">
    </docsify-element >

    <script type="module">
      import Docsify from "...";
      import plugin1 from "...";
      import plugin2 from "...";

      const rootFoo = new Docsify({
        el: '#foo',
        plugins: [plugin1],
        style: {
          'theme-color': 'red',
        },
        theme: 'custom-theme1',
      });

      const rootBar = new Docsify({
        el: '#bar',
        plugins: [plugin2],
      });
    </script>
  </body>
</html>
  • Docsify "elements" mock the address bar using a data-url attribute on the component root. Manual changes to this value will trigger page changes within the component.
    <!-- Single element -->
    <docsify-element data-url="/README.md"></docsify-element >
    
    <script>
      // Manual page navigation
      document.querySelector('docsify-root').setAttribute('data-url', "/page.md");
    </script>
    <!-- Multiple elements -->
    <docsify-element id="foo" data-url="/foo/README.md"></docsify-element >
    <docsify-element id="bar" data-url="/bar/README.md"></docsify-element >
    
    <script>
      // Manual page navigation
      document.querySelector('#foo').setAttribute('data-url', "/foo/page1.md");
      document.querySelector('#bar').setAttribute('data-url', "/bar/page2.md");
    </script>
  • Optionally, window URL parameters can be used to get/set Docsify instance URL(s):
    # Single element
    https://mydomain.com?docsify-url=/foo/page1.md
    
    # Multiple elements
    https://mydomain.com?docsify-url=foo(/foo/page1.md)&docsify-url=bar(/bar/page2.md)
    

If we go this route, life will be easier if we standardize on this as the one and only way to create Docsify instances. If we try to support both an IIFE for standard sites (as we do today) and a web component, everything gets more complicated because plugins, themes, testing, etc. have to support both types of Docsify instances.

To me, this all boils down to an early glimpse of what a release after v5 could look like. Would love to hear what others think.

@paulhibbitts
Copy link
Collaborator

paulhibbitts commented Apr 3, 2024

Thanks @trusktr @jhildenbiddle I love the sounds of this whole approach from a non-dev perspective, and particularly the use of the standard of Web Components and how nicely that builds (pardon the pun) off the non-build nature of Docsify itself. I also think that for a post v5 feature this leverages, and can benefit from, a recent upswing in interest of Web Components in general, especially with the new KickStarter of Web Awesome that has completely blown past it's initial funding goal🚀

@Koooooo-7
Copy link
Member

Koooooo-7 commented Apr 5, 2024

Hi @jhildenbiddle ,

Based on the solutions that we makes docsify support multi-instances ( as web-component ) in single page.
I think the <docsify-site> and <docsify-element> could be the same thing to me if we could resolve the problems below.
That's my assumptions, plz correct me If I do lack of any knowledge on it.

  1. Resolve Isolation
    Data Isolation, Especially in search (localStorage) or other sharing datas.

    Event Isolation, about the events (click ...).

    Resources Isolation (theme, sidebars...).

  2. Routing
    IMO, when we provide docsify as the component, it should not bind to the URL directly.
    Hence we may need a routingDispatcher thing to support the transformation and customize.

There need a way to distinguish the docsify instance and dispatch/manage it correctly.
In further, all the plugins/extensions need to be aware of and access it all as well.

Consider that refactoring docsify with Container ( or Namespace ...) scope.
The Container is the highest isolation level on each docsify instance.
Everything for the docsify instances should be isolated in different unique docsify Container (named same to the docisfy instance id).

So, the component <docsify-site> is a single <docsify-element> within a Default Container (id=default) which has the Default routingDispatcher.
The multi <docsify-element>s are in the different Containers with Default routingDispatcher.
We only need provide a single Default routingDispatcher since there is no different to dispatch things that mapping URL to one or more Containers.

The global HTML page is just a plain rootContainers with plain/common stuff.

@Koooooo-7 Koooooo-7 pinned this issue Apr 6, 2024
@trusktr
Copy link
Member Author

trusktr commented Apr 8, 2024

I really like the idea of a custom element more than a non-custom-element component (f.e. React/Solid/Vue/Svelte/etc) because the custom element can work everywhere.

The only thing though is we need SSR/SSG to work with the custom element.

Besides that, we can totally have a single element.

Routing

I really like the idea of multiple routes combined into a single URL. I thought about this idea before, and making a generic multi-route routing system that could allow any component in an app to have its own routes, and it looked similar to what you depicted above in terms of the URL.

For multi-element routing, each element could have an ID that is incremented from 1 (not random ID) so that the order elements are loaded determines which ID from the route is theirs. Optionally (and recommended) a user could give the elements specific names/ids that are used, so that if the order of elements in the DOM changes it won't have impact on the URLs.

backwards compatibility

We also need to support backwards compatibility for a while, marking it as deprecated for some time (6 moths? 1 year?), while the new format exists. This doesn't have to be complicated though. We simply switch to the new format (custom element), and the "legacy script" would simply construct an instance of the new custom element with JavaScript, insert it into the DOM (f.e. el in legacy global $docsify config), and it would map the rest of the legacy $docsify config options to the element instance.

SSR/SSG

We need to determine a setup that allows us to write a custom element including support for SSR/SSG. Lit has a renderToString API that can be plugged into a system like Astro. Solid also has renderToString, and for lume/element I'd need to update the LumeElement base class to simply call Solid's renderToString instead of plain render. I have a bias towards Lume Element over LitElement (after all, I made LumeElement to be my ideal custom element system, already having experience with Lit and others).

Next steps

How about this: we know SSR/SSG is a must for our future (without eliminating Docsify's key feature of being able to render everything client-side without a build as currently), so perhaps we should have a spike on setting up several SSR/SSG setups using custom elements, to get experience with them and determine what we like. This can highly influence which custom element direction we want to take. We could try:

  • Vanilla Lit SSR
  • Lit SSR in 11ty
  • Lit SSR in Astro
  • Rocket (built with Lit, has SSR)
  • Lit SSR for Next and Nuxt (preferably not, these frameworks were designed first class for React and Vue, and would rather not couple our setup to React or Vue just to have SSR)
  • Similar integration with LumeElement as above (once I add Solid renderToString (that should take one session, I just have to actually try it, and it will be subsequently straight forward to copy the integrations for Astro and 11ty and make a connector for Rocket)
  • If LumeElement has Solid renderToString, it will be fairly straight forward to connect it into Solid Start for SSR/SSG (although similarly to Next/Nuxt, not sure it would be ideal to support a system that encourages also having something else besides custom elements).

not sure it would be ideal to support a system that encourages also having something else besides custom elements

Regarding this last part, if we do want to allow users to bring any additional component system, maybe something like Astro is the best for that? Anywho, I think we just have to try some methods out and see what we like.

@paulhibbitts
Copy link
Collaborator

paulhibbitts commented Apr 9, 2024

Thanks very much @Koooooo-7 @trusktr for the additional details and possible approaches for the use of Web Components to provide an encapsulated Docsify.

While I don't have any technical feedback, I would like to highlight that the non-SSR nature of Docsify is arguably the single most beneficial (and unique) feature of the app and perhaps possible future SSR support could be explored as an add-on, fork etc. This is not only because of the additional development and on-going maintenance required of the Docsify core to support SSR + existing dynamic rendering, but then positioning and describing the app vs. countless other SSR solutions (including those with much bigger development/support teams, which are now available for use as well) could become a significant challenge. Alternatively, as SEO is the underlying goal does the current landscape of static site hosting offer any possible SEO-friendly options worth further testing etc? Sorry if this is off-topic re: encapsulated Docsify, this likely should be further discussed in a separate issue.

@trusktr
Copy link
Member Author

trusktr commented Apr 21, 2024

the non-SSR nature of Docsify is arguably the single most beneficial (and unique) feature of the app and perhaps possible future SSR support could be explored as an add-on, fork etc

We will keep this functionality first and foremost! By making a custom element, it will be usable without any build, in a plain HTML file. The SSR mode will be more specialized and opt-in.

countless other SSR solutions (including those with much bigger development/support teams, which are now available for use as well) could become a significant challenge

A possible strategy is to rely on one of those teams, for example the people making Astro have been thinking about SSR and SSG from the start. It will be possible to use a Docsify element in Astro to get SSR/SSG.

does the current landscape of static site hosting offer any possible SEO-friendly options

The static site hosts only serve static files. But someone has to generate those files in the first place. So, for example, Astro's SSG mode can generate output HTML files. Those files are then what you would place onto a static host.

Right now you can put an HTML file and all your markdown files on a static host to have a Docsify site, but that will not be automatically provide good SEO (or be AI friendly, there are now AIs that are reading websites) because the main HTML file with Docsify.js will still be fetching markdown ad dynamically rendering it (not all engines and bots run JavaScript, so they only see the initial HTML without the content generated from markdown).

We need SSG (static site generation) so that we can convert all markdown files into static HTML files ahead of time, and then the output HTML files are what we'd want to place onto a static host for it to be fully SEO/AI-friendly.


My next goal is I will experiment with Astro to get intimately familiar with everything it does, and then I'll add an astro plugin for @lume/element so that elements made with it will support SSR and SSG. Then I'll go from there to think about what would work for Docsify.

I'll work on a version of Docsify with custom elements. In the meantime @jhildenbiddle, if you want to do that too, I think it would be great. I think there is more to it that what we've depicted above, and we don't really know what exactly that will be, so I'm thinking that what we can do is just try starting prototypes on different branches. We could play it by ear, make prototypes to talk about, and eventually converge.

As for my goals with Lume, I want to get it SEO/AI-friendly ASAP, so what I'll do is roll full steam ahead on a new branch to get custom elements working (after I first get Astro with Lume Element working with SSR/SSG). Besides SSR/SSG stuff, we still need to think about how we want the elemebt interface to be, so even if you're working on your own concept to see what that could look like, it would be beneficial.

As an example I've been thinking that <docsify-app> would have various slots like sidebar, navbar, etc, to be able to easily fill in the different parts of the default layout, but then there would need to be some way to provide a full layout/template to replace the default one (I'm not sure exactly how this interface of the element would look like).

We might go in different directions, but then we can converge back together, and we can also think about it as we go along.

So, I'll see you all in a while after I get back from Astro land, or see you there (Astro Discord) if you happen to go there too!

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

No branches or pull requests

4 participants