tailwind-nextjs-blog/lib/mdx.ts

158 lines
4.8 KiB
TypeScript
Raw Normal View History

2022-10-17 23:37:01 +08:00
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';
2022-07-17 21:40:41 +08:00
// Remark packages
2022-10-17 23:37:01 +08:00
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';
2022-07-17 21:40:41 +08:00
// Rehype packages
2022-10-17 23:37:01 +08:00
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';
2022-07-17 21:40:41 +08:00
2022-10-17 23:37:01 +08:00
const root = process.cwd();
2022-07-17 21:40:41 +08:00
export function getFiles(type: 'blog' | 'authors') {
2022-10-17 23:37:01 +08:00
const prefixPaths = path.join(root, 'data', type);
const files = getAllFilesRecursively(prefixPaths);
2022-07-17 21:40:41 +08:00
// Only want to return blog/path and ignore root, replace is needed to work on Windows
2022-10-17 23:37:01 +08:00
return files.map((file) =>
file.slice(prefixPaths.length + 1).replace(/\\/g, '/')
);
2022-07-17 21:40:41 +08:00
}
export function formatSlug(slug: string) {
2022-10-17 23:37:01 +08:00
return slug.replace(/\.(mdx|md)/, '');
2022-07-17 21:40:41 +08:00
}
export function dateSortDesc(a: string, b: string) {
2022-10-17 23:37:01 +08:00
if (a > b) return -1;
if (a < b) return 1;
return 0;
2022-07-17 21:40:41 +08:00
}
2022-10-17 23:37:01 +08:00
export async function getFileBySlug<T>(
type: 'authors' | 'blog',
slug: string | string[]
) {
const mdxPath = path.join(root, 'data', type, `${slug}.mdx`);
const mdPath = path.join(root, 'data', type, `${slug}.md`);
2022-07-17 21:40:41 +08:00
const source = fs.existsSync(mdxPath)
? fs.readFileSync(mdxPath, 'utf8')
2022-10-17 23:37:01 +08:00
: fs.readFileSync(mdPath, 'utf8');
2022-07-17 21:40:41 +08:00
// https://github.com/kentcdodds/mdx-bundler#nextjs-esbuild-enoent
if (process.platform === 'win32') {
2022-10-17 23:37:01 +08:00
process.env.ESBUILD_BINARY_PATH = path.join(
root,
'node_modules',
'esbuild',
'esbuild.exe'
);
2022-07-17 21:40:41 +08:00
} else {
2022-10-17 23:37:01 +08:00
process.env.ESBUILD_BINARY_PATH = path.join(
root,
'node_modules',
'esbuild',
'bin',
'esbuild'
);
2022-07-17 21:40:41 +08:00
}
2022-10-17 23:37:01 +08:00
const toc: Toc = [];
2022-07-17 21:40:41 +08:00
const { code, frontmatter } = await bundleMDX({
source,
// mdx imports can be automatically source from the components directory
cwd: path.join(root, 'components'),
xdmOptions(options, frontmatter) {
2022-07-17 21:40:41 +08:00
// 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,
2022-10-17 23:37:01 +08:00
];
2022-07-17 21:40:41 +08:00
options.rehypePlugins = [
...(options.rehypePlugins ?? []),
rehypeSlug,
rehypeAutolinkHeadings,
rehypeKatex,
[rehypeCitation, { path: path.join(root, 'data') }],
[rehypePrismPlus, { ignoreMissing: true }],
rehypePresetMinify,
2022-10-17 23:37:01 +08:00
];
return options;
2022-07-17 21:40:41 +08:00
},
esbuildOptions: (options) => {
options.loader = {
...options.loader,
'.js': 'jsx',
2022-10-17 23:37:01 +08:00
};
return options;
2022-07-17 21:40:41 +08:00
},
2022-10-17 23:37:01 +08:00
});
2022-07-17 21:40:41 +08:00
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,
},
2022-10-17 23:37:01 +08:00
};
2022-07-17 21:40:41 +08:00
}
export async function getAllFilesFrontMatter(folder: 'blog') {
2022-10-17 23:37:01 +08:00
const prefixPaths = path.join(root, 'data', folder);
2022-07-17 21:40:41 +08:00
2022-10-17 23:37:01 +08:00
const files = getAllFilesRecursively(prefixPaths);
2022-07-17 21:40:41 +08:00
2022-10-17 23:37:01 +08:00
const allFrontMatter: PostFrontMatter[] = [];
2022-07-17 21:40:41 +08:00
files.forEach((file: string) => {
2022-07-17 21:40:41 +08:00
// Replace is needed to work on Windows
2022-10-17 23:37:01 +08:00
const fileName = file.slice(prefixPaths.length + 1).replace(/\\/g, '/');
2022-07-17 21:40:41 +08:00
// Remove Unexpected File
if (path.extname(fileName) !== '.md' && path.extname(fileName) !== '.mdx') {
2022-10-17 23:37:01 +08:00
return;
2022-07-17 21:40:41 +08:00
}
2022-10-17 23:37:01 +08:00
const source = fs.readFileSync(file, 'utf8');
const matterFile = matter(source);
const frontmatter = matterFile.data as AuthorFrontMatter | PostFrontMatter;
if ('draft' in frontmatter && frontmatter.draft !== true) {
2022-07-17 21:40:41 +08:00
allFrontMatter.push({
...frontmatter,
slug: formatSlug(fileName),
2022-10-17 23:37:01 +08:00
date: frontmatter.date
? new Date(frontmatter.date).toISOString()
: null,
});
2022-07-17 21:40:41 +08:00
}
2022-10-17 23:37:01 +08:00
});
2022-07-17 21:40:41 +08:00
2022-10-17 23:37:01 +08:00
return allFrontMatter.sort((a, b) => dateSortDesc(a.date, b.date));
2022-07-17 21:40:41 +08:00
}