view_manager.js 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. 'use strict';
  2. const assert = require('assert');
  3. const path = require('path');
  4. const fs = require('mz/fs');
  5. const { existsSync } = require('fs');
  6. /**
  7. * ViewManager will manage all view engine that is registered.
  8. *
  9. * It can find the real file, then retrieve the view engine based on extension.
  10. * The plugin just register view engine using {@link ViewManager#use}
  11. */
  12. class ViewManager extends Map {
  13. /**
  14. * @param {Application} app - application instance
  15. */
  16. constructor(app) {
  17. super();
  18. this.config = app.config.view;
  19. this.config.root = this.config.root
  20. .split(/\s*,\s*/g)
  21. .filter(filepath => existsSync(filepath));
  22. this.extMap = new Map();
  23. this.fileMap = new Map();
  24. for (const ext of Object.keys(this.config.mapping)) {
  25. this.extMap.set(ext, this.config.mapping[ext]);
  26. }
  27. }
  28. /**
  29. * This method can register view engine.
  30. *
  31. * You can define a view engine class contains two method, `render` and `renderString`
  32. *
  33. * ```js
  34. * class View {
  35. * render() {}
  36. * renderString() {}
  37. * }
  38. * ```
  39. * @param {String} name - the name of view engine
  40. * @param {Object} viewEngine - the class of view engine
  41. */
  42. use(name, viewEngine) {
  43. assert(name, 'name is required');
  44. assert(!this.has(name), `${name} has been registered`);
  45. assert(viewEngine, 'viewEngine is required');
  46. assert(viewEngine.prototype.render, 'viewEngine should implement `render` method');
  47. assert(viewEngine.prototype.renderString, 'viewEngine should implement `renderString` method');
  48. this.set(name, viewEngine);
  49. }
  50. /**
  51. * Resolve the path based on the given name,
  52. * if the name is `user.html` and root is `app/view` (by default),
  53. * it will return `app/view/user.html`
  54. * @param {String} name - the given path name, it's relative to config.root
  55. * @return {String} filename - the full path
  56. */
  57. async resolve(name) {
  58. const config = this.config;
  59. // check cache
  60. let filename = this.fileMap.get(name);
  61. if (config.cache && filename) return filename;
  62. // try find it with default extension
  63. filename = await resolvePath([ name, name + config.defaultExtension ], config.root);
  64. assert(filename, `Can't find ${name} from ${config.root.join(',')}`);
  65. // set cache
  66. this.fileMap.set(name, filename);
  67. return filename;
  68. }
  69. }
  70. module.exports = ViewManager;
  71. async function resolvePath(names, root) {
  72. for (const name of names) {
  73. for (const dir of root) {
  74. const filename = path.join(dir, name);
  75. if (await fs.exists(filename)) {
  76. if (inpath(dir, filename)) {
  77. return filename;
  78. }
  79. }
  80. }
  81. }
  82. }
  83. function inpath(parent, sub) {
  84. return sub.indexOf(parent) > -1;
  85. }