From a988223399f67859f8e2936a84c91b8e28a1f858 Mon Sep 17 00:00:00 2001 From: pikax Date: Tue, 5 May 2020 18:44:13 +0100 Subject: [PATCH] feat: add components from .components folder --- lib/app/index.js | 16 +++++++++++++++- src/build/bundle.ts | 2 +- src/config.ts | 5 ++++- src/server.ts | 25 ++++++++++++++++++++++++- src/utils/pathResolver.ts | 25 ++++++++++++++++++++++--- 5 files changed, 66 insertions(+), 7 deletions(-) diff --git a/lib/app/index.js b/lib/app/index.js index 9eacaa3b..ca11f186 100644 --- a/lib/app/index.js +++ b/lib/app/index.js @@ -1,4 +1,10 @@ -import { createApp as createClientApp, createSSRApp, ref, readonly } from 'vue' +import { + createApp as createClientApp, + createSSRApp, + ref, + readonly, + defineAsyncComponent +} from 'vue' import { createRouter, RouterSymbol } from './router' import { useUpdateHead } from './composables/head' import { siteDataRef } from './composables/siteData' @@ -6,6 +12,7 @@ import { pageDataSymbol } from './composables/pageData' import { Content } from './components/Content' import Debug from './components/Debug.vue' import Theme from '/@theme/index' +import Components from '@components' import { hot } from '@hmr' const inBrowser = typeof window !== 'undefined' @@ -76,6 +83,13 @@ export function createApp() { app.component('Content', Content) app.component('Debug', __DEV__ ? Debug : () => null) + for (const [name, ext] of Components) { + app.component( + name, + defineAsyncComponent(() => import(`/@components/${name}.${ext}`)) + ) + } + Object.defineProperties(app.config.globalProperties, { $site: { get() { diff --git a/src/build/bundle.ts b/src/build/bundle.ts index 150943ae..22333b80 100644 --- a/src/build/bundle.ts +++ b/src/build/bundle.ts @@ -14,7 +14,7 @@ export async function bundle( options: BuildOptions ): Promise { const root = config.root - const resolver = createResolver(config.themeDir) + const resolver = createResolver(config.themeDir, config.componentDir) const markdownToVue = createMarkdownToVueRenderFn(root) const { rollupInputOptions = {}, rollupOutputOptions = {} } = options diff --git a/src/config.ts b/src/config.ts index 20958647..e1d6105e 100644 --- a/src/config.ts +++ b/src/config.ts @@ -26,6 +26,7 @@ export interface SiteConfig { site: SiteData configPath: string themeDir: string + componentDir: string publicDir: string outDir: string tempDir: string @@ -61,6 +62,7 @@ export async function resolveConfig( const themeDir = (await exists(userThemeDir)) ? userThemeDir : path.join(__dirname, '../lib/theme-default') + const componentDir = resolve(root, 'components') const config: SiteConfig = { root, @@ -69,9 +71,10 @@ export async function resolveConfig( pages: await globby(['**.md'], { cwd: root, ignore: ['node_modules'] }), configPath: resolve(root, 'config.js'), publicDir: resolve(root, 'public'), + componentDir: componentDir, outDir: resolve(root, 'dist'), tempDir: path.resolve(APP_PATH, 'temp'), - resolver: createResolver(themeDir) + resolver: createResolver(themeDir, componentDir) } return config diff --git a/src/server.ts b/src/server.ts index a3fc0198..9a16c5d0 100644 --- a/src/server.ts +++ b/src/server.ts @@ -7,13 +7,19 @@ import { } from 'vite' import { resolveConfig, SiteConfig, resolveSiteData } from './config' import { createMarkdownToVueRenderFn } from './markdownToVue' -import { APP_PATH, SITE_DATA_REQUEST_PATH } from './utils/pathResolver' +import { + APP_PATH, + SITE_DATA_REQUEST_PATH, + COMPONENTS_DATA_REQUEST_PATH +} from './utils/pathResolver' +import { promises as fs, readdirSync } from 'fs' const debug = require('debug')('vitepress:serve') const debugHmr = require('debug')('vitepress:hmr') function createVitePressPlugin({ themeDir, + componentDir, configPath, site: initialSiteData }: SiteConfig): Plugin { @@ -64,6 +70,14 @@ function createVitePressPlugin({ } }) + // TODO watch if for dirChanges + let componentsFiles = readdirSync(componentDir).map((x) => [ + path.basename(x, path.extname(x)), + path.extname(x).slice(1) + ]) + let componentsString = JSON.stringify(componentsFiles) + watcher.add(COMPONENTS_DATA_REQUEST_PATH) + // 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 @@ -83,6 +97,9 @@ function createVitePressPlugin({ siteData = newData watcher.handleJSReload(SITE_DATA_REQUEST_PATH) } + if (file.startsWith(componentDir)) { + watcher.handleJSReload(resolver.fileToRequest(file)) + } }) // inject Koa middleware @@ -95,6 +112,12 @@ function createVitePressPlugin({ return } + if (ctx.path === COMPONENTS_DATA_REQUEST_PATH) { + ctx.type = 'js' + ctx.body = `export default ${componentsString}` + 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 e7b5e98b..588b34e6 100644 --- a/src/utils/pathResolver.ts +++ b/src/utils/pathResolver.ts @@ -1,17 +1,21 @@ import path from 'path' -import { Resolver } from "vite" +import { Resolver } from 'vite' // built ts files are placed into /dist export const APP_PATH = path.join(__dirname, '../../lib/app') // special virtual file export const SITE_DATA_REQUEST_PATH = '/@siteData' +export const COMPONENTS_DATA_REQUEST_PATH = '/@components' // this is a path resolver that is passed to vite -// so that we can resolve custom requests that start with /@app or /@theme +// so that we can resolve custom requests that start with /@app or /@theme or /@components // 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(themeDir: string): Resolver { +export function createResolver( + themeDir: string, + componentDir: string +): Resolver { return { requestToFile(publicPath) { if (publicPath.startsWith('/@app')) { @@ -20,6 +24,18 @@ export function createResolver(themeDir: string): Resolver { if (publicPath.startsWith('/@theme')) { return path.join(themeDir, publicPath.replace(/^\/@theme\/?/, '')) } + if (publicPath.startsWith('/@components/index')) { + return path.join( + componentDir, + publicPath.replace(/^\/@components\/?/, '') + ) + } + if (publicPath.startsWith('/@components')) { + return path.join( + componentDir, + publicPath.replace(/^\/@components\/?/, '') + ) + } if (publicPath === SITE_DATA_REQUEST_PATH) { return SITE_DATA_REQUEST_PATH } @@ -31,6 +47,9 @@ export function createResolver(themeDir: string): Resolver { if (filePath.startsWith(themeDir)) { return `/@theme/${path.relative(themeDir, filePath)}` } + if (filePath.startsWith(componentDir)) { + return `/@components/${path.relative(componentDir, filePath)}` + } if (filePath === SITE_DATA_REQUEST_PATH) { return SITE_DATA_REQUEST_PATH }