egg-view
Version:
Base view plugin for egg
116 lines (101 loc) • 3.95 kB
JavaScript
;
const path = require('path');
const assert = require('assert');
const RENDER = Symbol.for('contextView#render');
const RENDER_STRING = Symbol.for('contextView#renderString');
const GET_VIEW_ENGINE = Symbol.for('contextView#getViewEngine');
const SET_LOCALS = Symbol.for('contextView#setLocals');
/**
* View instance for each request.
*
* It will find the view engine, and render it.
* The view engine should be registered in {@link ViewManager}.
*/
class ContextView {
constructor(ctx) {
this.ctx = ctx;
this.app = this.ctx.app;
this.viewManager = ctx.app.view;
this.config = ctx.app.view.config;
}
/**
* Render a file by view engine
* @param {String} name - the file path based on root
* @param {Object} [locals] - data used by template
* @param {Object} [options] - view options, you can use `options.viewEngine` to specify view engine
* @param {...any} args arguments
* @return {Promise<String>} result - return a promise with a render result
*/
render(name, locals, options, ...args) {
return this[RENDER](name, locals, options, ...args);
}
/**
* Render a template string by view engine
* @param {String} tpl - template string
* @param {Object} [locals] - data used by template
* @param {Object} [options] - view options, you can use `options.viewEngine` to specify view engine
* @param {...any} args arguments
* @return {Promise<String>} result - return a promise with a render result
*/
renderString(tpl, locals, options, ...args) {
return this[RENDER_STRING](tpl, locals, options, ...args);
}
// ext -> viewEngineName -> viewEngine
async [RENDER](name, locals, options = {}) {
// retrieve fullpath matching name from `config.root`
const filename = await this.viewManager.resolve(name);
options.name = name;
options.root = filename.replace(path.normalize(name), '').replace(/[\/\\]$/, '');
options.locals = locals;
// get the name of view engine,
// if viewEngine is specified in options, don't match extension
let viewEngineName = options.viewEngine;
if (!viewEngineName) {
const ext = path.extname(filename);
viewEngineName = this.viewManager.extMap.get(ext);
}
// use the default view engine that is configured if no matching above
if (!viewEngineName) {
viewEngineName = this.config.defaultViewEngine;
}
assert(viewEngineName, `Can't find viewEngine for ${filename}`);
// get view engine and render
const view = this[GET_VIEW_ENGINE](viewEngineName);
return await view.render(filename, this[SET_LOCALS](locals), options);
}
[RENDER_STRING](tpl, locals, options) {
let viewEngineName = options && options.viewEngine;
if (!viewEngineName) {
viewEngineName = this.config.defaultViewEngine;
}
assert(viewEngineName, 'Can\'t find viewEngine');
// get view engine and render
const view = this[GET_VIEW_ENGINE](viewEngineName);
return view.renderString(tpl, this[SET_LOCALS](locals), options);
}
[GET_VIEW_ENGINE](name) {
// get view engine
const ViewEngine = this.viewManager.get(name);
assert(ViewEngine, `Can't find ViewEngine "${name}"`);
// use view engine to render
const engine = new ViewEngine(this.ctx);
// wrap render and renderString to support both async function and generator function
if (engine.render) engine.render = this.app.toAsyncFunction(engine.render);
if (engine.renderString) engine.renderString = this.app.toAsyncFunction(engine.renderString);
return engine;
}
/**
* set locals for view, inject `locals.ctx`, `locals.request`, `locals.helper`
* @param {Object} locals - locals
* @return {Object} locals
* @private
*/
[SET_LOCALS](locals) {
return Object.assign({
ctx: this.ctx,
request: this.ctx.request,
helper: this.ctx.helper,
}, this.ctx.locals, locals);
}
}
module.exports = ContextView;