Skip to content

Commit

Permalink
asyncify markdown processing
Browse files Browse the repository at this point in the history
  • Loading branch information
biltongza committed Apr 1, 2024
1 parent f479664 commit 4a4f04a
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 75 deletions.
1 change: 1 addition & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@sveltejs/vite-plugin-svelte": "^3.0.2",
"@types/glob": "^8.1.0",
"@types/js-yaml": "^4.0.9",
"@types/mdast": "^4.0.3",
"@typescript-eslint/eslint-plugin": "^7.4.0",
"@typescript-eslint/parser": "^7.4.0",
"copyfiles": "^2.4.1",
Expand Down
76 changes: 40 additions & 36 deletions frontend/src/lib/blog/markdown.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,54 @@
import type { BlogMetadata } from '$lib/types';
import dayjs from 'dayjs';
import yaml from 'js-yaml';
import type { Root } from 'mdast';
import rehypeExternalLinks from 'rehype-external-links';
import highlight from 'rehype-highlight';
import rehypeHighlight from 'rehype-highlight';
import rehypeStringify from 'rehype-stringify';
import frontmatter from 'remark-frontmatter';
import gfm from 'remark-gfm';
import parse from 'remark-parse';
import remarkFrontmatter from 'remark-frontmatter';
import remarkGfm from 'remark-gfm';
import remarkParse from 'remark-parse';
import remark2rehype from 'remark-rehype';
import remarkUnwrapImages from 'remark-unwrap-images';
import * as vfile from 'to-vfile';
import { unified } from 'unified';

const parser = unified().use(parse).use(gfm).use(frontmatter, ['yaml']);
const remarkParser = unified().use(remarkParse).use(remarkGfm).use(remarkFrontmatter, ['yaml']);

const runner = unified()
.use(remark2rehype)
.use(highlight)
.use(rehypeExternalLinks, { rel: ['nofollow', 'noopener'] })
.use(rehypeStringify)
.use(remarkUnwrapImages);
const rehypeConverter = unified()
.use(remark2rehype)
.use(remarkUnwrapImages)
.use(rehypeHighlight)
.use(rehypeExternalLinks, { rel: ['nofollow', 'noopener'] })
.use(rehypeStringify);

export function process(filename: string): { metadata: BlogMetadata; content: string } {
const tree = parser.parse(vfile.readSync(filename));
let metadata: BlogMetadata = null;
const slug = filename.slice(filename.lastIndexOf('/') + 1, -3);
if (tree.children.length > 0 && tree.children[0].type == 'yaml') {
metadata = yaml.load(tree.children[0].value) as BlogMetadata;
tree.children = tree.children.slice(1, tree.children.length);
metadata.date = dayjs(metadata.date).valueOf();
metadata.slug = slug;
}
export async function processMetadata(
filename: string
): Promise<{ metadata: BlogMetadata; tree: Root }> {
const file = await vfile.read(filename);
const remarkTree = remarkParser.parse(file);
const slug = filename.slice(filename.lastIndexOf('/') + 1, -3);
let metadata: BlogMetadata = null;
if (remarkTree.children.length > 0 && remarkTree.children[0].type == 'yaml') {
metadata = yaml.load(remarkTree.children[0].value) as BlogMetadata;
remarkTree.children = remarkTree.children.slice(1, remarkTree.children.length);
metadata.date = dayjs(metadata.date).valueOf();
metadata.slug = slug;
}
if (!metadata) {
throw new Error(`No Frontmatter in file ${filename}`);
}
return {
metadata,
tree: remarkTree
};
}

// the typings of remark and rehype are not compatible but remark2rehype works just fine anyway
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const remarkTree: any = runner.runSync(tree)
let content = runner.stringify(remarkTree);
if (!metadata) {
metadata = {
title: 'Error!',
date: dayjs().valueOf(),
excerpt: 'Missing Frontmatter! Expected at least a title and a date!',
slug: slug,
tags: []
};
content = 'Missing Frontmatter! Expected at least a title and a date!';
}
return { metadata, content };
export async function process(
filename: string
): Promise<{ metadata: BlogMetadata; content: string }> {
const { metadata, tree } = await processMetadata(filename);
const rehypeTree = await rehypeConverter.run(tree);
const content = rehypeConverter.stringify(rehypeTree);
return { metadata, content };
}
36 changes: 23 additions & 13 deletions frontend/src/lib/getBlogPosts.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
import { process } from '$lib/blog/markdown';
import { processMetadata } from '$lib/blog/markdown';
import dayjs from 'dayjs';
import fs from 'fs';

const mdRegex = /.+\.md$/;

export const getBlogPosts = function () {
const posts = fs
.readdirSync(`src/posts`)
.filter((fileName) => /.+\.md$/.test(fileName))
.map((fileName) => {
const { metadata } = process(`src/posts/${fileName}`);
return metadata;
});
// sort the posts by create date.
posts.sort((a, b) => dayjs(b.date).valueOf() - dayjs(a.date).valueOf());
export const getBlogPosts = async function () {
const filesPromise = new Promise<string[]>((resolve, reject) => {
fs.readdir(`src/posts`, (err, files) => {
if (err) {
reject(err);
}
resolve(files.filter((fileName) => mdRegex.test(fileName)));
});
});
const files = await filesPromise;
const postMetas = await Promise.all(
files.map(async (fileName) => {
const { metadata } = await processMetadata(`src/posts/${fileName}`);
return metadata;
})
);

return posts;
}
// sort the posts by create date.
postMetas.sort((a, b) => dayjs(b.date).valueOf() - dayjs(a.date).valueOf());

return postMetas;
};
11 changes: 5 additions & 6 deletions frontend/src/routes/blog/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ import { getBlogPosts } from '$lib/getBlogPosts';
import type { BlogMetadata } from '$lib/types';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = function () {

const result: BlogMetadata[] = getBlogPosts();
export const load: PageServerLoad = async function () {
const result: BlogMetadata[] = await getBlogPosts();

return {
posts: result
};
return {
posts: result
};
};
12 changes: 6 additions & 6 deletions frontend/src/routes/blog/[slug]/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import type { BlogResponse } from '$lib/types';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async function ({ params }) {
// we could get the dynamic slug from the parameter of get.
const { slug } = params;
// we could get the dynamic slug from the parameter of get.
const { slug } = params;

const post: BlogResponse = process(`src/posts/${slug}.md`);
const post: BlogResponse = await process(`src/posts/${slug}.md`);

return {
post
};
return {
post
};
};
28 changes: 14 additions & 14 deletions frontend/src/routes/rss.xml/+server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import type { RequestHandler } from '@sveltejs/kit';
import dayjs from 'dayjs';

export const GET: RequestHandler = async function () {
const headers = {
'Cache-Control': 'max-age=0, s-maxage=3600',
'Content-Type': 'application/xml'
};
const posts = getBlogPosts();
const rss = `<?xml version="1.0" encoding="UTF-8" ?>
const headers = {
'Cache-Control': 'max-age=0, s-maxage=3600',
'Content-Type': 'application/xml'
};
const posts = await getBlogPosts();
const rss = `<?xml version="1.0" encoding="UTF-8" ?>
<rss xmlns:dc="https://purl.org/dc/elements/1.1/"
xmlns:content="https://purl.org/rss/1.0/modules/content/"
xmlns:atom="https://www.w3.org/2005/Atom"
Expand All @@ -23,22 +23,22 @@ export const GET: RequestHandler = async function () {
<lastBuildDate>${new Date().toUTCString()}</lastBuildDate>
<language>en</language>
${posts
.map(
(post) =>
`<item>
.map(
(post) =>
`<item>
<title><![CDATA[${post.title}]]></title>
<description><![CDATA[${post.excerpt}]]></description>
<link>${website}/blog/${post.slug}</link>
<guid isPermaLink="false">${website}/blog/${post.slug}</guid>
<pubDate>${dayjs(post.date).toString()}</pubDate>
${post.tags.map((tag) => `<category><![CDATA[${tag}]]></category>`).join('')}
</item>`
)
.join('')}
)
.join('')}
</channel>
</rss>`;

return new Response(rss, {
headers
});
return new Response(rss, {
headers
});
};

0 comments on commit 4a4f04a

Please sign in to comment.