如何从EJS模板中获取属性列表?

时间:2017-09-11 18:23:45

标签: javascript node.js ejs

我将响应字符串以EJS格式存储在数据库中,并在Node中填写数据。我想要做的是能够使用我想要的任何属性,无论它来自哪个模型,然后在Node中,async /等待那些模型,一旦我有模板,基于什么属性是必需的。

所以,如果我有一个类似的模板:

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

我希望能够查看该模板并提取类似的内容:

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

或类似的东西。

3 个答案:

答案 0 :(得分:5)

如果您只想提取像user.firstName之类的简单内容,那么在EJS文件上运行RegExp可能与任何方法一样好。您可能正在寻找一组特定且已知的对象和属性,因此您可以专门针对它们,而不是尝试提取所有可能的对象/属性。

在更一般的情况下,事情很快变得困难。这样的事情很难处理:

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

这是一个愚蠢的例子,但它只是特定冰山的一角。正在从user读取locals并且感兴趣的对象,u可以是任何内容,我们无法轻松绘制连接firstName和{{ 1}}通过user。类似地,类似于数组上的u或对象上的forEach,很快就会无法将属性链接到相应的for/in条目。

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

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

我们无法确定前两种情况之间的区别,但很有可能你可以很容易地将全局变量分开。可以识别并丢弃uconsole之类的内容。

第三种情况是棘手的,告诉Math中的条目和模板中的变量之间的区别,如下例所示:

locals

在这种情况下,<% users.forEach(function(user) { %> <%= user.firstName %> <% }); %> 直接来自users,但locals不是。对于我们来说,需要进行类似于IDE中的变量范围分析。

所以这就是我的尝试:

  1. 将模板编译为JS。
  2. 使用esprima将JS解析为AST。
  3. 走AST,找到所有标识符。如果他们看起来是全球性的,他们会被退回。这里的“全局”意味着要么是真正全局的,要么是user对象中的条目。 EJS在内部使用locals,因此实际上无法知道它是哪一个。
  4. 我有想象力地调用了结果with (locals) {...}

    我没有尝试支持EJS支持的所有选项,因此如果您使用自定义分隔符或严格模式,它将无效。 (如果你使用严格模式,你必须在你的模板中明确地写ejsprima,这很难通过RegExp来完成。它不会尝试关注任何locals.user.firstName来电。

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

    主要演示中使用的EJS可以在HTML的顶部找到。我已经包含了一个“全局写作”的无偿例子,只是为了展示它们的样子,但我想它们并不是你通常想要的东西。有趣的是include部分。

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

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

    reads
    // 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>所需的所有条目。通常,识别这些对象中使​​用的属性是不可能的。如果你不介意失去准确性,那么你也可以使用RegExp。

答案 1 :(得分:1)

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

您可以使用Mustache template system

执行此操作

Mustache默认分隔符为{{ }}。您可以替换它们to custom delimiters。遗憾的是,Mustache不允许定义多个分隔符(例如<%= %><% %>),因此如果您尝试编译包含多个分隔符的模板,那么Mustache将抛出错误。可能的解决方案是创建一个接受模板和分隔符的函数,并将所有其他分隔符替换为中性的。并为每对分隔符调用此函数:

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']

答案 2 :(得分:-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>