前一篇文章
介紹瞭如何經過Cabloy-CMS快速搭建一個博客站點。javascript
這裏簡單介紹Cabloy-CMS靜態站點的渲染機制,更多詳細的內容請參見https://cms.cabloy.comcss
爲了平衡渲染性能,Cabloy-CMS提供了兩個渲染時機:一次構建
、文章單獨渲染
html
在CMS配置
頁面,點擊構建
按鈕,一次性渲染並輸出站點全部文件前端
當發佈文章時,當即渲染文章,並渲染與文章相關的頁面。java
好比首頁
頁面:爲了提高首頁加載性能,首頁可能會包含最近發佈的文章。因此,當文章
單獨渲染時,也會再次渲染首頁
node
SEO文件有三個:robots.txt
、sitemapindex.xml
、sitemap.xml
jquery
SEO文件均在構建
時一次性輸出git
sitemapindex.xml
包含不一樣語言的sitemap.xml
連接,一個語言對應一個sitemap.xml
文件github
當文章單獨渲染時,會修改sitemap.xml
的內容ajax
因爲使用了站點地圖文件,而且全部文章都已經渲染成靜態文件,因此,目錄
、標籤
、搜索
等場景下的文章清單,不必提早渲染,只需在須要時經過ajax調用後端API獲取清單並動態顯示
模塊a-cms
只提供了基本的渲染機制和渲染骨架,具體的頁面佈局、元素、功能,都經過主題
和插件
的組合實現。這種模式,既能夠快速開發部署,也能夠充分釋放CMS的可擴展性和靈活性
Cabloy-CMS目前提供了主題模塊cms-themeblog
、cms-themeaws
和插件模塊cms-pluginbase
、cms-pluginarticle
、cms-pluginsidebar
、cms-pluginmarkdowngithub
、cms-plugintrack
,實現了全功能的博客站點,後續也會持續推出一系列主題
和插件
您能夠自由組合主題
和插件
,甚至實現本身的主題
和插件
,呈現徹底不一樣的站點效果。
也但願您能分享您的智慧與成果,加入到Cabloy的生態中來
Cabloy-CMS採用精細的文件結構,帶來了以下便利:
建議先把服務運行起來,並
構建
一次,就能夠清晰的看到Cabloy-CMS的文件結構
在開發環境中,爲了便於調試,CMS文件根目錄
位於源代碼項目內部。而在生產環境中,源代碼項目多是隻讀的,因此CMS文件根目錄
缺省放置在當前用戶的Home目錄中。
根目錄:[ProjectDir]/src/backend/app/public/[InstanceId]/cms
根目錄:[HomeDir]/cabloy/[ProjectName]/public/[InstanceId]/cms
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文件,而後在最後進行合併和最小化。在渲染模版中提供佔位符,替換爲生成的實際URL連接
// 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('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>');
一個通用的ejs模版文件可能被多個主ejs模版文件包含引用。經過_path,能夠在通用ejs模版文件中知曉當前被哪一個主ejs模版文件引用,以便作不一樣的邏輯處理
爲了便於前端實現靈活且豐富的功能邏輯,須要把一些環境參數注入到前端。
Cabloy-CMS自己內置了一些前端環境對象,同時,也能夠經過後端上下文對象
的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
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}
cms: CMS配置信息
theme
: 聲明本模塊是一個主題extend
: 若是要繼承主題,填入原主題的模塊名如cms-themeblog
主題
能夠提供自定義的參數
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
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}
cms: CMS配置信息
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模塊,能夠添加前端頁面
和後端服務
大象無形
,終極武器掌握在您的手中,能呈現出什麼效果,徹底取決於您的想象力
歡迎貢獻您的智慧和產品到社區,謝謝!
有任何疑問,歡迎提交 issue!