mirror of https://github.com/vuejs/vitepress
parent
78722824cb
commit
13924c9734
@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"moduleResolution": "node",
|
||||
"checkJs": true,
|
||||
"noUnusedLocals": true,
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true,
|
||||
"paths": {
|
||||
"/@app/*": ["lib/app/*"],
|
||||
"/@theme/*": ["lib/theme-default/*"]
|
||||
}
|
||||
},
|
||||
"include": ["."]
|
||||
}
|
@ -1,2 +1,2 @@
|
||||
export * from './server/server'
|
||||
export * from './server'
|
||||
export * from './build/build'
|
||||
|
@ -0,0 +1,78 @@
|
||||
import path from 'path'
|
||||
import { promises as fs } from 'fs'
|
||||
import { createResolver } from './utils/pathResolver'
|
||||
import { Resolver } from 'vite'
|
||||
|
||||
const debug = require('debug')('vitepress:config')
|
||||
|
||||
export interface UserConfig<ThemeConfig = Record<string, any>> {
|
||||
base?: string
|
||||
title?: string
|
||||
description?: string
|
||||
head?:
|
||||
| [string, Record<string, string>]
|
||||
| [string, Record<string, string>, string]
|
||||
themeConfig?: ThemeConfig
|
||||
// TODO locales support etc.
|
||||
}
|
||||
|
||||
export interface SiteData<ThemeConfig = Record<string, any>> {
|
||||
title: string
|
||||
description: string
|
||||
base: string
|
||||
themeConfig: ThemeConfig
|
||||
pages: PageData[]
|
||||
}
|
||||
|
||||
export interface PageData {
|
||||
path: string
|
||||
}
|
||||
|
||||
export interface ResolvedConfig<ThemeConfig = Record<string, any>> {
|
||||
site: SiteData<ThemeConfig>
|
||||
root: string // project root on file system
|
||||
themePath: string
|
||||
resolver: Resolver
|
||||
}
|
||||
|
||||
export async function resolveConfig(root: string): Promise<ResolvedConfig> {
|
||||
// 1. load user config
|
||||
const configPath = path.join(root, '.vitepress/config.js')
|
||||
let hasUserConfig = false
|
||||
try {
|
||||
await fs.stat(configPath)
|
||||
hasUserConfig = true
|
||||
debug(`loading user config at ${configPath}`)
|
||||
} catch (e) {}
|
||||
const userConfig: UserConfig = hasUserConfig ? require(configPath) : {}
|
||||
|
||||
// 2. TODO scan pages data
|
||||
|
||||
// 3. resolve site data
|
||||
const site: SiteData = {
|
||||
title: userConfig.title || 'VitePress',
|
||||
description: userConfig.description || 'A VitePress site',
|
||||
base: userConfig.base || '/',
|
||||
themeConfig: userConfig.themeConfig || {},
|
||||
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
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
import path from 'path'
|
||||
import {
|
||||
createServer as createViteServer,
|
||||
cachedRead,
|
||||
Plugin,
|
||||
ServerConfig
|
||||
} from 'vite'
|
||||
import { resolveConfig, ResolvedConfig } from './resolveConfig'
|
||||
import { createMarkdownToVueRenderFn } from './markdown/markdownToVue'
|
||||
import { APP_PATH } from './utils/pathResolver'
|
||||
|
||||
const debug = require('debug')('vitepress:serve')
|
||||
const debugHmr = require('debug')('vitepress:hmr')
|
||||
|
||||
function createVitePressPlugin({
|
||||
themePath,
|
||||
resolver: vitepressResolver
|
||||
}: ResolvedConfig): Plugin {
|
||||
return ({ app, root, watcher, resolver }) => {
|
||||
const markdownToVue = createMarkdownToVueRenderFn(root)
|
||||
|
||||
// watch theme files if it's outside of project root
|
||||
if (path.relative(root, themePath).startsWith('..')) {
|
||||
debugHmr(`watching theme dir outside of project root: ${themePath}`)
|
||||
watcher.add(themePath)
|
||||
}
|
||||
|
||||
// hot reload .md files as .vue files
|
||||
watcher.on('change', async (file) => {
|
||||
if (file.endsWith('.md')) {
|
||||
debugHmr(`reloading ${file}`)
|
||||
const content = await cachedRead(null, file)
|
||||
watcher.handleVueReload(file, Date.now(), markdownToVue(content, file))
|
||||
}
|
||||
})
|
||||
|
||||
// inject Koa middleware
|
||||
app.use(async (ctx, next) => {
|
||||
// handle .md -> vue transforms
|
||||
if (ctx.path.endsWith('.md')) {
|
||||
const file = resolver.publicToFile(ctx.path)
|
||||
await cachedRead(ctx, file)
|
||||
// let vite know this is supposed to be treated as vue file
|
||||
ctx.vue = true
|
||||
ctx.body = markdownToVue(ctx.body, file)
|
||||
debug(ctx.url, ctx.status)
|
||||
return next()
|
||||
}
|
||||
|
||||
// detect and serve vitepress @app / @theme files
|
||||
const file = vitepressResolver.publicToFile(ctx.path, root)
|
||||
if (file) {
|
||||
await cachedRead(ctx, file)
|
||||
debug(ctx.url, ctx.status)
|
||||
await next()
|
||||
return
|
||||
}
|
||||
|
||||
await next()
|
||||
|
||||
// serve our index.html after vite history fallback
|
||||
if (ctx.url === '/index.html') {
|
||||
await cachedRead(ctx, path.join(APP_PATH, 'index-dev.html'))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export async function createServer(options: ServerConfig = {}) {
|
||||
const config = await resolveConfig(options.root || process.cwd())
|
||||
|
||||
return createViteServer({
|
||||
...options,
|
||||
plugins: [createVitePressPlugin(config)],
|
||||
resolvers: [config.resolver]
|
||||
})
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
import path from 'path'
|
||||
import { Resolver } from "vite"
|
||||
|
||||
// built ts files are placed into /dist
|
||||
export const APP_PATH = path.join(__dirname, '../../lib/app')
|
||||
// TODO detect user configured theme
|
||||
export const THEME_PATH = path.join(__dirname, '../../lib/theme-default')
|
||||
|
||||
export const VitePressResolver: Resolver = {
|
||||
publicToFile(publicPath) {
|
||||
if (publicPath.startsWith('/@app')) {
|
||||
return path.join(APP_PATH, publicPath.replace(/^\/@app\/?/, ''))
|
||||
}
|
||||
if (publicPath.startsWith('/@theme')) {
|
||||
return path.join(THEME_PATH, publicPath.replace(/^\/@theme\/?/, ''))
|
||||
}
|
||||
},
|
||||
fileToPublic(filePath) {
|
||||
if (filePath.startsWith(APP_PATH)) {
|
||||
return `/@app/${path.relative(APP_PATH, filePath)}`
|
||||
}
|
||||
if (filePath.startsWith(THEME_PATH)) {
|
||||
return `/@theme/${path.relative(THEME_PATH, filePath)}`
|
||||
}
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
import path from 'path'
|
||||
import {
|
||||
createServer as createViteServer,
|
||||
cachedRead,
|
||||
Plugin,
|
||||
ServerConfig
|
||||
} from 'vite'
|
||||
import { createMarkdownToVueRenderFn } from '../markdown/markdownToVue'
|
||||
import { VitePressResolver, THEME_PATH, APP_PATH } from './resolver'
|
||||
|
||||
const debug = require('debug')('vitepress:serve')
|
||||
const debugHmr = require('debug')('vitepress:hmr')
|
||||
|
||||
const VitePressPlugin: Plugin = ({ app, root, watcher, resolver }) => {
|
||||
const markdownToVue = createMarkdownToVueRenderFn(root)
|
||||
|
||||
// watch theme files if it's outside of project root
|
||||
if (path.relative(root, THEME_PATH).startsWith('..')) {
|
||||
debugHmr(`watching theme dir outside of project root: ${THEME_PATH}`)
|
||||
watcher.add(THEME_PATH)
|
||||
}
|
||||
|
||||
// hot reload .md files as .vue files
|
||||
watcher.on('change', async (file) => {
|
||||
if (file.endsWith('.md')) {
|
||||
debugHmr(`reloading ${file}`)
|
||||
const content = await cachedRead(null, file)
|
||||
watcher.handleVueReload(file, Date.now(), markdownToVue(content, file))
|
||||
}
|
||||
})
|
||||
|
||||
// inject Koa middleware
|
||||
app.use(async (ctx, next) => {
|
||||
// handle .md -> vue transforms
|
||||
if (ctx.path.endsWith('.md')) {
|
||||
const file = resolver.publicToFile(ctx.path)
|
||||
await cachedRead(ctx, file)
|
||||
// let vite know this is supposed to be treated as vue file
|
||||
ctx.vue = true
|
||||
ctx.body = markdownToVue(ctx.body, file)
|
||||
debug(ctx.url)
|
||||
return next()
|
||||
}
|
||||
|
||||
// detect and serve vitepress @app / @theme files
|
||||
const file = VitePressResolver.publicToFile(ctx.path, root)
|
||||
if (file) {
|
||||
ctx.type = path.extname(file)
|
||||
await cachedRead(ctx, file)
|
||||
|
||||
debug(ctx.url)
|
||||
return next()
|
||||
}
|
||||
|
||||
await next()
|
||||
|
||||
// serve our index.html after vite history fallback
|
||||
if (ctx.url === '/index.html') {
|
||||
await cachedRead(ctx, path.join(APP_PATH, 'index-dev.html'))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function createServer(options: ServerConfig = {}) {
|
||||
return createViteServer({
|
||||
...options,
|
||||
plugins: [VitePressPlugin],
|
||||
resolvers: [VitePressResolver]
|
||||
})
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
import path from 'path'
|
||||
import { Resolver } from "vite"
|
||||
|
||||
// built ts files are placed into /dist
|
||||
export const APP_PATH = path.join(__dirname, '../../lib/app')
|
||||
|
||||
// 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
|
||||
// vite HMR can send the correct update notifications to the client.
|
||||
export function createResolver(themePath: string): Resolver {
|
||||
return {
|
||||
publicToFile(publicPath) {
|
||||
if (publicPath.startsWith('/@app')) {
|
||||
return path.join(APP_PATH, publicPath.replace(/^\/@app\/?/, ''))
|
||||
}
|
||||
if (publicPath.startsWith('/@theme')) {
|
||||
return path.join(themePath, publicPath.replace(/^\/@theme\/?/, ''))
|
||||
}
|
||||
},
|
||||
fileToPublic(filePath) {
|
||||
if (filePath.startsWith(APP_PATH)) {
|
||||
return `/@app/${path.relative(APP_PATH, filePath)}`
|
||||
}
|
||||
if (filePath.startsWith(themePath)) {
|
||||
return `/@theme/${path.relative(themePath, filePath)}`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue