Koa-ejs 入门

前言

又是一周过去了,该给自己充充电了,使用了 koa 作为服务端渲染的服务引擎,那么使用 koa-ejs 作为 HTML 输出也是顺其自然的事了

开始

安装 koa,koa-ejs

1
npm install koa koa-ejs

index.js 目录,创建 koa 对象,koa-ejs 返回一个 render 对象,通过这个对象给 ctx 对象进行 render 赋值,之后就可以在路由中使用 ctx.render

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const Koa = require('koa')
const render = require('koa-ejs');

const app = new Koa();

render(app, {
// 模版根目录
root: path.join(__dirname, 'view'),
// 标准模版名字
layout: 'template',
// 模版后缀,默认是 html
viewExt: 'html',
// 是否使用缓存
cache: false,
// 是否开启 debug
debug: false,
});

app.use(async function (ctx) {
const users = [{ name: 'Dead Horse' }, { name: 'Jack' }, { name: 'Tom' }];
await ctx.render('content', {
users
});
});

template.html 文件

1
2
3
4
5
6
7
8
9
<html>
<head>
<title>koa ejs</title>
</head>
<body>
<h3>koa ejs</h3>
<%- body %>
</body>
</html>

content.html 文件

1
2
3
<div>
<% include user.html %>
</div>

user.html

1
2
3
4
5
<ul>
<% users.forEach(function (user) {%>
<li><%= user.name %></li>
<% })%>
</ul>
输出
1
2
3
Dead Horse
Jack
Tom

源码解析

其实 koa-ejs 的源码很简单,主要是引用了 ejs 框架,简单做了封装,将 render 挂载到 ctx 上

入口

当引入的时候,执行了 render 就会进行初始化,并且声明了 render 的异步方法,主要是获取 template 文件,然后使用 ejs.compile 去构造函数,该方法主要的作用就是替换 ejs 中的变量,生成 html。如果有缓存,那么则不读取文件了,直接进行替换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
exports = module.exports = function (app, settings) {
if (app.context.render) {
return;
}

settings.root = path.resolve(process.cwd(), settings.root);

/**
* cache the generate package
* @type {Object}
*/
const cache = Object.create(null);

settings = Object.assign({}, defaultSettings, settings);

settings.viewExt = settings.viewExt
? '.' + settings.viewExt.replace(/^\./, '')
: '';

async function render(view, options) {
view += settings.viewExt;
const viewPath = path.join(settings.root, view);
debug(`render: ${viewPath}`);
// get from cache
if (settings.cache && cache[viewPath]) {
return cache[viewPath].call(options.scope, options);
}

const tpl = await fs.readFile(viewPath, 'utf8');

// override `ejs` node_module `resolveInclude` function
ejs.resolveInclude = function(name, filename, isDir) {
if (!path.extname(name)) {
name += settings.viewExt;
}
return parentResolveInclude(name, filename, isDir);
}

const fn = ejs.compile(tpl, {
filename: viewPath,
_with: settings._with,
compileDebug: settings.debug && settings.compileDebug,
debug: settings.debug,
delimiter: settings.delimiter,
cache: settings.cache,
async: settings.async
});
if (settings.cache) {
cache[viewPath] = fn;
}

return fn.call(options.scope, options);
}


app.context.render = async function (view, _context) {
//
};
};

最后将为 app 的 context 新增 render 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
app.context.render = async function (view, _context) {
const ctx = this;

const context = Object.assign({}, ctx.state, _context);
// 获取 html
let html = await render(view, context);
// 如果使用了 layout,也就是有二级页面,再一次渲染
const layout = context.layout === false ? false : (context.layout || settings.layout);
if (layout) {
// if using layout
context.body = html;
html = await render(layout, context);
}
// 直接赋值给 body
const writeResp = context.writeResp === false ? false : (context.writeResp || settings.writeResp);

if (writeResp) {
// normal operation
ctx.type = 'html';
ctx.body = html;
} else {
// only return the html
return html;
}
};

之后就可以使用 ctx.render

1
2
3
await ctx.render('content', {
users
});

非常简单的源码,包含了缓存等逻辑,基本满足我们的日常需要

思考

🤔好好学习