Cabloy-CMS:动静结合,解决Hexo痛点问题(进阶篇)
前言
前一篇文章
介绍了如何通过Cabloy-CMS快速搭建一个博客站点。
这里简单介绍Cabloy-CMS静态站点的渲染机制,更多详细的内容请参见https://cms.cabloy.com
渲染规则
渲染时机
为了平衡渲染性能,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!
Cabloy-CMS:动静结合,解决Hexo痛点问题(进阶篇)的更多相关文章
- Cabloy-CMS:动静结合,解决Hexo痛点问题
介绍 Cabloy-CMS是什么 Cabloy-CMS是基于CabloyJS全栈业务开发框架开发的"动静结合"的CMS,可以快速构建企业网站.博客.社区.商城等Web应用. 在线演 ...
- webpack多页应用架构系列(一):一步一步解决架构痛点
这系列文章讲什么? 前些时间,写过一个项目,前后端分离,没有借助任何框架,项目页面特别的多,页面都是html直接写的,许多公共html,写了好多处,有一个地方需要改就得改好多地方,js也是随意写,每个 ...
- 解决hexo神烦的DTraceProviderBindings MODULE_NOT_FOUND
原文:http://kikoroc.com/2016/05/04/resolve-hexo-DTraceProviderBindings-MODULE-NOT-FOUND.html 今晚折腾hexo的 ...
- 解决Hexo博客模板hexo-theme-next的翻页按钮不正常显示问题
用Hexo搭了个Gitpage的博客,兴冲冲的发了11篇博文后发现翻页按钮不正常显示,显示为<i class="fa fa-angle-right"></i> ...
- 【vuejs深入一】深入学习vue指令,自定义指令解决开发痛点
写在前面 一个好的架构需要经过血与火的历练,一个好的工程师需要经过无数项目的摧残. 最近博主我沉淀了几个月,或者说懒了几个月.然而大佬的指点总是一针见血,能够让人看到方向.所以我现在有觉得,一个好的 ...
- 深入学习vue指令,自定义指令解决开发痛点
每天学习一点点 编程PDF电子书.视频教程免费下载:http://www.shitanlife.com/code v-model指令 vue.js的定义是一个mvvm框架,将它发挥到极致能够极大的提升 ...
- 解决hexo报错spwan failed
报错1 FATAL { err: Error: Spawn failed at ChildProcess.<anonymous> (/usr/local/src/hexo/cairbin/ ...
- hexo摸爬滚打之进阶教程
本文首发在我的个人博客:http://muyunyun.cn/ 写博客有三个层次,第一层次是借鉴居多的博文,第二层次是借鉴后经过消化后有一定量产出的博文,第三层次是原创好文居多的博文.在参考了大量前辈 ...
- hexo博客进阶-相册和独立域名
之前我已经写了一篇文章详细的讲述了如何使用hexo搭建github博客.如果还没有看的可以去看看,hexo搭建博客 其实,根据这篇文章的过程我们就能够搭建一个专属于自己,并且非常美观的博客了.但是如果 ...
随机推荐
- oracle 正则表达的使用
最近遇到有个项目,需要根据文件存储的根目录地址来判断是在云端获取,还是本地获取, 先看下具体有几个不同的根目录: , , 'i') from pmc.designmaterial d 去重关键字:di ...
- imwrite imshow机制
今天在做数据增强的时候,遇到一个奇怪的问题.调用imwite生成的图片,在本地用图片查看器打开的时候是正常的.但是在代码里imshow的时候是一片亮白. 代码类似如下 gaussian_img = a ...
- 终于找到可以一文多发的平台了! openwrite.cn
openwrite.cn 一文多发平台 有时候自己辛苦写了几个小时的技术文章,被爬虫抓走.自己去全平台一个一个发,又过于麻烦.而且每个平台都不一样,发文同步很困难.那么终于有了一款一文多发的利器:Op ...
- C++ 重载运算符(详)
C++ 重载运算符 C 重载运算符 一重载函数 1例程 2备注 二重载运算符 11 二元运算符重载 11 一元运算符重载 111 -- 2备注 3 特殊运算符重载 31 号运算符 32 下标运算符 3 ...
- 使用VS Code 开发.NET CORE 程序指南
1. 前言 近两年来,很多前端的同学都开始将 VSCode 作为前端主力开发工具,其丰富的扩展给程序开发尤其是前端开发带来了很多便利,但是作为微软主力语言的 .NET,却由于有宇宙第一编辑器 Visu ...
- 读书分享全网学习资源大合集,推荐Python学习手册等三本书「01」
0.前言 在此之前,我已经为准备学习python的小白同学们准备了轻量级但超无敌的python开发利器之visio studio code使用入门系列.详见 1.PYTHON开发利器之VS Code之 ...
- unity之加载场景
游戏中的Loading分为:静态Loading和动态Loading. 简单形象的做个比喻: 静态Loading可能就是一张背景图.而动态的Loading就是在读取的同时有一个东西在“转圈”. 1.静态 ...
- 使用SpringSecurity保护程序安全
首先,引入依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId&g ...
- 【linux】【root权限的掌控】
前言: 喜欢玩linux的都知道root权限是一个很重要的东西.因为linux里面万物皆文件,对于权限的掌控也就达到了一个前所未有的限制(不然随便一个用户rm -rf /*不就全完了,,哈哈). 下面 ...
- library not found for -ljpush-ios-3.2.1错误
很多人在更新pod后报 library not found for -ljpush-ios-3.2.1(举例)错误,这其实是包含版本号类型错误. 究其原因:使用了版本号做库名字,pod升级后 Podf ...