前言
前一篇文章 介紹瞭如何通過Cabloy-CMS快速搭建一個部落格站點。
這裡簡單介紹Cabloy-CMS靜態站點的渲染機制,更多詳細的內容請參見cms.cabloy.org
渲染規則
渲染時機
為了平衡渲染效能,Cabloy-CMS提供了兩個渲染時機:一次構建
、文章單獨渲染
一次構建
在CMS配置
頁面,點選構建
按鈕,一次性渲染並輸出站點所有檔案
文章單獨渲染
當釋出文章時,立即渲染文章,並渲染與文章相關的頁面。
比如首頁
頁面:為了提升首頁載入效能,首頁可能會包含最近釋出的文章。所以,當文章
單獨渲染時,也會再次渲染首頁
SEO相關
SEO檔案有三個:robots.txt
、sitemapindex.xml
、sitemap.xml
SEO檔案均在構建
時一次性輸出
sitemapindex.xml
包含不同語言的sitemap.xml
連結,一個語言對應一個sitemap.xml
檔案
當文章單獨渲染時,會修改sitemap.xml
的內容
目錄、標籤、搜尋
由於使用了站點地圖檔案,並且所有文章都已經渲染成靜態檔案,所以,目錄
、標籤
、搜尋
等場景下的文章清單,沒必要提前渲染,只需在需要時通過ajax呼叫後端API獲取清單並動態顯示
CMS、主題、外掛
模組a-cms
只提供了基本的渲染機制和渲染骨架,具體的頁面佈局、元素、功能,都通過主題
和外掛
的組合實現。這種模式,既可以快速開發部署,也可以充分釋放CMS的可擴充套件性和靈活性
Cabloy-CMS目前提供了主題模組cms-themeblog
、cms-themeaws
和外掛模組cms-pluginbase
、cms-pluginarticle
、cms-pluginsidebar
、cms-pluginmarkdowngithub
、cms-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!