您现在的位置是:网站首页 > 模板引擎集成与视图渲染文章详情

模板引擎集成与视图渲染

模板引擎的作用与选择

模板引擎在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>

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步