UNPKG

@eggjs/view

Version:

Base view plugin for egg

112 lines (98 loc) 4.07 kB
import path from 'node:path'; import assert from 'node:assert'; import type { Context, EggCore } from '@eggjs/core'; import { ViewManager, type ViewManagerConfig, type RenderOptions } from './view_manager.js'; 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}. */ export class ContextView { protected ctx: Context; protected app: EggCore; protected viewManager: ViewManager; protected config: ViewManagerConfig; constructor(ctx: Context) { this.ctx = ctx; this.app = this.ctx.app; this.viewManager = this.app.view; this.config = this.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 * @return {Promise<String>} result - return a promise with a render result */ async render(name: string, locals?: Record<string, any>, options?: RenderOptions): Promise<string> { return await this[RENDER](name, locals, options); } /** * 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 * @return {Promise<String>} result - return a promise with a render result */ async renderString(tpl: string, locals?: Record<string, any>, options?: RenderOptions): Promise<string> { return await this[RENDER_STRING](tpl, locals, options); } // ext -> viewEngineName -> viewEngine async [RENDER](name: string, locals?: Record<string, any>, options: RenderOptions = {}) { // 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 viewEngine = this[GET_VIEW_ENGINE](viewEngineName); return await viewEngine.render(filename, this[SET_LOCALS](locals), options); } async [RENDER_STRING](tpl: string, locals?: Record<string, any>, options?: RenderOptions) { let viewEngineName = options && options.viewEngine; if (!viewEngineName) { viewEngineName = this.config.defaultViewEngine; } assert(viewEngineName, 'Can\'t find viewEngine'); // get view engine and render const viewEngine = this[GET_VIEW_ENGINE](viewEngineName); return await viewEngine.renderString(tpl, this[SET_LOCALS](locals), options); } [GET_VIEW_ENGINE](name: string) { // 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); return engine; } /** * set locals for view, inject `locals.ctx`, `locals.request`, `locals.helper` * @private */ [SET_LOCALS](locals?: Record<string, any>) { return Object.assign({ ctx: this.ctx, request: this.ctx.request, helper: this.ctx.helper, }, this.ctx.locals, locals); } }