完成一個Hexo的主題其實很簡單,和寫靜態頁面差不多,只是內容部分通過Hexo的變量去獲取,而且Hexo還內置了一些輔助函數幫你快速方便地完成繁瑣的處理。
起步
在寫代碼之前要先把項目結構搭建好,一個Hexo主題的項目名就是主題名字本身,項目內的目錄結構如下: (生成樹形圖是用的tree, mac上直接brew install tree就可以了,以前不寫都不知道囧)
.
├── _config.yml //記錄主題配置信息
├── layout //存放布局模板文件
│ └── _partial //布局文件中可共用的模板
└── source //靜態資源文件夾
├── css
├── fonts
├── js
└── sass
項目結構搞好就可以開始寫代碼了!因為當初我是仿landscape寫的,而且ejs也是我之前看nodejs時就接觸過的,因此就直接用ejs寫模板文件了,樣式使用了sass (scss。
布局
編寫布局文件(layout.ejs)
模板文件在layout文件夾下,文件名對應Hexo中的模板名,有index,post,page,archive,category,tag幾種,對於普通的header + content + footer的頁面結構,header和footer往往是可以復用的,因此我們可以使用layout.ejs進行布局,動態的內容使用body變量去動態渲染,所以我的layout.ejs大概長這樣:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"/>
<title><%= config.title %></title>
<%- css('css/style') %>
</head>
<body>
<%- partial('_partial/header') %>
<div class="main">
<%- body %>
</div>
<%- partial('_partial/footer') %>
<%- js('js/index.js') %>
</body>
</html>
partial,js和css是Hexo提供的輔助函數,后面再說。
其他模板文件
每一個模板文件對應的是一種布局,當你使用hexo new <title>的時候,其實忽略了一個參數,完整的命令是hexo new [layout] <title>,這個layout就決定了文章使用何種方式布局,比如創建一個自己簡介的About頁面,hexo new page "about"其實就是使用了page布局。每種布局對應到我們的模板文件上就是index.ejs(首頁),post.ejs(文章),archive.ejs(歸檔),tag.ejs(標簽歸檔),page.ejs(分頁)。
如果更直觀一點,url和模板的對應關系是這樣的:
| Url | Description | Layout |
|---|---|---|
| / | 首頁 | index.ejs |
| /yyyy/mm/dd/:title/ | 文章 | post.ejs |
| /archives/ | 歸檔 | archive.ejs |
| /tags/:tagname/ | 某個標簽的歸檔 | tag.ejs |
| /:else/ | 其他 | page.ejs |
index.ejs
首頁一般是一些博文的摘要和一個分頁器,通過Hexo的page變量拿到頁面的數據渲染即可,這里我們不直接在index.ejs中寫HTML結構,新建一個_partial/article.ejs,將文章數據傳給子模板渲染,然后再額外傳入一個參數{index: true},對后面的post.ejs和page.ejs加以區分,讓子模板能正確渲染。最后,index.ejs大致是這樣的:
//index.ejs
<% page.posts.each(function(post, index){ %>
<%- partial('_partial/article', {index: true, post: post}) %>
<% }) %>
<div class="pagination">
<%- paginator({ total: Math.ceil(site.posts.length / config.per_page)}) %>
</div>
post.ejs
文章模板和首頁差不多,只是對應的是一篇具體的文章,所以就把文章傳入,再額外傳入{index: false}告訴子模板不要按首頁的方式去渲染就好了。就一行代碼(因為都在子模板里 XD
//post.ejs
<%- partial('_partial/article', {index: false, post: page}) %>
page.ejs
我個人對Page模板其實是有點懵逼的,在我自己的實踐中是添加about(hexo new page "about")頁面后,訪問/about會走分頁布局,實際上這個頁面對應的內容是/source/about里的index.md,也相當於對文章的渲染,因此我把Page模板也寫成了和文章模板一樣:
//page.ejs
<%- partial('_partial/article', {index: false, post: page}) %>
_partial/article.ejs
前面一共有三處共用了article模板,另外page和post的一樣的,所以實際上只有兩種情況:主頁(index: true)和非主頁(index: false)。對應的_partial/article.ejs里只要判斷這個值就可以正確渲染了,基本結構如下:
//_partial/article.ejs
<% if(index){ %>
//index logic...
<% }else{ %>
//post or page logic...
<% } %>
tag.ejs
標簽歸檔頁內容很少,直接用Hexo的輔助函數list_tags生成一個標簽的列表就ok了:
//tag.ejs
<%- list_tags() %>
歸檔頁模板和首頁差不多,歸檔頁只需要展示文章標題和最后的分頁器就好:
//archive.ejs
<div class="archive">
<% var lastyear; %>
<% page.posts.each(function(post){ %>
<% var year = post.date.year() %>
<% if(lastyear !== year){ %>
<h4 class="year"><%= year %></h4>
<% lastyear = year %>
<% } %>
<div class="archive_item">
<a class="title" href="<%- url_for(post.path) %>"><%= post.title %></a>
<span class="date"><%= post.date.format('YYYY-MM-DD') %></span>
</div>
<% }) %>
<div class="pagination">
<%- paginator({ total: Math.ceil(site.posts.length / config.per_page)}) %>
</div>
</div>
至此,模板文件就寫好了,對於category模板就放棄了,感覺比較雞肋。。。
變量
其實在模板文件中我們已經看到了page.post,site.posts.length,config.per_page等等,頁面的內容就是根據這些變量獲取的,由Hexo提供,拿來直接用,Hexo提供了很多變量,但不是都很常用,一般就用到以下變量:
site: 對應整個網站的變量,一般會用到site.posts.length制作分頁器page: 對應當前頁面的信息,例如我在index.ejs中使用page.posts獲取了當前頁面的所有文章而不是使用site.posts。config: 博客的配置信息,博客根目錄下的_config.yml。theme: 主題的配置信息,對於主題根目錄下的_config.yml。
輔助函數(Helper)
制作一個分頁器,我們需要知道文章的總數和每頁展示的文章數,然后通過循環生成每個link標簽,還要根據當前頁面判斷link標簽的active狀態,但是在Hexo中這些都不用我們自己來做了!Hexo提供了paginator這一輔助函數幫助我們生成分頁器,只需要將文章總數site.posts.length和每頁文章數config.per_page傳入就可以生成了。
其他的Helper:
list_tags([options]): 快速生成標簽列表js(path/to/js),css(path/to/css)用來載入靜態資源,path可以是字符串或數組(載入多個資源),默認會去source文件夾下去找。partial(path/to/partial)引用字模板,默認會去layout文件夾下找。
樣式
知道了Hexo的渲染方式,我們就可以使用HTML標簽+CSS樣式個性化我們的主題了,推薦大家使用CSS預處理語言的一種來寫樣式,這樣就可以通過預處理語言自身的特點讓樣式更靈活。
其他
添加對多說和Disqus的支持
評論是很常用的功能,不如就直接在我們的主題里支持了,然后通過配置變量決定是否開啟,評論區跟在文章內容下面,對於這種三方的代碼塊,最好也以partial的方式提取出來,方便移除或是替換。
//_partial/article.ejs
<section class='post-content'>
<%- post.content %>
</section>
//評論部分,post.comments判斷是否開啟評論,config.duoshuo_shortname
和config.disqus_shortname來判斷啟用那種評論插件,這里優先判斷了多說
<% if(post.comments){ %>
<section id="comments">
<% if (config.duoshuo_shortname){ %>
<%- partial('_partial/duoshuo') %>
<% }else if(config.disqus_shortname){ %>
<%- partial('_partial/disqus') %>
<% } %>
</section>
<% } %>
再將多說和Disqus提供的js腳本代碼放在_partial/duoshuo.ejs和_partial/disqus.ejs下就ok了~
使用highlight.js提供代碼高亮
highlight.js提供了多種語言的支持和多種皮膚,用法也很簡單,載入文件后調用初始化方法,一切都幫你搞定,對於使用那種皮膚,喜好因人而異,我們干脆在主題的配置文件中做成配置項讓用戶自己選擇:
//showonne/_config.yml
...other configs
# highlight.js
highlight_theme: zenburn
對應的layout.ejs中:
樣式文件通過CDN引入,因為不同皮膚對應不同的文件名,所以十分靈活。
最后
當初是對應着landscape照葫蘆畫瓢寫的,最近回頭來發現一些不合理的地方,所以就又改了改,也對應着寫了這么一篇總結,接下來准備再把樣式划分一下,對於顏色這類樣式通過變量的方式提取出來,也變得可配置,能讓主題更靈活一些。
