I'm using React and Vite for a project I'm working on.
What I currently need to do is compile and render all of the MDX files in a directory but I don't want to do the actual compilation on the client side and I want to exclude some of the files so that they aren't even in the final build.
I already tried using Vite's glob importing but I don't think that can be used to filter out files based on metadata.
My current plan is to:
I have all of it figured out except the last step, but I keep encountering errors.
From my research it appears vite offers both virtual modules and build hooks for executing code before the build.
I currently have tried using virtual modules and it seems like the right solution but I'm just not sure how to get my mdx data exported from a virtual module.
import fs from "fs"
import fm from "front-matter"
import { evaluate } from "@mdx-js/mdx"
import * as runtime from "react/jsx-runtime"
import remarkFrontmatter from "remark-frontmatter"
import remarkMdxFrontmatter from "remark-mdx-frontmatter"
import toSource from "tosource"
const path = "./src/posts"
const rawPosts = fs.readdirSync(path).map(fileName => {
const file = fs.readFileSync(`${path}/${fileName}`)
return String(file)
})
const filteredPosts = rawPosts.filter(async file => {
const frontmatter = fm(file)
// use frontmatter to decide which files to keep
return true
})
const finalPosts = await Promise.all(
filteredPosts.map(async file => {
const parsed = await evaluate(file, {
...runtime,
remarkPlugins: [
remarkFrontmatter,
remarkMdxFrontmatter,
],
})
return parsed
})
)
export default function posts() {
const moduleId = "virtual:posts"
const resolvedModuleId = "\0" + moduleId
return {
name: "my-plugin",
resolveId(id) {
if (id === moduleId) {
return resolvedModuleId
}
},
load(id) {
if (id === resolvedModuleId) {
// not sure what to do here
return ?
}
},
}
}
I already tried JSON.stringify
ing the array but the function disappears when doing that, so what is a better approach? Should I just be compiling the mdx and not evaluating it yet? In that case I don't know how I would dynamically create multiple virtual modules to import. I realize I could probably use the buildStart
hook to run a separate script but I feel like it could be achieved with virtual modules similar to how I have tried so far.
// vite.config.js
import fs from "fs"
import path from "path"
import fm from "front-matter"
import { compile } from "@mdx-js/mdx"
import remarkFrontmatter from "remark-frontmatter"
import remarkMdxFrontmatter from "remark-mdx-frontmatter"
const POSTS_VIRTUAL_ID = "virtual:posts"
const RESOLVED_POSTS_ID = "\0" + POSTS_VIRTUAL_ID
export default {
plugins: [
{
name: "virtual-mdx-posts",
resolveId(id) {
if (id === POSTS_VIRTUAL_ID) {
return RESOLVED_POSTS_ID
}
},
async load(id) {
if (id === RESOLVED_POSTS_ID) {
const postDir = path.resolve("src/posts")
const files = fs.readdirSync(postDir)
const mdxExports = await Promise.all(
files.map(async (fileName, index) => {
const filePath = path.join(postDir, fileName)
const raw = fs.readFileSync(filePath, "utf-8")
const { attributes } = fm(raw)
// Skip some posts based on frontmatter
if (attributes.draft) return null
const compiled = await compile(raw, {
outputFormat: "function-body",
remarkPlugins: [remarkFrontmatter, remarkMdxFrontmatter],
})
const exportName = `Post${index}`
return {
name: exportName,
code: compiled.value,
frontmatter: attributes,
}
})
)
const validPosts = mdxExports.filter(Boolean)
const moduleCode = validPosts
.map(
({ name, code, frontmatter }) => `
export const ${name} = {
frontmatter: ${JSON.stringify(frontmatter)},
component: (props) => {
${code}
return MDXContent(props)
}
};
`
)
.join("\n")
const exportList = validPosts
.map(({ name }) => name)
.join(", ")
return `${moduleCode}\nexport default [${exportList}];`
}
},
},
],
}