Hexo 资源路径优化

What’s hexo?
Hexo is a fast, simple and powerful blog framework. You write posts in Markdown (or other markup languages) and Hexo generates static files with a beautiful theme in seconds.

问题

在开始用 Hexo 写博客之后,就一直被它引用图片的方式所困扰。就是必须以 source 为根目录的方式引用,比方说:

.
├── source
│   ├── _posts
│   │   └── helloworld.md
│   └── img
│       └── cover.jpg
.

helloworld.md 里需要通过 ![alt txt](/img/cover.jpg) 来引用 cover.jpg,否则编译出的 html 就无法显示图片。而在编辑器内我更习惯 ../img/cover.jpg 的方式饮用图片。

Hexo 的引用方式会导致编辑器内预览时无法显示图片,而且这种引用方式也不符合直觉,不直观。Hexo 引用图片方式还有一个问题就是,以后切换到其它写作平台时可能需要逐一修改图片路径。

Hexo 大概想让用户直接通过 hexo server 预览,其他人不知道,反正我不喜欢预览还要切换到浏览器。

自己动手

现在的问题就是把写作时更加习惯的相对路径,在编译前替换成 Hexo 所支持的绝对路径。在 Github 上找了一圈没有找到支,所以就自己撸一个吧。

Hexo 提供的 Filter 让我们有机会在 Hexo 处理数据前对数据进行修改,这里的数据可能是 md 文件,也可能是其它文件。

不通类型的 Filter 在不同阶段执行,这里可以选择 before_post_render 在 md 文件编译成 html 之前进行处理。

实现

下面直接看代码:

const path = require('path');
const fs = require('fs');
 
let imageReg = /!\[(?<alt>[^\]]*)\]\((?<url>.*?)(?=\"|\))(?<title>\".*\")?\)/g;
let cwd = process.cwd();
 
let hexo_img_locator = function (data) {
    let postPath = data.full_source;
    if (!postPath.endsWith('.md')) {
        printInfo(`skip ${postPath}`);
        return data;
    }
    let mapping = new Map();
    let cannotAccess = new Array();
 
    let match = null;
    while ((match = imageReg.exec(data.content)) != null) {
        let imageUrl = match.groups.url;
        // skip online image and local file with absolute path
        if (imageUrl.startsWith('http://')
            || imageUrl.startsWith('https://')
            || imageUrl.startsWith('/')) {
            continue;
        }
        let imageAbsPath = path.resolve(path.dirname(postPath), imageUrl);
 
        if (!fs.existsSync(imageAbsPath)) {
            cannotAccess.push(imageUrl);
            continue;
        } else if (cannotAccess.length > 0) {
            // no need process any more
            continue;
        }
 
        let imageAbsPathFromSource = path.sep + path.relative(`${cwd}${path.sep}source`, imageAbsPath);
        let imageDirective = match[0];
        mapping.set(imageDirective,
            imageDirective.replace(imageUrl, imageAbsPathFromSource));
        printInfo(`[hexo-img-locator] ${imageUrl} -> ${imageAbsPathFromSource}`);
    }
 
    if (cannotAccess.length != 0) {
        printError(`[hexo-img-locator] cannot access ${cannotAccess[0]} and other ${cannotAccess.length - 1} from ${path.relative(cwd, data.full_source)}, file not found.`)
        process.exit(1)
    }
    mapping.forEach((v, k, map) => {
        data.content = data.content.replace(k, v);
    })
    return data;
}
 
hexo.extend.filter.register('before_post_render', hexo_img_locator);

这段代码做了两件事:

  • 使用正则匹配所有图片指令
  • 将图片指令中的路径替换成 Hexo 支持的路径

然后将 Filter 发布到 npm

待改进

正则匹配会无脑匹配,也就是说代码块中的 ![alt](/xxx) 也会被匹配,产生不符合预期的行为。

参考文章

https://hexo.io/api/filter