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],
    safeHtml: [Function],
    escapeHtml: [Function],
    escapeURL: [Function]
  },
  article: [Object],
  _path: [String]
}
名称 类型 说明
ctx 属性 通过ctx对象可以调用后端API接口,及挂载在ctx对象上的各类功能
site 属性 站点配置信息
require 方法 引用模块
url 方法 构造绝对链接
css 方法 声明css文件,以便最后合并和最小化
js 方法 声明js文件,以便最后合并和最小化
env 方法 注入环境变量,以便输出到前端使用
text 方法 文本国际化
util 属性 工具函数
util.safeHtml 方法 对Html字符串进行安全过滤处理
util.escapeHtml 方法 对Html字符串进行安全转义处理
util.escapeURL 方法 对URL字符串进行安全转义处理
article 属性 当前渲染的文章信息
_path 属性 标示当前模版文件的相对路径(相对于目录intermediate)

访问后端资源

通过ctx对象可以调用后端API接口,及挂载在ctx对象上的各类功能

比如,为了渲染菜单,需要获取目录树,可以如下操作

src/module-system/cms-pluginarticle

// categories
const categories = await ctx.performAction({
  method: 'post',
  url: '/a/base/category/tree',
  body: {
    atomClass: site.atomClass,
    language: site.language.current,
    categoryId: 0,
    categoryHidden: 0,
  },
});

引用模块

.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,
});

- 占位符

// ENV占位符
_ _ENV_ _

- 效果

<script type="text/javascript">
var env={
  "base": ...,
  "language": ...,
  "format": ...,
  "comment": ...,
  "site": ...,
  "index": {
    "main/index/index": 20
  }
};
</script>

国际化

如果需要让主题插件可以应用于不同的语言,需要对其中用到的文本资源进行国际化处理

因为主题插件本质上都是EggBornJS模块,所以可以直接使用EggBornJS模块提供的国际化机制

比如,插件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模版文件引用,以便做不同的逻辑处理

安全处理:escapeHtml/escapeURL

在ejs模版中输出任何资源时,都要牢记安全处理

1. safeHtml

注意,将富文本(包含 HTML 代码的文本)当成变量直接在模版里面输出时,需要用到 safeHtml 来处理。 使用 safeHtml 可以输出 HTML 的 tag,同时执行 XSS 的过滤动作,过滤掉非法的脚本

由于是一个非常复杂的安全处理过程,对服务器处理性能一定影响,如果不是输出 HTML,请勿使用

2. escapeHtml

对Html字符串进行安全转义处理,进行如下字符替换:

原字符 替换字符
& &amp;
< &gt;
> &gt;
" &quot;
&#039;

3. escapeURL

对URL字符串进行安全转义处理,进行如下字符替换:

原字符 替换字符
< %3C
> %3E
" %22
%27