Skip to content

Commit cd7252f

Browse files
pengzhanboMister-Hopemeteorlxy
authored
feat(client): add onContentUpdated composition API (#1620)
Co-authored-by: Mister-Hope <[email protected]> Co-authored-by: Xinyu Liu <[email protected]>
1 parent c971b79 commit cd7252f

File tree

10 files changed

+184
-3
lines changed

10 files changed

+184
-3
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<script lang="ts" setup>
2+
import { ref, watch } from 'vue'
3+
import { onContentUpdated, useRoutePath } from 'vuepress/client'
4+
5+
const mounted = ref('')
6+
const beforeUnmount = ref('')
7+
8+
const mountedCount = ref(0)
9+
const updatedCount = ref(0)
10+
11+
const routePath = useRoutePath()
12+
13+
watch(routePath, () => {
14+
updatedCount.value = 0
15+
})
16+
17+
onContentUpdated((reason) => {
18+
switch (reason) {
19+
case 'mounted':
20+
mounted.value = routePath.value
21+
mountedCount.value++
22+
break
23+
case 'updated':
24+
updatedCount.value++
25+
break
26+
case 'beforeUnmount':
27+
beforeUnmount.value = routePath.value
28+
break
29+
default:
30+
}
31+
})
32+
</script>
33+
34+
<template>
35+
<div class="markdown-content-hooks">
36+
<h3>markdown content hooks</h3>
37+
<p class="markdown-content-mounted">
38+
mounted: {{ mounted }} {{ mountedCount }}
39+
</p>
40+
<p class="markdown-content-beforeUnmount">
41+
beforeUnmount: {{ beforeUnmount }}
42+
</p>
43+
<p class="markdown-content-updated">updatedCount: {{ updatedCount }}</p>
44+
</div>
45+
</template>

e2e/docs/.vuepress/theme/client/layouts/Layout.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script setup lang="ts">
22
import { Content, useSiteData } from 'vuepress/client'
3+
import MarkdownContentHooks from '../components/MarkdownContentHooks.vue'
34
45
const siteData = useSiteData()
56
</script>
@@ -18,6 +19,8 @@ const siteData = useSiteData()
1819
<main class="e2e-theme-content">
1920
<Content />
2021
</main>
22+
23+
<MarkdownContentHooks />
2124
</div>
2225
</template>
2326

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## title
2+
3+
content
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { expect, test } from '@playwright/test'
2+
import { BUNDLER, IS_DEV } from '../../utils/env'
3+
import { readSourceMarkdown, writeSourceMarkdown } from '../../utils/source'
4+
5+
const updateMarkdownContent = async (): Promise<void> => {
6+
const content = await readSourceMarkdown('composables/on-content-updated.md')
7+
await writeSourceMarkdown(
8+
'composables/on-content-updated.md',
9+
`${content}\n\nUpdated content`,
10+
)
11+
}
12+
13+
const restoreMarkdownContent = async (): Promise<void> => {
14+
await writeSourceMarkdown(
15+
'composables/on-content-updated.md',
16+
'## title\n\ncontent\n',
17+
)
18+
}
19+
20+
test.afterAll(async () => {
21+
await restoreMarkdownContent()
22+
})
23+
24+
test('should call content hook on mounted', async ({ page }) => {
25+
await page.goto('composables/on-content-updated.html')
26+
const mountedLocator = page.locator(
27+
'.markdown-content-hooks .markdown-content-mounted',
28+
)
29+
await expect(mountedLocator).toHaveText(
30+
'mounted: /composables/on-content-updated.html 1',
31+
)
32+
33+
// update content but mounted hook should not be called twice
34+
await updateMarkdownContent()
35+
await expect(mountedLocator).toHaveText(
36+
'mounted: /composables/on-content-updated.html 1',
37+
)
38+
})
39+
40+
test('should call content hook on beforeUnmount', async ({ page }) => {
41+
await page.goto('composables/on-content-updated.html')
42+
43+
const beforeUnmountLocator = page.locator(
44+
'.markdown-content-hooks .markdown-content-beforeUnmount',
45+
)
46+
47+
await page.locator('.e2e-theme-nav ul > li > a').nth(0).click()
48+
49+
await expect(beforeUnmountLocator).toHaveText('beforeUnmount: /')
50+
})
51+
52+
/**
53+
* Updated hooks are only supported for use in development environments.
54+
* In CI environments, under both Linux and Windows, using Vite fails to correctly trigger hooks.
55+
*/
56+
if (IS_DEV && BUNDLER !== 'vite') {
57+
test('should call content hook on updated', async ({ page }) => {
58+
await page.goto('composables/on-content-updated.html')
59+
const updatedLocator = page.locator(
60+
'.markdown-content-hooks .markdown-content-updated',
61+
)
62+
63+
await updateMarkdownContent()
64+
await expect(updatedLocator).toHaveText(`updatedCount: 1`)
65+
66+
await updateMarkdownContent()
67+
await expect(updatedLocator).toHaveText(`updatedCount: 2`)
68+
})
69+
}

packages/client/src/components/Content.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
1-
import { computed, defineAsyncComponent, defineComponent, h } from 'vue'
2-
import { usePageComponent } from '../composables/index.js'
1+
import { computed, defineAsyncComponent, defineComponent, h, watch } from 'vue'
2+
import { usePageComponent, usePageFrontmatter } from '../composables/index.js'
3+
import { contentUpdatedCallbacks } from '../internal/contentUpdatedCallbacks'
34
import { resolveRoute } from '../router/index.js'
5+
import type { ContentUpdatedReason } from '../types/index.js'
6+
7+
/**
8+
* Execute all callbacks registered via `onContentUpdated`.
9+
*
10+
* @internal
11+
*/
12+
const runContentUpdatedCallbacks = (reason: ContentUpdatedReason): void => {
13+
contentUpdatedCallbacks.value.forEach((fn) => fn(reason))
14+
}
415

516
/**
617
* Markdown rendered content
@@ -26,6 +37,26 @@ export const Content = defineComponent({
2637
)
2738
})
2839

29-
return () => h(ContentComponent.value)
40+
const frontmatter = usePageFrontmatter()
41+
watch(
42+
frontmatter,
43+
() => {
44+
runContentUpdatedCallbacks('updated')
45+
},
46+
{ deep: true, flush: 'post' },
47+
)
48+
49+
return () =>
50+
h(ContentComponent.value, {
51+
onVnodeMounted: () => {
52+
runContentUpdatedCallbacks('mounted')
53+
},
54+
onVnodeUpdated: () => {
55+
runContentUpdatedCallbacks('updated')
56+
},
57+
onVnodeBeforeUnmount: () => {
58+
runContentUpdatedCallbacks('beforeUnmount')
59+
},
60+
})
3061
},
3162
})
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './clientData.js'
22
export * from './clientDataUtils.js'
3+
export * from './onContentUpdated.js'
34
export * from './updateHead.js'
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { onUnmounted } from 'vue'
2+
import { contentUpdatedCallbacks } from '../internal/contentUpdatedCallbacks'
3+
import type { ContentUpdatedCallback } from '../types/index.js'
4+
5+
/**
6+
* Register callback that is called every time the markdown content is updated
7+
* in the DOM.
8+
*/
9+
export const onContentUpdated = (fn: ContentUpdatedCallback): void => {
10+
contentUpdatedCallbacks.value.push(fn)
11+
onUnmounted(() => {
12+
contentUpdatedCallbacks.value = contentUpdatedCallbacks.value.filter(
13+
(f) => f !== fn,
14+
)
15+
})
16+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { Ref } from 'vue'
2+
import { shallowRef } from 'vue'
3+
import type { ContentUpdatedCallback } from '../types/index.js'
4+
5+
/**
6+
* Global content updated callbacks ref
7+
*/
8+
export const contentUpdatedCallbacks: Ref<ContentUpdatedCallback[]> =
9+
shallowRef([])

packages/client/src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export type * from './clientConfig.js'
22
export type * from './clientData.js'
3+
export type * from './onContentUpdated.js'
34
export type * from './createVueAppFunction.js'
45
export type * from './routes.js'
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export type ContentUpdatedReason = 'beforeUnmount' | 'mounted' | 'updated'
2+
3+
export type ContentUpdatedCallback = (reason: ContentUpdatedReason) => unknown

0 commit comments

Comments
 (0)