-
Notifications
You must be signed in to change notification settings - Fork 6.3k
/
Copy pathblogData.mjs
112 lines (89 loc) · 3.61 KB
/
blogData.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
'use strict';
import { createReadStream } from 'node:fs';
import { basename, extname, join } from 'node:path';
import readline from 'node:readline';
import graymatter from 'gray-matter';
import { getMarkdownFiles } from '../../next.helpers.mjs';
// gets the current blog path based on local module path
const blogPath = join(process.cwd(), 'pages/en/blog');
/**
* This contains the metadata of all available blog categories
*/
const blogCategories = new Set(['all']);
/**
* This method parses the source (raw) Markdown content into Frontmatter
* and returns basic information for blog posts
*
* @param {string} filename the filename related to the blogpost
* @param {string} source the source markdown content of the blog post
*/
const getFrontMatter = (filename, source) => {
const {
title = 'Untitled',
author = 'The Node.js Project',
username,
date = new Date(),
category = 'uncategorized',
} = graymatter(source).data;
// We also use publishing years as categories for the blog
const publishYear = new Date(date).getUTCFullYear();
// Provides a full list of categories for the Blog Post which consists of
// all = (all blog posts), publish year and the actual blog category
const categories = [category, `year-${publishYear}`, 'all'];
// we add the year to the categories set
blogCategories.add(`year-${publishYear}`);
// we add the category to the categories set
blogCategories.add(category);
// this is the url used for the blog post it based on the category and filename
const slug = `/blog/${category}/${basename(filename, extname(filename))}`;
return { title, author, username, date: new Date(date), categories, slug };
};
/**
* This method is used to generate the Node.js Website Blog Data
* for self-consumption during RSC and Static Builds
*
* @return {Promise<import('../../types').BlogData>}
*/
const generateBlogData = async () => {
// We retrieve the full pathnames of all Blog Posts to read each file individually
const filenames = await getMarkdownFiles(process.cwd(), 'pages/en/blog', [
'**/index.md',
]);
return new Promise(resolve => {
const posts = [];
const rawFrontmatter = [];
filenames.forEach(filename => {
// We create a stream for reading a file instead of reading the files
const _stream = createReadStream(join(blogPath, filename));
// We create a readline interface to read the file line-by-line
const _readLine = readline.createInterface({ input: _stream });
// Creates an array of the metadata based on the filename
// This prevents concurrency issues since the for-loop is synchronous
// and these event listeners are not
rawFrontmatter[filename] = [0, ''];
// We read line by line
_readLine.on('line', line => {
rawFrontmatter[filename][1] += `${line}\n`;
// We observe the frontmatter separators
if (line === '---') {
rawFrontmatter[filename][0] += 1;
}
// Once we have two separators we close the readLine and the stream
if (rawFrontmatter[filename][0] === 2) {
_readLine.close();
_stream.close();
}
});
// Then we parse gray-matter on the frontmatter
// This allows us to only read the frontmatter part of each file
// and optimise the read-process as we have thousands of markdown files
_readLine.on('close', () => {
posts.push(getFrontMatter(filename, rawFrontmatter[filename][1]));
if (posts.length === filenames.length) {
resolve({ categories: [...blogCategories], posts });
}
});
});
});
};
export default generateBlogData;