valaxy 0.26.13 → 0.28.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/client/app/data.ts +2 -2
  2. package/client/components/AppLink.vue +4 -0
  3. package/client/components/ClientOnly.ts +12 -0
  4. package/client/components/ValaxyDynamicComponent.vue +22 -14
  5. package/client/composables/common.ts +6 -5
  6. package/client/composables/dark.ts +18 -11
  7. package/client/composables/features/copy-markdown.ts +85 -0
  8. package/client/composables/features/index.ts +1 -0
  9. package/client/composables/post/index.ts +35 -22
  10. package/client/config.ts +4 -2
  11. package/client/entry-ssr.ts +25 -0
  12. package/client/index.d.ts +2 -8
  13. package/client/locales/en.yml +8 -0
  14. package/client/locales/zh-CN.yml +8 -0
  15. package/client/main.ts +82 -22
  16. package/client/modules/components.ts +2 -2
  17. package/client/modules/floating-vue.ts +2 -3
  18. package/client/modules/valaxy.ts +2 -3
  19. package/client/setup/main.ts +2 -3
  20. package/client/setups.ts +24 -3
  21. package/client/stores/site.ts +11 -6
  22. package/client/styles/common/view-transition.css +4 -9
  23. package/client/styles/css-vars.scss +7 -6
  24. package/client/tsconfig.json +0 -1
  25. package/client/types/index.ts +2 -2
  26. package/dist/node/cli/index.mjs +15 -10
  27. package/dist/node/index.d.mts +155 -10
  28. package/dist/node/index.mjs +15 -10
  29. package/dist/shared/{valaxy.CEmGx65r.mjs → valaxy.CKsfoRMA.mjs} +3265 -1908
  30. package/dist/shared/{valaxy.gqLhCu14.d.mts → valaxy.Dgja0_Y0.d.mts} +165 -58
  31. package/dist/types/index.d.mts +4 -7
  32. package/package.json +27 -28
  33. package/shared/node/i18n.ts +15 -1
  34. package/types/config.ts +74 -0
  35. package/types/frontmatter/page.ts +6 -1
  36. package/types/index.ts +3 -0
  37. package/types/vue-router.d.ts +29 -0
  38. package/client/templates/loader.vue +0 -10
@@ -1,6 +1,6 @@
1
1
  import type { Ref } from 'vue'
2
2
  import type { Router } from 'vue-router'
3
- import type { PageData } from '../../types'
3
+ import type { PageData, Post } from '../../types'
4
4
  import { computed } from 'vue'
5
5
 
6
6
  export interface ValaxyData<FM = PageData['frontmatter']> {
@@ -16,6 +16,6 @@ export function initData(router: Router): ValaxyData {
16
16
  page: computed(() => (router.currentRoute.value as unknown as {
17
17
  data: PageData
18
18
  }).data),
19
- frontmatter: computed(() => router.currentRoute.value.meta.frontmatter),
19
+ frontmatter: computed(() => router.currentRoute.value.meta.frontmatter!) as Ref<Post>,
20
20
  }
21
21
  }
@@ -4,6 +4,10 @@ import { computed } from 'vue'
4
4
  import { RouterLink } from 'vue-router'
5
5
  import { EXTERNAL_URL_RE } from '../../shared'
6
6
 
7
+ defineOptions({
8
+ inheritAttrs: false,
9
+ })
10
+
7
11
  const props = defineProps<{
8
12
  showExternalIcon?: boolean
9
13
  to?: string
@@ -0,0 +1,12 @@
1
+ import { defineComponent, onMounted, ref } from 'vue'
2
+
3
+ export default defineComponent({
4
+ name: 'ClientOnly',
5
+ setup(_, { slots }) {
6
+ const show = ref(false)
7
+ onMounted(() => {
8
+ show.value = true
9
+ })
10
+ return () => show.value ? slots.default?.() : null
11
+ },
12
+ })
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import type { Component } from 'vue'
3
- import { compile, defineAsyncComponent, shallowRef, watch } from 'vue'
3
+ import { compile, computed, defineComponent } from 'vue'
4
4
 
5
5
  const props = withDefaults(
6
6
  defineProps<{
@@ -12,22 +12,30 @@ const props = withDefaults(
12
12
  },
13
13
  )
14
14
 
15
- const dynamicComponent = shallowRef<Component | null>(null)
15
+ // Separate compile from component creation — compile() is expensive and
16
+ // should only re-run when the template string itself changes.
17
+ const compiledRender = computed(() => {
18
+ if (!props.templateStr)
19
+ return null
20
+ return compile(props.templateStr)
21
+ })
16
22
 
17
- async function createComponent() {
18
- const render = compile(props.templateStr)
19
- const componentDefinition = {
23
+ // defineComponent creates a proper component instance so that compile()'s
24
+ // render function can access data properties through the instance context.
25
+ // Avoids defineAsyncComponent which causes "missing template" warnings in SSG.
26
+ const dynamicComponent = computed<Component | null>(() => {
27
+ const render = compiledRender.value
28
+ if (!render)
29
+ return null
30
+ return defineComponent({
31
+ data: () => ({ ...props.data }),
20
32
  render,
21
- data: () => props.data,
22
- }
23
- dynamicComponent.value = defineAsyncComponent(() => Promise.resolve(componentDefinition))
24
- }
25
-
26
- watch(() => [props.templateStr, props.data], () => {
27
- createComponent()
28
- }, { immediate: true })
33
+ })
34
+ })
29
35
  </script>
30
36
 
31
37
  <template>
32
- <component :is="dynamicComponent" />
38
+ <div v-if="dynamicComponent">
39
+ <component :is="dynamicComponent" />
40
+ </div>
33
41
  </template>
@@ -2,7 +2,7 @@ import type { PostFrontMatter } from '../../types'
2
2
  import type { ValaxyData } from '../app/data'
3
3
 
4
4
  import { isClient } from '@vueuse/core'
5
- import { computed, inject } from 'vue'
5
+ import { computed, hasInjectionContext, inject } from 'vue'
6
6
  import { useRoute } from 'vue-router'
7
7
  import { dataSymbol, useSiteConfig } from '../config'
8
8
 
@@ -73,10 +73,11 @@ export function useEncryptedPhotos() {
73
73
  * inject pageData
74
74
  */
75
75
  export function useData<FM = Record<string, any>>(): ValaxyData<FM> {
76
- const data = inject(dataSymbol, {} as any)
77
- if (!data) {
78
- throw new Error('Valaxy data not properly injected in app')
79
- }
76
+ if (!hasInjectionContext())
77
+ throw new Error('[Valaxy] useData() must be called inside setup() or a component lifecycle')
78
+ const data = inject<ValaxyData<FM>>(dataSymbol)
79
+ if (!data)
80
+ throw new Error('[Valaxy] data not properly injected in app')
80
81
  return data
81
82
  }
82
83
 
@@ -1,6 +1,6 @@
1
1
  import type { UseDarkOptions } from '@vueuse/core'
2
2
  import { useDark, useToggle } from '@vueuse/core'
3
- import { computed } from 'vue'
3
+ import { computed, nextTick } from 'vue'
4
4
 
5
5
  export function useValaxyDark(options: {
6
6
  /**
@@ -31,8 +31,12 @@ export function useValaxyDark(options: {
31
31
  if (options.circleTransition)
32
32
  import('valaxy/client/styles/common/view-transition.css')
33
33
 
34
+ const enableTransitions = () =>
35
+ 'startViewTransition' in document
36
+ && window.matchMedia('(prefers-reduced-motion: no-preference)').matches
37
+
34
38
  function toggleDarkWithTransition(event: MouseEvent, options: { duration?: number, easing?: EffectTiming['easing'] } = {}) {
35
- if (!document.startViewTransition) {
39
+ if (!enableTransitions()) {
36
40
  toggleDark()
37
41
  return
38
42
  }
@@ -44,23 +48,26 @@ export function useValaxyDark(options: {
44
48
  Math.max(y, innerHeight - y),
45
49
  )
46
50
 
47
- const transition = document.startViewTransition(() => {
51
+ const clipPath = [
52
+ `circle(0px at ${x}px ${y}px)`,
53
+ `circle(${endRadius}px at ${x}px ${y}px)`,
54
+ ]
55
+
56
+ const transition = document.startViewTransition(async () => {
48
57
  toggleDark()
58
+ await nextTick()
49
59
  })
50
60
 
51
61
  transition.ready.then(() => {
52
- const clipPath = [
53
- `circle(0px at ${x}px ${y}px)`,
54
- `circle(${endRadius}px at ${x}px ${y}px)`,
55
- ]
56
62
  document.documentElement.animate(
57
63
  {
58
- clipPath: isDark.value ? clipPath.reverse() : clipPath,
64
+ clipPath: isDark.value ? [...clipPath].reverse() : clipPath,
59
65
  },
60
66
  {
61
- duration: options.duration || 300,
62
- easing: options.easing || 'ease-in',
63
- pseudoElement: isDark.value ? '::view-transition-old(root)' : '::view-transition-new(root)',
67
+ duration: options.duration ?? 200,
68
+ easing: options.easing ?? 'ease-in',
69
+ fill: 'forwards',
70
+ pseudoElement: `::view-transition-${isDark.value ? 'old' : 'new'}(root)`,
64
71
  },
65
72
  )
66
73
  })
@@ -0,0 +1,85 @@
1
+ import { isClient, useClipboard } from '@vueuse/core'
2
+ import { computed, ref, watchEffect } from 'vue'
3
+ import { useRoute } from 'vue-router'
4
+
5
+ /**
6
+ * Composable for copying raw Markdown content of the current post.
7
+ * Requires `siteConfig.llms.files: true` to have .md files available at build output.
8
+ *
9
+ * The `available` ref is initially `false` and becomes `true` after a HEAD request
10
+ * confirms the `.md` file exists. This allows themes to conditionally render
11
+ * the copy button only when the llms feature is enabled.
12
+ *
13
+ * @example
14
+ * ```vue
15
+ * <script setup>
16
+ * import { useCopyMarkdown } from 'valaxy'
17
+ * const { copy, copied, loading, available, error } = useCopyMarkdown()
18
+ * </script>
19
+ * <template>
20
+ * <button v-if="available" @click="copy" :disabled="loading">
21
+ * {{ copied ? 'Copied!' : 'Copy Markdown' }}
22
+ * </button>
23
+ * <span v-if="error" class="text-red">{{ error }}</span>
24
+ * </template>
25
+ * ```
26
+ */
27
+ export function useCopyMarkdown() {
28
+ const route = useRoute()
29
+ const copied = ref(false)
30
+ const loading = ref(false)
31
+ const available = ref(false)
32
+ const error = ref<string | null>(null)
33
+ const { copy: copyToClipboard } = useClipboard({ legacy: true })
34
+
35
+ const mdUrl = computed(() => {
36
+ const p = route.path !== '/' && route.path.endsWith('/')
37
+ ? route.path.slice(0, -1)
38
+ : route.path
39
+ return `${p}.md`
40
+ })
41
+
42
+ // Probe the .md file to detect availability (siteConfig.llms.files enabled at build time)
43
+ if (isClient) {
44
+ watchEffect(() => {
45
+ available.value = false
46
+ error.value = null
47
+ fetch(mdUrl.value, { method: 'HEAD' })
48
+ .then((res) => { available.value = res.ok })
49
+ .catch(() => { available.value = false })
50
+ })
51
+ }
52
+
53
+ async function copy() {
54
+ if (loading.value)
55
+ return
56
+
57
+ error.value = null
58
+ loading.value = true
59
+ try {
60
+ const res = await fetch(mdUrl.value)
61
+ if (!res.ok)
62
+ throw new Error(`Failed to fetch ${mdUrl.value}: ${res.status}`)
63
+
64
+ const text = await res.text()
65
+ await copyToClipboard(text)
66
+ copied.value = true
67
+ setTimeout(() => {
68
+ copied.value = false
69
+ }, 2000)
70
+ }
71
+ catch (err) {
72
+ const msg = err instanceof Error ? err.message : 'Unknown error'
73
+ error.value = msg
74
+ console.error('[valaxy] Failed to copy markdown:', err)
75
+ setTimeout(() => {
76
+ error.value = null
77
+ }, 3000)
78
+ }
79
+ finally {
80
+ loading.value = false
81
+ }
82
+ }
83
+
84
+ return { copy, copied, loading, mdUrl, available, error }
85
+ }
@@ -1,3 +1,4 @@
1
1
  export * from './collapse-code'
2
2
  export * from './copy-code'
3
+ export * from './copy-markdown'
3
4
  export * from './medium-zoom'
@@ -1,4 +1,4 @@
1
- import type { Post } from 'valaxy'
1
+ import type { Post, SiteConfig } from 'valaxy'
2
2
  import type { ComputedRef } from 'vue'
3
3
  import { orderByMeta, useSiteConfig } from 'valaxy'
4
4
  import { computed } from 'vue'
@@ -37,6 +37,39 @@ export function usePageList() {
37
37
  })
38
38
  }
39
39
 
40
+ /**
41
+ * Pure function to filter and sort posts from page list
42
+ * Can be used in both composables and stores without inject() issues
43
+ */
44
+ export function filterAndSortPosts(
45
+ pages: Post[],
46
+ siteConfig: SiteConfig,
47
+ params: { type?: string } = {},
48
+ ): Post[] {
49
+ // Filter posts
50
+ const routes = pages
51
+ .filter(i =>
52
+ i.path?.startsWith('/posts')
53
+ && !i.path?.endsWith('.html')
54
+ && i.date
55
+ && (!params.type || i.type === params.type)
56
+ && (!i.hide || i.hide === 'index'), // hide `hide: all` posts
57
+ )
58
+
59
+ function sortBySiteConfigOrderBy(posts: Post[]) {
60
+ const orderBy = siteConfig.orderBy
61
+ return orderByMeta(posts, orderBy)
62
+ }
63
+
64
+ /**
65
+ * 置顶
66
+ */
67
+ const topPosts = sortBySiteConfigOrderBy(routes.filter(i => i.top)).sort((a, b) => b.top! - a.top!)
68
+ const otherPosts = sortBySiteConfigOrderBy(routes.filter(i => !i.top))
69
+
70
+ return topPosts.concat(otherPosts)
71
+ }
72
+
40
73
  /**
41
74
  * get post list in 'pages/posts' folder
42
75
  * todo: use vue provide/inject to global
@@ -47,26 +80,6 @@ export function usePostList(params: {
47
80
  const siteConfig = useSiteConfig()
48
81
  const pageList = usePageList()
49
82
  return computed(() => {
50
- const routes = pageList.value
51
- .filter(i =>
52
- i.path?.startsWith('/posts')
53
- && !i.path?.endsWith('.html')
54
- && i.date
55
- && (!params.type || i.type === params.type)
56
- && (!i.hide || i.hide === 'index'), // hide `hide: all` posts
57
- )
58
-
59
- function sortBySiteConfigOrderBy(posts: Post[]) {
60
- const orderBy = siteConfig.value.orderBy
61
- return orderByMeta(posts, orderBy)
62
- }
63
-
64
- /**
65
- * 置顶
66
- */
67
- const topPosts = sortBySiteConfigOrderBy(routes.filter(i => i.top)).sort((a, b) => b.top! - a.top!)
68
- const otherPosts = sortBySiteConfigOrderBy(routes.filter(i => !i.top))
69
-
70
- return topPosts.concat(otherPosts)
83
+ return filterAndSortPosts(pageList.value, siteConfig.value, params)
71
84
  })
72
85
  }
package/client/config.ts CHANGED
@@ -6,7 +6,7 @@ import type { ComputedRef, InjectionKey } from 'vue'
6
6
  // https://github.com/microsoft/TypeScript/issues/42873
7
7
  import type { DefaultTheme, ValaxyConfig } from '../types'
8
8
  import type { ValaxyData } from './app/data'
9
- import { computed, inject, readonly, shallowRef } from 'vue'
9
+ import { computed, hasInjectionContext, inject, readonly, shallowRef } from 'vue'
10
10
 
11
11
  // @ts-expect-error virtual module @valaxyjs/config
12
12
  import valaxyConfig from '/@valaxyjs/config'
@@ -62,10 +62,12 @@ export function initContext() {
62
62
  * @public
63
63
  */
64
64
  export function useValaxyConfig<ThemeConfig = DefaultTheme.Config>() {
65
+ if (!hasInjectionContext())
66
+ throw new Error('[Valaxy] useValaxyConfig() must be called inside setup() or a component lifecycle')
65
67
  const config = inject<ComputedRef<ValaxyConfig<ThemeConfig>>>(valaxyConfigSymbol)
66
68
  if (!config)
67
69
  throw new Error('[Valaxy] site config not properly injected in app')
68
- return config!
70
+ return config
69
71
  }
70
72
 
71
73
  /**
@@ -0,0 +1,25 @@
1
+ import { createHead } from '@unhead/vue/server'
2
+ import { renderToString } from 'vue/server-renderer'
3
+ import { createValaxyApp, routesWithLayout } from './main'
4
+
5
+ export async function render(routePath: string) {
6
+ const ctx = createValaxyApp({ routePath, createHead })
7
+ const { app, router, head } = ctx
8
+
9
+ await router.push(routePath)
10
+ await router.isReady()
11
+
12
+ const ssrCtx: Record<string, any> = {}
13
+ const html = await renderToString(app, ssrCtx)
14
+ await ctx.triggerOnSSRAppRendered()
15
+
16
+ return {
17
+ html,
18
+ head,
19
+ modules: ssrCtx.modules as Set<string> | undefined,
20
+ teleports: ssrCtx.teleports as Record<string, string> | undefined,
21
+ initialState: ctx.initialState,
22
+ }
23
+ }
24
+
25
+ export { routesWithLayout as routes }
package/client/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
- import type { Header } from '@valaxyjs/utils'
2
1
  import type { Ref } from 'vue'
3
2
  import type { Post } from '../types'
4
3
 
5
4
  import './shims.d'
5
+ // Import vue-router RouteMeta augmentation
6
+ import '../types/vue-router.d'
6
7
 
7
8
  export * from '../dist/types/index.mjs'
8
9
  export * from './index'
@@ -12,13 +13,6 @@ declare module '@docsearch/js' {
12
13
  export default docsearch
13
14
  }
14
15
 
15
- declare module 'vue-router' {
16
- interface RouteMeta {
17
- headers: Header[]
18
- frontmatter: Post
19
- }
20
- }
21
-
22
16
  declare interface Window {
23
17
  // for devtools
24
18
  __VUE_DEVTOOLS_ROUTER__: import('vue-router').Router
@@ -50,6 +50,14 @@ post:
50
50
  related_posts: Related posts
51
51
  view_link: View link
52
52
  read_more: READ MORE
53
+ copy_markdown: Copy Markdown
54
+ copy_markdown_link: Copy Markdown Link
55
+ copy_page: Copy page
56
+ copied_markdown: Copied!
57
+ view_as_markdown: View as Markdown
58
+ open_in_github: Open in GitHub
59
+ open_in_chatgpt: Open in ChatGPT
60
+ open_in_claude: Open in Claude
53
61
  cover: Cover
54
62
  time_warning: This article was last updated {ago}. The information described in this article may have changed.
55
63
  copyright:
@@ -50,6 +50,14 @@ post:
50
50
  related_posts: 相关文章
51
51
  view_link: 查看链接
52
52
  read_more: 阅读更多
53
+ copy_markdown: 复制 Markdown
54
+ copy_markdown_link: 复制 Markdown 链接
55
+ copy_page: 复制页面
56
+ copied_markdown: 已复制!
57
+ view_as_markdown: 查看 Markdown
58
+ open_in_github: 在 GitHub 中打开
59
+ open_in_chatgpt: 在 ChatGPT 中打开
60
+ open_in_claude: 在 Claude 中打开
53
61
  cover: 封面
54
62
  time_warning: '本文最后更新于 {ago},文中所描述的信息可能已发生改变。'
55
63
  copyright:
package/client/main.ts CHANGED
@@ -1,13 +1,15 @@
1
- import { DataLoaderPlugin } from 'unplugin-vue-router/data-loaders'
1
+ import type { ValaxySSGContext } from './setups'
2
+
2
3
  import { dataSymbol, initValaxyConfig, valaxyConfigSymbol } from 'valaxy'
3
4
  import { setupLayouts } from 'virtual:generated-layouts'
4
- import { ViteSSG } from 'vite-ssg'
5
-
5
+ import { createSSRApp, createApp as vueCreateApp } from 'vue'
6
+ import { createMemoryHistory, createRouter, createWebHistory } from 'vue-router'
6
7
  import { routes as autoRoutes } from 'vue-router/auto-routes'
8
+
7
9
  // import App from '/@valaxyjs/App.vue'
8
10
  import App from './App.vue'
9
-
10
11
  import { initData } from './app/data'
12
+ import ClientOnly from './components/ClientOnly'
11
13
 
12
14
  import setupMain from './setup/main'
13
15
  import { setupValaxyDevTools } from './utils/dev'
@@ -44,7 +46,7 @@ function filterDraft(routes: any[]) {
44
46
  }
45
47
 
46
48
  // not filter hide for ssg
47
- const routesWithLayout = setupLayouts(import.meta.env.DEV
49
+ export const routesWithLayout = setupLayouts(import.meta.env.DEV
48
50
  ? routes
49
51
  : filterDraft(routes),
50
52
  )
@@ -52,29 +54,87 @@ const routesWithLayout = setupLayouts(import.meta.env.DEV
52
54
  if (import.meta.env.DEV)
53
55
  setupValaxyDevTools()
54
56
 
55
- // https://github.com/antfu/vite-ssg
56
- export const createApp = ViteSSG(
57
- App,
58
- {
57
+ interface CreateValaxyAppOptions {
58
+ routePath?: string
59
+ createHead?: () => any
60
+ hydrate?: boolean
61
+ }
62
+
63
+ export function createValaxyApp(options: CreateValaxyAppOptions = {}): ValaxySSGContext {
64
+ const { routePath, createHead, hydrate } = options
65
+ const isSSR = import.meta.env.SSR
66
+
67
+ // Use createSSRApp for server-side rendering and for client-side hydration
68
+ // of SSG pre-rendered pages. vueCreateApp is only for pure SPA mode.
69
+ const app = (isSSR || hydrate) ? createSSRApp(App) : vueCreateApp(App)
70
+
71
+ const history = isSSR
72
+ ? createMemoryHistory(import.meta.env.BASE_URL)
73
+ : createWebHistory(import.meta.env.BASE_URL)
74
+
75
+ const router = createRouter({
76
+ history,
59
77
  routes: routesWithLayout,
60
- base: import.meta.env.BASE_URL,
61
78
  scrollBehavior(to, from) {
62
79
  if (to.path !== from.path)
63
80
  return { top: 0 }
64
81
  },
65
- },
66
- (ctx) => {
67
- // app-level provide
68
- const { app, router } = ctx
82
+ })
69
83
 
70
- const data = initData(router)
71
- app.provide(dataSymbol, data)
84
+ const head = createHead ? createHead() : undefined
72
85
 
73
- // Register the plugin before the router
74
- app.use(DataLoaderPlugin, { router })
86
+ app.use(router)
87
+ if (head)
88
+ app.use(head)
89
+ app.component('ClientOnly', ClientOnly)
75
90
 
76
- app.provide(valaxyConfigSymbol, valaxyConfig)
91
+ // SSR render callback management
92
+ const appRenderCallbacks: (() => void)[] = []
77
93
 
78
- setupMain(ctx, valaxyConfig)
79
- },
80
- )
94
+ const ctx: ValaxySSGContext = {
95
+ app,
96
+ router,
97
+ head,
98
+ routes: routesWithLayout,
99
+ isClient: !isSSR,
100
+ initialState: {},
101
+ onSSRAppRendered: isSSR ? (cb: () => void) => appRenderCallbacks.push(cb) : () => {},
102
+ triggerOnSSRAppRendered: () => Promise.all(appRenderCallbacks.map(cb => cb())),
103
+ routePath,
104
+ }
105
+
106
+ // Restore serialised state on client
107
+ if (!isSSR && typeof window !== 'undefined' && (window as any).__INITIAL_STATE__) {
108
+ ctx.initialState = JSON.parse((window as any).__INITIAL_STATE__)
109
+ }
110
+
111
+ // app-level provide
112
+ const data = initData(router)
113
+ app.provide(dataSymbol, data)
114
+ app.provide(valaxyConfigSymbol, valaxyConfig)
115
+
116
+ setupMain(ctx, valaxyConfig)
117
+
118
+ return ctx
119
+ }
120
+
121
+ // Client-side auto-mount
122
+ if (!import.meta.env.SSR) {
123
+ ;(async () => {
124
+ const { createHead } = await import('@unhead/vue/client')
125
+ // Detect SSG pre-rendered content for proper hydration
126
+ const appEl = document.getElementById('app')
127
+ const hydrate = !!(appEl && appEl.innerHTML.trim())
128
+ const { app, router } = createValaxyApp({ createHead, hydrate })
129
+ await router.isReady()
130
+ app.mount('#app', hydrate)
131
+ })()
132
+ }
133
+
134
+ /**
135
+ * Legacy compatibility export for vite-ssg.
136
+ * vite-ssg expects `createApp(routePath)` returning `{ app, router, ... }`.
137
+ * This wraps `createValaxyApp` to match that signature so
138
+ * `--ssg-engine vite-ssg` continues to work.
139
+ */
140
+ export const createApp = (routePath?: string) => createValaxyApp({ routePath })
@@ -1,4 +1,4 @@
1
- import type { ViteSSGContext } from 'vite-ssg'
1
+ import type { ValaxySSGContext } from '../setups'
2
2
 
3
3
  import AppLink from '../components/AppLink.vue'
4
4
  import ValaxyTranslate from '../components/builtin/ValaxyTranslate.vue'
@@ -7,7 +7,7 @@ import ValaxyTranslate from '../components/builtin/ValaxyTranslate.vue'
7
7
  * register global components
8
8
  * @param ctx
9
9
  */
10
- export function registerGlobalComponents(ctx: ViteSSGContext) {
10
+ export function registerGlobalComponents(ctx: ValaxySSGContext) {
11
11
  ctx.app.component('AppLink', AppLink)
12
12
  ctx.app.component('VT', ValaxyTranslate)
13
13
  }
@@ -1,11 +1,10 @@
1
1
  import type { DefaultTheme, ValaxyConfig } from 'valaxy/types'
2
- import type { ViteSSGContext } from 'vite-ssg'
3
-
4
2
  import type { ComputedRef } from 'vue'
3
+ import type { ValaxySSGContext } from '../setups'
5
4
  import FloatingVue from 'floating-vue'
6
5
  import 'floating-vue/dist/style.css'
7
6
 
8
- export async function install({ app }: ViteSSGContext, config: ComputedRef<ValaxyConfig<DefaultTheme.Config>>) {
7
+ export async function install({ app }: ValaxySSGContext, config: ComputedRef<ValaxyConfig<DefaultTheme.Config>>) {
9
8
  // @see https://floating-vue.starpad.dev/guide/config#default-values
10
9
  const defaultFloatingVueConfig = {}
11
10
  app.use(FloatingVue, Object.assign(defaultFloatingVueConfig, config.value.siteConfig.floatingVue || {}))
@@ -8,11 +8,10 @@ import type { DefaultTheme, ValaxyConfig } from 'valaxy/types'
8
8
  */
9
9
  // import messages from '@intlify/unplugin-vue-i18n/messages'
10
10
 
11
- import type { ViteSSGContext } from 'vite-ssg'
12
-
13
11
  import type { ComputedRef } from 'vue'
14
12
  import type { Router } from 'vue-router'
15
13
  import type { PageDataPayload } from '../../types'
14
+ import type { ValaxySSGContext } from '../setups'
16
15
  import { ensureSuffix } from '@antfu/utils'
17
16
  import { useStorage } from '@vueuse/core'
18
17
  import { createI18n } from 'vue-i18n'
@@ -52,7 +51,7 @@ export const i18n = createI18n({
52
51
  missingWarn: false,
53
52
  })
54
53
 
55
- export async function install({ app, router }: ViteSSGContext, config: ComputedRef<ValaxyConfig<DefaultTheme.Config>>) {
54
+ export async function install({ app, router }: ValaxySSGContext, config: ComputedRef<ValaxyConfig<DefaultTheme.Config>>) {
56
55
  const locale = useStorage('valaxy-locale', config?.value.siteConfig.lang || 'en')
57
56
  i18n.global.locale.value = locale.value
58
57
 
@@ -4,9 +4,8 @@
4
4
  // https://github.com/microsoft/TypeScript/issues/42873
5
5
  import type { DefaultTheme, ValaxyConfig } from 'valaxy/types'
6
6
  /* __imports__ */
7
- import type { ViteSSGContext } from 'vite-ssg'
8
-
9
7
  import type { ComputedRef } from 'vue'
8
+ import type { ValaxySSGContext } from '../setups'
10
9
 
11
10
  import { consola } from 'consola'
12
11
  import { registerGlobalComponents } from '../modules/components'
@@ -16,7 +15,7 @@ import { install as installPinia } from '../modules/pinia'
16
15
  import { install as installUnhead } from '../modules/unhead'
17
16
  import { install as installValaxy } from '../modules/valaxy'
18
17
 
19
- export default function setupMain(ctx: ViteSSGContext, config: ComputedRef<ValaxyConfig<DefaultTheme.Config>>) {
18
+ export default function setupMain(ctx: ValaxySSGContext, config: ComputedRef<ValaxyConfig<DefaultTheme.Config>>) {
20
19
  // @ts-expect-error inject in runtime
21
20
  // eslint-disable-next-line unused-imports/no-unused-vars
22
21
  const injection_arg = ctx