From 8f40afb0e08b28ffa74c382f7038a5000e6c4f02 Mon Sep 17 00:00:00 2001 From: daiwei Date: Mon, 13 Apr 2026 18:19:09 +0800 Subject: [PATCH 01/14] wip: vapor mode --- __tests__/unit/node/markdownToVue.test.ts | 85 +++++++++ docs/.vitepress/config.ts | 8 + package.json | 4 +- pnpm-lock.yaml | 178 +++++++++++------- src/client/app/components/Content.ts | 105 ++++++++--- src/client/app/index.ts | 101 +++++++--- src/client/shims.d.ts | 2 + .../components/VPAlgoliaSearchBox.vue | 9 +- .../components/VPDocAsideCarbonAds.vue | 8 +- .../components/VPNavBarSearch.vue | 20 +- .../theme-default/components/VPSwitch.vue | 6 +- .../components/VPTeamPageSection.vue | 10 +- .../components/VPTeamPageTitle.vue | 8 +- src/node/markdownToVue.ts | 67 ++++++- src/node/plugin.ts | 23 ++- src/node/siteConfig.ts | 9 +- 16 files changed, 504 insertions(+), 139 deletions(-) create mode 100644 __tests__/unit/node/markdownToVue.test.ts diff --git a/__tests__/unit/node/markdownToVue.test.ts b/__tests__/unit/node/markdownToVue.test.ts new file mode 100644 index 000000000000..7ad2f381a2f1 --- /dev/null +++ b/__tests__/unit/node/markdownToVue.test.ts @@ -0,0 +1,85 @@ +import { describe, expect, test } from 'vitest' +import { createMarkdownToVueRenderFn } from '../../../src/node/markdownToVue' +import type { SiteConfig } from '../../../src/node/siteConfig' + +describe('markdownToVue', () => { + test('uses script setup for generated markdown page options in vapor mode', async () => { + const render = await createMarkdownToVueRenderFn( + process.cwd(), + { cache: false }, + '/', + false, + false, + createSiteConfig(true) + ) + + const { vueSrc } = await render('# Hello', `${process.cwd()}/index.md`, '') + + expect(vueSrc).toMatchInlineSnapshot(` + " + + " + `) + }) + + test('marks existing markdown script setup as vapor', async () => { + const render = await createMarkdownToVueRenderFn( + process.cwd(), + { cache: false }, + '/', + false, + false, + createSiteConfig(true) + ) + + const { vueSrc } = await render( + ` + +# {{ label }}`, + `${process.cwd()}/guide/index.md`, + '' + ) + + expect(vueSrc).toMatchInlineSnapshot(` + " + + " + `) + }) +}) + +function createSiteConfig(vapor: boolean): SiteConfig { + return { + __dirty: true, + pages: [], + dynamicRoutes: [], + rewrites: { map: {}, inv: {} }, + site: { + base: '/', + lang: 'en-US', + dir: 'ltr', + title: '', + description: '', + head: [], + appearance: true, + themeConfig: {}, + scrollOffset: 0, + locales: {}, + router: { prefetchLinks: true } + }, + vue: vapor ? { script: { vapor: true } } : undefined, + ignoreDeadLinks: true + } as unknown as SiteConfig +} diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index b809371393a5..a58e2f782110 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -150,6 +150,14 @@ export default defineConfig({ enableNativePlugin: true } }, + vue: { + // allow VDOM and Vapor components to be used together + vdomInterop: true, + script: { + // force enable Vapor Mode + vapor: true + } + }, // prettier-ignore transformPageData: prod ? (pageData, ctx) => { diff --git a/package.json b/package.json index 70775c9d79b1..bb5fbc795ef2 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,7 @@ "@shikijs/transformers": "^4.0.2", "@shikijs/types": "^4.0.2", "@types/markdown-it": "^14.1.2", - "@vitejs/plugin-vue": "^6.0.5", + "@vitejs/plugin-vue": "^6.0.6", "@vue/devtools-api": "^8.1.1", "@vue/shared": "^3.5.31", "@vueuse/core": "^14.2.1", @@ -114,7 +114,7 @@ "minisearch": "^7.2.0", "shiki": "^4.0.2", "vite": "^7.3.1", - "vue": "^3.5.31" + "vue": "^3.6.0-beta.10" }, "devDependencies": { "@clack/prompts": "^1.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 09d943eba814..9b7fbfb7215a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,8 +51,8 @@ importers: specifier: ^14.1.2 version: 14.1.2 '@vitejs/plugin-vue': - specifier: ^6.0.5 - version: 6.0.5(rolldown-vite@7.3.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@1.21.7)(yaml@2.8.3))(vue@3.5.31(typescript@5.9.3)) + specifier: ^6.0.6 + version: 6.0.6(rolldown-vite@7.3.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@1.21.7)(yaml@2.8.3))(vue@3.6.0-beta.10(typescript@5.9.3)) '@vue/devtools-api': specifier: ^8.1.1 version: 8.1.1 @@ -61,10 +61,10 @@ importers: version: 3.5.31 '@vueuse/core': specifier: ^14.2.1 - version: 14.2.1(vue@3.5.31(typescript@5.9.3)) + version: 14.2.1(vue@3.6.0-beta.10(typescript@5.9.3)) '@vueuse/integrations': specifier: ^14.2.1 - version: 14.2.1(axios@1.13.6)(focus-trap@8.0.1)(vue@3.5.31(typescript@5.9.3)) + version: 14.2.1(axios@1.13.6)(focus-trap@8.0.1)(vue@3.6.0-beta.10(typescript@5.9.3)) focus-trap: specifier: ^8.0.1 version: 8.0.1 @@ -81,8 +81,8 @@ importers: specifier: npm:rolldown-vite@latest version: rolldown-vite@7.3.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@1.21.7)(yaml@2.8.3) vue: - specifier: ^3.5.31 - version: 3.5.31(typescript@5.9.3) + specifier: ^3.6.0-beta.10 + version: 3.6.0-beta.10(typescript@5.9.3) devDependencies: '@clack/prompts': specifier: ^1.1.0 @@ -856,8 +856,8 @@ packages: '@rolldown/pluginutils@1.0.0-beta.53': resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==} - '@rolldown/pluginutils@1.0.0-rc.2': - resolution: {integrity: sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==} + '@rolldown/pluginutils@1.0.0-rc.13': + resolution: {integrity: sha512-3ngTAv6F/Py35BsYbeeLeecvhMKdsKm4AoOETVhAA+Qc8nrA2I0kF7oa93mE9qnIurngOSpMnQ0x2nQY2FPviA==} '@rollup/plugin-alias@6.0.0': resolution: {integrity: sha512-tPCzJOtS7uuVZd+xPhoy5W4vThe6KWXNmsFCNktaAh5RTqcLiSfT4huPQIXkgJ6YCOjJHvecOAzQxLFhPxKr+g==} @@ -1207,8 +1207,8 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - '@vitejs/plugin-vue@6.0.5': - resolution: {integrity: sha512-bL3AxKuQySfk1iGcBsQnoRVexTPJq0Z/ixFVM8OhVJAP6ZXXXLtM7NFKWhLl30Kg7uTBqIaPXbh+nuQCuBDedg==} + '@vitejs/plugin-vue@6.0.6': + resolution: {integrity: sha512-u9HHgfrq3AjXlysn0eINFnWQOJQLO9WN6VprZ8FXl7A2bYisv3Hui9Ij+7QZ41F/WYWarHjwBbXtD7dKg3uxbg==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -1258,14 +1258,23 @@ packages: '@vue/compiler-core@3.5.31': resolution: {integrity: sha512-k/ueL14aNIEy5Onf0OVzR8kiqF/WThgLdFhxwa4e/KF/0qe38IwIdofoSWBTvvxQOesaz6riAFAUaYjoF9fLLQ==} + '@vue/compiler-core@3.6.0-beta.10': + resolution: {integrity: sha512-hzkiI5eeO3YgCBlXRXM6HT8E7l2aE3FY4bBBdzHkXDEXy+13gDpztU/TdJQmr6OsER51tNponBOpqwkaZvpL7g==} + '@vue/compiler-dom@3.5.31': resolution: {integrity: sha512-BMY/ozS/xxjYqRFL+tKdRpATJYDTTgWSo0+AJvJNg4ig+Hgb0dOsHPXvloHQ5hmlivUqw1Yt2pPIqp4e0v1GUw==} - '@vue/compiler-sfc@3.5.31': - resolution: {integrity: sha512-M8wpPgR9UJ8MiRGjppvx9uWJfLV7A/T+/rL8s/y3QG3u0c2/YZgff3d6SuimKRIhcYnWg5fTfDMlz2E6seUW8Q==} + '@vue/compiler-dom@3.6.0-beta.10': + resolution: {integrity: sha512-syaKfkW2D4VFFndlNYweR/EqYWxpf1zCF2URtDcN8IJXV3jvsfOSZA+p8+kyNSbDtBEq8CAeulUXrRfORrM0mQ==} + + '@vue/compiler-sfc@3.6.0-beta.10': + resolution: {integrity: sha512-Pw6EkQB1a4wfh137rIgYwMwqc8v8JcsIwspzfGYG7a+St38gcnNGJFGVvfJh689EOLgfv1Z8was6ktrESfowwQ==} + + '@vue/compiler-ssr@3.6.0-beta.10': + resolution: {integrity: sha512-Pp5QQYgEhtv3C7irbeZP64gAWQjrsSnMlaNtBfgge/Cla/xE+HCQqE+8aJUndirtkmFutEA8CvHJ9K4FDHlOjQ==} - '@vue/compiler-ssr@3.5.31': - resolution: {integrity: sha512-h0xIMxrt/LHOvJKMri+vdYT92BrK3HFLtDqq9Pr/lVVfE4IyKZKvWf0vJFW10Yr6nX02OR4MkJwI0c1HDa1hog==} + '@vue/compiler-vapor@3.6.0-beta.10': + resolution: {integrity: sha512-9DqhmWnv06wefy/gBIix0bfphEDba0La96tt8jbNZb9+CbnbB0VyNCz70ynXyhbvrM5K9yhGcIqNfhkNhcepAQ==} '@vue/devtools-api@8.1.1': resolution: {integrity: sha512-bsDMJ07b3GN1puVwJb/fyFnj/U2imyswK5UQVLZwVl7O05jDrt6BHxeG5XffmOOdasOj/bOmIjxJvGPxU7pcqw==} @@ -1279,23 +1288,31 @@ packages: '@vue/language-core@3.2.6': resolution: {integrity: sha512-xYYYX3/aVup576tP/23sEUpgiEnujrENaoNRbaozC1/MA9I6EGFQRJb4xrt/MmUCAGlxTKL2RmT8JLTPqagCkg==} - '@vue/reactivity@3.5.31': - resolution: {integrity: sha512-DtKXxk9E/KuVvt8VxWu+6Luc9I9ETNcqR1T1oW1gf02nXaZ1kuAx58oVu7uX9XxJR0iJCro6fqBLw9oSBELo5g==} + '@vue/reactivity@3.6.0-beta.10': + resolution: {integrity: sha512-YOdRI7G9SAI/Ic3dBUE1Mwzvb7J5h4Oqe0FijSi0y3mI4MWkMxNvWSSLkjqALSfAgixxjSRRfSR9Ln2jm53WeQ==} - '@vue/runtime-core@3.5.31': - resolution: {integrity: sha512-AZPmIHXEAyhpkmN7aWlqjSfYynmkWlluDNPHMCZKFHH+lLtxP/30UJmoVhXmbDoP1Ng0jG0fyY2zCj1PnSSA6Q==} + '@vue/runtime-core@3.6.0-beta.10': + resolution: {integrity: sha512-5cFbYbxtTm7H2Ch1qEdmUFySrPI/+aSmUJsJpMltG5gQJCuWN3kjVqeZp2AZB7pI3a2Jkq2NJC0gR++4eAw1xw==} - '@vue/runtime-dom@3.5.31': - resolution: {integrity: sha512-xQJsNRmGPeDCJq/u813tyonNgWBFjzfVkBwDREdEWndBnGdHLHgkwNBQxLtg4zDrzKTEcnikUy1UUNecb3lJ6g==} + '@vue/runtime-dom@3.6.0-beta.10': + resolution: {integrity: sha512-w02s59Gq1NoZtSCpVKRWzk9No9BEq/eq9n1+KLre5WvYMIYjK0XBNN9cltZfE0/j5uZQlb87i4qR51YxFFbHog==} - '@vue/server-renderer@3.5.31': - resolution: {integrity: sha512-GJuwRvMcdZX/CriUnyIIOGkx3rMV3H6sOu0JhdKbduaeCji6zb60iOGMY7tFoN24NfsUYoFBhshZtGxGpxO4iA==} + '@vue/runtime-vapor@3.6.0-beta.10': + resolution: {integrity: sha512-MQOs7WughaIUFB6nL02kIHgjPpmiZeXnCLP303kihdfaS3vdsYzvc9u6cnTTtvfSGbyHq8F15rcz5QoRlF+zew==} peerDependencies: - vue: 3.5.31 + '@vue/runtime-dom': 3.6.0-beta.10 + + '@vue/server-renderer@3.6.0-beta.10': + resolution: {integrity: sha512-K9bq8O1Hv2fHS0/aVkAxvvukkdy+U8W/nfFRQLcCCvQ92uLuMYsBp8+E3KZRdGaQbAAeuIf4GwJVCGXHTQEg9Q==} + peerDependencies: + vue: 3.6.0-beta.10 '@vue/shared@3.5.31': resolution: {integrity: sha512-nBxuiuS9Lj5bPkPbWogPUnjxxWpkRniX7e5UBQDWl6Fsf4roq9wwV+cR7ezQ4zXswNvPIlsdj1slcLB7XCsRAw==} + '@vue/shared@3.6.0-beta.10': + resolution: {integrity: sha512-13JUfIAd06F+IBnObE8mExDAMOknPIBjPBUN2JeemmuQwj5i20GduCLbHLVbxSkpFD0RGH4z2mOxUUdD+8M/Aw==} + '@vueuse/core@14.2.1': resolution: {integrity: sha512-3vwDzV+GDUNpdegRY6kzpLm4Igptq+GA0QkJ3W61Iv27YWwW/ufSlOfgQIpN6FZRMG0mkaz4gglJRtq5SeJyIQ==} peerDependencies: @@ -3074,8 +3091,8 @@ packages: peerDependencies: typescript: '>=5.0.0' - vue@3.5.31: - resolution: {integrity: sha512-iV/sU9SzOlmA/0tygSmjkEN6Jbs3nPoIPFhCMLD2STrjgOU8DX7ZtzMhg4ahVwf5Rp9KoFzcXeB1ZrVbLBp5/Q==} + vue@3.6.0-beta.10: + resolution: {integrity: sha512-Bkc/Qd6FuPh6YbXkxDNtxqVQ3r6O3+hzSZfR9OtL5R74x3pIpjB/Qovqf6ysq0Fs6AIMFpClUOo8JfhhiVW2ow==} peerDependencies: typescript: '*' peerDependenciesMeta: @@ -3531,7 +3548,7 @@ snapshots: '@rolldown/pluginutils@1.0.0-beta.53': {} - '@rolldown/pluginutils@1.0.0-rc.2': {} + '@rolldown/pluginutils@1.0.0-rc.13': {} '@rollup/plugin-alias@6.0.0(rollup@4.60.0)': optionalDependencies: @@ -3821,11 +3838,11 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-vue@6.0.5(rolldown-vite@7.3.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@1.21.7)(yaml@2.8.3))(vue@3.5.31(typescript@5.9.3))': + '@vitejs/plugin-vue@6.0.6(rolldown-vite@7.3.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@1.21.7)(yaml@2.8.3))(vue@3.6.0-beta.10(typescript@5.9.3))': dependencies: - '@rolldown/pluginutils': 1.0.0-rc.2 + '@rolldown/pluginutils': 1.0.0-rc.13 vite: rolldown-vite@7.3.1(@types/node@25.5.0)(esbuild@0.27.4)(jiti@1.21.7)(yaml@2.8.3) - vue: 3.5.31(typescript@5.9.3) + vue: 3.6.0-beta.10(typescript@5.9.3) '@vitest/expect@4.0.0-beta.4': dependencies: @@ -3893,27 +3910,49 @@ snapshots: estree-walker: 2.0.2 source-map-js: 1.2.1 + '@vue/compiler-core@3.6.0-beta.10': + dependencies: + '@babel/parser': 7.29.2 + '@vue/shared': 3.6.0-beta.10 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + '@vue/compiler-dom@3.5.31': dependencies: '@vue/compiler-core': 3.5.31 '@vue/shared': 3.5.31 - '@vue/compiler-sfc@3.5.31': + '@vue/compiler-dom@3.6.0-beta.10': + dependencies: + '@vue/compiler-core': 3.6.0-beta.10 + '@vue/shared': 3.6.0-beta.10 + + '@vue/compiler-sfc@3.6.0-beta.10': dependencies: '@babel/parser': 7.29.2 - '@vue/compiler-core': 3.5.31 - '@vue/compiler-dom': 3.5.31 - '@vue/compiler-ssr': 3.5.31 - '@vue/shared': 3.5.31 + '@vue/compiler-core': 3.6.0-beta.10 + '@vue/compiler-dom': 3.6.0-beta.10 + '@vue/compiler-ssr': 3.6.0-beta.10 + '@vue/compiler-vapor': 3.6.0-beta.10 + '@vue/shared': 3.6.0-beta.10 estree-walker: 2.0.2 magic-string: 0.30.21 postcss: 8.5.8 source-map-js: 1.2.1 - '@vue/compiler-ssr@3.5.31': + '@vue/compiler-ssr@3.6.0-beta.10': dependencies: - '@vue/compiler-dom': 3.5.31 - '@vue/shared': 3.5.31 + '@vue/compiler-dom': 3.6.0-beta.10 + '@vue/shared': 3.6.0-beta.10 + + '@vue/compiler-vapor@3.6.0-beta.10': + dependencies: + '@babel/parser': 7.29.2 + '@vue/compiler-dom': 3.6.0-beta.10 + '@vue/shared': 3.6.0-beta.10 + estree-walker: 2.0.2 + source-map-js: 1.2.1 '@vue/devtools-api@8.1.1': dependencies: @@ -3938,51 +3977,59 @@ snapshots: path-browserify: 1.0.1 picomatch: 4.0.4 - '@vue/reactivity@3.5.31': + '@vue/reactivity@3.6.0-beta.10': dependencies: - '@vue/shared': 3.5.31 + '@vue/shared': 3.6.0-beta.10 - '@vue/runtime-core@3.5.31': + '@vue/runtime-core@3.6.0-beta.10': dependencies: - '@vue/reactivity': 3.5.31 - '@vue/shared': 3.5.31 + '@vue/reactivity': 3.6.0-beta.10 + '@vue/shared': 3.6.0-beta.10 - '@vue/runtime-dom@3.5.31': + '@vue/runtime-dom@3.6.0-beta.10': dependencies: - '@vue/reactivity': 3.5.31 - '@vue/runtime-core': 3.5.31 - '@vue/shared': 3.5.31 + '@vue/reactivity': 3.6.0-beta.10 + '@vue/runtime-core': 3.6.0-beta.10 + '@vue/shared': 3.6.0-beta.10 csstype: 3.2.3 - '@vue/server-renderer@3.5.31(vue@3.5.31(typescript@5.9.3))': + '@vue/runtime-vapor@3.6.0-beta.10(@vue/runtime-dom@3.6.0-beta.10)': dependencies: - '@vue/compiler-ssr': 3.5.31 - '@vue/shared': 3.5.31 - vue: 3.5.31(typescript@5.9.3) + '@vue/reactivity': 3.6.0-beta.10 + '@vue/runtime-dom': 3.6.0-beta.10 + '@vue/shared': 3.6.0-beta.10 + + '@vue/server-renderer@3.6.0-beta.10(vue@3.6.0-beta.10(typescript@5.9.3))': + dependencies: + '@vue/compiler-ssr': 3.6.0-beta.10 + '@vue/shared': 3.6.0-beta.10 + vue: 3.6.0-beta.10(typescript@5.9.3) '@vue/shared@3.5.31': {} - '@vueuse/core@14.2.1(vue@3.5.31(typescript@5.9.3))': + '@vue/shared@3.6.0-beta.10': {} + + '@vueuse/core@14.2.1(vue@3.6.0-beta.10(typescript@5.9.3))': dependencies: '@types/web-bluetooth': 0.0.21 '@vueuse/metadata': 14.2.1 - '@vueuse/shared': 14.2.1(vue@3.5.31(typescript@5.9.3)) - vue: 3.5.31(typescript@5.9.3) + '@vueuse/shared': 14.2.1(vue@3.6.0-beta.10(typescript@5.9.3)) + vue: 3.6.0-beta.10(typescript@5.9.3) - '@vueuse/integrations@14.2.1(axios@1.13.6)(focus-trap@8.0.1)(vue@3.5.31(typescript@5.9.3))': + '@vueuse/integrations@14.2.1(axios@1.13.6)(focus-trap@8.0.1)(vue@3.6.0-beta.10(typescript@5.9.3))': dependencies: - '@vueuse/core': 14.2.1(vue@3.5.31(typescript@5.9.3)) - '@vueuse/shared': 14.2.1(vue@3.5.31(typescript@5.9.3)) - vue: 3.5.31(typescript@5.9.3) + '@vueuse/core': 14.2.1(vue@3.6.0-beta.10(typescript@5.9.3)) + '@vueuse/shared': 14.2.1(vue@3.6.0-beta.10(typescript@5.9.3)) + vue: 3.6.0-beta.10(typescript@5.9.3) optionalDependencies: axios: 1.13.6 focus-trap: 8.0.1 '@vueuse/metadata@14.2.1': {} - '@vueuse/shared@14.2.1(vue@3.5.31(typescript@5.9.3))': + '@vueuse/shared@14.2.1(vue@3.6.0-beta.10(typescript@5.9.3))': dependencies: - vue: 3.5.31(typescript@5.9.3) + vue: 3.6.0-beta.10(typescript@5.9.3) '@xmldom/xmldom@0.9.8': {} @@ -5850,13 +5897,14 @@ snapshots: '@vue/language-core': 3.2.6 typescript: 5.9.3 - vue@3.5.31(typescript@5.9.3): + vue@3.6.0-beta.10(typescript@5.9.3): dependencies: - '@vue/compiler-dom': 3.5.31 - '@vue/compiler-sfc': 3.5.31 - '@vue/runtime-dom': 3.5.31 - '@vue/server-renderer': 3.5.31(vue@3.5.31(typescript@5.9.3)) - '@vue/shared': 3.5.31 + '@vue/compiler-dom': 3.6.0-beta.10 + '@vue/compiler-sfc': 3.6.0-beta.10 + '@vue/runtime-dom': 3.6.0-beta.10 + '@vue/runtime-vapor': 3.6.0-beta.10(@vue/runtime-dom@3.6.0-beta.10) + '@vue/server-renderer': 3.6.0-beta.10(vue@3.6.0-beta.10(typescript@5.9.3)) + '@vue/shared': 3.6.0-beta.10 optionalDependencies: typescript: 5.9.3 diff --git a/src/client/app/components/Content.ts b/src/client/app/components/Content.ts index 1245645151ae..e4577a929b99 100644 --- a/src/client/app/components/Content.ts +++ b/src/client/app/components/Content.ts @@ -1,31 +1,84 @@ import { useData, useRoute } from 'vitepress' -import { defineComponent, h, watch } from 'vue' +import { + createComponent, + createIf, + createKeyedFragment, + defineComponent, + defineVaporComponent, + h, + onMounted, + onUnmounted, + onUpdated, + renderEffect, + setDynamicProps, + setInsertionState, + template, + watch +} from 'vue' import { contentUpdatedCallbacks } from '../utils' const runCbs = () => contentUpdatedCallbacks.forEach((fn) => fn()) +const inVaporMode = !import.meta.env.SSR && __VAPOR__ -export const Content = defineComponent({ - name: 'VitePressContent', - props: { - as: { type: [Object, String], default: 'div' } - }, - setup(props) { - const route = useRoute() - const { frontmatter, site } = useData() - watch(frontmatter, runCbs, { deep: true, flush: 'post' }) - return () => - h( - props.as, - site.value.contentProps ?? { style: { position: 'relative' } }, - [ - route.component - ? h(route.component, { - onVnodeMounted: runCbs, - onVnodeUpdated: runCbs, - onVnodeUnmounted: runCbs - }) - : '404 Page Not Found' - ] - ) - } -}) +export const Content = inVaporMode + ? defineVaporComponent({ + name: 'VitePressContent', + props: { + as: { type: [Object, String], default: 'div' } + }, + setup(props) { + const route = useRoute() + const { frontmatter, site } = useData() + watch(frontmatter, runCbs, { deep: true, flush: 'post' }) + const tag = props.as || 'div' + const container = template(`<${tag}>`)() as any + setInsertionState(container, null) + const Wrapper = defineVaporComponent({ + setup() { + onMounted(runCbs) + onUpdated(runCbs) + onUnmounted(runCbs) + return createIf( + () => route.component, + () => createComponent(route.component! as any), + () => template('404 Page Not Found')() + ) + } + }) + createKeyedFragment( + () => route.path, + () => createComponent(Wrapper) + ) + renderEffect(() => { + setDynamicProps(container, [ + () => site.value.contentProps ?? { style: { position: 'relative' } } + ]) + }) + return container + } + }) + : defineComponent({ + name: 'VitePressContent', + props: { + as: { type: [Object, String], default: 'div' } + }, + setup(props) { + const route = useRoute() + const { frontmatter, site } = useData() + watch(frontmatter, runCbs, { deep: true, flush: 'post' }) + return () => + h( + props.as, + site.value.contentProps ?? { style: { position: 'relative' } }, + [ + route.component + ? h(route.component, { + onVnodeMounted: runCbs, + onVnodeUpdated: runCbs, + onVnodeUnmounted: runCbs + }) + : '404 Page Not Found' + ] + ) + } + }) diff --git a/src/client/app/index.ts b/src/client/app/index.ts index 761c2d92f4c6..cfd4fd021406 100644 --- a/src/client/app/index.ts +++ b/src/client/app/index.ts @@ -1,12 +1,18 @@ import RawTheme from '@theme/index' import { createApp as createClientApp, + createComponent, createSSRApp, + createVaporApp, + createVaporSSRApp, defineComponent, + defineVaporComponent, h, onMounted, + vaporInteropPlugin, watchEffect, - type App + type App, + type VaporComponent } from 'vue' import { ClientOnly } from './components/ClientOnly' import { Content } from './components/Content' @@ -34,34 +40,63 @@ function resolveThemeExtends(theme: typeof RawTheme): typeof RawTheme { } const Theme = resolveThemeExtends(RawTheme) +const inVaporMode = !import.meta.env.SSR && __VAPOR__ + +const VitePressApp = inVaporMode + ? defineVaporComponent({ + name: 'VitePressApp', + setup() { + const { site, lang, dir } = useData() + + // change the language on the HTML element based on the current lang + onMounted(() => { + watchEffect(() => { + document.documentElement.lang = lang.value + document.documentElement.dir = dir.value + }) + }) + + if (import.meta.env.PROD && site.value.router.prefetchLinks) { + // in prod mode, enable intersectionObserver based pre-fetch + usePrefetch() + } + + // setup global copy code handler + useCopyCode() + // setup global code groups handler + useCodeGroups() -const VitePressApp = defineComponent({ - name: 'VitePressApp', - setup() { - const { site, lang, dir } = useData() - - // change the language on the HTML element based on the current lang - onMounted(() => { - watchEffect(() => { - document.documentElement.lang = lang.value - document.documentElement.dir = dir.value - }) + if (Theme.setup) Theme.setup() + return createComponent(Theme.Layout! as VaporComponent) + } }) + : defineComponent({ + name: 'VitePressApp', + setup() { + const { site, lang, dir } = useData() + + // change the language on the HTML element based on the current lang + onMounted(() => { + watchEffect(() => { + document.documentElement.lang = lang.value + document.documentElement.dir = dir.value + }) + }) - if (import.meta.env.PROD && site.value.router.prefetchLinks) { - // in prod mode, enable intersectionObserver based pre-fetch - usePrefetch() - } + if (import.meta.env.PROD && site.value.router.prefetchLinks) { + // in prod mode, enable intersectionObserver based pre-fetch + usePrefetch() + } - // setup global copy code handler - useCopyCode() - // setup global code groups handler - useCodeGroups() + // setup global copy code handler + useCopyCode() + // setup global code groups handler + useCodeGroups() - if (Theme.setup) Theme.setup() - return () => h(Theme.Layout!) - } -}) + if (Theme.setup) Theme.setup() + return () => h(Theme.Layout!) + } + }) export async function createApp() { ;(globalThis as any).__VITEPRESS__ = true @@ -112,9 +147,21 @@ export async function createApp() { } function newApp(): App { - return import.meta.env.PROD - ? createSSRApp(VitePressApp) - : createClientApp(VitePressApp) + if (import.meta.env.SSR) { + return createSSRApp(VitePressApp) + } + + const app = inVaporMode + ? import.meta.env.PROD + ? createVaporSSRApp(VitePressApp as VaporComponent) + : createVaporApp(VitePressApp as VaporComponent) + : import.meta.env.PROD + ? createSSRApp(VitePressApp) + : createClientApp(VitePressApp) + if (__VAPOR_VDOM_INTEROP__) { + app.use(vaporInteropPlugin) + } + return app } function newRouter(): Router { diff --git a/src/client/shims.d.ts b/src/client/shims.d.ts index e12889d5d2db..deabfa9f85fe 100644 --- a/src/client/shims.d.ts +++ b/src/client/shims.d.ts @@ -4,6 +4,8 @@ declare const __ALGOLIA__: boolean declare const __CARBON__: boolean declare const __VUE_PROD_DEVTOOLS__: boolean declare const __ASSETS_DIR__: string +declare const __VAPOR__: boolean +declare const __VAPOR_VDOM_INTEROP__: boolean declare module '*.vue' { import type { DefineComponent } from 'vue' diff --git a/src/client/theme-default/components/VPAlgoliaSearchBox.vue b/src/client/theme-default/components/VPAlgoliaSearchBox.vue index 26df2b7c0aac..739ddd9d5842 100644 --- a/src/client/theme-default/components/VPAlgoliaSearchBox.vue +++ b/src/client/theme-default/components/VPAlgoliaSearchBox.vue @@ -3,13 +3,18 @@ import type { DocSearchInstance, DocSearchProps } from '@docsearch/js' import type { SidepanelInstance, SidepanelProps } from '@docsearch/sidepanel-js' import { inBrowser, useRouter } from 'vitepress' import type { DefaultTheme } from 'vitepress/theme' -import { nextTick, onUnmounted, watch } from 'vue' +import { nextTick, onBeforeMount, onUnmounted, watch } from 'vue' import type { DocSearchAskAi } from '../../../../types/docsearch' import { useData } from '../composables/data' import { resolveMode, validateCredentials } from '../support/docsearch' import '../styles/docsearch.css' - +const emit = defineEmits<{ + (e: 'beforeMount'): void +}>() +onBeforeMount(()=>{ + emit('beforeMount') +}) const props = defineProps<{ algoliaOptions: DefaultTheme.AlgoliaSearchOptions openRequest?: { diff --git a/src/client/theme-default/components/VPDocAsideCarbonAds.vue b/src/client/theme-default/components/VPDocAsideCarbonAds.vue index d10598dd3132..616f7b10d1ae 100644 --- a/src/client/theme-default/components/VPDocAsideCarbonAds.vue +++ b/src/client/theme-default/components/VPDocAsideCarbonAds.vue @@ -1,13 +1,17 @@ diff --git a/src/client/theme-default/components/VPNavBarSearch.vue b/src/client/theme-default/components/VPNavBarSearch.vue index e23d5cefa852..96240ebbcff6 100644 --- a/src/client/theme-default/components/VPNavBarSearch.vue +++ b/src/client/theme-default/components/VPNavBarSearch.vue @@ -1,19 +1,31 @@