Midway 模板渲染(View)组件完全指南:在 Koa/Faas/Web 中集成 EJS 与 Nunjucks
后端微服务云原生【免费下载链接】midway A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 项目地址https://gitcode.com/gh_mirrors/mi/midway点击查看免费下载本篇技术指南聚焦 midway 框架的View模板渲染能力如何在 midway 体系中接入 ejs、nunjucks 等服务端模板引擎通过ctx.render()渲染页面并完成多模板目录、自定义引擎等高级配置。读者读完本文后将能够独立完成模板组件的安装、配置、渲染调用并掌握编写自定义模板引擎、在组件间复用模板目录的完整实战方案。组件概览与适用场景View 组件用于在 midway 体系中使用服务端渲染SSR技术输出 HTML 页面。仓库中与之对应的核心实现位于packages/view而引擎适配分别由packages/view-ejs与packages/view-nunjucks提供。各 Web 框架对该组件的支持情况如下web 支持情况说明midwayjs/koa✅ 支持midwayjs/faas✅ 支持midwayjs/web✅ 支持midwayjs/express❌ 不支持从源码看支持范围的判定依据清晰可见packages/view/src/configuration.ts中onReady阶段通过applicationManager.getApplications([koa, egg, faas])只为这三种应用类型在app.context上挂载render、renderView、renderString方法因此 express 场景下不会注入渲染能力。使用 EJS安装依赖选择对应的模板包安装依赖$ npm i midwayjs/view-ejs3 --save或者在package.json中增加如下依赖后重新安装{ dependencies: { midwayjs/view-ejs: ^3.0.0, // ... }, devDependencies: { // ... } }引入组件在src/configuration.ts中导入并注册 ejs 组件import { Configuration } from midwayjs/core; import * as view from midwayjs/view-ejs; import { join } from path Configuration({ imports: [ view // 导入 ejs 组件 ], importConfigs: [ join(__dirname, config) ] }) export class MainConfiguration { }配置在配置文件中配置模板后缀到引擎的映射关系// src/config/config.default.ts export default { // ... view: { mapping: { .ejs: ejs, }, }, // ejs config ejs: {} }这里view.mapping是“文件后缀 → 引擎名”的映射表。从packages/view/src/viewManager.ts的init()方法可以看到框架会遍历mapping的每一个 key将扩展名注册到内部的extMap中渲染时依据模板文件的扩展名反查对应的引擎名。使用默认的 view 目录为${appDir}/view即应用根目录下的view目录在其中创建一个hello.ejs文件。目录结构如下➜ my_midway_app tree . ├── src │ └── controller ## Controller 目录 │ └── home.ts ├── view ## 模板目录 │ └── hello.ejs ├── test ├── package.json └── tsconfig.json在模板里写一些 ejs 格式的内容比如// view/hello.ejs hello % data %在 Controller 中渲染import { Inject, Provide } from midwayjs/core; import { Context } from midwayjs/koa; Controller(/) export class HomeController { Inject() ctx: Context; Get(/) async render(){ await this.ctx.render(hello.ejs, { data: world, }); } }访问对应路由后ctx.render会将渲染结果直接写入this.body并输出。需要说明的是ctx.render是异步方法务必使用await等待其完成。关于渲染链路的底层细节ctx.render实际上是从当前请求的requestContext中获取一个请求作用域的ContextView实例并调用其render方法见packages/view/src/contextView.ts。ContextView.render会依次完成通过viewManager.resolve(name)在rootDir中解析出模板文件的绝对路径 → 依据文件扩展名在mapping中查找引擎名 → 若未命中则回退到defaultViewEngine→ 从容器中取出对应的引擎实例并执行渲染。此外渲染时的数据locals并不是简单直传。ContextView.setLocals()会做一层合并其合并顺序为ViewManager.addLocals注册的全局数据 →{ ctx, request }→this.ctx.locals→ 本次调用传入的locals。这意味着你在模板中除了业务数据还可以直接访问ctx和request对象前提是未被同名数据覆盖。配置后缀默认后缀为.html为了改成习惯的.ejs后缀可以增加defaultExtension配置// src/config/config.default.ts export default { // ... view: { defaultExtension: .ejs, mapping: { .ejs: ejs, }, }, // ejs config ejs: {} }这样渲染时就不需要写后缀了Controller(/) export class HomeController { Inject() ctx: Context; Get(/) async render(){ await this.ctx.render(hello, { data: world, }); } }defaultExtension的工作原理可参考ViewManager.resolve()的实现当传入的名称无法直接命中文件时框架会自动尝试name config.defaultExtension再进行一次查找两个候选路径会依次在配置的所有 root 目录中探测packages/view/src/viewManager.ts。默认渲染引擎可以通过defaultViewEngine设置默认渲染引擎。其作用是当遇到的模板后缀比如.html未在配置的mapping字段中找到时使用该字段指定的引擎来渲染。// src/config/config.default.ts export default { // ... view: { defaultViewEngine: ejs, mapping: { .ejs: ejs, }, }, // ejs config ejs: {} }这样如果模板是.html后缀由于mapping中未指定依旧会使用ejs来渲染。从packages/view/src/contextView.ts的实现可以看到完整的引擎选择优先级单次渲染传入的options.viewEngine 模板文件扩展名在mapping中的命中 defaultViewEngine。也就是说defaultViewEngine只是兜底方案显式指定的mapping与options.viewEngine优先级更高。配置多个模板目录如果我们需要将代码封装为组件提供就需要支持不同的模板目录。默认的模板目录在${appDir}/view。可以在rootDir字段中增加其他的目录// src/config/config.default.ts // 修改默认 view 组件的 default 目录 export default { // ... view: { rootDir: { default: path.join(__dirname, ./view), } }, } // 其他组件需要增加目录的配置 export default { // ... // view 组件的配置 view: { rootDir: { anotherRoot: path.join(__dirname, ./view), } }, }通过对象合并的机制所有的rootDir都能合并到一起组件内部会获取Object.values(rootDir)做匹配。该机制的底层实现在packages/view/src/viewManager.ts的init()中框架将所有rootDir的值收集进一个 Set同时还会兼容兼容旧的root配置项以逗号分隔的多个路径最后只保留实际存在existsSync通过的目录作为可搜索的 root 列表。渲染时resolvePath()会按“候选名称 × root 目录”的双重循环逐一探测文件是否存在fs.promises.access检查可读性命中后还会校验文件路径确实位于 root 目录之下inpath安全检查。使用 Nunjucks和 ejs 类似引入对应组件即可。1、安装依赖$ npm i midwayjs/view-nunjucks3 --save或者在package.json中增加如下依赖后重新安装{ dependencies: { midwayjs/view-nunjucks: ^3.0.0, // ... }, devDependencies: { // ... } }2、引入组件在configuration.ts中导入import { Configuration } from midwayjs/core; import * as view from midwayjs/view-nunjucks; import { join } from path Configuration({ imports: [ view // 导入 nunjucks 组件 ], importConfigs: [ join(__dirname, config) ] }) export class MainConfiguration { }3、增加 nunjucks 的配置比如默认使用 nunjucksexport default { // ... view: { defaultViewEngine: nunjucks, mapping: { .nj: nunjucks, }, }, }4、在 view 目录增加模板// view/test.nj hi, {{ user }}在 Controller 中渲染import { Inject, Provide } from midwayjs/core; import { Context } from midwayjs/koa; Controller(/) export class HomeController { Inject() ctx: Context; Get(/) async render(){ await ctx.render(test.nj, { user: midway }); } }访问后会输出hi, midway。从组件实现上看midwayjs/view-nunjucks的配置类在onReady阶段通过viewManager.use(nunjucks, NunjucksView)将引擎注册到全局ViewManager见packages/view-nunjucks/src/configuration.ts并导入了基础midwayjs/view组件因此它的引入方式与 ejs 完全一致。实际的渲染由NunjucksView完成它注入了一个全局的NunjucksEnvironment单例通过nunjucks.render/nunjucks.renderString执行渲染见packages/view-nunjucks/src/view.ts。Nunjucks 自定义 Filter如果有自定义 filter 的需求可以在入口处增加。比如下面增加了一个名为hello的 filterimport { App, Configuration, Inject } from midwayjs/core; import * as view from midwayjs/view-nunjucks; import { join } from path Configuration({ imports: [view], importConfigs: [join(__dirname, config)] }) export class MainConfiguration { App() app; Inject() env: view.NunjucksEnvironment; async onReady(){ this.env.addFilter(hello, (str) { return hi, str; }); } }在模板里可以使用{{ name | hello }}然后渲染// controller // ... await ctx.render(test.nj, { name: midway });也会输出hi, midway。这里注入的view.NunjucksEnvironment即NunjucksView所依赖的全局环境实例通过addFilter注册的过滤器会立即被模板解析器识别无需重启或额外注册流程。自定义模板引擎默认我们只提供了 ejs 和 nunjucks 两个模板引擎你也可以编写自己的模板引擎代码。实现模板引擎首先需要创建一个请求作用域的模板引擎类它将在每个请求执行时初始化。你需要实现其中的render和renderString方法。如果你的模板引擎不支持某个方法可以抛出异常。// lib/view.ts import { Provide, Config } from midwayjs/core; import { IViewEngine } from midwayjs/view; Provide() export class MyView implements IViewEngine { Config(xxxx) viewConfig; async render(name: string, locals?: Recordstring, any, options?: RenderOptions) { return myengine.render(name, locals, options); } async renderString(tpl: string, locals?: Recordstring, any, options?: RenderOptions) { throw new Error(not implement); } };这两个方法接受类似的三个参数renderString第一个参数需要传入待解析的模板内容本身而render方法会解析模板文件。render(name, locals, viewOptions)name: 从root默认是/view相对的 pathlocals: 模板需要的数据viewOptions: 每次渲染的模板参数可覆盖的配置可以在配置文件中重写其中包含几个参数root: 模板的绝对路径name: 调用 render 方法的原始 name 值locals: 调用 render 方法的原始 locals 值renderString(tpl, locals, viewOptions)tpl: 模板名实际传入的是模板字符串内容locals: 和render一样viewOptions: 和render一样自定义引擎需要实现的是IViewEngine接口。从packages/view/src/interface.ts可以看到该接口还额外定义了RenderOptions中的viewEngine字段——它允许你在单次渲染时直接指定使用哪个引擎优先级最高这对于同一应用中混用多套引擎非常有用。ContextView.renderString也是直接取options.viewEngine ?? defaultViewEngine来决定引擎。另外ContextView.getViewEngine()在拿到引擎实例后还会通过Utils.toAsyncFunction对render/renderString做一层包装兼容异步函数与 generator 函数两种写法因此自定义引擎中既可以使用async/await也可以返回Promise。注册模板引擎在实现自定义的模板引擎后需要在启动入口注册它。通过引入ViewManager可以使用use方法注册自定义模板引擎// src/configuration.ts import { Configuration, Inject, Provide } from midwayjs/core; import * as koa from midwayjs/koa; import * as view from midwayjs/view; import { MyView } from ./lib/my; Configuration({ imports: [koa, view], importConfigs: [join(__dirname, config)] }) export class MainConfiguration { Inject() viewManager: view.ViewManager; async onReady(){ this.viewManager.use(ejs, MyView); } }ViewManager本身是一个继承自Map的单例Scope(ScopeEnum.Singleton)use(name, viewEngine)在注册时会做四项校验见packages/view/src/viewManager.tsname不能为空该名称不能被重复注册assert.ok(!this.has(name))引擎类原型上必须存在render方法引擎类原型上必须存在renderString方法。注册成功后即可在view.mapping中通过扩展名或通过defaultViewEngine指向该引擎名。注意事项如需在 eggmidwayjs/web场景下使用请在plugin.ts中关闭 view 和其相关插件import { EggPlugin } from egg; export default { // ... view: false, } as EggPlugin;否则会出现下面类似的错误TypeError: Cannot set property view of #EggApplication which has only a getter该错误的根源在于egg 应用自身已经内置了 view 能力EggApplication上存在只读的viewgetter而 midway 的 View 组件会在onReady阶段向app.context注入render等方法两者冲突导致属性无法被覆盖。因此在 egg 场景下使用 midway 的模板渲染时必须先关闭 egg 原生的 view 插件。总结配置项速查表综合文档与源码默认值见packages/view/src/config/config.default.tsView 组件的核心配置项整理如下配置项默认值说明view.mapping{}文件扩展名到引擎名的映射如{ .ejs: ejs }view.defaultExtension.html调用ctx.render时未写后缀时自动补充的扩展名view.defaultViewEngine空当扩展名未命中mapping时的兜底渲染引擎view.cachetrue是否缓存模板文件解析出的路径view.rootDir{ default: appDir/view }模板根目录集合可配置多个并自动合并view.root无兼容旧的逗号分隔多目录配置会与rootDir合并ejs/nunjucks{}各引擎自身的配置项如 ejs 的layout整套流程可以概括为组件注册引擎 →ViewManager维护引擎与根目录 →ContextView按“options.viewEngine mapping defaultViewEngine”选择引擎并执行渲染 → 结果写入ctx.body。掌握这一链路后无论是接入现成的 ejs/nunjucks还是编写自定义引擎都能做到心中有数。赞分享后端微服务云原生【免费下载链接】midway A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 项目地址https://gitcode.com/gh_mirrors/mi/midway点击查看免费下载相关推荐Midway 模板渲染组件实战在 Koa/Faas/Web 应用中使用 ejs 与 Nunjucks 服务端渲染Midway 模板渲染组件实战在 Koa/Faas/Web 应用中使用 ejs 与 Nunjucks 服务端渲染 Midway 内置了统一的模板渲染View后端微服务云原生Midway 模板渲染指南在 Koa/Web/Serverless 场景集成 EJS 与 NunjucksMidway 模板渲染指南在 Koa/Web/Serverless 场景集成 EJS 与 Nunjucks Midway midwayjs/core ht后端微服务云原生Midway 模板渲染实战基于 midwayjs/view 接入 ejs / nunjucks 并自定义渲染引擎Midway 模板渲染实战基于 midwayjs/view 接入 ejs / nunjucks 并自定义渲染引擎 本文围绕 Midway 框架的模板渲染组件后端微服务云原生上一篇Orleans Dashboard 核心抽象层Microsoft.Orleans.Dashboard.Abstractions指南监控服务、数据模型与 Grain 级性能分析基础下一篇ESP32-S2-Saola-1 开发板完全指南硬件规格、引脚映射与 Arduino 环境配置详解创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考