context_view.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. 'use strict';
  2. const path = require('path');
  3. const assert = require('assert');
  4. const RENDER = Symbol.for('contextView#render');
  5. const RENDER_STRING = Symbol.for('contextView#renderString');
  6. const GET_VIEW_ENGINE = Symbol.for('contextView#getViewEngine');
  7. const SET_LOCALS = Symbol.for('contextView#setLocals');
  8. /**
  9. * View instance for each request.
  10. *
  11. * It will find the view engine, and render it.
  12. * The view engine should be registered in {@link ViewManager}.
  13. */
  14. class ContextView {
  15. constructor(ctx) {
  16. this.ctx = ctx;
  17. this.app = this.ctx.app;
  18. this.viewManager = ctx.app.view;
  19. this.config = ctx.app.view.config;
  20. }
  21. /**
  22. * Render a file by view engine
  23. * @param {String} name - the file path based on root
  24. * @param {Object} [locals] - data used by template
  25. * @param {Object} [options] - view options, you can use `options.viewEngine` to specify view engine
  26. * @return {Promise<String>} result - return a promise with a render result
  27. */
  28. render(...args) {
  29. return this[RENDER](...args);
  30. }
  31. /**
  32. * Render a template string by view engine
  33. * @param {String} tpl - template string
  34. * @param {Object} [locals] - data used by template
  35. * @param {Object} [options] - view options, you can use `options.viewEngine` to specify view engine
  36. * @return {Promise<String>} result - return a promise with a render result
  37. */
  38. renderString(...args) {
  39. return this[RENDER_STRING](...args);
  40. }
  41. // ext -> viewEngineName -> viewEngine
  42. async [RENDER](name, locals, options = {}) {
  43. // retrieve fullpath matching name from `config.root`
  44. const filename = await this.viewManager.resolve(name);
  45. options.name = name;
  46. options.root = filename.replace(path.normalize(name), '').replace(/[\/\\]$/, '');
  47. options.locals = locals;
  48. // get the name of view engine,
  49. // if viewEngine is specified in options, don't match extension
  50. let viewEngineName = options.viewEngine;
  51. if (!viewEngineName) {
  52. const ext = path.extname(filename);
  53. viewEngineName = this.viewManager.extMap.get(ext);
  54. }
  55. // use the default view engine that is configured if no matching above
  56. if (!viewEngineName) {
  57. viewEngineName = this.config.defaultViewEngine;
  58. }
  59. assert(viewEngineName, `Can't find viewEngine for ${filename}`);
  60. // get view engine and render
  61. const view = this[GET_VIEW_ENGINE](viewEngineName);
  62. return await view.render(filename, this[SET_LOCALS](locals), options);
  63. }
  64. [RENDER_STRING](tpl, locals, options) {
  65. let viewEngineName = options && options.viewEngine;
  66. if (!viewEngineName) {
  67. viewEngineName = this.config.defaultViewEngine;
  68. }
  69. assert(viewEngineName, 'Can\'t find viewEngine');
  70. // get view engine and render
  71. const view = this[GET_VIEW_ENGINE](viewEngineName);
  72. return view.renderString(tpl, this[SET_LOCALS](locals), options);
  73. }
  74. [GET_VIEW_ENGINE](name) {
  75. // get view engine
  76. const ViewEngine = this.viewManager.get(name);
  77. assert(ViewEngine, `Can't find ViewEngine "${name}"`);
  78. // use view engine to render
  79. const engine = new ViewEngine(this.ctx);
  80. // wrap render and renderString to support both async function and generator function
  81. if (engine.render) engine.render = this.app.toAsyncFunction(engine.render);
  82. if (engine.renderString) engine.renderString = this.app.toAsyncFunction(engine.renderString);
  83. return engine;
  84. }
  85. /**
  86. * set locals for view, inject `locals.ctx`, `locals.request`, `locals.helper`
  87. * @param {Object} locals - locals
  88. * @return {Object} locals
  89. * @private
  90. */
  91. [SET_LOCALS](locals) {
  92. return Object.assign({
  93. ctx: this.ctx,
  94. request: this.ctx.request,
  95. helper: this.ctx.helper,
  96. }, this.ctx.locals, locals);
  97. }
  98. }
  99. module.exports = ContextView;