UNPKG

1post

Version:

A tiny, fast and fun static site generator

176 lines (139 loc) 5.76 kB
const crypto = require('crypto'); const fs = require('fs'); const path = require('path'); const cwd = process.cwd(); const marked = require('marked'); const postsDir = path.resolve(cwd, 'posts'); const blogConfig = require(`${cwd}/blog-config.json`); const postTemplateFilePath = path.resolve(cwd, 'template-post.html'); const indexTemplateFilePath = path.resolve(`${cwd}`, 'template-index.html'); const cache = {}; const posts = []; // -------------------------------------- initial checking if (!fs.existsSync(postTemplateFilePath)){ console.error(`FAILED: File "template-post.html" not found on the current directory.`); process.exit(1); } if (!fs.existsSync(indexTemplateFilePath)){ console.error(`FAILED: File "template-index.html" not found on the current directory.`); process.exit(1); } if (!fs.existsSync(postsDir)) { fs.mkdirSync(postsDir); } if (fs.existsSync(`${postsDir}/cache.json`) && !process.argv.includes('--force')) { try { const cached = require(`${postsDir}/cache.json`); Object.assign(cache, cached); } catch { console.warn('WARN: Could not retrieve posts,json, skipping cache.'); fs.unlinkSync(`${postsDir}/cache.json`); } } // -------------------------------------- utils function hashContent(content) { return crypto.createHash('md5').update(content).digest('hex'); } function getMetaConfigStr(contentTemplate) { return contentTemplate .match(/(<!--:::.*?:::-->)|(<!--:::[\S\s]+?:::-->)|(<!--:::[\S\s]*?$)/g)[0]; } function metaConfigStrToObj(metaConfigComment) { const metaConfigString = metaConfigComment .replace('<!--:::', '') .replace(':::-->', '') .replace(/[\r\n]/gm, ''); return JSON.parse(metaConfigString); } function formatDate(dateStr, locale) { return new Date(dateStr).toLocaleString(locale); } function getContentTemplateConfig(entry, contentTemplate) { try { const metaConfigComment = getMetaConfigStr(contentTemplate); const metaConfigObject = metaConfigStrToObj(metaConfigComment); return { ...metaConfigObject, entryName: entry, hash: hashContent(contentTemplate), post_url: `${blogConfig.blog_url}/posts/${entry}`, post_created_at_formated: formatDate(metaConfigObject.post_created_at, blogConfig.blog_locale || 'en') } } catch(error) { console.error(`FATAL (!): Could not parse the meta information (<!--::: :::-->) for post: \n${entry}`); console.error(error); process.exit(1); } } function bindPostTemplateAndContent(postTemplate, contentTemplate) { const metaConfigString = getMetaConfigStr(contentTemplate); contentTemplate = contentTemplate.replace(metaConfigString, ''); return postTemplate.replace('{{post}}', contentTemplate); } function sortFeedNewerFirst(feed) { return feed.sort((a, b) => { const dateA = new Date(a.info.post_created_at); const dateB = new Date(b.info.post_created_at); if (dateA < dateB) return 1; if (dateA > dateB) return -1; return 0; }); } function markdown(mdString) { return marked.parse(mdString); } // -------------------------------------- bulding posts fs.readdirSync(postsDir, { withFileTypes: true }) .filter(entry => entry.isDirectory()) .map(entry => entry.name) .forEach(entry => { const ext = fs.existsSync(`${postsDir}/${entry}/post.md`) ? 'md' : 'html'; const postContent = fs.readFileSync(`${postsDir}/${entry}/post.${ext}`, 'utf-8') const contentTemplate = (ext === 'md' ? markdown(postContent) : postContent); const contentTemplateConfig = getContentTemplateConfig(entry, postContent); if (!cache[entry] || cache[entry].hash !== contentTemplateConfig.hash) { const postMetaConfig = { ...blogConfig, ...contentTemplateConfig }; const postTemplate = fs.readFileSync(postTemplateFilePath, 'utf-8'); let postContent = bindPostTemplateAndContent(postTemplate, contentTemplate); for(key in postMetaConfig) { postContent = postContent.replace(new RegExp(`{{${key}}}`, 'g'), postMetaConfig[key]); } posts.push(postMetaConfig); cache[entry] = contentTemplateConfig; postContent = '<!-- This is an automatically generated file, do not edit it directly -->\n' + postContent; fs.writeFileSync(`${postsDir}/${entry}/index.html`, postContent); console.log(`* Created: "/posts/${entry}"`); } else { posts.push({ ...blogConfig, ...cache[entry] }); } }); // -------------------------------------- saves the cache fs.writeFileSync(`${postsDir}/cache.json`, JSON.stringify(cache)); // -------------------------------------- bulding index page const postsFeed = sortFeedNewerFirst(posts.map(entry => { return { info: entry, html: ` <li style="list-style: none"> <article> <a href="posts/${entry.entryName}"> <p class="pm-h6 pm-no-margin"><strong>§ ${entry.post_title}</strong></p> </a> <p class="pm-no-margin">${entry.post_description}</p> <p class="pm-disabled">${entry.post_created_at_formated}</p> </article> </li> ` } })); const indexTemplateContent = fs.readFileSync(indexTemplateFilePath, 'utf-8'); const postsFeedHtml = `<ul>${postsFeed.map(item => item.html).join('\n')}</ul>`; const noPostsHint = `<p>${blogConfig.blog_no_posts_hint}</p>`; let index = indexTemplateContent.replace('{{posts_feed}}', postsFeedHtml || noPostsHint); for(key in blogConfig) { index = index.replace(new RegExp(`{{${key}}}`, 'g'), blogConfig[key]); } index = '<!-- This is an automatically generated file, do not edit it directly -->\n' + index; // -------------------------------------- done fs.writeFileSync(path.resolve(`${cwd}`, 'index.html'), index); console.log('\nBlog building done');