- View 插件开发
- 插件目录结构
- 插件命名规范
- View 基类
- 参数
- 插件配置
- helper
- 安全相关
- 单元测试
View 插件开发
绝大多数情况,我们都需要读取数据后渲染模板,然后呈现给用户,而框架并不强制使用某种模板引擎,由开发者来自行选型,具体参见模板渲染。
本文将阐述框架对 View 插件的规范约束, 我们可以依此来封装对应的模板引擎插件。以下以 egg-view-ejs 为例。
插件目录结构
egg-view-ejs├── config│ ├── config.default.js│ └── config.local.js├── lib│ └── view.js├── app.js├── test├── History.md├── README.md└── package.json
插件命名规范
- 遵循插件开发规范
- 插件命名约定以
egg-view-开头 package.json配置如下,插件名以模板引擎命名,比如 ejs{"name": "egg-view-ejs","eggPlugin": {"name": "ejs"},"keywords": ["egg","egg-plugin","egg-view","ejs"],}
配置项也以模板引擎命名
// config/config.default.jsmodule.exports = {ejs: {},};
View 基类
接下来需提供一个 View 基类,这个类会在每次请求实例化。
View 基类需提供 render 和 renderString 两个方法,支持 generator function 和 async function(也可以是函数返回一个 Promise)。render 方法用于渲染文件,而 renderString 方法用于渲染模板字符串。
以下为简化代码,可直接查看源码
const ejs = require('ejs');module.exports = class EjsView {render(filename, locals) {return new Promise((resolve, reject) => {// 异步调用 APIejs.renderFile(filename, locals, (err, result) => {if (err) {reject(err);} else {resolve(result);}});});}renderString(tpl, locals) {try {// 同步调用 APIreturn Promise.resolve(ejs.render(tpl, locals));} catch (err) {return Promise.reject(err);}}};
参数
render 方法的三个参数
- filename: 是完整的文件的路径,框架查找文件时已确认文件是否存在,这里不需要处理
- locals: 渲染所需的数据,数据来自
app.locals,ctx.locals和调用render方法传入的。框架还内置了ctx,request,ctx.helper这几个对象。 - viewOptions: 用户传入的配置,可覆盖模板引擎的默认配置,这个可根据模板引擎的特征考虑是否支持。比如默认开启了缓存,而某个页面不需要缓存。
renderString 方法的三个参数
- tpl: 模板字符串,没有文件路径。
- locals: 同
render。 - viewOptions: 同
render。
插件配置
根据上面的命名约定,配置名一般为模板引擎的名字,比如 ejs。
插件的配置主要来自模板引擎的配置,可根据具体情况定义配置项,如 ejs 的配置。
// config/config.default.jsmodule.exports = {ejs: {cache: true,}};
helper
框架本身提供了 ctx.helper 供开发者使用,但有些情况下,我们希望对 helper 方法进行覆盖,仅在模板渲染时生效。
在模板渲染中,我们经常会需要输出用户提供的 html 片段,通常需要使用 egg-security 插件提供的 helper.shtml 清洗下
<div>{{ helper.shtml(data.content) | safe }}</div>
但如上代码所示,我们需要加上 | safe 来告知模板引擎,该 html 是安全的,无需再次 escape,直接渲染。
而这样用起来比较麻烦,而且容易遗忘,所以我们可以封装下:
- 先提供一个 helper 子类:
// {plugin_root}/lib/helper.jsmodule.exports = app => {return class ViewHelper extends app.Helper {// safe 由 [egg-view-nunjucks] 注入,在渲染时不会转义,// 否则在模板调用 shtml 会被转义shtml(str) {return this.safe(super.shtml(str));}}};
- 在渲染时使用自定义的 helper
// {plugin_root}/lib/view.jsconst ViewHelper = require('./helper');module.exports = class MyCustomView {render(filename, locals) {locals.helper = new ViewHelper(this.ctx);// 调用 Nunjucks render}}
具体代码可查看
安全相关
模板和安全息息相关,egg-security 也给模板提供了一些方法,模板引擎可以根据需求使用。
首先声明对 egg-security 的依赖:
{"name": "egg-view-nunjucks","eggPlugin": {"name": "nunjucks","dep": ["security"]}}
此外,框架提供了 app.injectCsrf 和 app.injectNonce,更多可查看安全章节。
单元测试
作为一个高质量的插件,完善的单元测试是必不可少的,我们也提供了很多辅助工具使插件开发者可以无痛的编写测试,具体参见单元测试和插件中的相关内容。
