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