Hexo+NexT(六):手把手教你編寫一個Hexo過濾器插件

精於心 簡於形

Hexo+NexT介紹到這裏,我認爲已經能夠很好地完成任務了。它所提供的一些基礎功能及配置,都已經進行了講解。你已經能夠爲所欲爲地配置一個本身的博客環境,而後享受碼字的樂趣。javascript

把博客託管到Github上,是個很好的想法,沒有本身空間的博主確定很歡迎。其實文章編譯以後,他就是一個很是簡單的靜態網站。部署的目的就是簡單的把靜態網站文件夾拷貝到Github的一個倉庫裏,而後把這個倉庫看成一個網站文件夾,僅此而已,很是簡單。因此,沒有講的價值。css

可是,做爲一個Coder,研究了Hexo,總得來點真本事,提出一個方案,解決一個痛點,而後實現它。html

痛點固然有,每次用Typora碼文章,習慣對文中圖片所見即所得,無奈,Typora對圖片的處理方式,Hexo不承認,轉換以後url錯亂,沒法識別。因此,我但願TyporaHexo用統一的方式處理圖片,在Typora中和Hexo編譯以後均可以正常顯示。java

沒有人解決,我就想解決它。node

Hexo博客專題索引頁git

1. Typora的圖片和NexT的資源文件的統一

Typora中,圖片能夠採用相對位置保存,而且能夠用文章文件名進行靈活定製。若是咱們在Typora中,把圖片的保存位置指定爲與文章同名的文件夾,那麼跟NexT提供的資源文件夾就不謀而合了。github

Typora中,把圖片的存儲位置設置爲./${filename},見圖。正則表達式

NexT的主題配置文件中,打開資源文件夾功能,Hexo編譯時會把資源文件夾下的資源對象,根據引用它的頁面而賦予相應的urlnpm

post_asset_folder: true

若是,咱們把這二者統一塊兒來,在markdown文章中咱們可以在文章編譯爲html以前,實現這樣的轉換json

![img](postname/sample.jpg) => {%asset_img sample.jpg%}

那就幸福了:在Typora下采用![img](postname/sample.jpg)使用圖片,享受所見即所得,在編譯過程當中轉化爲資源文件,自動得到,正確的url,魚與熊掌兼得,完美。

2. 解決思路

2.1 瞭解Hexo運做模式

研究Hexo的項目結構,主要研究頁面的編譯過程,也就是Hexo g命令是如何執行的。

根據Hexo的概述,Hexo項目的執行過程以下:

  1. 初始化
  2. 載入文件
  3. 執行指令
  4. 結束

第一步:初始化

初始化階段,會建立Hexo實例,各類配置,各類插件,各類擴展所有就位,就等待載入文章進行處理。

Hexo經過項目包管理文件package.json引入各類插件擴展。

第二步:載入文件

載入source下全部的文章及樣式、腳本等資源。若有指令,則能夠監控該文件下面文件的變化。

第三步:執行指令

執行控制檯指令,根據指令執行相應的命令。

第四步:退出

2.2 着手點

須要達成的目的,主要在編譯頁面的過程當中,也就是主要在渲染render階段。

Hexo的源代碼中當然能夠找到蛛絲馬跡,可是這太麻煩了,速度也不快。有沒有其餘的方式。

換換思路,研究下Hexo提供的API,忽然發現,其中的擴展是這樣的。

基本上全部的擴展都可以望文生義,最有可能入手的地方就是Filter過濾器。

把它的定義擺上來:

hexo.extend.filter.register(type, function(data){
}, priority);
  • type是類型,表示過濾器的類型,過濾器的類型是什麼意思?好吧,看看有什麼類型

    before_post_renderafter_post_renderbefore_exitbefore_generate,這就是過濾器的插入時機啊。

  • function(data)是回調函數,這個很好地理解,其中的data是什麼,回頭再說。

  • prioritytype是過濾器的插入時機,若是在同一時機插入多個過濾器,那麼就由priority來決定執行前後順序,`priority值小就先執行。

重點在render

在上面的過濾器類型(就是過濾器的插入點)中,有一個重要的類型是before_post_render,意思就是在渲染以前執行過濾器。查一下Hexo的API,渲染的過程以下:

  • 執行 before_post_render 過濾器
  • 使用 Markdown 或其餘渲染器渲染(根據擴展名而定)
  • 使用 Nunjucks 渲染
  • 執行 after_post_render 過濾器

好啊,那麼咱們拿before_post_render來嘗試一下。

2.3 編寫一個過濾器

找一個例子學習一下

https://hexo.io/plugins/裏面找一個簡單的過濾器例子,發現它就是一個特別簡單的Node的包。好比過濾器插件hexo-filter-auto-spacingmmhy,它的文件清單以下:

  • lib
    • renderer.js
  • README.md
  • index.js
  • package.json

其中有用的也就是package.jsonindex.js。而package.json也就是典型的Node包文件,它的輸出對象由main字段指定,本例中main字段指向index,也就是咱們的index.js文件。

看一下index.js內容

var assign = require('deep-assign');
var renderer = require('./lib/renderer');
hexo.extend.filter.register('before_post_render', renderer.render, 9);

再看一下/lib/renderer.js的內容

var reg = /(\s*)(```) *(.*?) *\n?\[hide\]([\s\S]+?)\s*(\2)(\n+|$)/g;

function ignore(data) {
  var source = data.source;
  var ext = source.substring(source.lastIndexOf('.')).toLowerCase();
  return ['.js', '.css', '.html', '.htm'].indexOf(ext) > -1;
}

exports.render = function (data) {
  if (!ignore(data)) {

    data.content = data.content
      .replace(reg, function (raw, start, startQuote, lang, content, endQuote, end) {
        return start + end;
      });
  }
};

太簡單了,對於上面這個例子,就是實現了過濾器的定義

hexo.extend.filter.register(type, function(data){
}, priority);

照貓畫虎

Hexo項目文件並排新建一個文件node_modules,並在裏面新建項目hexo-image2asset。結構以下:

├─guide2it-blog
│  ├─node_modules
│  ├─public
│  ├─scaffolds
│  ├─source
│  │  ├─about
│  │  │  └─index
│  │  ├─categories
│  │  ├─images
│  │  ├─tags
│  │  └─_posts
│  │      ├─2019-04-19-01測試插件.md
│  │      └─2019-04-19-01測試插件
│  │        └─guide2it.jpg
│  ├─themes
│  │  └─next
└─node_modules
    └─hexo-image2asset
      ├─package.json
      └─index.js

至於爲何要這樣,這都是血的教訓。對於Node項目,新建模塊應該在/guide2it-blog/node_modules下面,我以前也是這樣創建的,後來由於莫名奇妙的問題,採用萬能的修復大法delete node_modules & npm install以後,個人hexo-image2asset項目找不到了,駕鶴西去了。

而我把hexo-image2asset按上述方式佈置,它也在Node項目的搜索路徑上,也能夠避免萬能修復大法重蹈覆轍。

探究data的數據結構

爲了弄清楚回調函數中data的結構,我決定用一個例子來測試。

請看2019-04-19-01測試插件.md的內容

---
內容略
---
測試hexo-image2asset插件
下面我要加入一張圖片了。
![測試](2019-04-19-01測試插件/guide2it.jpg)

而後我編寫index.js,內容以下:

var deal_image=function(data){
  console.log(data);
}
hexo.extend.filter.register('before_post_render', deal_image, 9);

執行hexo g激發渲染過程。

Document {
  layout: 'post',
  title: '測試插件',
  date: moment("2019-03-05T09:00:00.000"),
  _content:
   '\n測試hexo-image2asset插件\n\n下面我要加入一張圖片了。\n\n![測試](2019-04-19-01測試插件/guide2it.jpg)',
  source: '_posts/2019-04-19-01測試插件.md',
  raw:
   '---\nlayout: post\ntitle: \'測試插件\'\ndate: 2019/3/5 09:00:00\ncategory: [\'博客\',\'Hexo\']\ntags: [\'博客\',\'Hexo\',\'NexT\']\n---\n\n測試hexo-image2asset插件\n\n下面我要加入一張圖片了。\n\n![測試](2019-04-19-01測試插件/guide2it.jpg)',
  slug: '01測試插件',
  published: true,
  updated: moment("2019-04-21T01:15:15.699"),
  comments: true,
  photos: [],
  link: '',
  _id: 'cjuprkojw0001o4d4cbawzsgo',
  path: [Getter],
  permalink: [Getter],
  full_source: [Getter],
  asset_dir: [Getter],
  tags: [Getter],
  categories: [Getter],
  content:
   '\n測試hexo-image2asset插件\n\n下面我要加入一張圖片了。\n\n![測試](2019-04-19-01測試插件/guide2it.jpg)',
  site: { data: {} } }

原來這個data是一個Document,它的內容及結構如上所示。跟內容相關的主要有三個字段_contentcontentrawraw表示原始文章,_content這種帶前綴_的通常是內部屬性,不能動,那麼就動content的內容。

按照資源對象的格式要求,應該把

![測試](2019-04-19-01測試插件/guide2it.jpg)轉換爲

{%asset_img guide2it.jpg 測試%}

轉換圖片對象爲資源對象

這個須要採用正則表達式來全局轉換,被轉換的字符串中有文章名字,這個須要首先找出來。

已知source形如_posts/2019-04-19-01測試插件.md,那麼文件名應該是,找到最右邊的/,其後的字符串,去掉.md

創建正則表達式來進行替換,把[]內的內容用()肯定爲$1,把圖片文件名用()定義爲$2,最終的正則表達式以下。

插件的index.js完整內容以下。

var deal_image = function(data) {
    var reverseSource = data.source.split("").reverse().join("");
    var fileName = reverseSource.substring(3, reverseSource.indexOf("/")).split("").reverse().join("");
    var regExp = RegExp("!\\[([^\\f\\n\\r\\t\\v\\[\\]]+)\\]\\(" + fileName +
        '\\/([^\\\\\\/\\:\\*\\?\\"\\<\\>\\|\\,\\)]+)\\)');
    data.content = data.content.replace(regExp, "{%asset_img $2 %}","g");
    return data;
}
hexo.extend.filter.register('before_post_render', deal_image, 9);

這裏有個bug,替換對象爲"{%asset_img $2 $1 %}"時,若是正則匹配的%1是純數字,則它被解釋爲圖片寬度,這好像就離題了。因此暫時把$1去掉。

相關文章
相關標籤/搜索