随着 SSG 静态生成工具的蓬勃发展,市面上能看到越来越多的静态站点,一般的使用方法是通过静态展点生成工具生成静态页面,然后进行发布。

我个人使用了一年多Hugo ,不论是稳定性还是易用性方面,都无愧于开源社区里关注度第一。

但是这类静态站点生成器就只能做一些静态站点使用了么?当然不是。

Hugo 的自定义输出功能,搭配模板生成,可以轻松输出一些静态 API 接口,而内容可以使用 Markdown 来进行编写,还允许使用目录树的方式进行管理。

不论是搭配静态生成站点使用、还是简单提供给外部其他的 SPA 应用都是很方便的。

下面就来介绍如何使用 Hugo 输出 API 接口。

声明配置

首先需要在 config.toml 中指定各种页面的输出类型为 json

[outputs]
    page = ["json"]
    home = ["json"]
    teams = ["json"]
    section = ["json"]
    taxonomy = ["json"]

定义模板

接着

接着需要在 layouts/_default/ 中创建 single.jsonitem.jsonlist.json 并使用变量和模板函数编写即可,比如:

{
    "title": "{{ .Title }}",
    "date": "{{ .Date }}",
    "type": "{{ .Type }}",
    "permalink" : "{{ .Permalink }}",
    "summary" : "{{ .Summary }}"
}

即可将某个 Markdown 文件转换为 下面的内容:

{
  "data": {
    "title": "About",
    "date": "2018-02-09 11:47:06 -0500 -0500",
    "type": "page",
    "permalink": "/page/about/index.json",
    "summary": "An API about our school athletes!"
  }
}

列表文件、和独立的页面文件同理,可以参考我提供的示例项目代码。

调整输出

Hugo 输出 API 依赖模板,使用的是拼凑的方法,所以输出结果难免会像下面一样,掺杂大量无用的空格字符,甚至有可能包含错误结果。

{
	"data" : 
	{
    "name": "Frank J. Robinson",
    "contact" : "+1 (555) 555 5555",
    "permalink" : "/players/frank-j-robinson/index.json",
    "year" : "junior"
    
    	
    		,"practices" : "Monday, Thursday"
    	
    
    	
    		,"sports" : "soccer, baseball"
    	
    
   	
}

}

为了最后产物的可用性(性能、预发正确),需要使用脚本对产物进行额外处理,这里我使用 Node.js

'use strict';

const {readdirSync, statSync, readFileSync, writeFileSync} = require('fs');
const {join} = require('path');

function getAllFiles(dirPath, ext) {
  function flatten(arr) {
    return arr.reduce((flat, toFlatten) => flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten), []);
  }

  function scanDir(dirPath, ext) {
    const result = readdirSync(dirPath);
    if (!result.length) return [];
    return result.map((dirName) => {
      const filePath = join(dirPath, dirName);
      if (statSync(filePath).isDirectory()) {
        return scanDir(join(dirPath, dirName), ext);
      } else {
        if (!ext) return filePath;
        if (filePath.lastIndexOf(ext) === filePath.indexOf(ext) && filePath.indexOf(ext) > -1) return filePath;
        return '';
      }
    });
  }

  return flatten(scanDir(dirPath, ext)).filter((file) => file);
}

const allMarkdownFiles = getAllFiles('./public', '.json');
allMarkdownFiles.forEach((item) => {
  try {
    const jsonData = JSON.stringify(JSON.parse(readFileSync(item, 'utf8')));
    writeFileSync(item, jsonData);
  } catch (e) {
    console.error(`${item} content error.`);
    writeFileSync(item, JSON.stringify({code: 500, desc: 'Unexpected token'}));
  }
});

将脚本保存为 checker.js 执行 node checker 即可批量对 Hugo 的产物进行正确性校验和结果压缩。

比如上面的产物在执行完毕处理脚本后会变成:

{"data":{"name":"Frank J. Robinson","contact":"+1 (555) 555 5555","permalink":"/players/frank-j-robinson/index.json","year":"junior","practices":"Monday, Thursday","sports":"soccer, baseball"}}

把这个脚本配置到 CI 中,即可完成修改 Markdown 文件,就能够自动生成高性能可用的静态 API 了。

其他

相关示例代码,可以访问:

Hugo 还有一堆其他的玩法,后面有机会再聊。

-EOF