Open-Source Wikis

/

Express

/

Systems

/

View

expressjs/express

View

Active contributors: dougwilson, wesleytodd, ulisesgascon

Purpose

lib/view.js is the smallest meaningful file in lib/: it implements filesystem template lookup and dispatches to a registered template engine. app.render (in lib/application.js) constructs a View and calls view.render(options, callback).

Directory layout

lib/
└── view.js   # this file (205 lines)

Key abstractions

Symbol Type What it does
View(name, options) constructor Resolves the file extension (defaulting to defaultEngine), loads the engine module if not already cached, and looks up the file path on disk. Throws if no extension and no defaultEngine, or if the engine module does not export __express.
View.prototype.lookup(name) method Iterates over this.root (string or array of root directories) and returns the first existing file path.
View.prototype.resolve(dir, file) method For a single root, tries <dir>/<file> then <dir>/<basename>/index<ext>. Both must be regular files (not directories).
View.prototype.render(options, callback) method Calls this.engine(this.path, options, ...) and normalises sync callbacks to async with process.nextTick.
tryStat(path) private fs.statSync wrapped in try/catch, returns undefined on error. Used by resolve to avoid throwing when probing paths.

How it works

graph TD
    AppRender[app.render] --> NewView[new View name, opts]
    NewView --> Ext{has extension?}
    Ext -- yes --> EngineLoad{engines ext loaded?}
    Ext -- no --> NeedDefault{defaultEngine?}
    NeedDefault -- no --> ThrowErr[throw No default engine]
    NeedDefault -- yes --> AddExt[ext = defaultEngine; fileName += ext]
    AddExt --> EngineLoad
    EngineLoad -- no --> Require[require ext slice 1]
    Require --> CheckExpress[require mod __express must be a function]
    CheckExpress --> Cache[engines ext = fn]
    EngineLoad -- yes --> UseCached[use existing engine]
    Cache --> Lookup[view.lookup fileName]
    UseCached --> Lookup
    Lookup --> Resolve[for each root: resolve dir, file]
    Resolve --> SetPath[this.path = first match]
    SetPath --> Done[ready to render]

view.render itself just ensures the callback is asynchronous, even if the engine calls back synchronously:

View.prototype.render = function render(options, callback) {
  var sync = true;
  this.engine(this.path, options, function onRender() {
    if (!sync) return callback.apply(this, arguments);
    var args = new Array(arguments.length);
    for (var i = 0; i < arguments.length; i++) args[i] = arguments[i];
    var cntx = this;
    return process.nextTick(function () {
      return callback.apply(cntx, args);
    });
  });
  sync = false;
};

This guards against the Zalgo problem (sometimes-async callbacks).

Engine convention

Express expects engines to expose either:

  • require('engine').__express(path, options, callback) => callback(err, html)
  • Or be wrapped via app.engine(ext, fn) to map an extension to any compatible function.

Most popular template engines either expose __express themselves or are wrapped by @ladjs/consolidate. app.engine('html', require('ejs').renderFile) is the documented escape hatch.

File resolution

View.prototype.resolve(dir, file) checks for the file in two patterns, in order:

  1. <dir>/<file> — direct match.
  2. <dir>/<basename(file, ext)>/index<ext> — index-style match for templates organised in folders.

fs.statSync is used (synchronous I/O). This is acceptable because views are typically resolved once per file and then cached on app.cache when the view cache setting is enabled (which is the default in production).

Integration points

  • Imports: debug, node:path, node:fs.
  • Constructed by: app.render in lib/application.js.
  • Caches: nothing on its own; the cache lives on app.cache via app.render.

Entry points for modification

  • Change template lookup precedence → edit View.prototype.resolve.
  • Add support for an alternative engine signature → edit the engine load logic in the constructor (the __express requirement is currently hard-coded for engines auto-loaded by file extension).
  • Make stat asynchronous → would require API changes; the view.lookup is currently synchronous and called from inside app.render before invoking the engine.

Key source files

File Purpose
lib/view.js The View constructor and lookup logic
lib/application.js app.engine, app.render, the view cache setting
lib/response.js res.render (a thin wrapper around app.render)

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

View – Express wiki | Factory