diff --git a/lib/app/composables/pageData.js b/lib/app/composables/pageData.js
index 2bbe3925..c062f904 100644
--- a/lib/app/composables/pageData.js
+++ b/lib/app/composables/pageData.js
@@ -1,5 +1,3 @@
export function usePageData() {
- return {
- msg: 'this is page'
- }
+
}
diff --git a/lib/app/composables/siteData.js b/lib/app/composables/siteData.js
index 8eb788ce..63048e53 100644
--- a/lib/app/composables/siteData.js
+++ b/lib/app/composables/siteData.js
@@ -1,5 +1,21 @@
+import serialized from '@siteData'
+import { hot } from '@hmr'
+import { shallowRef, readonly } from 'vue'
+
+/**
+ * @param {string} data
+ */
+const parse = (data) =>
+ __DEV__ ? readonly(JSON.parse(data)) : JSON.parse(data)
+
+// site data
+export const siteDataRef = shallowRef(parse(serialized))
+
export function useSiteData() {
- return {
- msg: 'this is site'
- }
+ return siteDataRef
}
+
+// hmr
+hot.accept('/@siteData', (m) => {
+ siteDataRef.value = parse(m.default)
+})
diff --git a/lib/app/index-dev.html b/lib/app/index-dev.html
index 69e2efbb..80e530c2 100644
--- a/lib/app/index-dev.html
+++ b/lib/app/index-dev.html
@@ -1,2 +1,3 @@
+
diff --git a/lib/app/index.js b/lib/app/index.js
index 8b466b7f..2b13a0c4 100644
--- a/lib/app/index.js
+++ b/lib/app/index.js
@@ -1,7 +1,7 @@
import { createApp, h } from 'vue'
import { Content } from './components/Content'
import { useRouter } from './composables/router'
-import { useSiteData } from './composables/siteData'
+import { siteDataRef } from './composables/siteData'
import Theme from '/@theme/index'
const App = {
@@ -18,7 +18,7 @@ const App = {
const app = createApp(App)
Object.defineProperty(app.config.globalProperties, '$site', {
- get: useSiteData
+ get: () => siteDataRef.value
})
app.component('Content', Content)
diff --git a/lib/shim.d.ts b/lib/shim.d.ts
index 1ae3df6b..d31babf0 100644
--- a/lib/shim.d.ts
+++ b/lib/shim.d.ts
@@ -1,5 +1,18 @@
+declare const __DEV__: boolean
+
declare module "*.vue" {
import { ComponentOptions } from 'vue'
const comp: ComponentOptions
export default comp
}
+
+declare module "@siteData" {
+ const data: string
+ export default data
+}
+
+declare module "@hmr" {
+ export declare const hot: {
+ accept(path: string, cb: (module: any) => void)
+ }
+}
diff --git a/lib/theme-default/Layout.vue b/lib/theme-default/Layout.vue
index 54dd80e1..6d3124cb 100644
--- a/lib/theme-default/Layout.vue
+++ b/lib/theme-default/Layout.vue
@@ -13,10 +13,12 @@
import { useSiteData, usePageData } from 'vitepress'
export default {
- data: () => ({
- site: useSiteData(),
- page: usePageData()
- })
+ setup() {
+ return {
+ site: useSiteData(),
+ page: usePageData()
+ }
+ }
}
diff --git a/src/resolveConfig.ts b/src/resolveConfig.ts
index 975be2da..a43f4c43 100644
--- a/src/resolveConfig.ts
+++ b/src/resolveConfig.ts
@@ -5,7 +5,7 @@ import { Resolver } from 'vite'
const debug = require('debug')('vitepress:config')
-export interface UserConfig> {
+export interface UserConfig {
base?: string
title?: string
description?: string
@@ -16,7 +16,7 @@ export interface UserConfig> {
// TODO locales support etc.
}
-export interface SiteData> {
+export interface SiteData {
title: string
description: string
base: string
@@ -28,22 +28,49 @@ export interface PageData {
path: string
}
-export interface ResolvedConfig> {
+export interface ResolvedConfig {
site: SiteData
- root: string // project root on file system
themePath: string
resolver: Resolver
}
+export const getConfigPath = (root: string) =>
+ path.join(root, '.vitepress/config.js')
+
export async function resolveConfig(root: string): Promise {
+ const site = await resolveSiteData(root)
+
+ // resolve theme path
+ const userThemePath = path.join(root, '.vitepress/theme')
+ let themePath: string
+ try {
+ await fs.stat(userThemePath)
+ themePath = userThemePath
+ } catch (e) {
+ themePath = path.join(__dirname, '../lib/theme-default')
+ }
+
+ const config: ResolvedConfig = {
+ site,
+ themePath,
+ resolver: createResolver(themePath)
+ }
+
+ return config
+}
+
+export async function resolveSiteData(root: string): Promise {
// 1. load user config
- const configPath = path.join(root, '.vitepress/config.js')
+ const configPath = getConfigPath(root)
let hasUserConfig = false
try {
await fs.stat(configPath)
hasUserConfig = true
debug(`loading user config at ${configPath}`)
} catch (e) {}
+
+ // always delete cache first before loading config
+ delete require.cache[configPath]
const userConfig: UserConfig = hasUserConfig ? require(configPath) : {}
// 2. TODO scan pages data
@@ -57,22 +84,5 @@ export async function resolveConfig(root: string): Promise {
pages: []
}
- // 4. resolve theme path
- const userThemePath = path.join(root, '.vitepress/theme')
- let themePath: string
- try {
- await fs.stat(userThemePath)
- themePath = userThemePath
- } catch (e) {
- themePath = path.join(__dirname, '../lib/theme-default')
- }
-
- const config: ResolvedConfig = {
- root,
- site,
- themePath,
- resolver: createResolver(themePath)
- }
-
- return config
+ return site
}
diff --git a/src/server.ts b/src/server.ts
index a8682c1e..0d4e530c 100644
--- a/src/server.ts
+++ b/src/server.ts
@@ -5,17 +5,25 @@ import {
Plugin,
ServerConfig
} from 'vite'
-import { resolveConfig, ResolvedConfig } from './resolveConfig'
+import {
+ resolveConfig,
+ ResolvedConfig,
+ getConfigPath,
+ resolveSiteData
+} from './resolveConfig'
import { createMarkdownToVueRenderFn } from './markdown/markdownToVue'
-import { APP_PATH } from './utils/pathResolver'
+import { APP_PATH, SITE_DATA_REQUEST_PATH } from './utils/pathResolver'
const debug = require('debug')('vitepress:serve')
const debugHmr = require('debug')('vitepress:hmr')
-function createVitePressPlugin({
- themePath,
- resolver: vitepressResolver
-}: ResolvedConfig): Plugin {
+function createVitePressPlugin(config: ResolvedConfig): Plugin {
+ const {
+ themePath,
+ site: initialSiteData,
+ resolver: vitepressResolver
+ } = config
+
return ({ app, root, watcher, resolver }) => {
const markdownToVue = createMarkdownToVueRenderFn(root)
@@ -39,8 +47,38 @@ function createVitePressPlugin({
}
})
+ // hot reload handling for siteData
+ // the data is stringified twice so it is sent to the client as a string
+ // it is then parsed on the client via JSON.parse() which is faster than
+ // parsing the object literal as JavaScript.
+ let siteData = initialSiteData
+ let stringifiedData = JSON.stringify(JSON.stringify(initialSiteData))
+ const configPath = getConfigPath(root)
+ watcher.add(configPath)
+ watcher.on('change', async (file) => {
+ if (file === configPath) {
+ const newData = await resolveSiteData(root)
+ stringifiedData = JSON.stringify(JSON.stringify(newData))
+ if (newData.base !== siteData.base) {
+ console.warn(
+ `[vitepress]: config.base has changed. Please restart the dev server.`
+ )
+ }
+ siteData = newData
+ watcher.handleJSReload(SITE_DATA_REQUEST_PATH)
+ }
+ })
+
// inject Koa middleware
app.use(async (ctx, next) => {
+ // serve siteData (which is a virtual file)
+ if (ctx.path === SITE_DATA_REQUEST_PATH) {
+ ctx.type = 'js'
+ ctx.body = `export default ${stringifiedData}`
+ debug(ctx.url)
+ return
+ }
+
// handle .md -> vue transforms
if (ctx.path.endsWith('.md')) {
const file = resolver.requestToFile(ctx.path)
diff --git a/src/utils/pathResolver.ts b/src/utils/pathResolver.ts
index 7b4ef089..849be1ce 100644
--- a/src/utils/pathResolver.ts
+++ b/src/utils/pathResolver.ts
@@ -4,6 +4,9 @@ import { Resolver } from "vite"
// built ts files are placed into /dist
export const APP_PATH = path.join(__dirname, '../../lib/app')
+// speical virtual file
+export const SITE_DATA_REQUEST_PATH = '/@siteData'
+
// this is a path resolver that is passed to vite
// so that we can resolve custom requests that start with /@app or /@theme
// we also need to map file paths back to their public served paths so that
@@ -25,6 +28,9 @@ export function createResolver(themePath: string): Resolver {
if (filePath.startsWith(themePath)) {
return `/@theme/${path.relative(themePath, filePath)}`
}
+ if (filePath === SITE_DATA_REQUEST_PATH) {
+ return SITE_DATA_REQUEST_PATH
+ }
},
idToRequest(id) {
if (id === 'vitepress') {