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