javascript - 如何从我的 EJS 模板中获取属性列表?

标签 javascript node.js ejs

我将响应字符串以 EJS 形式存储在数据库中,并在 Node.js 中填写数据。我想要做的是能够使用我想要的任何属性,无论它来自什么模型,然后在 Node 中,一旦我有了模板,就根据所需的属性异步/等待这些模型。

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

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

我希望能够查看该模板并提取如下内容:
ejsProperties = ["user", "user.firstName"]

或类似的东西。

最佳答案

如果你只是想提取一些简单的东西,比如 user.firstName然后在 EJS 文件上运行 RegExp 可能是一种很好的方法。您可能正在寻找一组特定且已知的对象和属性,因此您可以专门针对它们,而不是尝试提取所有可能的对象/属性。

在更一般的情况下,事情会很快变得困难。这样的事情处理起来非常棘手:

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

这是一个愚蠢的例子,但这只是冰山一 Angular 。鉴于 user正在从 locals 读取并且是感兴趣的对象,u几乎可以是任何东西,我们不能轻易画出连接 firstName 的线。和 user通过 u .类似的类似 forEach在数组或 for/in在一个对象上将很快导致无法将属性链接到适当的 locals入口。

但是,我们可以做的是识别 locals 中的条目。 ,或者至少是非常接近的东西。

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

我们无法真正区分前两种情况,但您可以很容易地分离出全局变量。像 console 这样的事情和 Math可以识别和丢弃。

第三种情况比较棘手,说明 locals 中的条目之间的区别。和模板中的一个变量,就像在这个例子中一样:
<% users.forEach(function(user) { %>
    <%= user.firstName %>
<% }); %>

在这种情况下 users直接来自locals但是 user不是。要解决这个问题,我们需要类似于 IDE 中的变量范围分析。

所以这就是我尝试过的:
  • 将模板编译为 JS。
  • 使用 esprima 将 JS 解析为 AST .
  • 走 AST 找到所有的标识符。如果它们看起来是全局的,它们就会被退回。这里的“全局”是指真正的全局性或它们是locals 中的一个条目。目的。 EJS 使用 with (locals) {...}内部所以真的没有办法知道它是哪一个。

  • 我富有想象力地将结果称为 ejsprima .

    我没有尝试支持 EJS 支持的所有选项,所以如果您使用自定义分隔符或严格模式,它将无法工作。 (如果您使用的是严格模式,则无论如何都必须在模板中显式写入 locals.user.firstName,而这需要通过 RegExp 来完成)。它不会尝试关注任何 include来电。

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

    主演示中使用的 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。

    关于javascript - 如何从我的 EJS 模板中获取属性列表?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46162102/

    相关文章:

    javascript - 根据浏览器类型调整文本框大小

    javascript - 查找单个数组元素是否存在于 mongodb 文档的数组中

    node.js - 使用 Sequelize 进行数据库迁移后,列名不同

    sublimetext3 - 用于 EJS 格式的 Sublime Text 3 包

    javascript - 如何在 javascript 中从 XMLHttpRequest 解析该值并将其用作变量?

    javascript - ionic 切换检查属性

    javascript - 调试时如何忽略某些脚本文件/行?

    node.js - 无法调用未定义的方法 'use'

    javascript - 渲染 EJS 模板报错 this.templateText.replace is not a function

    javascript - 显示自 mongodb 对象的时间戳 + 重新格式化时间戳以来耗时