Skip to content
On this page

Koa模板引擎集成

Koa本身不包含模板引擎功能,但可以通过中间件来集成各种模板引擎。本指南介绍如何在Koa应用中集成和使用模板引擎。

常用模板引擎

Koa可以集成多种模板引擎,包括:

  • EJS - 嵌入式JavaScript模板
  • Nunjucks - 功能丰富的模板引擎
  • Pug - 高效、强大且富有表现力的模板引擎

Koa模板中间件

安装koa-views中间件

bash
npm install koa-views

基础配置

javascript
const Koa = require('koa');
const views = require('koa-views');
const path = require('path');

const app = new Koa();

// 配置模板引擎
app.use(views(path.join(__dirname, 'views'), {
  extension: 'ejs' // 默认使用ejs模板
}));

app.use(async (ctx) => {
  // 渲染模板
  await ctx.render('index', {
    title: 'My Page',
    message: 'Hello Koa!'
  });
});

app.listen(3000);

EJS模板引擎

安装和配置

bash
npm install ejs
javascript
const views = require('koa-views');

app.use(views(path.join(__dirname, 'views'), {
  extension: 'ejs'
}));

// 或者指定引擎
app.use(views(path.join(__dirname, 'views'), {
  map: {
    html: 'ejs'
  }
}));

EJS模板示例

html
<!-- views/index.ejs -->
<!DOCTYPE html>
<html>
<head>
  <title><%= title %></title>
</head>
<body>
  <h1><%= message %></h1>
  
  <!-- 条件渲染 -->
  <% if (user) { %>
    <p>Welcome, <%= user.name %>!</p>
  <% } else { %>
    <p>Please log in.</p>
  <% } %>
  
  <!-- 循环渲染 -->
  <ul>
    <% users.forEach(function(user) { %>
      <li><%= user.name %> - <%= user.email %></li>
    <% }); %>
  </ul>
  
  <!-- 包含其他模板 -->
  <%- include('partials/header') %>
  
  <!-- 输出HTML(不转义) -->
  <div><%- rawHtml %></div>
  
  <!-- 输出转义HTML -->
  <div><%= safeHtml %></div>
</body>
</html>

在Koa中使用EJS

javascript
const Koa = require('koa');
const views = require('koa-views');
const path = require('path');

const app = new Koa();

app.use(views(path.join(__dirname, 'views'), {
  extension: 'ejs'
}));

app.use(async (ctx) => {
  await ctx.render('index', {
    title: 'EJS Example',
    message: 'Hello from EJS',
    user: { name: 'John' },
    users: [
      { name: 'Alice', email: 'alice@example.com' },
      { name: 'Bob', email: 'bob@example.com' }
    ],
    rawHtml: '<script>alert("This will not execute")</script>',
    safeHtml: '<strong>This is bold text</strong>'
  });
});

Nunjucks模板引擎

安装和配置

bash
npm install nunjucks
javascript
const views = require('koa-views');

app.use(views(path.join(__dirname, 'views'), {
  map: { html: 'nunjucks' },
  options: {
    nunjucksEnv: require('nunjucks').configure(path.join(__dirname, 'views'), {
      autoescape: true,
      noCache: process.env.NODE_ENV !== 'production'
    })
  }
}));

Nunjucks模板示例

html
<!-- views/index.html -->
<!DOCTYPE html>
<html>
<head>
  <title>{{ title }}</title>
</head>
<body>
  <h1>{{ message }}</h1>
  
  <!-- 条件渲染 -->
  {% if user %}
    <p>Welcome, {{ user.name }}!</p>
  {% else %}
    <p>Please log in.</p>
  {% endif %}
  
  <!-- 循环渲染 -->
  <ul>
    {% for user in users %}
      <li>{{ user.name }} - {{ user.email }}</li>
    {% endfor %}
  </ul>
  
  <!-- 包含其他模板 -->
  {% include 'partials/header.html' %}
  
  <!-- 宏定义和使用 -->
  {% macro renderUser(user) %}
    <div class="user">
      <h3>{{ user.name }}</h3>
      <p>{{ user.email }}</p>
    </div>
  {% endmacro %}
  
  {{ renderUser(user) }}
</body>
</html>

Pug模板引擎

安装和配置

bash
npm install pug
javascript
const views = require('koa-views');

app.use(views(path.join(__dirname, 'views'), {
  extension: 'pug'
}));

Pug模板示例

pug
doctype html
html
  head
    title= title
  body
    h1= message
    
    if user
      p Welcome, #{user.name}!
    else
      p Please log in.
    
    ul
      each user in users
        li #{user.name} - #{user.email}
    
    // 包含其他模板
    include ./partials/header
    
    // 混合宏
    mixin userCard(user)
      .user-card
        h3= user.name
        p= user.email
    
    +userCard(user)

高级模板功能

模板助手函数

javascript
// 自定义助手函数
const nunjucks = require('nunjucks');

const env = nunjucks.configure(path.join(__dirname, 'views'), {
  autoescape: true,
  noCache: true
});

// 添加全局助手函数
env.addFilter('upper', (str) => str.toUpperCase());
env.addFilter('date', (date, format) => {
  // 日期格式化助手
  return new Date(date).toLocaleDateString();
});

app.use(views(path.join(__dirname, 'views'), {
  map: { html: 'nunjucks' },
  options: { nunjucksEnv: env }
}));

模板继承

html
<!-- views/layout.html (Nunjucks) -->
<!DOCTYPE html>
<html>
<head>
  <title>{% block title %}Default Title{% endblock %}</title>
  <link rel="stylesheet" href="/css/style.css">
</head>
<body>
  <header>
    {% block header %}
      <h1>Default Header</h1>
    {% endblock %}
  </header>
  
  <main>
    {% block content %}{% endblock %}
  </main>
  
  <footer>
    {% block footer %}
      <p>&copy; 2023 My Site</p>
    {% endblock %}
  </footer>
</body>
</html>

<!-- views/page.html -->
{% extends "layout.html" %}

{% block title %}{{ title }} - My Site{% endblock %}

{% block content %}
  <h2>{{ heading }}</h2>
  <p>{{ content }}</p>
{% endblock %}

模板安全

XSS防护

javascript
// Koa默认会对输出进行转义,但需要确保模板引擎配置正确
app.use(views(path.join(__dirname, 'views'), {
  extension: 'ejs',
  options: {
    // 确保启用自动转义
    escape: require('lodash/escape')
  }
}));

// 手动转义敏感数据
app.use(async (ctx) => {
  const userInput = ctx.query.name || '';
  // 使用转义函数
  const safeInput = require('lodash/escape')(userInput);
  
  await ctx.render('page', {
    userInput: safeInput
  });
});

模板沙箱

javascript
// 使用安全的模板配置
const secureViews = views(path.join(__dirname, 'views'), {
  extension: 'nunjucks',
  options: {
    // 安全配置
    autoescape: true, // 自动转义
    throwOnUndefined: false, // 不因未定义变量抛出错误
    tags: {
      // 自定义标签分隔符
      variableStart: '{',
      variableEnd: '}',
      blockStart: '{%',
      blockEnd: '%}',
      commentStart: '{#',
      commentEnd: '#}'
    }
  }
});

app.use(secureViews);

模板性能优化

模板缓存

javascript
// 启用模板缓存
const isProduction = process.env.NODE_ENV === 'production';

app.use(views(path.join(__dirname, 'views'), {
  extension: 'ejs',
  options: {
    // 生产环境启用缓存
    cache: isProduction
  }
}));

// 自定义缓存策略
const templateCache = new Map();

const cachedViews = async (ctx, next) => {
  const render = ctx.render;
  ctx.render = async function (name, locals) {
    const cacheKey = `${name}:${JSON.stringify(locals)}`;
    
    if (isProduction && templateCache.has(cacheKey)) {
      ctx.body = templateCache.get(cacheKey);
      return;
    }
    
    await render.call(this, name, locals);
    
    if (isProduction) {
      templateCache.set(cacheKey, ctx.body);
    }
  };
  
  await next();
};

app.use(cachedViews);

条件渲染

javascript
// 优化条件渲染
app.use(async (ctx) => {
  // 预处理数据以减少模板中的逻辑
  const processedData = {
    ...originalData,
    hasUser: !!originalData.user,
    isAdmin: originalData.user && originalData.user.role === 'admin',
    canEdit: originalData.user && originalData.user.canEdit
  };
  
  await ctx.render('template', processedData);
});

模板错误处理

模板渲染错误

javascript
const renderWithErrorHandling = async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    if (err.code === 'ENOENT') {
      // 模板文件不存在
      ctx.status = 500;
      ctx.body = 'Template not found';
    } else if (err.name === 'Template render error') {
      // 模板渲染错误
      ctx.status = 500;
      ctx.body = 'Template render error';
    } else {
      throw err; // 其他错误继续抛出
    }
  }
};

app.use(renderWithErrorHandling);

通过合理使用模板引擎,可以构建动态且安全的Koa应用界面。