絕大多數(shù)情況,我們都需要讀取數(shù)據(jù)后渲染模板,然后呈現(xiàn)給用戶,而框架并不強(qiáng)制使用某種模板引擎,由開發(fā)者來自行選型,具體參見模板渲染。
本文將闡述框架對 View 插件的規(guī)范約束, 我們可以依此來封裝對應(yīng)的模板引擎插件。以下以 egg-view-ejs 為例。
插件目錄結(jié)構(gòu)
egg-view-ejs ├── config │ ├── config.default.js │ └── config.local.js ├── lib │ └── view.js ├── app.js ├── test ├── History.md ├── README.md └── package.json
|
插件命名規(guī)范
- 遵循插件開發(fā)規(guī)范
- 插件命名約定以 egg-view- 開頭
- package.json 配置如下,插件名以模板引擎命名,比如 ejs{ "name": "egg-view-ejs", "eggPlugin": { "name": "ejs" }, "keywords": [ "egg", "egg-plugin", "egg-view", "ejs" ],}
- 配置項(xiàng)也以模板引擎命名// config/config.default.jsmodule.exports = { ejs: {},};
View 基類
接下來需提供一個(gè) View 基類,這個(gè)類會在每次請求實(shí)例化。
View 基類需提供 render 和 renderString 兩個(gè)方法,支持 generator function 和 async function(也可以是函數(shù)返回一個(gè) Promise)。render 方法用于渲染文件,而 renderString 方法用于渲染模板字符串。
以下為簡化代碼,可直接查看源碼
const ejs = require('ejs');
module.exports = class EjsView {
render(filename, locals) { return new Promise((resolve, reject) => { // 異步調(diào)用 API ejs.renderFile(filename, locals, (err, result) => { if (err) { reject(err); } else { resolve(result); } }); }); }
renderString(tpl, locals) { try { // 同步調(diào)用 API return Promise.resolve(ejs.render(tpl, locals)); } catch (err) { return Promise.reject(err); } } };
|
參數(shù)
render 方法的三個(gè)參數(shù)
- filename: 是完整的文件的路徑,框架查找文件時(shí)已確認(rèn)文件是否存在,這里不需要處理
- locals: 渲染所需的數(shù)據(jù),數(shù)據(jù)來自 app.locals,ctx.locals 和調(diào)用 render 方法傳入的。框架還內(nèi)置了 ctx,request, ctx.helper 這幾個(gè)對象。
- viewOptions: 用戶傳入的配置,可覆蓋模板引擎的默認(rèn)配置,這個(gè)可根據(jù)模板引擎的特征考慮是否支持。比如默認(rèn)開啟了緩存,而某個(gè)頁面不需要緩存。
renderString 方法的三個(gè)參數(shù)
- tpl: 模板字符串,沒有文件路徑。
- locals: 同 render。
- viewOptions: 同 render。
插件配置
根據(jù)上面的命名約定,配置名一般為模板引擎的名字,比如 ejs。
插件的配置主要來自模板引擎的配置,可根據(jù)具體情況定義配置項(xiàng),如 ejs 的配置。
// config/config.default.js module.exports = { ejs: { cache: true, } };
|
helper
框架本身提供了 ctx.helper 供開發(fā)者使用,但有些情況下,我們希望對 helper 方法進(jìn)行覆蓋,僅在模板渲染時(shí)生效。
在模板渲染中,我們經(jīng)常會需要輸出用戶提供的 html 片段,通常需要使用 egg-security 插件提供的 helper.shtml 清洗下
<div>{{ helper.shtml(data.content) | safe }}</div>
|
但如上代碼所示,我們需要加上 | safe 來告知模板引擎,該 html 是安全的,無需再次 escape,直接渲染。
而這樣用起來比較麻煩,而且容易遺忘,所以我們可以封裝下:
// {plugin_root}/lib/helper.js module.exports = app => { return class ViewHelper extends app.Helper { // safe 由 [egg-view-nunjucks] 注入,在渲染時(shí)不會轉(zhuǎn)義, // 否則在模板調(diào)用 shtml 會被轉(zhuǎn)義 shtml(str) { return this.safe(super.shtml(str)); } } };
|
// {plugin_root}/lib/view.js const ViewHelper = require('./helper');
module.exports = class MyCustomView { render(filename, locals) { locals.helper = new ViewHelper(this.ctx);
// 調(diào)用 Nunjucks render } }
|
具體代碼可查看
安全相關(guān)
模板和安全息息相關(guān),egg-security 也給模板提供了一些方法,模板引擎可以根據(jù)需求使用。
首先聲明對 egg-security 的依賴:
{ "name": "egg-view-nunjucks", "eggPlugin": { "name": "nunjucks", "dep": [ "security" ] } }
|
此外,框架提供了 app.injectCsrf 和 app.injectNonce,更多可查看安全章節(jié)。
單元測試
作為一個(gè)高質(zhì)量的插件,完善的單元測試是必不可少的,我們也提供了很多輔助工具使插件開發(fā)者可以無痛的編寫測試,具體參見單元測試和插件中的相關(guān)內(nèi)容。
更多建議: