vitepress
vitepress copied to clipboard
Provide chunk splitting for shared objects like sidebar and nav
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
- [X] Follow our Code of Conduct
- [X] Read the docs.
- [X] Read the Contributing Guidelines.
- [X] Check that there isn't already an issue that asks for the same feature to avoid creating a duplicate.
I am happy to work on a PR regarding this if someone could provide me an idea of where to look at.
Try setting metaChunk: true in your vitepress config. It won't skip SSR but will be better than having the data on each page.
Try setting
metaChunk: truein 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?
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 🤔
Can you try patching
node_modules/vitepress/dist/client/theme-default/Layout.vuedirectly to wrap theVPNavandVPSidebarcomponents 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;
}