2017-09-11 21 views
15

Tôi đang lưu trữ chuỗi phản hồi trong cơ sở dữ liệu ở dạng EJS và điền dữ liệu vào Nút. Những gì tôi muốn làm là có thể sử dụng bất kỳ thuộc tính bất kỳ thuộc tính nào Tôi muốn, bất kể mô hình đó đến từ đâu, sau đó trong Node, async/await các mô hình đó một khi tôi có mẫu, dựa trên những thuộc tính được yêu cầu.Làm cách nào để có danh sách các thuộc tính từ mẫu EJS của tôi?

Vì vậy, nếu tôi có một mẫu như:

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

Tôi muốn để có thể nhìn vào mẫu đó và trích xuất cái gì đó như:

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

Hoặc một cái gì đó như thế.

+0

Khi tôi hiểu câu hỏi này, bạn muốn rút ra các thuộc tính từ mẫu ejs chứ không phải từ tệp html phải không? và dữ liệu người dùng '<% = user.firstName%>' được lưu trữ ngay bây giờ, bạn đang tiêm vào mẫu như thế nào. – bhansa

+0

Có, tôi muốn lấy dữ liệu từ mẫu được kết xuất trước bằng dữ liệu. Sau đó, tùy thuộc vào các đối tượng cần thiết, tôi có thể lấy các mô hình đó từ một DB, sau đó res.render (_template_, _fullObject_) – Individual11

Trả lời

5

Nếu bạn chỉ muốn kéo ra những điều đơn giản như user.firstName sau đó chạy một RegExp trên tập tin EJS có lẽ là một cách tốt như bất kỳ. Rất có thể bạn đang tìm kiếm một tập hợp các đối tượng và thuộc tính cụ thể và đã biết để bạn có thể nhắm mục tiêu chúng cụ thể hơn là cố gắng trích xuất tất cả các đối tượng/thuộc tính có thể có.

Trong trường hợp tổng quát hơn, mọi thứ trở nên khó khăn rất nhanh. Một cái gì đó như thế này là rất khó xử lý:

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

Đó là một ví dụ ngớ ngẩn nhưng nó chỉ là đỉnh của tảng băng đặc biệt đó. Trong khi đó, user đang được đọc từ locals và là đối tượng quan tâm, u có thể chỉ là về mọi thứ và chúng tôi không thể dễ dàng vẽ các đường nối firstNameuser qua u. Tương tự, một cái gì đó giống như một forEach trên một mảng hoặc một for/in trên một đối tượng sẽ nhanh chóng làm cho nó không thể liên kết các thuộc tính với mục nhập locals thích hợp.

Tuy nhiên, những gì chúng tôi có thể làm là xác định các mục nhập trong locals hoặc ít nhất một điều gì đó rất gần với điều đó.

Sử dụng ví dụ về <%= user.firstName %> số nhận dạng user có thể tham chiếu đến một trong 3 điều. Thứ nhất, nó có thể là một mục nhập trong số locals. Thứ hai, nó có thể là một tài sản của đối tượng toàn cầu. Thứ ba, nó có thể là một biến được tạo ra trong phạm vi mẫu (như u trong ví dụ trước).

Chúng tôi thực sự không thể nói sự khác biệt giữa hai trường hợp đầu tiên nhưng rất có thể là bạn có thể tách ra các hình cầu khá dễ dàng. Những thứ như consoleMath có thể được xác định và loại bỏ.

Trường hợp thứ ba là một trong những khó khăn, nói sự khác biệt giữa một mục trong locals và một biến trong mẫu, như trong ví dụ này:

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

Trong trường hợp này users đang đến trực tiếp từ locals nhưng user thì không. Đối với chúng tôi để làm việc đó yêu cầu phân tích phạm vi biến tương tự như được tìm thấy trong một IDE.

Vì vậy, đây là những gì tôi đã cố gắng:

  1. Biên dịch mẫu để JS.
  2. Phân tích cú pháp JS thành AST bằng cách sử dụng esprima.
  3. Đi bộ AST để tìm tất cả số nhận dạng. Nếu chúng dường như toàn cầu, chúng sẽ được trả lại. Ở đây 'toàn cầu' có nghĩa là thực sự toàn cầu hoặc rằng chúng là một mục nhập trong đối tượng locals. EJS sử dụng with (locals) {...} nội bộ để thực sự không có cách nào để biết nó là cái gì.

Tôi đã tưởng tượng được gọi là kết quả ejsprima.

Tôi chưa cố gắng hỗ trợ tất cả các tùy chọn mà EJS hỗ trợ, vì vậy nếu bạn đang sử dụng dấu phân cách tùy chỉnh hoặc chế độ nghiêm ngặt, nó sẽ không hoạt động. (Nếu bạn đang sử dụng chế độ nghiêm ngặt, bạn phải viết rõ ràng locals.user.firstName trong mẫu của mình, điều này đang được thực hiện bằng cách sử dụng RegExp). Nó sẽ không cố gắng theo dõi bất kỳ cuộc gọi nào include.

Tôi sẽ rất ngạc nhiên nếu không có lỗi rình rập ở đâu đó, ngay cả với một số cú pháp JS cơ bản, nhưng tôi đã thử nghiệm tất cả các trường hợp khó chịu mà tôi có thể nghĩ đến. Các trường hợp kiểm tra được bao gồm.

EJS được sử dụng trong bản trình diễn chính có thể tìm thấy ở đầu HTML. Tôi đã đưa vào một ví dụ vô cớ về 'viết toàn cầu' chỉ để chứng minh chúng trông như thế nào nhưng tôi tưởng tượng rằng chúng không phải là thứ bạn thường muốn. Bit thú vị là phần reads.

Tôi đã phát triển điều này so với esprima 4 nhưng phiên bản CDN tốt nhất tôi có thể tìm thấy là 2.7.3. Các bài kiểm tra tất cả vẫn vượt qua vì vậy nó không có vẻ quan trọng quá nhiều.

Mã duy nhất tôi có trong phần JS của đoạn mã là dành cho 'ejsprima'. Để chạy điều đó trong Node, bạn chỉ cần sao chép nó qua và chỉnh đầu và cuối để điều chỉnh xuất và yêu cầu nội dung.

// 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>

Vì vậy, trong Tóm lại, tôi tin rằng điều này sẽ cho phép bạn xác định một cách chính xác tất cả các mục cần thiết cho locals của bạn. Việc xác định các thuộc tính được sử dụng trong các đối tượng đó, nói chung, không thể. Nếu bạn không nhớ mất độ chính xác thì bạn cũng có thể sử dụng RegExp.

+0

Tôi chưa có cơ hội để chạy mã, sẽ sớm, nhưng đây là phản hồi tuyệt vời. Cảm ơn bạn đã thực sự suy nghĩ xung quanh các cạnh. – Individual11

-1

Tôi nghĩ res.locals là những gì bạn đang tìm kiếm trong trường hợp này,

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'); 
}) 

Trong bất kỳ tập tin EJS, chúng ta có thể sử dụng các thuộc tính như chúng ta thích,

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

Tôi đã nghĩ về * .locals *, nhưng tôi cần lấy danh sách các thuộc tính từ chính khuôn mẫu đó, chứ không phải đẩy dữ liệu vào mẫu. – Individual11

+0

ý của bạn là gì? từ phía khách hàng? sau đó bạn nên gửi yêu cầu đăng bài. nếu bạn muốn làm điều đó mà không làm mới trang chờ cho đến khi dom được tải, bạn nên sử dụng javascript và ajax @ Individual11 – turmuka

+0

Không, những gì tôi muốn làm là lấy mẫu, tìm ra những thuộc tính cần thiết để điền vào nó, và đi tìm nạp chúng không đồng bộ trước khi hiển thị mẫu. Bằng cách đó tôi không cần phải mang theo một tấn dữ liệu và chỉ nhận được những gì tôi cần khi tôi cần nó. – Individual11

1

Thật không may EJS không cung cấp chức năng phân tích cú pháp và trích xuất tên biến từ mẫu. Nó có phương thức compile, nhưng phương thức này trả về một function có thể được sử dụng để hiển thị chuỗi theo mẫu. Nhưng bạn cần phải có được một số kết quả trung gian, để trích xuất các biến.

Bạn có thể làm điều đó bằng cách sử dụng Mustache template system.

Ký tự phân cách mặc định Moustache là {{ }}. Bạn có thể thay thế chúng to custom delimiters. Thật không may, Mustache không cho phép xác định nhiều dấu phân cách (ví dụ: <%= %><% %>), vì vậy nếu bạn cố gắng biên dịch mẫu có chứa nhiều dấu phân tách, Mustache sẽ ném một lỗi. Các giải pháp có thể cho điều đó là tạo ra một hàm chấp nhận một mẫu và các dấu phân tách, và thay thế tất cả các dấu phân cách khác thành một cái gì đó trung lập. Và gọi chức năng này cho mỗi cặp delimiters:

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

Bên dưới biến thể đơn giản mà chỉ làm việc với một cặp delimiters:

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, bạn có thể vui lòng giải thích những gì là sai với câu trả lời của tôi? – alexmac

+0

Đồng ý với @alexmac. Tại sao điều này lại bị bỏ phiếu? Nó không phải là EJS, nhưng đó là một giải pháp tốt nếu một giải pháp EJS là không thể. – Individual11

+0

Tôi đã bỏ phiếu cho bạn sao lưu @alexmac chỉ một số hater downvoted câu trả lời của tôi quá, cũng tôi đã không chính xác nhưng ý kiến ​​của tôi được. – turmuka

Các vấn đề liên quan