前言
又是一周过去了,该给自己充充电了,使用了 koa 作为服务端渲染的服务引擎,那么使用 koa-ejs 作为 HTML 输出也是顺其自然的事了
开始
安装 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>
|
输出
源码解析
其实 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 });
|
非常简单的源码,包含了缓存等逻辑,基本满足我们的日常需要
思考
🤔好好学习