import { bundleMDX } from 'mdx-bundler' import fs from 'fs' import matter from 'gray-matter' import path from 'path' import readingTime from 'reading-time' import getAllFilesRecursively from './utils/files' import { PostFrontMatter } from 'types/PostFrontMatter' import { AuthorFrontMatter } from 'types/AuthorFrontMatter' import { Toc } from 'types/Toc' // Remark packages import remarkGfm from 'remark-gfm' import remarkFootnotes from 'remark-footnotes' import remarkMath from 'remark-math' import remarkExtractFrontmatter from './remark-extract-frontmatter' import remarkCodeTitles from './remark-code-title' import remarkTocHeadings from './remark-toc-headings' import remarkImgToJsx from './remark-img-to-jsx' // Rehype packages import rehypeSlug from 'rehype-slug' import rehypeAutolinkHeadings from 'rehype-autolink-headings' import rehypeKatex from 'rehype-katex' import rehypeCitation from 'rehype-citation' import rehypePrismPlus from 'rehype-prism-plus' import rehypePresetMinify from 'rehype-preset-minify' const root = process.cwd() export function getFiles(type: 'blog' | 'authors') { const prefixPaths = path.join(root, 'data', type) const files = getAllFilesRecursively(prefixPaths) // Only want to return blog/path and ignore root, replace is needed to work on Windows return files.map((file) => file.slice(prefixPaths.length + 1).replace(/\\/g, '/')) } export function formatSlug(slug: string) { return slug.replace(/\.(mdx|md)/, '') } export function dateSortDesc(a: string, b: string) { if (a > b) return -1 if (a < b) return 1 return 0 } export async function getFileBySlug(type: 'authors' | 'blog', slug: string | string[]) { const mdxPath = path.join(root, 'data', type, `${slug}.mdx`) const mdPath = path.join(root, 'data', type, `${slug}.md`) const source = fs.existsSync(mdxPath) ? fs.readFileSync(mdxPath, 'utf8') : fs.readFileSync(mdPath, 'utf8') // https://github.com/kentcdodds/mdx-bundler#nextjs-esbuild-enoent if (process.platform === 'win32') { process.env.ESBUILD_BINARY_PATH = path.join(root, 'node_modules', 'esbuild', 'esbuild.exe') } else { process.env.ESBUILD_BINARY_PATH = path.join(root, 'node_modules', 'esbuild', 'bin', 'esbuild') } const toc: Toc = [] const { code, frontmatter } = await bundleMDX({ source, // mdx imports can be automatically source from the components directory cwd: path.join(root, 'components'), mdxOptions(options, frontmatter) { // this is the recommended way to add custom remark/rehype plugins: // The syntax might look weird, but it protects you in case we add/remove // plugins in the future. options.remarkPlugins = [ ...(options.remarkPlugins ?? []), remarkExtractFrontmatter, [remarkTocHeadings, { exportRef: toc }], remarkGfm, remarkCodeTitles, [remarkFootnotes, { inlineNotes: true }], remarkMath, remarkImgToJsx, ] options.rehypePlugins = [ ...(options.rehypePlugins ?? []), rehypeSlug, rehypeAutolinkHeadings, rehypeKatex, [rehypeCitation, { path: path.join(root, 'data') }], [rehypePrismPlus, { ignoreMissing: true }], rehypePresetMinify, ] return options }, esbuildOptions: (options) => { options.loader = { ...options.loader, '.js': 'jsx', } return options }, }) return { mdxSource: code, toc, frontMatter: { readingTime: readingTime(code), slug: slug || null, fileName: fs.existsSync(mdxPath) ? `${slug}.mdx` : `${slug}.md`, ...frontmatter, date: frontmatter.date ? new Date(frontmatter.date).toISOString() : null, }, } } export async function getAllFilesFrontMatter(folder: 'blog') { const prefixPaths = path.join(root, 'data', folder) const files = getAllFilesRecursively(prefixPaths) const allFrontMatter: PostFrontMatter[] = [] files.forEach((file: string) => { // Replace is needed to work on Windows const fileName = file.slice(prefixPaths.length + 1).replace(/\\/g, '/') // Remove Unexpected File if (path.extname(fileName) !== '.md' && path.extname(fileName) !== '.mdx') { return } const source = fs.readFileSync(file, 'utf8') const matterFile = matter(source) const frontmatter = matterFile.data as AuthorFrontMatter | PostFrontMatter if ('draft' in frontmatter && frontmatter.draft !== true) { allFrontMatter.push({ ...frontmatter, slug: formatSlug(fileName), date: frontmatter.date ? new Date(frontmatter.date).toISOString() : null, }) } }) return allFrontMatter.sort((a, b) => dateSortDesc(a.date, b.date)) }