pull/4663/merge
Yuxuan Zhang 4 months ago committed by GitHub
commit a137a6d6bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -54,7 +54,7 @@ describe('static data file support in vite 3', () => {
document.querySelector('pre#basic')?.textContent ===
JSON.stringify([{ a: false }, { b: true }], null, 2),
undefined,
{ timeout: 3000 }
{ timeout: 5000 }
)
} finally {
await fs.writeFile(a, JSON.stringify({ a: true }, null, 2) + '\n')
@ -69,7 +69,7 @@ describe('static data file support in vite 3', () => {
document.querySelector('pre#basic')?.textContent ===
JSON.stringify([{ a: true }], null, 2),
undefined,
{ timeout: 3000 }
{ timeout: 5000 }
)
err = false
} finally {
@ -85,7 +85,7 @@ describe('static data file support in vite 3', () => {
document.querySelector('pre#basic')?.textContent ===
JSON.stringify([{ a: true }, { b: false }], null, 2),
undefined,
{ timeout: 3000 }
{ timeout: 5000 }
)
} finally {
await fs.writeFile(b, JSON.stringify({ b: true }, null, 2) + '\n')

@ -107,6 +107,7 @@
"@vueuse/core": "^13.1.0",
"@vueuse/integrations": "^13.1.0",
"focus-trap": "^7.6.4",
"living-object": "0.0.8",
"mark.js": "8.11.1",
"minisearch": "^7.1.2",
"shiki": "^3.4.0",

@ -55,6 +55,9 @@ importers:
focus-trap:
specifier: ^7.6.4
version: 7.6.4
living-object:
specifier: 0.0.8
version: 0.0.8
mark.js:
specifier: 8.11.1
version: 8.11.1
@ -2200,6 +2203,10 @@ packages:
resolution: {integrity: sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==}
engines: {node: '>=18.0.0'}
living-object@0.0.8:
resolution: {integrity: sha512-o4SGS1iyo3H6m/OAbrxty0RFgXNIkx0vojHDGcAk/RjQ+noB0lsK2znmq19BZ+jyOS7P0jj5zywxgVNuKt5/Gg==}
engines: {node: '>=16.0.0'}
local-pkg@1.1.1:
resolution: {integrity: sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==}
engines: {node: '>=14'}
@ -5123,6 +5130,8 @@ snapshots:
rfdc: 1.4.1
wrap-ansi: 9.0.0
living-object@0.0.8: {}
local-pkg@1.1.1:
dependencies:
mlly: 1.7.4

@ -1,7 +1,17 @@
import { defineComponent, onMounted, ref } from 'vue'
export const ClientOnly = defineComponent({
setup(_, { slots }) {
props: {
isClientOnly: {
type: Boolean,
default: true
}
},
setup(props, { slots }) {
// Programmatically determine if this component should be
// client-only based on the presence of the isClientOnly attribute.
if (!props.isClientOnly) return () => slots.default?.(props) || null
const show = ref(false)
onMounted(() => {

@ -2,11 +2,13 @@
import { useData } from '../composables/data'
import VPNavBarMenuLink from './VPNavBarMenuLink.vue'
import VPNavBarMenuGroup from './VPNavBarMenuGroup.vue'
import { isClientOnly } from '../../shared'
const { theme } = useData()
</script>
<template>
<ClientOnly :is-client-only="isClientOnly(theme.nav)">
<nav
v-if="theme.nav"
aria-labelledby="main-nav-aria-label"
@ -25,6 +27,7 @@ const { theme } = useData()
<VPNavBarMenuGroup v-else :item />
</template>
</nav>
</ClientOnly>
</template>
<style scoped>

@ -5,7 +5,7 @@ import { ref, watch } from 'vue'
import { useLayout } from '../composables/layout'
import VPSidebarGroup from './VPSidebarGroup.vue'
const { sidebarGroups, hasSidebar } = useLayout()
const { sidebarGroups, hasSidebar, isSidebarClientOnly } = useLayout()
const props = defineProps<{
open: boolean
@ -38,27 +38,18 @@ watch(
</script>
<template>
<aside
v-if="hasSidebar"
class="VPSidebar"
:class="{ open }"
ref="navEl"
@click.stop
>
<aside v-if="hasSidebar" class="VPSidebar" :class="{ open }" ref="navEl" @click.stop>
<div class="curtain" />
<nav
class="nav"
id="VPSidebarNav"
aria-labelledby="sidebar-aria-label"
tabindex="-1"
>
<nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1">
<span class="visually-hidden" id="sidebar-aria-label">
Sidebar Navigation
</span>
<slot name="sidebar-nav-before" />
<ClientOnly :is-client-only="isSidebarClientOnly">
<VPSidebarGroup :items="sidebarGroups" :key />
</ClientOnly>
<slot name="sidebar-nav-after" />
</nav>
</aside>

@ -2,6 +2,7 @@
import type { DefaultTheme } from 'vitepress/theme'
import { onBeforeUnmount, onMounted, ref } from 'vue'
import VPSidebarItem from './VPSidebarItem.vue'
import { isClientOnly } from '../../shared'
defineProps<{
items: DefaultTheme.SidebarItem[]
@ -33,7 +34,9 @@ onBeforeUnmount(() => {
class="group"
:class="{ 'no-transition': disableTransition }"
>
<ClientOnly :is-client-only="isClientOnly(item)">
<VPSidebarItem :item :depth="0" />
</ClientOnly>
</div>
</template>

@ -3,6 +3,7 @@ import type { DefaultTheme } from 'vitepress/theme'
import { computed } from 'vue'
import { useSidebarItemControl } from '../composables/sidebar'
import VPLink from './VPLink.vue'
import { isClientOnly } from '../../shared'
const props = defineProps<{
item: DefaultTheme.SidebarItem
@ -55,6 +56,7 @@ function onCaretClick() {
</script>
<template>
<ClientOnly :is-client-only="isClientOnly(item)">
<component :is="sectionTag" class="VPSidebarItem" :class="classes">
<div
v-if="item.text"
@ -105,6 +107,7 @@ function onCaretClick() {
</template>
</div>
</component>
</ClientOnly>
</template>
<style scoped>

@ -5,6 +5,7 @@ import { getSidebar, getSidebarGroups } from '../support/sidebar'
import { useData } from './data'
import { getHeaders } from './outline'
import { useCloseSidebarOnEscape } from './sidebar'
import { isClientOnly } from '../../shared'
const headers = shallowRef<DefaultTheme.OutlineItem[]>([])
const sidebar = shallowRef<DefaultTheme.SidebarItem[]>([])
@ -28,6 +29,10 @@ export function useLayout(): ReturnType<typeof expected> {
const isSidebarEnabled = computed(() => hasSidebar.value && is960.value)
const isSidebarClientOnly = computed(
() => isClientOnly(theme.value.sidebar) || isClientOnly(sidebar.value)
)
const sidebarGroups = computed(() => {
return hasSidebar.value ? getSidebarGroups(sidebar.value) : []
})
@ -55,6 +60,7 @@ export function useLayout(): ReturnType<typeof expected> {
sidebarGroups,
hasSidebar,
isSidebarEnabled,
isSidebarClientOnly,
hasAside,
leftAside,
headers: shallowReadonly(headers),

@ -1,5 +1,5 @@
import type { DefaultTheme } from 'vitepress/theme'
import { isActive } from '../../shared'
import { isActive, propagateClientOnly } from '../../shared'
import { ensureStartingSlash } from './utils'
export interface SidebarLink {
@ -108,12 +108,13 @@ export function hasActiveLink(
: false
}
function addBase(items: SidebarItem[], _base?: string): SidebarItem[] {
return [...items].map((_item) => {
function addBase(items: SidebarItem[], _base?: string) {
const result = [...items].map((_item) => {
const item = { ..._item }
const base = item.base || _base
if (base && item.link) item.link = base + item.link
if (item.items) item.items = addBase(item.items, base)
return item
return propagateClientOnly(item, _item)
})
return propagateClientOnly(items, result)
}

@ -12,11 +12,11 @@ import type { BuildOptions, Rollup } from 'vite'
import { resolveConfig, type SiteConfig } from '../config'
import { clearCache } from '../markdownToVue'
import { slash, type Awaitable, type HeadConfig } from '../shared'
import { deserializeFunctions, serializeFunctions } from '../utils/fnSerialize'
import { task } from '../utils/task'
import { bundle } from './bundle'
import { generateSitemap } from './generateSitemap'
import { renderPage } from './render'
import LivingObject from 'living-object'
const require = createRequire(import.meta.url)
@ -216,15 +216,11 @@ function generateMetadataScript(
// It's also embedded as a string and JSON.parsed from the client because
// it's faster than embedding as JS object literal.
const hashMapString = JSON.stringify(JSON.stringify(pageToHashMap))
const siteDataString = JSON.stringify(
JSON.stringify(serializeFunctions({ ...config.site, head: [] }))
)
const siteDataString = new LivingObject({ ...config.site, head: [] })
.compile()
.complete((root) => `window.__VP_SITE_DATA__=${root}`)
const metadataContent = `window.__VP_HASH_MAP__=JSON.parse(${hashMapString});${
siteDataString.includes('_vp-fn_')
? `${deserializeFunctions};window.__VP_SITE_DATA__=deserializeFunctions(JSON.parse(${siteDataString}));`
: `window.__VP_SITE_DATA__=JSON.parse(${siteDataString});`
}`
const metadataContent = `{window.__VP_HASH_MAP__=JSON.parse(${hashMapString});${siteDataString};}`
if (!config.metaChunk) {
return { html: `<script>${metadataContent}</script>`, inHead: false }

@ -15,7 +15,7 @@ import type { DefaultTheme } from './defaultTheme'
import { resolvePages } from './plugins/dynamicRoutesPlugin'
import {
APPEARANCE_KEY,
VP_SOURCE_KEY,
VP_CONFIG_SOURCE,
isObject,
slash,
type AdditionalConfig,
@ -28,6 +28,7 @@ import type { RawConfigExports, SiteConfig, UserConfig } from './siteConfig'
export { resolvePages } from './plugins/dynamicRoutesPlugin'
export { resolveSiteDataByRoute } from './shared'
export * from './siteConfig'
export { clientOnly, isClientOnly } from './shared'
const debug = _debug('vitepress:config')
@ -218,7 +219,7 @@ async function gatherAdditionalConfig(
)
if (mode === 'development') {
;(configExports.config as any)[VP_SOURCE_KEY] = '/' + slash(file)
;(configExports.config as any)[VP_CONFIG_SOURCE] = '/' + slash(file)
}
return [id, configExports.config as AdditionalConfig] as const

@ -33,7 +33,7 @@ import { rewritesPlugin } from './plugins/rewritesPlugin'
import { staticDataPlugin } from './plugins/staticDataPlugin'
import { webFontsPlugin } from './plugins/webFontsPlugin'
import { slash, type PageDataPayload } from './shared'
import { deserializeFunctions, serializeFunctions } from './utils/fnSerialize'
import { stringify as serializeSiteData } from 'living-object'
declare module 'vite' {
interface UserConfig {
@ -184,8 +184,7 @@ export async function createVitePressPlugin(
return `export default window.__VP_SITE_DATA__`
}
}
data = serializeFunctions(data)
return `${deserializeFunctions};export default deserializeFunctions(JSON.parse(${JSON.stringify(JSON.stringify(data))}))`
return serializeSiteData(data, { target: 'module' })
}
},

@ -1,42 +0,0 @@
export function serializeFunctions(value: any, key?: string): any {
if (Array.isArray(value)) {
return value.map((v) => serializeFunctions(v))
} else if (typeof value === 'object' && value !== null) {
return Object.keys(value).reduce((acc, key) => {
if (key[0] === '_') return acc
acc[key] = serializeFunctions(value[key], key)
return acc
}, {} as any)
} else if (typeof value === 'function') {
let serialized = value.toString()
if (
key &&
(serialized.startsWith(key) || serialized.startsWith('async ' + key))
) {
serialized = serialized.replace(key, 'function')
}
return `_vp-fn_${serialized}`
} else {
return value
}
}
/*
export function deserializeFunctions(value: any): any {
if (Array.isArray(value)) {
return value.map(deserializeFunctions)
} else if (typeof value === 'object' && value !== null) {
return Object.keys(value).reduce((acc, key) => {
acc[key] = deserializeFunctions(value[key])
return acc
}, {} as any)
} else if (typeof value === 'string' && value.startsWith('_vp-fn_')) {
return new Function(`return ${value.slice(7)}`)()
} else {
return value
}
}
*/
export const deserializeFunctions =
'function deserializeFunctions(r){return Array.isArray(r)?r.map(deserializeFunctions):typeof r=="object"&&r!==null?Object.keys(r).reduce((t,n)=>(t[n]=deserializeFunctions(r[n]),t),{}):typeof r=="string"&&r.startsWith("_vp-fn_")?new Function(`return ${r.slice(7)}`)():r}'

@ -25,7 +25,11 @@ export type {
export const EXTERNAL_URL_RE = /^(?:[a-z]+:|\/\/)/i
export const APPEARANCE_KEY = 'vitepress-theme-appearance'
export const VP_SOURCE_KEY = '[VP_SOURCE]'
// Global symbols will be preserved by living-object.
export const VP_CONFIG_SOURCE = Symbol.for('VP:ConfigSource')
export const VP_CLIENT_ONLY = Symbol.for('VP:ClientOnly')
// Private symbols are distinct in each runtime environment.
// This should only be used internally.
const UnpackStackView = Symbol('stack-view:unpack')
const HASH_RE = /#.*$/
@ -111,7 +115,7 @@ export function resolveSiteDataByRoute(
const additionalConfigs = resolveAdditionalConfig(siteData, relativePath)
if (inBrowser && (import.meta as any).env?.DEV) {
;(localeConfig as any)[VP_SOURCE_KEY] = `locale config (${localeIndex})`
;(localeConfig as any)[VP_CONFIG_SOURCE] = `locale config (${localeIndex})`
reportConfigLayers(relativePath, [
...additionalConfigs,
localeConfig,
@ -297,7 +301,7 @@ function reportConfigLayers(path: string, layers: Partial<SiteData>[]) {
const summary = layers.map((c, i, arr) => {
const n = i + 1
if (n === arr.length) return `${n}. .vitepress/config (root)`
return `${n}. ${(c as any)?.[VP_SOURCE_KEY] ?? '(Unknown Source)'}`
return `${n}. ${(c as any)?.[VP_CONFIG_SOURCE] ?? '(Unknown Source)'}`
})
console.debug(
@ -353,3 +357,19 @@ type ObjectType = Record<PropertyKey, any>
export function isObject(value: unknown): value is ObjectType {
return Object.prototype.toString.call(value) === '[object Object]'
}
export function clientOnly<T extends object>(object: T) {
;(object as any)[VP_CLIENT_ONLY] = true
return object
}
export function isClientOnly<T extends object>(object?: T): boolean {
return (object as any)?.[VP_CLIENT_ONLY] ?? false
}
export function propagateClientOnly<T extends object>(src: T, dst: T): T {
if (isClientOnly(src)) {
clientOnly(dst)
}
return dst
}

1
theme.d.ts vendored

@ -20,6 +20,7 @@ export declare const useLayout: () => {
sidebarGroups: ComputedRef<DefaultTheme.SidebarItem[]>
hasSidebar: ComputedRef<boolean>
isSidebarEnabled: ComputedRef<boolean>
isSidebarClientOnly: ComputedRef<boolean>
hasAside: ComputedRef<boolean>
leftAside: ComputedRef<boolean>

Loading…
Cancel
Save