手摸手教你撸一个静态网站生成器

手摸手教你撸一个静态网站生成器

两个月前个人ECS到期,续费太贵重新买了个实例,个人网站之前用的是Keystone.js (node.js + mongo)迁移起来比较麻烦,同时一直以来也感觉为了弄个博客,这个架构显得有点重,就自己撸了个静态网页生成器。想要试用的朋友可以直接点击mdblog文档,,mdblog主页本身就由mdblog静态网页生成器实现的。

本文主要来回顾一下做这个静态网页生成器过程中的一些思考。

先明确一个什么是静态网页生成器?静态网页生成器的工作主要是基于一些静态的文本来生成静态的HTML页面。所以可以想象一个极简的静态网站生成器核心一般会有以下几个步骤

  1. 读取博文markdown文件
  2. 把markdown转成html
  3. 生成html文件

这三个步骤做起来并不难:

  1. 用node.js 的fs读文件api fs.readFileSync(path, 'utf-8')
  2. marked库把markdown文件转成 HTML DOM
  3. 把2中生成的HTML DOM 作为内容插入html模板,通过fs 写文件api fs.writeFileSync('blog1.html', rawHtml, 'utf8');生成html文件

所以要做成这个事情,核心技术点上就调调API,用用库,没有什么深刻或谈得上有技术难度的东西。在设计时,更多要考虑的是:在产品层面上,怎么做才能让所做的东西变得好用

首先我先明确下自己的需求,我心中滑板鞋(静态网页生成器)的是什么样子的?

Talk is cheap , Let's see the code,整个项目,最核心的是一个用于构建的node文件build.js,现在就以构建文章页的代码为例子,看一看

// 遍历一个文件夹下所有文件 ,生产的时候,不用排序
function readFolder(filePath){
    var files = fs.readdirSync(filePath);
    files.forEach(function(filename){
        if(filename.indexOf('.md') === -1) return;
        // 无后缀文件名
        let targetFileName = filename.replace('.md','');
        let markdownContent = readFileAndParse(filePath + '/' + filename);
        writeHtml(targetFileName, markdownContent);
     });
}
// 读取一个文件内容
function readFileAndParse(path){
    text = fs.readFileSync(path, 'utf-8')
    try {
        var result = marked(text).replace(/./assets/g,'./assets');
        return result;
    } catch (err) {
        console.error('解析文件出错:'+path);
    }
}
// 文章详情页把内容写入模板html
function writeHtml(fileName,content) {
    let rawHtml = readFileAndParse('./src/index.html');
    let headDOM = fs.readFileSync('./src/components/TopNav/nav.html', 'utf-8');
    let headCSS = readCssFile('./src/components/TopNav/nav.css');
    let headJS = fs.readFileSync('./src/components/TopNav/nav.js', 'utf-8');
    let dryTitle = /<!--<title>(.*)<\/title>-->/.exec(content);
    let dryIntro = /<!--<intro>(.*)<\/intro>-->/.exec(content);
    // 读取模板 ,把markdown内容注入
    let html = rawHtml
                .replace('<!-- content -->', blogTopBanner(dryTitle && dryTitle[1] ||fileName)+' <div class="markdown-body">' + content + '</div>')
                .replace('<!-- title -->', dryTitle && dryTitle[1] )
                .replace('静态网页生成器的工作主要是基于一些静态的文本来生成静态的HTML', dryIntro && dryIntro[1])
                .replace('<!-- header DOM -->', headDOM)
                .replace('<!-- header CSS -->', '<style>' + headCSS + '</style>')
                .replace('<!-- header JS -->', '<script>' + headJS + '</script>')
            ;
    fs.writeFileSync('./'+ outputFold +'/' + fileName + '.html', html, 'utf8');
    console.log('文章网页生成成功:' + fileName + '.html');
}

总共三个函数:一个遍历文件,一个读文件,一个写文件。其中writeHtml()函数那些花里胡哨的代码就是把动态把内容(Html DOM,js,css)注入到下面的html模板里,然后产出html。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="description" content="mdblog描述">
    <title>
        <!-- title -->
    </title>
    <!-- header CSS -->
    <!-- homepage CSS -->
    <!-- postsPageCSS CSS -->
</head>
<body>
    <!-- header DOM -->
    <!-- homepage DOM -->
    <!-- content -->
    <!-- header JS -->
    <!-- homepage JS -->
    <!-- posts JS -->
</body>
</html>

还记得上面有一个feature是支持组件的本地开发模式,三个支持可自定义的组件为如图所示的结构

本地开发需要把组件的三部分(html,css,js)打包到一起,所以我这边用了下webpack,以uiDevelop.js为入口文件,把本地组件都打包到了一起做本地热部署调试,uiDevelop.js如下

import './components/HomePage/homepage.css';
import './components/TopNav/nav.css';
import './components/Posts/posts.css';
import homePageDom from './components/HomePage/homepage.html';
import topNavDom from './components/TopNav/nav.html';
import postsDom from './components/Posts/posts.html';
document.write(topNavDom);
document.write('<h1 ><div>FBI WARNING</div>温馨提示:本页把src/components 目录下的三个组件导入在一个页面,如果你也了解一些前端知识,可用它来进行页面DRY,进行本地调试。</h1>');
document.write('<div>homePageDom 组件样式 start</div>');
document.write(homePageDom);
document.write('<div>homePageDom 组件样式 end</div>');
document.write('<div>postsDom 组件样式 start</div>');
document.write(postsDom);
document.write('<div>postsDom 组件样式 start</div>');

import './components/TopNav/nav.js';
import './components/HomePage/homepage.js';

博客列表页博文自定义(包括文章配图,标题,介绍,标签,日期),在markdown头部采用html注释的方式标识,构建的时候正则匹配取值

<!--<img-url>./assets/default.jpeg</img-url>--> 
<!--<title> mdblog 说明文档 </title>--> 
<!--<intro> 介绍mdblog </intro>--> 
<!--<tag>README</tag>-->
<!--<date>2018-08-20</date>-->

所以大致比较关键的就以上几点,如果你对源码感兴趣,可以访问github mdblog源码地址,欢迎各位在评论区多多交流。

最后来个小彩蛋

mdblog网站首页会有个小红心,那是我想你的时候,摸着心用css画的,它描述的是想你的时我心跳动的节奏。

11.5,我们在一起两周年了。

小雨,我爱你!