mini-ejs 的实现

107 阅读1分钟

什么是模板引擎

模板引擎是一种用于生成 HTML 页面的工具,它可以将数据插入到 HTML 页面中,从而生成完整的 HTML 页面。

实现一个 mini-ejs

前置准备

  1. 如何执行一段字符串中的 JavaScript 代码?

方案一: 使用 eval()

const str = console.log('hello world')
eval(str)

方案二: 使用 new Function()

const str = 'console.log("hello world")'
const fn = new Function(str)
fn()

如何传递参数?

const str = 'console.log("hello world", a, b)'
const fun = new Function("a", "b", str)
fun(1, 2)

优化: 封装成对象传递

const str = 'console.log("hello world", obj.a, obj.b)'
const fun = new Function("obj", str)
fun({ a: 1, b: 2 })

再优化: 使用 with 语句

const str = 'with(obj) {console.log("hello world", a, b)}'
const fun = new Function("obj", str)
fun({ a: 1, b: 2 })

开始实现

这个是 ejs 中的内容

{% if (isShow) { %}
<div>{%= name %}</div>
{% } else { %}
<div>age: {%= age %}</div>
{% } %}
  1. 解析变量
// 将 {%= 解析为 ${,将 %} 解析为 }
// `<div>{%= name %}</div>` => `<div>${name}</div>`
let temp =
  "with(obj) { return `" +
  content.replace(/\{\%\=([^\}]+)\%\}/g, ($0, $1) => `\$\{${$1.trim()}\}`) +
  "`}"
  1. 剥离其中的 js 语句
// 将 %{ 变为 `,将 %} 变为 str += `
with(obj) { return `{% if (isShow) { %}
<div>${name}</div>
{% } else { %}
<div>age: ${age}</div>
{% } %}
`}
// 就可以转化为下面的结构
let str = ''
with(obj) { return str += ``
if (isShow) {
  str += `<div>${name}</div>`
} else {
  str += `<div>age: ${age}</div>`
}
 str += ``
}
return str
let temp =
  "let str = ''; with(obj) { str += `" +
  content.replace(/\{\%\=([^\}]+)\%\}/g, ($0, $1) => `\$\{${$1.trim()}\}`) +
  "`} return str"
temp = temp.replace(/\{\%([^\%]+)\%\}/g, ($0, $1) => `\`\n${$1}str+=\``)
  1. 构建函数并执行
const build = (template, data) => {
  const fn = new Function("obj", template)
  return fn(data)
}
const res = build(temp, {
  name: "whx111",
  age: "100",
  isShow: true
})

完整代码

安全性

EJS 实际上是一个 JavaScript 运行时。它的全部工作是执行 JavaScript。如果您运行 EJS 渲染方法而不检查你自己投入,你对结果负责。

简而言之,请勿提交包含以下代码片段的“漏洞”:

app.get('/', (req, res) => {
  res.render('index', req.query);
});

参考

SECURITY.md 模板引擎是什么?