So im trying to use ESBuild to, during compile time read a bunch of index.mdx
files from subfolders in the folder src/content
.
What i want to do is:
- get all the folder names
- parse the frontmatter in the header of each mdxfile
- parse the body of the MDX file and return as a JSX.Element (React component)
In the end i wish to return a list of Articles
containing objects with the following type structure:
interface Article {
path: string,
content: JSX.Element,
metadata: Metadata
}
interface Metadata {
// Different metadata params
}
I have written a custom ESBuild plugin:
// Read folder names
const getFolderNamesInDirectory = (dir) => {
return fs.readdirSync(dir).filter((item) => {
const itemPath = path.join(dir, item);
return fs.statSync(itemPath).isDirectory();
});
};
// Helper function to read and parse an MDX file
const parseMDXFile = async (filePath) => {
const content = fs.readFileSync(filePath, 'utf8');
const { data: metadata, content: mdxContent } = matter(content);
const compiledMdx = await compile(mdxContent, {
remarkPlugins: [remarkPrism, remarkImages],
jsx: true,
});
return {
metadata: metadata,
jsx: String(compiledMdx),
};
};
// Define the plugin
const ArticlesPlugin = (folder) => ({
name: 'articles-plugin',
setup(build) {
// Read all folder names in the specified directory
const folders = getFolderNamesInDirectory(folder);
const articles = [];
// Read and parse each index.mdx file
folders.forEach(async (folderName) => {
const mdxFilePath = path.join(folder, folderName, 'index.mdx');
if (fs.existsSync(mdxFilePath)) {
const { metadata, jsx } = await parseMDXFile(mdxFilePath);
articles.push({
path: folderName,
componentCode: `
import React from 'react';
${jsx}
export default MDXContent;
`,
metadata: metadata,
});
}
});
// Create a virtual module exporting the articles array
build.onResolve({ filter: /^virtual:articles$/ }, (args) => {
console.log(args);
return { path: args.path, namespace: 'virtual' };
});
build.onLoad({ filter: /.*/, namespace: 'virtual' }, (args) => {
if (args.path === 'virtual:articles') {
const contents = `
import React from 'react';
export const articles = {
${articles.map((article) => `path: '${article.path}', article: () => { ${article.componentCode} }, metadata: ${article.metadata}` ).join(',n')}
};
`;
return {
contents,
loader: 'jsx',
};
}
});
},
});
The problem im having with the above code is that im getting the following build error when running my esbuild build script:
✘ [ERROR] Unexpected "React"
virtual:virtual:articles:5:19:
5 │ import React from 'react';
I have a had quite the hard time to understand what type of content a ESBuild plugin should return. It was my understanding that i could return content as a correctly formatted React module.
If i remove the import React from 'react'
and remove the jsx: true
flag on the mdx compiler i get a different error:
✘ [ERROR] Expected "(" but found "{"
virtual:virtual:articles:4:19:
4 │ import {Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs} from "react/jsx-runtime";
│ ^
╵
I have spent a lot of time reading the ESBuild plugin documentation and the MDXCompiler docs and i feel im getting really close, but its this last part that is giving me a rough time.
So how do i return React components from an ESBuild plugin so i can import them in my react code.