/** * Vite Plugin Setup for Sykehuspartner Slidev Theme * * This plugin automatically resolves relative image paths in frontmatter * so they work correctly after build. Without this, images referenced in * frontmatter like `imageSrc: ./image.png` would break after build because * Vite can't statically analyze frontmatter values. * * The plugin intercepts the frontmatter virtual modules generated by Slidev * and transforms relative image paths into proper imports. */ import { defineVitePluginsSetup } from '@slidev/types' import type { Plugin } from 'vite' import { resolve, dirname, isAbsolute } from 'node:path' import { existsSync } from 'node:fs' // Image properties that should be resolved in frontmatter const IMAGE_PROPS = [ 'imageSrc', 'background', 'image', ] // Supported image extensions const IMAGE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'avif', 'ico'] const IMAGE_EXT_PATTERN = IMAGE_EXTENSIONS.join('|') export default defineVitePluginsSetup((options) => { const { userRoot, data } = options /** * Plugin that transforms frontmatter image paths into imports */ const frontmatterImagePlugin: Plugin = { name: 'slidev-theme-sykehuspartner:frontmatter-images', enforce: 'post', // Run after Slidev's loader generates the frontmatter module transform(code, id) { // Only process frontmatter virtual modules from Slidev if (!id.includes('__slidev_') || !id.endsWith('.frontmatter')) { return null } // Find the slide's markdown file path to resolve relative images const slideMatch = id.match(/(.+?)__slidev_(\d+)\.frontmatter$/) if (!slideMatch) return null const mdFilePath = slideMatch[1] const mdDir = dirname(mdFilePath) // Regex to find relative image paths in frontmatter properties // Matches: "imageSrc": "./image.png" or 'imageSrc': './image.png' // Also matches paths without ./ prefix like "image.png" const relativePathRegex = new RegExp( `(["'])((?:\\.\\/|\\.\\.\\/|(?![/]|https?:|data:))[^"']*\\.(${IMAGE_EXT_PATTERN}))\\1`, 'gi' ) const matches = [...code.matchAll(relativePathRegex)] if (matches.length === 0) { return null } // Track unique imports to avoid duplicates const imports: Map = new Map() let importIndex = 0 let newCode = code for (const match of matches) { const fullMatch = match[0] const quote = match[1] const relativePath = match[2] // Resolve the absolute path to check if file exists const absolutePath = resolve(mdDir, relativePath) // Only transform if the file actually exists if (!existsSync(absolutePath)) { console.warn(`[slidev-theme-sykehuspartner] Image not found: ${relativePath} (resolved to ${absolutePath})`) continue } // Get or create import variable name for this path let varName = imports.get(relativePath) if (!varName) { varName = `__spImg${importIndex++}` imports.set(relativePath, varName) } // Replace the string path with the import variable // "imageSrc": "./image.png" becomes "imageSrc": __spImg0 newCode = newCode.replace(fullMatch, varName) } if (imports.size === 0) { return null } // Generate import statements const importStatements = Array.from(imports.entries()) .map(([path, varName]) => `import ${varName} from "${path}"`) .join('\n') // Prepend imports to the module code const transformedCode = importStatements + '\n' + newCode return { code: transformedCode, map: null, // Source map not needed for this transformation } }, } /** * Plugin that ensures images referenced in layouts are resolved correctly * This handles the case where images are passed as props to layout components */ const layoutImagePlugin: Plugin = { name: 'slidev-theme-sykehuspartner:layout-images', enforce: 'pre', async resolveId(source, importer) { // Only process if importer is a markdown slide file if (!importer || !importer.includes('__slidev_')) { return null } // Check if this looks like a relative image path const isRelativePath = source.startsWith('./') || source.startsWith('../') const isImageFile = new RegExp(`\\.(${IMAGE_EXT_PATTERN})$`, 'i').test(source) if (isRelativePath && isImageFile) { // Find the original markdown file path const mdMatch = importer.match(/(.+?)__slidev_\d+/) if (mdMatch) { const mdDir = dirname(mdMatch[1]) const resolved = resolve(mdDir, source) if (existsSync(resolved)) { return resolved } } } return null }, } return [ frontmatterImagePlugin, layoutImagePlugin, ] })