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