之前我们完善了Express的路由系统,以及中间件的支持,这也算是完成了Express中比较核心的部分,今天完成模板引擎的渲染后就不往后面继续了,因为后面的很多东西也不是太麻烦,大家可以自己去研究研究源码
- 进入今天的正题,Express模板引擎渲染: 通俗的说,其实有点类似于SSR吧(个人觉得),当然肯定还是有区别的,我们先抛开不管,服务端模板引擎渲染其实就是将数据整合进入到前端html页面,将模板引擎中使用到的js语法,需要的服务端 变量进行替换成真实的数据信息整合到html文件中返回给客户端,一般返回的格式都是 “text/html”,而express中的模板引擎还是很多的,比如常见的ejs, jade等等, 所谓的模板引擎渲染都是交由这些去做的,而在express中只是做了一层调起的作用罢了,一起来看看实现吧:
- 首先我们还是以需求带动开发的方式进行编码,先来看看我们这次的测试实例:
const express = require("express")
const path = require("path")
const app = express()
const fs = require("fs")
// 用于设置模板引擎,遇到html结尾的模板使用html来进行渲染
// require("ejs").__express => render(filePath, options, callback)
app.engine("html", require("ejs").__express);
// views是用来设置模板存放的目录, 实际就是设置一个变量保存在app内部
app.set("views", path.resolve("views"))
// 设置模板引擎, 指定模板的后缀名,及render函数中的名字加上这个后缀进行渲染
app.set("view engine", "html");
app.get("/", function(req, res, next){
// render方法的第一个参数是模板的相对路径 模板的名称, 传递到模板的数据
res.render("index", {title: "hello", user: {name: "chen"}});
})
app.listen(3000)
简单解释下: 这里我们使用的模板引擎是ejs, 而上面的设置也是在express中配置模板引擎比较常见的几步,可看代码中的具体注释,我们说一下上面使用到的express中的两个比较重要的函数: app.set() 和 app.engine(): app.set()函数实际可理解为设置一些express中会用到的变量,它会被放到一个对象里面进行存储起来,所有使用app.set()设置的值都可以使用app.get()传入变量名的形式进行获取到。而关于app.engine()函数则是进行文件类型模板引擎渲染函数进行对应的一组映射关系的创建,它传入的参数一般就是需要进行模板引擎渲染的文件类型,以及处理渲染的函数。
- 首先我们就先来实现上面说到的app.set 和 app.engine函数:
(进入到Application文件)
// 1. 第一步: 现在构造函数中添加两个对象: settings 和 engines 用来存储调用app.set()和app.engine()存储的内容:
function Application(){
this.settings = {}; // 用来保存set设置的参数
this.engines = {}; // 用于保存文件扩展名和渲染函数的对应关系
}
// 第二步: 创建set方法:
// 实现思路: 判断用户传递的参数,如果是一个参数,就获取setting中的值返回; 如果是两个参数,则分别做为key和value存入到setting中
Application.prototype.set = function(key, value){
if(arguments.length === 1){
return this.settings[key];
}
this.settings[key] = value;
}
// 第三步: 实现app.engine()方法
// 实现思路: 其实就是将传入进来的文件类型和渲染方法放到engins对象中,但是需要做一层预处理
// 就是将"html" 这样的文件格式处理为 ".html"后缀名的格式
Application.prototype.engine = function(ext, render){
let extension = ext[0] === "." ? ext : "." + ext;
this.engins[extension] = render;
}
- 实现app.render调起engins中的渲染函数进行模板渲染:
(这里我们就先用着ejs的模板渲染,后面我会实现一个简易的模板渲染函数)
// 实际的express中会有一个response.js文件,用来往response对像上挂载一些方法,而我们不做其他的那些处理,那么我们就单独抽离处理这个render函数:
// res.render有三个参数,但是使用的时候我们基本就用到前两个参数就ok了,但是实际却是可以传递一个回调函数进去的,它可以接收到的参数是一个error对象和渲染后的html模板字符串,当然源码中有默认实现这个函数
// 前两个常用的参数分别是渲染的html文件名称,和传递去渲染这个模板的数据options
res.render = function(view, options, callback){
// 从settings中取得我们之前存入的模板文件的后缀名和文件名结合得到完整的文件名:
let ext = "." + app.get("view engine");
// 这里解释一下,我们app.get()本来是用来创建路由匹配的,但是这里我们也可以用来获取settings中设置的值,原因是在get方法中做了一层预处理,实现也很简单,就是判断当get中只传入了一个参数时就代用app.set()去获取内容返回
view = view.indexOf(".") != -1 ? view : view + ext; // 这里这样处理是不严谨的,但是为了方便我们就这样用着,大家理解思路就行
let filePath = path.join(app.get("views"), view); // 拼接完整的文件路径
let render = app.engines[ext]; // 从engins对象中取得app.engin()设置好的渲染方法
// 定义默认的callback 函数, 这里我们就不做容错处理了
function done(err, html){
res.setHeader("Content-Type", "text/html");
res.end(html)
}
callback = callback || done
render(filePath, options, callback);
}
- 简单实现一个自己的模板渲染引擎:
先提思路: 其实ejs模板渲染中简单来说就是通过一定的正则,匹配出html字符串中的 "<%%>“或者是”<%=%>"这样的字符,然后提取出里面的内容进行预处理,而提取出的js字段或者是变量我们应该如何处理呢,这里就需要提到模板渲染引擎中非常常见的实现思路了,那就是 Function + with 机制(包括在Vue中也是这样实现的), 关于with我们简单说一下,with其实和js中的作用域有关系,可以把它理解为会在with包裹的区域内形成一个新的作用域,在with里面可以正常编写js的任何代码,而传递给with()的参数就是在这个作用域中可以直接拿到的对象内容。
这里我们先来看一下一个html字符串我们渲染出来是什么样子:
// html模板字符串:
let str = `
<%if(user.name){%>
user : <%=user.name%>
<%}else{%>
Sorry
<%}%>
<ul>
<%for(let i = 0; i < total; i++){%>
<li><%=i%></li>
<%}%>
</ul>
`
// 处理出来应该是:
`
let tpl = "";
with(obj){
if(user.name){
tpl += "user:" + user.name
}else{
tpl += "Sorry";
}
tpl += "<ul>";
for(let i = 0; i < 5; i++){
tpl += "<li>" + i + "</li>";
}
tpl += "</ul>"
}
return tpl;
`
现在我们就根据上面的样子来实现一个简单的模板渲染函数:
// 这里我们需要用到node的fs模块用于之后读取html文件的内容
const fs = require("fs")
function render(filePath, options, callback){
fs.readFile(filePath, 'utf8', function(err, str){
// 这里我们主要做的就是字符串的拼接,没有太多的麻烦的东西
let head = "let tpl = ``;\n with(obj){\n tpl += `";
let tail = "`}\n return tpl;"
// 匹配 <% %>进行替换
str = str.replace(/<%([\s\S]+?)%>/g, function(){
return "`;\n" + arguments[1] + "\n tpl += `"
})
// 匹配 <%= %>进行替换,取出使用的变量值
str += str.replace(/<%=([\s\S]+?)%>/g, function(){
return "${" + arguments[1] + "}";
})
let html = head + str + tail;
let fn = new Function('obj', html);
let result = fn(options);
// 这里我们就不对错误进行处理了,简单传递一个null就好,理解思路
callback(null, result);
})
}
ok, 至此,我们在express中实现模板引擎渲染的原理基本就差不多了,很多代码和源码有出入,但是思路是一样的。同时,关于express深入了解我就写到这里了,大家加油!!