Cabloy-CMS:動靜結合,解決Hexo痛點問題(進階篇)

zhennann發表於2018-10-21

前言

前一篇文章 介紹瞭如何通過Cabloy-CMS快速搭建一個部落格站點。

這裡簡單介紹Cabloy-CMS靜態站點的渲染機制,更多詳細的內容請參見cms.cabloy.com

渲染規則

渲染時機

為了平衡渲染效能,Cabloy-CMS提供了兩個渲染時機:一次構建文章單獨渲染

一次構建

CMS配置頁面,點選構建按鈕,一次性渲染並輸出站點所有檔案

文章單獨渲染

當釋出文章時,立即渲染文章,並渲染與文章相關的頁面。

比如首頁頁面:為了提升首頁載入效能,首頁可能會包含最近釋出的文章。所以,當文章單獨渲染時,也會再次渲染首頁

SEO相關

SEO檔案有三個:robots.txtsitemapindex.xmlsitemap.xml

SEO檔案均在構建時一次性輸出

sitemapindex.xml包含不同語言的sitemap.xml連結,一個語言對應一個sitemap.xml檔案

當文章單獨渲染時,會修改sitemap.xml的內容

目錄、標籤、搜尋

由於使用了站點地圖檔案,並且所有文章都已經渲染成靜態檔案,所以,目錄標籤搜尋等場景下的文章清單,沒必要提前渲染,只需在需要時通過ajax呼叫後端API獲取清單並動態顯示

CMS、主題、外掛

模組a-cms只提供了基本的渲染機制和渲染骨架,具體的頁面佈局、元素、功能,都通過主題外掛的組合實現。這種模式,既可以快速開發部署,也可以充分釋放CMS的可擴充套件性和靈活性

Cabloy-CMS目前提供了主題模組cms-themeblogcms-themeaws和外掛模組cms-pluginbasecms-pluginarticlecms-pluginsidebarcms-pluginmarkdowngithubcms-plugintrack,實現了全功能的部落格站點,後續也會持續推出一系列主題外掛

您可以自由組合主題外掛,甚至實現自己的主題外掛,呈現完全不同的站點效果。

也希望您能分享您的智慧與成果,加入到Cabloy的生態中來

檔案結構

Cabloy-CMS採用精細的檔案結構,帶來了如下便利:

  • 便於定製CSS、JS
  • 便於定製圖片等各類靜態資源
  • 便於實現多語言
  • 便於除錯與釋出

建議先把服務執行起來,並構建一次,就可以清晰的看到Cabloy-CMS的檔案結構

根目錄

在開發環境中,為了便於除錯,CMS檔案根目錄位於原始碼專案內部。而在生產環境中,原始碼專案可能是隻讀的,所以CMS檔案根目錄預設放置在當前使用者的Home目錄中。

開發環境

根目錄:[ProjectDir]/src/backend/app/public/[InstanceId]/cms

  • InstanceId: 例項Id,通過多例項可以實現多CMS站點的搭建

執行環境

根目錄:[HomeDir]/cabloy/[ProjectName]/public/[InstanceId]/cms

  • HomeDir: 預設為當前使用者的Home目錄,可以通過模組a-file配置

src/backend/config/config.prod.js

config.modules = {
  'a-file': {
    publicDir: 'CustomDir',
  },
};
複製程式碼

一級目錄

Cabloy-CMS:動靜結合,解決Hexo痛點問題(進階篇)

名稱 說明
dist 構建的輸出目錄
en-us/zh-cn 語言原始碼目錄

輸出目錄

Cabloy-CMS:動靜結合,解決Hexo痛點問題(進階篇)

名稱 說明 渲染時機 備註
articles 儲存所有渲染的文章頁面 一次構建
assets 資原始檔 一次構建
plugins 外掛的資原始檔 一次構建
static 靜態檔案 一次構建 如檔案articles.html,通過ajax呼叫後端API獲取文章清單,從而可以集中實現目錄標籤搜尋等功能
zh-cn 其他語言的檔案輸出目錄 支援多語言時,預設語言在根目錄下,其他語言在子目錄
index.html 首頁 兩個渲染時機 為了提升首頁載入效能,首頁可能會包含最近釋出的文章。所以,當文章單獨渲染時,也會再次渲染首頁
robots.txt SEO相關 一次構建 不論是否有多語言,只有一個robots.txt根目錄
sitemap.xml SEO相關,當前語言的站點地圖檔案 一次構建,文章單獨渲染時修改內容
sitemapindex.xml SEO相關,站點地圖檔案索引 一次構建 不論是否有多語言,只有一個sitemapindex.xml根目錄

語言原始碼目錄

Cabloy-CMS:動靜結合,解決Hexo痛點問題(進階篇)

名稱 說明 備註
intermediate 中間檔案目錄 在一次構建時,將主題外掛自定義原始碼的所有原始碼檔案和資源統一寫入intermediate目錄,然後再執行渲染邏輯
custom 自定義原始碼目錄 使用者可以在custom目錄新增自定義原始碼檔案,在一次性構建時,會自動覆蓋intermediate中相同路徑的檔案
custom/dist 特別輸出目錄 在實際生產環境中,會有一些第三方用途的檔案,如Google站點驗證檔案,可以放置在這個目錄,以便一次構建時輸出
名稱 說明 渲染時機 備註
assets 資原始檔 一次構建
layout 佈局目錄 中間檔案 layout不是官方強制定義的目錄。主題可根據自己的需要新增,規劃自己的頁面元素
main 主渲染模版目錄 兩個渲染時機
main/article.ejs 文章渲染模版 當需要渲染文章時使用此模版檔案
main/index 首頁渲染模版目錄 當需要渲染首頁時使用此目錄中的模版檔案。為什麼是目錄?在一個複雜的站點中,根據場景需要可以有多個類首頁模版檔案
plugins 外掛目錄 一次構建 在一次構建時,把所有外掛原始碼檔案和資源寫入plugins目錄
static 靜態檔案目錄 一次構建 如檔案articles.ejs,通過ajax呼叫後端API獲取文章清單,從而可以集中實現目錄標籤搜尋等功能

為什麼需要把所有原始碼檔案(主題外掛自定義原始碼)都寫入intermediate目錄?

  • 寫入一個目錄,便於各檔案之間的包含引用

渲染流程

Cabloy-CMS提供了兩個渲染時機:一次構建文章單獨渲染,下面分別描述兩個時機的渲染流程

合併站點配置

在渲染之前,先合併站點配置資訊

Cabloy-CMS:動靜結合,解決Hexo痛點問題(進階篇)

一次構建

Cabloy-CMS:動靜結合,解決Hexo痛點問題(進階篇)

文章單獨渲染

Cabloy-CMS:動靜結合,解決Hexo痛點問題(進階篇)

後端上下文物件

Cabloy-CMS採用ejs模版引擎進行頁面渲染,在渲染之前建立一個上下文物件,歸集相關的資料和方法,以便在模版檔案中使用

上下文物件結構

{
  ctx: [Object],
  site: [Object],
  require: [Function],
  url: [Function],
  css: [Function],
  js: [Function],
  env: [Function],
  text: [Function],
  util: {
    time: {
      now: [Function],
      today: [Function],
      formatDateTime: [Function],
      formatDate: [Function],
      formatTime: [Function]
    },
    formatDateTime: [Function]
  },
  article: [Object],
  _path: [String]
}
複製程式碼
名稱 型別 說明
ctx 屬性 通過ctx物件可以呼叫後端API及各種資源
site 屬性 站點配置資訊
require 方法 引用模組
url 方法 構造絕對連結
css 方法 宣告css檔案,以便最後合併和最小化
js 方法 宣告js檔案,以便最後合併和最小化
env 方法 注入環境變數,以便輸出到前端使用
text 方法 文字國際化
util 屬性 工具函式
article 屬性 當前渲染的文章資訊
_path 屬性 標示當前模版檔案的相對路徑(相對於目錄intermediate)

訪問後端資源

通過ctx物件可以呼叫後端API及各種資源

比如,為了渲染選單,需要獲取目錄樹,可以如下操作

const res = await ctx.performAction({
  method:'post',
  url: '/a/cms/category/tree',
  body: { language:site.language.current,hidden:0 },
});
const tree=res.list;
複製程式碼

引用模組

在.ejs檔案中,也可以像在NodeJS中一樣引用模組

// 引用node_modules中的模組
const moment=require('moment');
// 引用專案內的檔案模組
const test=require('./test.js');
複製程式碼

絕對地址

建議頁面中所有資源的URL連結都渲染成絕對地址

// 相對於網站根目錄
<%=url('assets/images/background.png')%>
// 相對於當前檔案
<%=url('./fonts/github/700i.woff')%>
複製程式碼

合併和最小化CSS、JS

在渲染過程中,先宣告CSS和JS檔案,然後在最後進行合併和最小化。在渲染模版中提供佔位符,替換為生成的實際URL連結

宣告CSS、JS

// css
css('../assets/css/markdown/github.css.ejs');
css('../assets/css/article.css');
css('../assets/css/sidebar.css');
// js
js('../assets/js/lib/json2.min.js');
js('../assets/js/lib/bootbox.min.js');
js('../assets/js/util.js.ejs');
js('../assets/js/article.js.ejs');
js('../assets/js/sidebar.js.ejs');
複製程式碼

如果引用的CSS、JS檔案字尾名為'.ejs',也會作為ejs模版進行渲染

佔位符

// CSS檔案連結佔位符
<link rel="stylesheet" href="__CSS__">
// JS檔案連結佔位符
<script src="__JS__"></script>
複製程式碼

效果

<link rel="stylesheet" href="https://zhennann.me/assets/css/8d38154d198309325c0759a22213dbd6ff0b7edecd2f4868dc72311335ccbe25.css">
<script src="https://zhennann.me/assets/js/b17e06ccb536dee939d4b1deaa595436363a52769c210d74d6a77f011e0f6461.js"></script>
複製程式碼

注入環境引數

為了便於前端實現靈活且豐富的功能邏輯,需要把一些環境引數注入到前端。後端通過env宣告環境引數,這些引數最後會進行合併注入到前端。

同樣,也需要在前端提供佔位符,替換為生成的實際環境引數

宣告env

env('index',{
  [_path]:data.index,
});
複製程式碼

佔位符

// CSS檔案連結佔位符
<link rel="stylesheet" href="__CSS__">
// ENV佔位符
__ENV__
複製程式碼

效果

<script type="text/javascript">
var env={
  "base": ...,
  "language": ...,
  "format": ...,
  "comment": ...,
  "site": ...,
  "index": {
    "main/index/index": 20
  }
};
</script>
複製程式碼

國際化

如果需要讓主題外掛可以應用於不同的語言,需要對其中用到的文字資源進行國際化處理

因為主題外掛本質上都是EggBorn模組,所以可以直接使用EggBorn模組提供的國際化機制

比如,外掛cms-pluginbase提供了無限滾動的功能,如果載入失敗需要在頁面中提示Load error, try again,可以如下操作

定義語言資源

cms-pluginbase/backend/src/config/locale/zh-cn.js

module.exports = {
  'Load error, try again': '載入失敗,請重試',
};
複製程式碼

引用

cms-pluginbase/backend/cms/plugin/assets/js/util.js.ejs

const $buttonTry = $('<button type="button" class="btn btn-warning btn-xs"><%=text("Load error, try again")%></button>');
複製程式碼

路徑標示:_path

一個通用的ejs模版檔案可能被多個主ejs模版檔案包含引用。通過_path,可以在通用ejs模版檔案中知曉當前被哪個主ejs模版檔案引用,以便做不同的邏輯處理

前端環境物件

為了便於前端實現靈活且豐富的功能邏輯,需要把一些環境引數注入到前端。

Cabloy-CMS本身內建了一些前端環境物件,同時,也可以通過後端上下文物件env方法注入自定義屬性,這些引數最後會進行合併注入到前端

注入env

env('index',{
  [_path]:data.index,
});
複製程式碼

佔位符

// CSS檔案連結佔位符
<link rel="stylesheet" href="__CSS__">
// ENV佔位符
__ENV__
複製程式碼

前端環境物件結構

<script type="text/javascript">
var env={
  "base": {
    "title": "my blog",
    "subTitle": "gone with the wind",
    "description": "",
    "keywords": ""
  },
  "language": {
    "items": "en-us,zh-cn",
    "default": "en-us",
    "current": "en-us"
  },
  "format": {
    "date": "YYYY-MM-DD",
    "time": "HH:mm:ss"
  },
  "comment": {
    "order": "asc",
    "recentNum": 5
  },
  "site": {
    "path": "main/article",
    "serverUrl": "https://zhennann.cabloy.org",
    "rawRootUrl": "https://zhennann.me"
  },
  "article": ...,
  "index": {
    "main/index/index": 20
  }
};
</script>
複製程式碼
名稱 來源 說明
base 站點配置 站點基本資訊
language 站點配置 語言資訊
format 站點配置 時間格式化
comment 站點配置 評論引數
site 內建引數 站點引數
site.path 當前頁面路徑標示
site.serverUrl 後端服務URL字首
site.rawRootUrl 前端站點URL字首
article 內建引數 如果是文章頁面,會自動注入此屬性
index 自定義引數 由主題cms-themeblog注入的引數

製作主題

主題既可以全新制作,也可以繼承自其他主題

在這裡新建一個主題模組test-cmsthemehello,在首頁渲染一行Hello world

新建主題模組

主題本質上也是EggBorn模組

進入專案目錄,執行EggBorn提供的腳手架建立一個新模組

$ cd /path/to/project
$ egg-born src/module/test-cmsthemehello --type=module
複製程式碼

修改package.json

test-cmsthemehello/package.json

{
  "name": "egg-born-module-test-cmsthemehello",
  "version": "1.0.0",
  "title": "cms:theme:hello",
  "eggBornModule": {
    "cms": {
      "name": "hello",
      "theme": true,
      "extend": ""
    },
    ...
  },
  "dependencies": {
    ...
    "egg-born-module-cms-pluginbase": "^1.1.1",
    "egg-born-module-cms-pluginarticle": "^1.0.0",
    "egg-born-module-cms-pluginsidebar": "^1.0.0",
    "egg-born-module-cms-pluginmarkdowngithub": "^1.0.0",
    "egg-born-module-cms-plugintrack": "^1.0.1"
  }
}
複製程式碼
  • name: 必須按照EggBorn模組的命名規範: egg-born-module-{providerId}-{moduleName}
    • providerId: 開發者Id,強烈建議採用Github的Username,從而確保貢獻到社群的模組不會衝突
  • cms: CMS配置資訊
    • name: 主題名稱
    • theme: 宣告本模組是一個主題
    • extend: 如果要繼承主題,填入原主題的模組名如cms-themeblog
  • dependencies: 如果使用了外掛,在這裡填入外掛模組資訊。如果繼承了主題,也需要在這裡填入原主題的模組資訊

配置引數

主題可以提供自定義的引數

test-cmsthemehello/backend/src/config/config.js

module.exports = appInfo => {
  const config = {};

  // theme
  config.theme = {
    _message: 'Hello World',
  };

  return config;
};
複製程式碼

建立首頁渲染模版

test-cmsthemehello/backend/cms/theme/main/index/index.ejs

<html>
 <head></head>
 <body><%=site._message%></body>
</html>
複製程式碼

其他原始碼及資源

根據需要新增其他原始碼及資源,這裡從略

構建模組

作為EggBorn模組,如果在專案內部使用,不需要構建,可以直接使用。如果分享到社群,供其他使用者安裝使用,必須進行構建

$ cd src/module/test-cmsthemehello   -- 進入模組目錄
$ npm run build:front             -- 構建前端程式碼
$ npm run build:backend           -- 構建後端程式碼
複製程式碼

釋出模組

可以將製作好的模組釋出到社群

$ npm publish
複製程式碼

製作外掛

在這裡新建一個外掛模組test-cmspluginhello,在頁面載入完成時彈出提示Hello world

新建外掛模組

外掛本質上也是EggBorn模組

進入專案目錄,執行EggBorn提供的腳手架建立一個新模組

$ cd /path/to/project
$ egg-born src/module/test-cmspluginhello --type=module
複製程式碼

修改package.json

test-cmspluginhello/package.json

{
  "name": "egg-born-module-test-cmspluginhello",
  "version": "1.0.0",
  "title": "cms:plugin:hello",
  "eggBornModule": {
    "cms": {
      "name": "hello",
      "plugin": true
    },
  },
  ...
}
複製程式碼
  • name: 必須按照EggBorn模組的命名規範: egg-born-module-{providerId}-{moduleName}
    • providerId: 開發者Id,強烈建議採用Github的Username,從而確保貢獻到社群的模組不會衝突
  • cms: CMS配置資訊
    • name: 主題名稱
    • plugin: 宣告本模組是一個外掛

配置引數

外掛可以提供自定義的引數

test-cmspluginhello/backend/src/config/config.js

module.exports = appInfo => {
  const config = {};

  // plugin
  config.plugin = {
    _message: 'Hello World',
  };

  return config;
};
複製程式碼

建立初始指令碼

test-cmspluginhello/backend/cms/plugin/init.js.ejs

$(document).ready(function() {
  // alert
  const message='<%=site.plugins['test-cmspluginhello']._message%>';
  window.alert(message);
});
複製程式碼

指令碼如何引用

只需在渲染模版中宣告JS檔案即可

在這裡,可以在主題test-cmsthemehello的首頁模版中引用

test-cmsthemehello/backend/cms/theme/main/index/index.ejs

<% js('plugins/test-cmspluginhello/init.js.ejs') %>
<html>
 <head></head>
 <body>
  <div><%=site._message%></div>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
  <script src="__JS__"></script>
</body>
</html>
複製程式碼

其他原始碼及資源

根據需要新增其他原始碼及資源,這裡從略

構建模組

作為EggBorn模組,如果在專案內部使用,不需要構建,可以直接使用。如果分享到社群,供其他使用者安裝使用,必須進行構建

$ cd src/module/test-cmspluginhello  -- 進入模組目錄
$ npm run build:front             -- 構建前端程式碼
$ npm run build:backend           -- 構建後端程式碼
複製程式碼

釋出模組

可以將製作好的模組釋出到社群

$ npm publish
複製程式碼

終極篇

請允許再次強調,主題外掛本質上還是EggBorn模組,可以新增前端頁面後端服務

大象無形,終極武器掌握在您的手中,能呈現出什麼效果,完全取決於您的想象力

歡迎貢獻您的智慧和產品到社群,謝謝!

GitHub貢獻

有任何疑問,歡迎提交 issue

相關文章