前一篇文章
介绍了如何经过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" } }
egg-born-module-{providerId}-{moduleName}
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 }, }, ... }
egg-born-module-{providerId}-{moduleName}
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!