集成Nunjucks

时间:2021-07-28 00:44:53

集成Nunjucks

集成Nunjucks实际上也是编写一个middleware,这个middleware的作用是给​​ctx​​对象绑定一个​​render(view, model)​​的方法,这样,后面的Controller就可以调用这个方法来渲染模板了。

我们创建一个​​templating.js​​来实现这个middleware:

const nunjucks = require('nunjucks');

function createEnv(path, opts) {
var
autoescape = opts.autoescape === undefined ? true : opts.autoescape,
noCache = opts.noCache || false,
watch = opts.watch || false,
throwOnUndefined = opts.throwOnUndefined || false,
env = new nunjucks.Environment(
new nunjucks.FileSystemLoader(path || 'views', {
noCache: noCache,
watch: watch,
}), {
autoescape: autoescape,
throwOnUndefined: throwOnUndefined
});
if (opts.filters) {
for (var f in opts.filters) {
env.addFilter(f, opts.filters[f]);
}
}
return env;
}

function templating(path, opts) {
// 创建Nunjucks的env对象:
var env = createEnv(path, opts);
return async (ctx, next) => {
// 给ctx绑定render函数:
ctx.render = function (view, model) {
// 把render后的内容赋值给response.body:
ctx.response.body = env.render(view, Object.assign({}, ctx.state || {}, model || {}));
// 设置Content-Type:
ctx.response.type = 'text/html';
};
// 继续处理请求:
await next();
};
}

module.exports = templating;

注意到​​createEnv()​​函数和前面使用Nunjucks时编写的函数是一模一样的。我们主要关心​​tempating()​​函数,它会返回一个middleware,在这个middleware中,我们只给​​ctx​​“安装”了一个​​render()​​函数,其他什么事情也没干,就继续调用下一个middleware。

使用的时候,我们在​​app.js​​添加如下代码:

const isProduction = process.env.NODE_ENV === 'production';

app.use(templating('views', {
noCache: !isProduction,
watch: !isProduction
}));

这里我们定义了一个常量​​isProduction​​,它判断当前环境是否是production环境。如果是,就使用缓存,如果不是,就关闭缓存。在开发环境下,关闭缓存后,我们修改View,可以直接刷新浏览器看到效果,否则,每次修改都必须重启Node程序,会极大地降低开发效率。

Node.js在全局变量​​process​​中定义了一个环境变量​​env.NODE_ENV​​,为什么要使用该环境变量?因为我们在开发的时候,环境变量应该设置为​​'development'​​,而部署到服务器时,环境变量应该设置为​​'production'​​。在编写代码的时候,要根据当前环境作不同的判断。

注意:生产环境上必须配置环境变量​​NODE_ENV = 'production'​​,而开发环境不需要配置,实际上​​NODE_ENV​​可能是​​undefined​​,所以判断的时候,不要用​​NODE_ENV === 'development'​​。

类似的,我们在使用上面编写的处理静态文件的middleware时,也可以根据环境变量判断:

if (! isProduction) {
let staticFiles = require('./static-files');
app.use(staticFiles('/static/', __dirname + '/static'));
}

这是因为在生产环境下,静态文件是由部署在最前面的反向代理服务器(如Nginx)处理的,Node程序不需要处理静态文件。而在开发环境下,我们希望koa能顺带处理静态文件,否则,就必须手动配置一个反向代理服务器,这样会导致开发环境非常复杂。

编写View

在编写View的时候,非常有必要先编写一个​​base.html​​作为骨架,其他模板都继承自​​base.html​​,这样,才能大大减少重复工作。

编写HTML不在本教程的讨论范围之内。这里我们参考Bootstrap的官网简单编写了​​base.html​​。

运行

一切顺利的话,这个​​view-koa​​工程应该可以顺利运行。运行前,我们再检查一下​​app.js​​里的middleware的顺序:

第一个middleware是记录URL以及页面执行时间:

app.use(async (ctx, next) => {
console.log(`Process ${ctx.request.method} ${ctx.request.url}...`);
var
start = new Date().getTime(),
execTime;
await next();
execTime = new Date().getTime() - start;
ctx.response.set('X-Response-Time', `${execTime}ms`);
});

第二个middleware处理静态文件:

if (! isProduction) {
let staticFiles = require('./static-files');
app.use(staticFiles('/static/', __dirname + '/static'));
}

第三个middleware解析POST请求:

app.use(bodyParser());

第四个middleware负责给​​ctx​​加上​​render()​​来使用Nunjucks:

app.use(templating('view', {
noCache: !isProduction,
watch: !isProduction
}));

最后一个middleware处理URL路由:

app.use(controller());

现在,在VS Code中运行代码,不出意外的话,在浏览器输入​​localhost:3000/​​,可以看到首页内容:

集成Nunjucks

直接在首页登录,如果输入正确的Email和Password,进入登录成功的页面:

集成Nunjucks

如果输入的Email和Password不正确,进入登录失败的页面:

集成Nunjucks

怎么判断正确的Email和Password?目前我们在​​signin.js​​中是这么判断的:

if (email === 'admin@example.com' && password === '123456') {
...
}

当然,真实的网站会根据用户输入的Email和Password去数据库查询并判断登录是否成功,不过这需要涉及到Node.js环境如何操作数据库,我们后面再讨论。

扩展

注意到​​ctx.render​​内部渲染模板时,Model对象并不是传入的model变量,而是:

Object.assign({}, ctx.state || {}, model || {})

这个小技巧是为了扩展。

首先,​​model || {}​​确保了即使传入​​undefined​​,model也会变为默认值​​{}​​。​​Object.assign()​​会把除第一个参数外的其他参数的所有属性复制到第一个参数中。第二个参数是​​ctx.state || {}​​,这个目的是为了能把一些公共的变量放入​​ctx.state​​并传给View。

例如,某个middleware负责检查用户权限,它可以把当前用户放入​​ctx.state​​中:

app.use(async (ctx, next) => {
var user = tryGetUserFromCookie(ctx.request);
if (user) {
ctx.state.user = user;
await next();
} else {
ctx.response.status = 403;
}
});