import { readFileSync, readdirSync, existsSync } from 'fs' import { join, resolve, extname, basename } from 'path' import matter from 'gray-matter' import { marked } from 'marked' import * as yaml from 'js-yaml' const CONTENT_DIR = resolve(process.cwd(), 'content') const VIRTUAL_MODULE_ID = 'virtual:content' const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID const DETAIL_LOADERS_MODULE_ID = 'virtual:content-detail-loaders' const RESOLVED_DETAIL_LOADERS_MODULE_ID = '\0' + DETAIL_LOADERS_MODULE_ID const DETAIL_MODULE_PREFIX = 'virtual:content-detail/' const RESOLVED_DETAIL_MODULE_PREFIX = '\0' + DETAIL_MODULE_PREFIX const DETAIL_TYPES = ['posts', 'kniepunkt'] function readContentDir(subdir, includeBodies = false) { const dir = join(CONTENT_DIR, subdir) if (!existsSync(dir)) return [] return readdirSync(dir) .filter(f => !f.startsWith('.')) .map(filename => { const filepath = join(dir, filename) const ext = extname(filename) const slug = basename(filename, ext) if (ext === '.md') { const raw = readFileSync(filepath, 'utf-8') const { data, content } = matter(raw) const body = renderMarkdownContent(content) const searchText = body.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 320) return { ...data, slug, ...(includeBodies ? { body } : { searchText }), _source: `${subdir}/${filename}` } } if (ext === '.yaml' || ext === '.yml') { const raw = readFileSync(filepath, 'utf-8') const data = yaml.load(raw) return { ...data, slug, _source: `${subdir}/${filename}` } } return null }) .filter(Boolean) } export function renderMarkdownContent(content) { const tokens = marked.lexer(content) const firstContentToken = tokens.findIndex(token => token.type !== 'space') const firstToken = tokens[firstContentToken] if (firstToken?.type === 'heading' && firstToken.depth === 1) { tokens.splice(firstContentToken, 1) } return marked.parser(tokens) } function readMetaFile(filename) { const filepath = join(CONTENT_DIR, 'meta', filename) if (!existsSync(filepath)) return null const raw = readFileSync(filepath, 'utf-8') return yaml.load(raw) } function loadAllContent(includeBodies = false) { return { posts: readContentDir('posts', includeBodies), kniepunkt: readContentDir('kniepunkt', includeBodies), podcast: readContentDir('podcast', includeBodies), talks: readContentDir('talks', includeBodies), consulting: readContentDir('consulting', includeBodies), resources: readContentDir('resources', includeBodies), events: readContentDir('events', includeBodies), meta: { profile: readMetaFile('profile.yaml'), stats: readMetaFile('stats.yaml'), testimonials: readMetaFile('testimonials.yaml'), logos: readMetaFile('logos.yaml'), services: readMetaFile('services.yaml'), about: readMetaFile('about.yaml'), speaking: readMetaFile('speaking.yaml'), newsletter: readMetaFile('newsletter.yaml'), } } } function createDetailLoadersModule() { const typeEntries = DETAIL_TYPES.map(type => { const loaders = readContentDir(type) .map(item => `${JSON.stringify(item.slug)}: () => import(${JSON.stringify(`${DETAIL_MODULE_PREFIX}${type}/${item.slug}`)})`) .join(',\n') return `${JSON.stringify(type)}: {\n${loaders}\n}` }) return `export default {\n${typeEntries.join(',\n')}\n}` } function loadDetailModule(id) { const relativeId = id.slice(RESOLVED_DETAIL_MODULE_PREFIX.length) const separator = relativeId.indexOf('/') if (separator < 1) return null const type = relativeId.slice(0, separator) const slug = relativeId.slice(separator + 1) if (!DETAIL_TYPES.includes(type)) return null const item = readContentDir(type, true).find(candidate => candidate.slug === slug) return item ? `export default ${JSON.stringify(item)}` : null } export default function contentPlugin() { return { name: 'vite-plugin-content', resolveId(id) { if (id === VIRTUAL_MODULE_ID) return RESOLVED_VIRTUAL_MODULE_ID if (id === DETAIL_LOADERS_MODULE_ID) return RESOLVED_DETAIL_LOADERS_MODULE_ID if (id.startsWith(DETAIL_MODULE_PREFIX)) return `\0${id}` }, load(id) { if (id === RESOLVED_VIRTUAL_MODULE_ID) { const content = loadAllContent() return `export default ${JSON.stringify(content)}` } if (id === RESOLVED_DETAIL_LOADERS_MODULE_ID) return createDetailLoadersModule() if (id.startsWith(RESOLVED_DETAIL_MODULE_PREFIX)) return loadDetailModule(id) }, handleHotUpdate({ file, server }) { if (file.includes('content')) { const modules = [ server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_MODULE_ID), server.moduleGraph.getModuleById(RESOLVED_DETAIL_LOADERS_MODULE_ID), ...Array.from(server.moduleGraph.idToModuleMap.entries()) .filter(([id]) => id.startsWith(RESOLVED_DETAIL_MODULE_PREFIX)) .map(([, module]) => module), ].filter(Boolean) modules.forEach(module => server.moduleGraph.invalidateModule(module)) return modules } } } }