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

Provide chunk splitting for shared objects like sidebar and nav #3367

Open
4 tasks done
zhangyx1998 opened this issue Dec 21, 2023 · 5 comments
Open
4 tasks done

Provide chunk splitting for shared objects like sidebar and nav #3367

zhangyx1998 opened this issue Dec 21, 2023 · 5 comments

Comments

@zhangyx1998
Copy link
Contributor

zhangyx1998 commented Dec 21, 2023

Is your feature request related to a problem? Please describe.

After addressing the problem of memory overflow (issue #3362, pr #3366), I ended up producing a bundle that, although worked fine, turned out to be very large in size. Especially when compared to the original content size.

I investigated the produced html source tree and found each of them contains a copy of their corresponding navbar and sidebar, parsed into html template.

Describe the solution you'd like

Instead of hard-coding the sidebar (and maybe the navbar) as html templates into each entry file, provide a configuration switch to fall back to client side rendering with their data chunked separately as JSON files.

Describe alternatives you've considered

Not applicable

Additional context

To provide an idea of the scale of size, here is a summary of related objects in my project:

File Size Description
nav.json 10KB Serialized nav object
sidebar.json 414KB Serialized sidebar object
cache/ 25MB Cached raw html files before conversion to vitepress
dist/ 1.7GB Bundle produced by vitepress

And here is a link of the deployed site: x.z-yx.cc

Validations

@zhangyx1998
Copy link
Contributor Author

I am happy to work on a PR regarding this if someone could provide me an idea of where to look at.

@brc-dd
Copy link
Member

brc-dd commented Dec 21, 2023

Try setting metaChunk: true in your vitepress config. It won't skip SSR but will be better than having the data on each page.

@zhangyx1998
Copy link
Contributor Author

zhangyx1998 commented Dec 21, 2023

Try setting metaChunk: true in your vitepress config. It won't skip SSR but will be better than having the data on each page.

It did reduce the overall size from 1.7GB to 1.2GB.

I compared the results with and without the flag. Sidebar and nav items did NOT go away as you told me.

I noticed the following changes to a specific html file as an example:

--- before/xorg-docs/README.html
+++ after/xorg-docs/README.html

@@ -7,7 +7,7 @@
     <meta name="description" content="Modern looking documentation for X. Content ported from X.org (version 11, release 7.7).">
     <meta name="generator" content="VitePress v1.0.0-rc.32">
     <link rel="preload stylesheet" href="/assets/style.a3MijG5e.css" as="style">
+    <script type="module" src="/assets/chunks/metadata.8c95dd74.js"></script>
     <script type="module" src="/assets/app.h1QJGMyB.js"></script>
     <link rel="preload" href="/assets/inter-roman-latin.bvIUbFQP.woff2" as="font" type="font/woff2" crossorigin="">
     <link rel="modulepreload" href="/assets/chunks/framework.7MjZCsaf.js">

@@ -155,7 +155,7 @@
 </p></div></div></div></div></main><footer class="VPDocFooter" data-v-29e741fd data-v-604f3077> (omitted) </footer>
-    <script>window.__VP_HASH_MAP__=JSON.parse(/* 347440 Characters Omitted */);</script>
   </body>
 </html>

Would you think it helpful to have an option that leaves the sidebar and nav items for client side rendering?

@brc-dd
Copy link
Member

brc-dd commented Dec 21, 2023

Can you try patching node_modules/vitepress/dist/client/theme-default/Layout.vue directly to wrap the VPNav and VPSidebar components with <ClientOnly>? If the results are promising, we can try making their client side rendering configurable.

There is another potential optimization that can be done for space - an option to ditch the SPA routing - if the whole side is re-rendered every time one opens a page or clicks a link to open a page, we don't need JS files other *.lean.js (except few chunks like theme/framework/app). This could theoretically reduce the size by nearly half. But might not seem good experience for end users 🤔

@zhangyx1998
Copy link
Contributor Author

zhangyx1998 commented Dec 21, 2023

Can you try patching node_modules/vitepress/dist/client/theme-default/Layout.vue directly to wrap the VPNav and VPSidebar components with <ClientOnly>? If the results are promising, we can try making their client side rendering configurable.

There is another potential optimization that can be done for space - an option to ditch the SPA routing - if the whole side is re-rendered every time one opens a page or clicks a link to open a page, we don't need JS files other *.lean.js (except few chunks like theme/framework/app). This could theoretically reduce the size by nearly half. But might not seem good experience for end users 🤔

It worked great! Here is a comparison for all 3 conditions:

Description Size Inflate
No config, using official config template 1.7GB $\times~69.6$
Enable metaChunk config option 1.2GB $\times~49.2$
Enable metaChunk, and ClientOnly for VPNav, VPLocalNav and VPSidebar 91MB $\times~~~3.6$
Raw content (before converting to VitePress) 25MB $\times~~~1.0$

I also noticed significant speedup for bundling. Memory pressure was reduced as well.

In addition, here is what I expect the config to look like:

import { defineConfig } from 'vitepress';

defineConfig({
  defaultTheme: {

    // Navbar does not change between routes,
    // so we only need a switch between SSR and CSR
    nav: "nav.json",

    // option 1: pass a `json` file path as string
    //           so the entire component will be client only
    sidebar: "docs/index.sidebar.json",

    // option 2 and 3: pass an object, with EACH entry configurable
    sidebar: {
      // Compatible with the original config
      "/a/": {
        text: "I want full SSR",
        items: ["/a/page-1", /* ... */]
      },
      // option 2: Server side render the skeleton,
      //           and lazy fetch items
      "/b/": {
        text: "I want partial SSR, items shall be fetched when uncollapsed",
        items: "docs/b/items.json",
        collapsed: true,
      },
      // option 3: Client side render on **route match**,
      //           smaller chunks than option 1
      "/c/": "docs/c/sidebar.json",
    },
  }
})

Or we can provide a wrapper like markRaw in Vue:

import { defineConfig, clientOnly } from 'vitepress';

defineConfig({
  defaultTheme: {
    nav: clientOnly([/* ... */]),
    // Options similar to above
    sidebar: clientOnly({ /* ... */ }),
  }
})

A demo internal implementation for clientOnly markers:

// Trick to mark an object by extending its prototype chain
export class ClientOnlyObject extends Object { };

// The marker function
export function
clientOnly<T extends ClientOnlyObject>(obj: T) : T & ClientOnlyObject {
  return Object.assign(new ClientOnlyObject(), obj);
}

// Utility to check if an object is marked as client-only
export function
isClientOnly(obj: any) : boolean {
  return obj instanceof ClientOnlyObject;
}

@brc-dd brc-dd added the perf label Dec 31, 2023
@github-actions github-actions bot added the stale label Feb 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants