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'
|
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