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

前言

前一篇文章
介紹瞭如何經過Cabloy-CMS快速搭建一個博客站點。javascript

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

渲染規則

渲染時機

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

一次構建

CMS配置頁面,點擊構建按鈕,一次性渲染並輸出站點全部文件前端

文章單獨渲染

當發佈文章時,當即渲染文章,並渲染與文章相關的頁面。java

好比首頁頁面:爲了提高首頁加載性能,首頁可能會包含最近發佈的文章。因此,當文章單獨渲染時,也會再次渲染首頁node

SEO相關

SEO文件有三個:robots.txtsitemapindex.xmlsitemap.xmljquery

SEO文件均在構建時一次性輸出git

sitemapindex.xml包含不一樣語言的sitemap.xml連接,一個語言對應一個sitemap.xml文件github

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

目錄、標籤、搜索

因爲使用了站點地圖文件,而且全部文章都已經渲染成靜態文件,因此,目錄標籤搜索等場景下的文章清單,不必提早渲染,只需在須要時經過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',
  },
};

一級目錄

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

輸出目錄

名稱 說明 渲染時機 備註
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根目錄

語言源碼目錄

名稱 說明 備註
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採用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
相關文章
相關標籤/搜索