2017-09-11 35 views
15

我将响应字符串存储在EJS表单中的数据库中并填充Node中的数据。我想要做的是能够使用任何属性我想要的,无论它来自哪种模型,然后在Node中,异步/等待这些模型,一旦我有模板,基于什么属性是必需的。如何从我的EJS模板获取属性列表?

所以,如果我有这样一个模板:

"Hello <%=user.firstName%>." 

我希望能够看看该模板,并提取类似:

ejsProperties = ["user", "user.firstName"] 

或类似的东西。

+0

据我了解这个问题,你想从ejs模板中拉出属性而不是从html文件中取出属性?和现在存储'<%= user.firstName%>'用户数据的位置,你如何注入模板。 – bhansa

+0

是的,我想从数据预渲染的模板中提取数据。然后根据所需的对象,我可以从数据库中获取这些模型,然后res.render(_template_,_fullObject_) – Individual11

回答

5

如果你只是想拉出来像user.firstName简单的事情,然后运行通过EJS文件的RegExp可能是最好的方式。机会是你会寻找一个特定的和已知的对象和属性集,所以你可以专门针对它们,而不是试图提取所有可能的对象/属性。

在更一般的情况下,事情变得非常迅速。这样的事情是非常棘手的处理:

<% var u = user; %><%= u.firstName %> 

这是一个愚蠢的例子,但它是特别的只是冰山的一角。鉴于user正在从locals读取,并且是感兴趣的对象,u可能几乎是任何事情,而且我们无法通过u轻松绘制连接firstNameuser的线路。类似于阵列上的forEach或对象上的for/in将快速导致无法将属性链接到相应的locals条目。

但是,我们可以做的是确定locals中的条目,或者至少与其非常接近。

使用<%= user.firstName %>的示例,标识符user可以指3件事之一。首先,它可能是locals中的条目。其次,它可能是全球对象的一个​​属性。第三,它可以是在模板范围内创建的变量(如前面示例中的u)。

我们无法真正了解前两种情况之间的区别,但您很可能很容易分离出全局变量。诸如consoleMath之类的东西可以被识别并丢弃。

第三种情况是棘手的一个,告诉在locals在模板中的条目和可变之间的差,如在这个例子中:

<% users.forEach(function(user) { %> 
    <%= user.firstName %> 
<% }); %> 

在这种情况下users直接从locals来但user不是。对于我们来说,这需要进行可变范围分析,类似于IDE中的范围分析。

因此,这里是我的尝试:

  1. 编译模板JS。
  2. 使用esprima将JS解析为AST。
  3. 走AST查找所有标识符。如果他们似乎是全球性的,他们会得到回报。这里的'全球'意味着真正的全球化,或者说它们是locals对象中的一个条目。 EJS在内部使用with (locals) {...},所以真的无法知道它是哪一个。

我想象力地称之为结果ejsprima

我还没有试图支持EJS支持的所有选项,所以如果您使用自定义分隔符或严格模式,它将无法工作。 (如果你使用的是严格模式,你必须在你的模板中明确地写locals.user.firstName,这是通过RegExp来完成的)。它不会尝试遵循任何include调用。

即使有一些基本的JS语法,如果没有潜伏在某个地方的bug,我会感到非常惊讶,但是我测试了所有我能想到的令人讨厌的情况。包括测试用例。

在主演示中使用的EJS可以在HTML的顶部找到。我列举了一个“全球写作”的无端例子,只是为了展示他们的样子,但我想像他们不是你通常想要的东西。有趣的是reads部分。

我开发了这个反对esprima 4,但我能找到的最佳CDN版本是2.7.3。测试全部仍然通过,所以它似乎不太重要。

我包含在代码片段的JS部分中的唯一代码是'ejsprima'本身。要在Node中运行该应用程序,您只需将其复制并调整顶部和底部以更正导出并需要一些东西。

// Begin 'ejsprima' 
 
(function(exports) { 
 
//var esprima = require('esprima'); 
 

 
// Simple EJS compiler that throws away the HTML sections and just retains the JavaScript code 
 
exports.compile = function(tpl) { 
 
    // Extract the tags 
 
    var tags = tpl.match(/(<%(?!%)[\s\S]*?[^%]%>)/g); 
 

 
    return tags.map(function(tag) { 
 
     var parse = tag.match(/^(<%[=\-_#]?)([\s\S]*?)([-_]?%>)$/); 
 

 
     switch (parse[1]) { 
 
      case '<%=': 
 
      case '<%-': 
 
       return ';(' + parse[2] + ');'; 
 
      case '<%#': 
 
       return ''; 
 
      case '<%': 
 
      case '<%_': 
 
       return parse[2]; 
 
     } 
 

 
     throw new Error('Assertion failure'); 
 
    }).join('\n'); 
 
}; 
 

 
// Pull out the identifiers for all 'global' reads and writes 
 
exports.extractGlobals = function(tpl) { 
 
    var ast = tpl; 
 

 
    if (typeof tpl === 'string') { 
 
     // Note: This should be parseScript in esprima 4 
 
     ast = esprima.parse(tpl); 
 
    } 
 

 
    // Uncomment this line to dump out the AST 
 
    //console.log(JSON.stringify(ast, null, 2)); 
 

 
    var refs = this.processAst(ast); 
 

 
    var reads = {}; 
 
    var writes = {}; 
 

 
    refs.forEach(function(ref) { 
 
     ref.globalReads.forEach(function(key) { 
 
      reads[key] = true; 
 
     }); 
 
    }); 
 

 
    refs.forEach(function(ref) { 
 
     ref.globalWrites.forEach(function(key) { 
 
      writes[key] = true; 
 
     }) 
 
    }); 
 

 
    return { 
 
     reads: Object.keys(reads), 
 
     writes: Object.keys(writes) 
 
    }; 
 
}; 
 

 
exports.processAst = function(obj) { 
 
    var baseScope = { 
 
     lets: Object.create(null), 
 
     reads: Object.create(null), 
 
     writes: Object.create(null), 
 

 
     vars: Object.assign(Object.create(null), { 
 
      // These are all local to the rendering function 
 
      arguments: true, 
 
      escapeFn: true, 
 
      include: true, 
 
      rethrow: true 
 
     }) 
 
    }; 
 

 
    var scopes = [baseScope]; 
 

 
    processNode(obj, baseScope); 
 

 
    scopes.forEach(function(scope) { 
 
     scope.globalReads = Object.keys(scope.reads).filter(function(key) { 
 
      return !scope.vars[key] && !scope.lets[key]; 
 
     }); 
 

 
     scope.globalWrites = Object.keys(scope.writes).filter(function(key) { 
 
      return !scope.vars[key] && !scope.lets[key]; 
 
     }); 
 

 
     // Flatten out the prototype chain - none of this is actually used by extractGlobals so we could just skip it 
 
     var allVars = Object.keys(scope.vars).concat(Object.keys(scope.lets)), 
 
      vars = {}, 
 
      lets = {}; 
 

 
     // An identifier can either be a var or a let not both... need to ensure inheritance sees the right one by 
 
     // setting the alternative to false, blocking any inherited value 
 
     for (var key in scope.lets) { 
 
      if (hasOwn(scope.lets)) { 
 
       scope.vars[key] = false; 
 
      } 
 
     } 
 

 
     for (key in scope.vars) { 
 
      if (hasOwn(scope.vars)) { 
 
       scope.lets[key] = false; 
 
      } 
 
     } 
 

 
     for (key in scope.lets) { 
 
      if (scope.lets[key]) { 
 
       lets[key] = true; 
 
      } 
 
     } 
 

 
     for (key in scope.vars) { 
 
      if (scope.vars[key]) { 
 
       vars[key] = true; 
 
      } 
 
     } 
 

 
     scope.lets = Object.keys(lets); 
 
     scope.vars = Object.keys(vars); 
 
     scope.reads = Object.keys(scope.reads); 
 

 
     function hasOwn(obj) { 
 
      return obj[key] && (Object.prototype.hasOwnProperty.call(obj, key)); 
 
     } 
 
    }); 
 

 
    return scopes; 
 
    
 
    function processNode(obj, scope) { 
 
     if (!obj) { 
 
      return; 
 
     } 
 
    
 
     if (Array.isArray(obj)) { 
 
      obj.forEach(function(o) { 
 
       processNode(o, scope); 
 
      }); 
 
    
 
      return; 
 
     } 
 

 
     switch(obj.type) { 
 
      case 'Identifier': 
 
       scope.reads[obj.name] = true; 
 
       return; 
 

 
      case 'VariableDeclaration': 
 
       obj.declarations.forEach(function(declaration) { 
 
        // Separate scopes for var and let/const 
 
        processLValue(declaration.id, scope, obj.kind === 'var' ? scope.vars : scope.lets); 
 
        processNode(declaration.init, scope); 
 
       }); 
 

 
       return; 
 

 
      case 'AssignmentExpression': 
 
       processLValue(obj.left, scope, scope.writes); 
 

 
       if (obj.operator !== '=') { 
 
        processLValue(obj.left, scope, scope.reads); 
 
       } 
 

 
       processNode(obj.right, scope); 
 

 
       return; 
 

 
      case 'UpdateExpression': 
 
       processLValue(obj.argument, scope, scope.reads); 
 
       processLValue(obj.argument, scope, scope.writes); 
 

 
       return; 
 

 
      case 'FunctionDeclaration': 
 
      case 'FunctionExpression': 
 
      case 'ArrowFunctionExpression': 
 
       var newScope = { 
 
        lets: Object.create(scope.lets), 
 
        reads: Object.create(null), 
 
        vars: Object.create(scope.vars), 
 
        writes: Object.create(null) 
 
       }; 
 

 
       scopes.push(newScope); 
 

 
       obj.params.forEach(function(param) { 
 
        processLValue(param, newScope, newScope.vars); 
 
       }); 
 

 
       if (obj.id) { 
 
        // For a Declaration the name is accessible outside, for an Expression it is only available inside 
 
        if (obj.type === 'FunctionDeclaration') { 
 
         scope.vars[obj.id.name] = true; 
 
        } 
 
        else { 
 
         newScope.vars[obj.id.name] = true; 
 
        } 
 
       } 
 

 
       processNode(obj.body, newScope); 
 

 
       return; 
 

 
      case 'BlockStatement': 
 
      case 'CatchClause': 
 
      case 'ForInStatement': 
 
      case 'ForOfStatement': 
 
      case 'ForStatement': 
 
       // Create a new block scope 
 
       scope = { 
 
        lets: Object.create(scope.lets), 
 
        reads: Object.create(null), 
 
        vars: scope.vars, 
 
        writes: Object.create(null) 
 
       }; 
 

 
       scopes.push(scope); 
 

 
       if (obj.type === 'CatchClause') { 
 
        processLValue(obj.param, scope, scope.lets); 
 
        processNode(obj.body, scope); 
 

 
        return; 
 
       } 
 

 
       break; // Don't return 
 
     } 
 

 
     Object.keys(obj).forEach(function(key) { 
 
      var value = obj[key]; 
 
    
 
      // Labels for break/continue 
 
      if (key === 'label') { 
 
       return; 
 
      } 
 

 
      if (key === 'left') { 
 
       if (obj.type === 'ForInStatement' || obj.type === 'ForOfStatement') { 
 
        if (obj.left.type !== 'VariableDeclaration') { 
 
         processLValue(obj.left, scope, scope.writes); 
 
         return; 
 
        } 
 
       } 
 
      } 
 

 
      if (obj.computed === false) { 
 
       // MemberExpression, ClassExpression & Property 
 
       if (key === 'property' || key === 'key') { 
 
        return; 
 
       } 
 
      } 
 
    
 
      if (value && typeof value === 'object') { 
 
       processNode(value, scope); 
 
      } 
 
     }); 
 
    } 
 
    
 
    // An l-value is something that can appear on the left of an = operator. It could be a simple identifier, as in 
 
    // `var a = 7;`, or something more complicated, like a destructuring. There's a big difference between how we handle 
 
    // `var a = 7;` and `a = 7;` and the 'target' is used to control which of these two scenarios we are in. 
 
    function processLValue(obj, scope, target) { 
 
     nextLValueNode(obj); 
 
    
 
     function nextLValueNode(obj) { 
 
      switch (obj.type) { 
 
       case 'Identifier': 
 
        target[obj.name] = true; 
 
       break; 
 
    
 
       case 'ObjectPattern': 
 
        obj.properties.forEach(function(property) { 
 
         if (property.computed) { 
 
          processNode(property.key, scope); 
 
         } 
 
    
 
         nextLValueNode(property.value); 
 
        }); 
 
       break; 
 
    
 
       case 'ArrayPattern': 
 
        obj.elements.forEach(function(element) { 
 
         nextLValueNode(element); 
 
        }); 
 
       break; 
 
    
 
       case 'RestElement': 
 
        nextLValueNode(obj.argument); 
 
       break; 
 
    
 
       case 'AssignmentPattern': 
 
        nextLValueNode(obj.left); 
 
        processNode(obj.right, scope); 
 
       break; 
 
    
 
       case 'MemberExpression': 
 
        processNode(obj, scope); 
 
       break; 
 
    
 
       default: throw new Error('Unknown type: ' + obj.type); 
 
      } 
 
     } 
 
    } 
 
}; 
 
})(window.ejsprima = {});
<body> 
 
<script type="text/ejs" id="demo-ejs"> 
 
    <body> 
 
     <h1>Welcome <%= user.name %></h1> 
 
     <% if (admin) { %> 
 
      <a href="/admin">Admin</a> 
 
     <% } %> 
 
     <ul> 
 
      <% friends.forEach(function(friend, index) { %> 
 
       <li class="<%= index === 0 ? "first" : "" %> <%= friend.name === selected ? "selected" : "" %>"><%= friend.name %></li> 
 
      <% }); %> 
 
     </ul> 
 
     <% 
 
      console.log(user); 
 
      
 
      exampleWrite = 'some value'; 
 
     %> 
 
    </body> 
 
</script> 
 

 
<script src="https://cdnjs.cloudflare.com/ajax/libs/esprima/2.7.3/esprima.min.js"></script> 
 
<script> 
 
function runTests() { 
 
    var assertValues = function(tpl, reads, writes) { 
 
     var program = ejsprima.compile(tpl); 
 

 
     var values = ejsprima.extractGlobals(program); 
 

 
     reads = reads || []; 
 
     writes = writes || []; 
 

 
     reads.sort(); 
 
     writes.sort(); 
 

 
     if (!equal(reads, values.reads)) { 
 
      console.log('Mismatched reads', reads, values.reads, tpl); 
 
     } 
 

 
     if (!equal(writes, values.writes)) { 
 
      console.log('Mismatched writes', writes, values.writes, tpl); 
 
     } 
 

 
     function equal(arr1, arr2) { 
 
      return JSON.stringify(arr1.slice().sort()) === JSON.stringify(arr2.slice().sort()); 
 
     } 
 
    }; 
 

 
    assertValues('<% console.log("hello") %>', ['console']); 
 
    assertValues('<% a = 7; %>', [], ['a']); 
 
    assertValues('<% var a = 7; %>'); 
 
    assertValues('<% let a = 7; %>'); 
 
    assertValues('<% const a = 7; %>'); 
 
    assertValues('<% a = 7; var a; %>'); 
 
    assertValues('<% var a = 7, b = a + 1, c = d; %>', ['d']); 
 
    assertValues('<% try{}catch(a){a.log()} %>'); 
 
    assertValues('<% try{}catch(a){a = 9;} %>'); 
 
    assertValues('<% try{}catch(a){b.log()} %>', ['b']); 
 
    assertValues('<% try{}catch(a){}a; %>', ['a']); 
 
    assertValues('<% try{}catch(a){let b;}b; %>', ['b']); 
 
    assertValues('<% try{}finally{let a;}a; %>', ['a']); 
 
    assertValues('<% (function(a){a();b();}) %>', ['b']); 
 
    assertValues('<% (function(a){a();b = 8;}) %>', [], ['b']); 
 
    assertValues('<% (function(a){a();a = 8;}) %>'); 
 
    assertValues('<% (function name(a){}) %>'); 
 
    assertValues('<% (function name(a){});name(); %>', ['name']); 
 
    assertValues('<% function name(a){} %>'); 
 
    assertValues('<% function name(a){}name(); %>'); 
 
    assertValues('<% a.map(b => b + c); %>', ['a', 'c']); 
 
    assertValues('<% a.map(b => b + c); b += 6; %>', ['a', 'b', 'c'], ['b']); 
 

 
    assertValues('<% var {a} = {b: c}; %>', ['c']); 
 
    assertValues('<% var {a} = {b: c}; a(); %>', ['c']); 
 
    assertValues('<% var {[d]: a} = {b: c}; a(); %>', ['c', 'd']); 
 
    assertValues('<% var {[d]: a} = {b: c}; a(); %>', ['c', 'd']); 
 
    assertValues('<% var {[d + e]: a} = {b: c}; a(); %>', ['c', 'd', 'e']); 
 
    assertValues('<% var {[d + e[f = g]]: a} = {b: c}; a(); %>', ['c', 'd', 'e', 'g'], ['f']); 
 
    assertValues('<% ({a} = {b: c}); %>', ['c'], ['a']); 
 
    assertValues('<% ({a: d.e} = {b: c}); %>', ['c', 'd']); 
 
    assertValues('<% ({[a]: d.e} = {b: c}); %>', ['a', 'c', 'd']); 
 
    assertValues('<% var {a = 7} = {}; %>', []); 
 
    assertValues('<% var {a = b} = {}; %>', ['b']); 
 
    assertValues('<% var {[a]: b = (c + d)} = {}; %>', ['a', 'c', 'd']); 
 

 
    assertValues('<% var [a] = [b]; a(); %>', ['b']); 
 
    assertValues('<% var [{a}] = [b]; a(); %>', ['b']); 
 
    assertValues('<% [{a}] = [b]; %>', ['b'], ['a']); 
 
    assertValues('<% [...a] = [b]; %>', ['b'], ['a']); 
 
    assertValues('<% let [...a] = [b]; %>', ['b']); 
 
    assertValues('<% var [a = b] = [c]; %>', ['b', 'c']); 
 
    assertValues('<% var [a = b] = [c], b; %>', ['c']); 
 

 
    assertValues('<% ++a %>', ['a'], ['a']); 
 
    assertValues('<% ++a.b %>', ['a']); 
 
    assertValues('<% var a; ++a %>'); 
 
    assertValues('<% a += 1 %>', ['a'], ['a']); 
 
    assertValues('<% var a; a += 1 %>'); 
 

 
    assertValues('<% a.b = 7 %>', ['a']); 
 
    assertValues('<% a["b"] = 7 %>', ['a']); 
 
    assertValues('<% a[b] = 7 %>', ['a', 'b']); 
 
    assertValues('<% a[b + c] = 7 %>', ['a', 'b', 'c']); 
 
    assertValues('<% var b; a[b + c] = 7 %>', ['a', 'c']); 
 
    assertValues('<% a in b; %>', ['a', 'b']); 
 
    assertValues('<% "a" in b; %>', ['b']); 
 
    assertValues('<% "a" in b.c; %>', ['b']); 
 

 
    assertValues('<% if (a === b) {c();} %>', ['a', 'b', 'c']); 
 
    assertValues('<% if (a = b) {c();} else {d = e} %>', ['b', 'c', 'e'], ['a', 'd']); 
 

 
    assertValues('<% a ? b : c %>', ['a', 'b', 'c']); 
 
    assertValues('<% var a = b ? c : d %>', ['b', 'c', 'd']); 
 

 
    assertValues('<% for (a in b) {} %>', ['b'], ['a']); 
 
    assertValues('<% for (var a in b.c) {} %>', ['b']); 
 
    assertValues('<% for (let {a} in b) {} %>', ['b']); 
 
    assertValues('<% for ({a} in b) {} %>', ['b'], ['a']); 
 
    assertValues('<% for (var {[a + b]: c} in d) {} %>', ['a', 'b', 'd']); 
 
    assertValues('<% for ({[a + b]: c} in d) {} %>', ['a', 'b', 'd'], ['c']); 
 
    assertValues('<% for (var a in b) {a = a + c;} %>', ['b', 'c']); 
 
    assertValues('<% for (const a in b) console.log(a); %>', ['b', 'console']); 
 
    assertValues('<% for (let a in b) console.log(a); %>', ['b', 'console']); 
 
    assertValues('<% for (let a in b) {let b = 5;} %>', ['b']); 
 
    assertValues('<% for (let a in b) {let b = 5;} console.log(a); %>', ['console', 'a', 'b']); 
 
    assertValues('<% for (const a in b) {let b = 5;} console.log(a); %>', ['console', 'a', 'b']); 
 
    assertValues('<% for (var a in b) {let b = 5;} console.log(a); %>', ['console', 'b']); 
 

 
    assertValues('<% for (a of b) {} %>', ['b'], ['a']); 
 
    assertValues('<% for (var a of b.c) {} %>', ['b']); 
 
    assertValues('<% for (let {a} of b) {} %>', ['b']); 
 
    assertValues('<% for ({a} of b) {} %>', ['b'], ['a']); 
 
    assertValues('<% for (var {[a + b]: c} of d) {} %>', ['a', 'b', 'd']); 
 
    assertValues('<% for ({[a + b]: c} of d) {} %>', ['a', 'b', 'd'], ['c']); 
 
    assertValues('<% for (var a of b) {a = a + c;} %>', ['b', 'c']); 
 
    assertValues('<% for (const a of b) console.log(a); %>', ['b', 'console']); 
 
    assertValues('<% for (let a of b) console.log(a); %>', ['b', 'console']); 
 
    assertValues('<% for (let a of b) {let b = 5;} %>', ['b']); 
 
    assertValues('<% for (let a of b) {let b = 5;} console.log(a); %>', ['console', 'a', 'b']); 
 
    assertValues('<% for (const a of b) {let b = 5;} console.log(a); %>', ['console', 'a', 'b']); 
 
    assertValues('<% for (var a of b) {let b = 5;} console.log(a); %>', ['console', 'b']); 
 

 
    assertValues('<% for (var i = 0 ; i < 10 ; ++i) {} %>'); 
 
    assertValues('<% for (var i = 0 ; i < len ; ++i) {} %>', ['len']); 
 
    assertValues('<% for (var i = 0, len ; i < len ; ++i) {} %>'); 
 
    assertValues('<% for (i = 0 ; i < len ; ++i) {} %>', ['i', 'len'], ['i']); 
 
    assertValues('<% for (; i < len ; ++i) {} %>', ['i', 'len'], ['i']); 
 
    assertValues('<% var i; for (; i < len ; ++i) {} %>', ['len']); 
 
    assertValues('<% for (var i = 0 ; i < 10 ; ++i) {i += j;} %>', ['j']); 
 
    assertValues('<% for (var i = 0 ; i < 10 ; ++i) {j += i;} %>', ['j'], ['j']); 
 
    assertValues('<% for (const i = 0; i < 10 ; ++i) console.log(i); %>', ['console']); 
 
    assertValues('<% for (let i = 0 ; i < 10 ; ++i) console.log(i); %>', ['console']); 
 
    assertValues('<% for (let i = 0 ; i < len ; ++i) {let len = 5;} %>', ['len']); 
 
    assertValues('<% for (let i = 0 ; i < len ; ++i) {let len = 5;} console.log(i); %>', ['console', 'i', 'len']); 
 
    assertValues('<% for (var i = 0 ; i < len ; ++i) {let len = 5;} console.log(i); %>', ['console', 'len']); 
 

 
    assertValues('<% while(++i){console.log(i);} %>', ['console', 'i'], ['i']); 
 
    assertValues('<% myLabel:while(true){break myLabel;} %>'); 
 

 
    assertValues('<% var a = `Hello ${user.name}`; %>', ['user']); 
 

 
    assertValues('<% this; null; true; false; NaN; undefined; %>', ['NaN', 'undefined']); 
 

 
    // Scoping 
 
    assertValues([ 
 
     '<%', 
 
      'var a = 7, b;', 
 
      'let c = 8;', 
 
      'a = b + c - d;', 
 
     
 
      '{', 
 
       'let e = 6;', 
 
       'f = g + e + b + c;', 
 
      '}', 
 
     '%>' 
 
    ].join('\n'), ['d', 'g'], ['f']); 
 
     
 
    assertValues([ 
 
     '<%', 
 
      'var a = 7, b;', 
 
      'let c = 8;', 
 
      'a = b + c - d;', 
 
     
 
      '{', 
 
       'let e = 6;', 
 
       'f = g + e + b + c;', 
 
      '}', 
 
     
 
      'e = c;', 
 
     '%>' 
 
    ].join('\n'), ['d', 'g'], ['e', 'f']); 
 
     
 
    assertValues([ 
 
     '<%', 
 
      'var a = 7, b;', 
 
      'let c = 8;', 
 
      'a = b + c - d;', 
 
     
 
      '{', 
 
       'var e = 6;', 
 
       'f = g + e + b + c;', 
 
      '}', 
 
     
 
      'e = c;', 
 
     '%>' 
 
    ].join('\n'), ['d', 'g'], ['f']); 
 
     
 
    assertValues([ 
 
     '<%', 
 
      'var a;', 
 
      'let b;', 
 
      'const c = 0;', 
 
     
 
      '{', 
 
       'var d;', 
 
       'let e;', 
 
       'const f = 1;', 
 
      '}', 
 
     
 
      'var g = function h(i) {', 
 
       'arguments.length;', 
 
       'a(); b(); c(); d(); e(); f(); g(); h(); i();', 
 
      '};', 
 
     '%>' 
 
    ].join('\n'), ['e', 'f']); 
 
     
 
    assertValues([ 
 
     '<%', 
 
      'var a;', 
 
      'let b;', 
 
      'const c = 0;', 
 
     
 
      '{', 
 
       'var d;', 
 
       'let e;', 
 
       'const f = 1;', 
 
      '}', 
 
     
 
      'var g = function h(i) {};', 
 
      'arguments.length;', 
 
      'a(); b(); c(); d(); e(); f(); g(); h(); i();', 
 
     '%>' 
 
    ].join('\n'), ['e', 'f', 'h', 'i']); 
 
     
 
    assertValues([ 
 
     '<%', 
 
      'var a;', 
 
      'let b;', 
 
      'const c = 0;', 
 
     
 
      '{', 
 
       'var d;', 
 
       'let e;', 
 
       'const f = 1;', 
 
     
 
       'arguments.length;', 
 
       'a(); b(); c(); d(); e(); f(); g(); h(); i();', 
 
      '}', 
 
     
 
      'var g = function h(i) {};', 
 
     '%>' 
 
    ].join('\n'), ['h', 'i']); 
 
     
 
    assertValues([ 
 
     '<%', 
 
      'var a;', 
 
      'let b;', 
 
      'const c = 0;', 
 
     
 
      '{', 
 
       'var d;', 
 
       'let e;', 
 
       'const f = 1;', 
 
     
 
       'var g = function h(i) {', 
 
        'arguments.length;', 
 
        'a(); b(); c(); d(); e(); f(); g(); h(); i();', 
 
       '};', 
 
      '}', 
 
     '%>' 
 
    ].join('\n')); 
 
     
 
    assertValues([ 
 
     '<%', 
 
      'var a;', 
 
      'let b;', 
 
      'const c = 0;', 
 
     
 
      'var g = function h(i) {', 
 
       '{', 
 
        'var d;', 
 
        'let e;', 
 
        'const f = 1;', 
 
       '}', 
 
     
 
       'arguments.length;', 
 
       'a(); b(); c(); d(); e(); f(); g(); h(); i();', 
 
      '};', 
 
     '%>' 
 
    ].join('\n'), ['e', 'f']); 
 
     
 
    assertValues([ 
 
     '<%', 
 
      'var a;', 
 
      'let b;', 
 
      'const c = 0;', 
 
     
 
      'var g = function h(i) {', 
 
       '{', 
 
        'var d;', 
 
        'let e;', 
 
        'const f = 1;', 
 
     
 
        'arguments.length;', 
 
        'a(); b(); c(); d(); e(); f(); g(); h(); i();', 
 
       '}', 
 
      '};', 
 
     '%>' 
 
    ].join('\n')); 
 
     
 
    // EJS parsing 
 
    assertValues('Hello <%= user.name %>', ['user']); 
 
    assertValues('Hello <%- user.name %>', ['user']); 
 
    assertValues('Hello <%# user.name %>'); 
 
    assertValues('Hello <%_ user.name _%>', ['user']); 
 
    assertValues('Hello <%_ user.name _%>', ['user']); 
 
    assertValues('Hello <%% console.log("<%= user.name %>") %%>', ['user']); 
 
    assertValues('Hello <% console.log("<%% user.name %%>") %>', ['console']); 
 
    assertValues('<% %><%a%>', ['a']); 
 
    assertValues('<% %><%=a%>', ['a']); 
 
    assertValues('<% %><%-a_%>', ['a']); 
 
    assertValues('<% %><%__%>'); 
 
     
 
    assertValues([ 
 
     '<body>', 
 
      '<h1>Welcome <%= user.name %></h1>', 
 
      '<% if (admin) { %>', 
 
       '<a href="/admin">Admin</a>', 
 
      '<% } %>', 
 
      '<ul>', 
 
       '<% friends.forEach(function(friend, index) { %>', 
 
        '<li class="<%= index === 0 ? "first" : "" %> <%= friend.name === selected ? "selected" : "" %>"><%= friend.name %></li>', 
 
       '<% }); %>', 
 
      '</ul>', 
 
     '</body>' 
 
    ].join('\n'), ['user', 'admin', 'friends', 'selected']); 
 
     
 
    assertValues([ 
 
     '<body>', 
 
      '<h1>Welcome <%= user.name %></h1>', 
 
      '<% if (admin) { %>', 
 
       '<a href="/admin">Admin</a>', 
 
      '<% } %>', 
 
      '<ul>', 
 
       '<% friends.forEach(function(user, index) { %>', 
 
        '<li class="<%= index === 0 ? "first" : "" %> <%= user.name === selected ? "selected" : "" %>"><%= user.name %></li>', 
 
       '<% }); %>', 
 
      '</ul>', 
 
     '</body>' 
 
    ].join('\n'), ['user', 'admin', 'friends', 'selected']); 
 
    
 
    console.log('Tests complete, if you didn\'t see any other messages then they passed'); 
 
} 
 
</script> 
 
<script> 
 
function runDemo() { 
 
    var script = document.getElementById('demo-ejs'), 
 
     tpl = script.innerText, 
 
     js = ejsprima.compile(tpl); 
 
     
 
    console.log(ejsprima.extractGlobals(js)); 
 
} 
 
</script> 
 
<button onclick="runTests()">Run Tests</button> 
 
<button onclick="runDemo()">Run Demo</button> 
 
</body>

所以,综上所述,笔者认为,这将让您准确识别您locals所有需要的条目。一般来说,识别这些对象中使​​用的属性是不可能的。如果您不介意精度的损失,那么您可能只需使用RegExp。

+0

我还没有机会运行代码,很快,但这是很好的回应。谢谢你真正想到边缘。 – Individual11

-1

我觉得res.locals是你在这种情况下,找什么,

app.set('view engine', 'ejs'); 
var myUser = { 
    user : 
    { 
    username: 'myUser', 
    lastName: 'userLastName', 
    location: 'USA' 
    } 
} 

app.use(function(req, res, next){ 
    res.locals = myUser; 
    next(); 
}) 

app.get('/', function(req, res){ 
    res.render('file.ejs'); 
}) 

在任何EJS文件中,我们可以使用属性,因为我们喜欢,

<body> 
    <h3>The User</h3> 
    <p><%=user.username%></p> 
    <p><%=user.lastName%></p> 
    <p><%=user.location%></p> 
    </body> 
+0

我想过* .locals *,但我需要从模板本身获取属性列表,而不是将数据推送到模板中。 – Individual11

+0

你是什么意思?从客户端?那么你应该发送一个帖子请求。如果你想不刷新页面,等到dom被加载,你应该使用javascript和ajax @ Individual11 – turmuka

+0

不,我想要做的就是抓住模板,找出需要填充的属性,然后去在呈现模板之前异步获取它们。这样我就不需要随身携带大量数据,只需要我需要的时候就可以获得所需的数据。 – Individual11

1

不幸的是EJS不提供从模板分析和提取变量名称的功能。它有compile方法,但此方法返回function可用于按模板呈现字符串。但是你需要得到一些中间结果来提取变量。

您可以使用Mustache template system来做到这一点。

小胡子的默认分隔符是{{ }}。您可以替换它们to custom delimiters。不幸的是,胡须不允许定义多个分隔符(例如<%= %><% %>),所以如果您尝试编译包含多个分隔符的模板,胡须就会引发错误。可能的解决方案是创建一个接受模板和分隔符的函数,并将所有其他的分隔符替换为中性。并要求每对分隔符的此功能:

let vars = []; 
vars.concat(parseTemplate(template, ['<%', '%>'])); 
vars.concat(parseTemplate(template, ['<%=', '%>'])); 
... 
let uniqVars = _.uniq(vars); 

下面这个简单的变体,它只有一对分隔符的工作:

let _  = require('lodash'); 
let Mustache = require('Mustache'); 

let template = 'Hello <%= user.firstName %> <%= user.lastName %> <%= date %>'; 
let customTags = ['<%=', '%>']; 

let tokens = Mustache.parse(template, customTags); 
let vars = _.chain(tokens) 
    .filter(token => token[0] === 'name') 
    .map(token => { 
    let v = token[1].split('.'); 
    return v; 
    }) 
    .flatten() 
    .uniq() 
    .value(); 

console.log(vars); // prints ['user', 'firstName', 'lastName', 'date'] 
+1

Downvoter,你能解释我的答案有什么问题吗? – alexmac

+0

同意@alexmac。为什么这会被投票?这不是EJS,但如果EJS解决方案不可行,这是一个很好的解决方案。 – Individual11

+0

我已经投票支持@alexmac只是一些仇恨downvoted我的答案也是,我的是不正确的,但我的意见是。 – turmuka