Appearance
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>© 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应用界面。