您现在的位置是:网站首页 > 模板引擎集成与视图渲染文章详情
模板引擎集成与视图渲染
陈川
【
Node.js
】
64089人已围观
6714字
模板引擎的作用与选择
模板引擎在Express框架中负责动态生成HTML内容,将数据与视图分离。它允许开发者在HTML中嵌入变量、逻辑和控制结构,最终渲染出完整的页面。Express支持多种模板引擎,每种引擎有各自的语法特点和适用场景。
常见的模板引擎包括:
- EJS:嵌入式JavaScript模板,语法接近原生HTML
- Pug(原名Jade):缩进式语法,简洁但需要适应
- Handlebars:Mustache风格的模板,强调逻辑与表现分离
- Nunjucks:受Jinja2启发,功能丰富且灵活
// 使用EJS模板的示例
const express = require('express');
const app = express();
app.set('view engine', 'ejs');
Express中配置模板引擎
配置模板引擎需要几个关键步骤。首先通过app.set()
方法设置视图引擎和视图目录,默认情况下Express会在项目根目录的views
文件夹中查找模板文件。
// 基本配置示例
app.set('views', path.join(__dirname, 'views')); // 设置模板目录
app.set('view engine', 'ejs'); // 设置模板引擎
// 可选配置:自定义文件扩展名
app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');
对于需要特殊配置的模板引擎,可以通过引擎的API进行定制。例如Pug允许设置全局变量:
const pug = require('pug');
app.set('view engine', 'pug');
app.locals.basedir = path.join(__dirname, 'views');
视图渲染的基本方法
Express提供res.render()
方法进行视图渲染,它接受模板文件名和数据对象作为参数。渲染过程是异步的,引擎会解析模板中的指令并替换变量。
// 基本渲染示例
app.get('/', (req, res) => {
res.render('index', {
title: '首页',
user: { name: '张三', age: 28 }
});
});
模板中可以访问传入的所有属性和Express提供的局部变量。常用局部变量包括:
settings
:应用设置req.baseUrl
:基础路由路径req.path
:请求路径req.query
:查询参数
常用模板引擎的语法对比
不同模板引擎的语法差异较大,但核心功能相似。以下是几种常见引擎的对比示例:
EJS语法示例:
<!-- views/user.ejs -->
<h1><%= user.name %></h1>
<ul>
<% for(let i=0; i<user.skills.length; i++) { %>
<li><%= user.skills[i] %></li>
<% } %>
</ul>
<%- include('footer') %> <!-- 包含其他模板 -->
Pug语法示例:
//- views/user.pug
h1= user.name
ul
each skill in user.skills
li= skill
include footer //- 包含其他模板
Handlebars语法示例:
<!-- views/user.hbs -->
<h1>{{user.name}}</h1>
<ul>
{{#each user.skills}}
<li>{{this}}</li>
{{/each}}
</ul>
{{> footer}} <!-- 包含其他模板 -->
高级视图渲染技巧
在实际项目中,经常需要更复杂的渲染策略。Express支持多种高级用法:
布局模板(Layouts):
许多模板引擎支持布局模板,避免重复的HTML结构。例如使用express-ejs-layouts
:
// 安装布局支持
const expressLayouts = require('express-ejs-layouts');
app.use(expressLayouts);
// 在视图中指定布局
app.get('/', (req, res) => {
res.render('index', {
layout: 'layouts/main',
title: '使用布局'
});
});
局部视图(Partials): 将重复的UI组件提取为局部视图:
// EJS中使用局部视图
<%- include('partials/header') %>
// Handlebars中注册并使用局部视图
const hbs = require('hbs');
hbs.registerPartials(path.join(__dirname, 'views/partials'));
自定义辅助函数: 为模板添加自定义功能:
// Handlebars自定义辅助函数
hbs.registerHelper('formatDate', (date) => {
return new Date(date).toLocaleDateString();
});
// 在模板中使用
<p>{{formatDate post.createdAt}}</p>
性能优化与缓存
模板渲染可能成为性能瓶颈,合理的缓存策略很重要:
// 生产环境启用模板缓存
app.set('view cache', true);
// 自定义缓存策略
const memoize = require('lodash.memoize');
const renderFile = memoize(pug.compileFile);
app.engine('pug', (filePath, options, callback) => {
renderFile(filePath)(options, callback);
});
对于高流量应用,可以考虑预编译模板:
// 预编译Pug模板
const compiledTemplate = pug.compileFile('views/user.pug');
app.get('/user', (req, res) => {
res.send(compiledTemplate({ user: req.user }));
});
错误处理与调试
模板渲染错误需要妥善处理:
// 全局错误处理中间件
app.use((err, req, res, next) => {
if (err.view) {
// 模板渲染错误
res.status(500).render('error', {
message: '渲染视图时出错',
error: process.env.NODE_ENV === 'development' ? err : {}
});
} else {
next(err);
}
});
// 调试模板变量
app.use((req, res, next) => {
res.locals.showDebug = req.query.debug === 'true';
next();
});
在模板中添加调试信息:
<% if (showDebug) { %>
<pre><%= JSON.stringify(data, null, 2) %></pre>
<% } %>
与前端框架的集成
现代应用常需要将Express模板引擎与前端框架结合:
输出JSON供前端渲染:
app.get('/api/data', (req, res) => {
res.json({
user: req.user,
csrfToken: req.csrfToken()
});
});
在模板中初始化前端应用:
<script>
window.__INITIAL_STATE__ = <%- JSON.stringify(data) %>;
</script>
<div id="app"></div>
<script src="/dist/main.js"></script>
服务端渲染React/Vue:
import { renderToString } from 'react-dom/server';
app.get('/', (req, res) => {
const html = renderToString(<App user={req.user} />);
res.render('layout', { content: html });
});
安全注意事项
模板渲染需要注意安全防护:
转义输出:
<!-- 自动转义HTML -->
<p><%= userInput %></p>
<!-- 原始输出(慎用) -->
<p><%- trustedHtml %></p>
防范模板注入:
// 禁用不安全的模板特性
const ejs = require('ejs');
ejs.renderFile('template.ejs', {
userInput: req.query.input,
escape: function(markup) {
return sanitizeHtml(markup);
}
});
CSRF防护:
<!-- 在表单中添加CSRF令牌 -->
<form action="/submit" method="POST">
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
<!-- 其他表单字段 -->
</form>
多语言与本地化
模板引擎可以结合国际化方案:
// 使用i18n中间件
const i18n = require('i18n');
i18n.configure({
locales: ['en', 'zh'],
directory: path.join(__dirname, 'locales'),
defaultLocale: 'zh'
});
app.use(i18n.init);
// 在模板中使用
<h1><%= __('Welcome') %></h1>
<p><%= __n('You have %s message', count) %></p>
静态资源处理
正确处理模板中的静态资源:
<!-- 使用绝对路径 -->
<link rel="stylesheet" href="/css/style.css">
<!-- 动态资源版本控制 -->
<script src="/js/app.js?v=<%= appVersion %>"></script>
<!-- CDN回退方案 -->
<script src="https://cdn.example.com/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="/js/jquery.min.js"><\/script>')</script>
测试与验证
确保模板渲染的正确性:
// 使用Supertest测试路由
const request = require('supertest');
const assert = require('assert');
describe('模板渲染测试', () => {
it('应返回正确的标题', (done) => {
request(app)
.get('/')
.expect(200)
.end((err, res) => {
assert(res.text.includes('<title>首页</title>'));
done();
});
});
});
// 快照测试
const fs = require('fs');
const rendered = pug.renderFile('views/user.pug', { user: testUser });
fs.writeFileSync('__snapshots__/user.html', rendered);
部署注意事项
不同环境下的配置差异:
// 根据环境加载不同配置
const env = process.env.NODE_ENV || 'development';
app.set('view cache', env === 'production');
// 多实例部署时的共享缓存
const redis = require('redis');
const redisClient = redis.createClient();
app.set('view cache store', {
get: (key, cb) => redisClient.get(key, cb),
set: (key, val, cb) => redisClient.setex(key, 3600, val, cb)
});
自定义模板引擎
实现简单的自定义引擎:
// 实现一个极简模板引擎
app.engine('simple', (filePath, options, callback) => {
fs.readFile(filePath, 'utf8', (err, content) => {
if (err) return callback(err);
const rendered = content
.replace(/\{\{(\w+)\}\}/g, (_, key) => options[key] || '');
callback(null, rendered);
});
});
app.set('view engine', 'simple');
// 使用示例
// views/index.simple
// <h1>{{title}}</h1>
上一篇: <nav>-导航链接
下一篇: 静态文件服务与资源托管