応答文字列をEJSフォームのデータベースに保存し、ノードにデータを入力しています。私がやりたいのは、どのモデルから来たとしても、必要なanyプロパティを使用できるようにすることです。次に、Nodeで、テンプレートを取得したら、プロパティが何であるかに基づいて、それらのモデルを非同期/待機します。必須。
したがって、次のようなテンプレートがある場合:
"Hello <%=user.firstName%>."
そのテンプレートを見て、次のようなものを抽出できるようにしたいと思います。
ejsProperties = ["user", "user.firstName"]
またはそのようなもの。
user.firstName
のような単純なものを引き出したいだけの場合は、EJSファイルに対してRegExpを実行するのがおそらく他の方法と同じくらい良い方法です。可能性のあるすべてのオブジェクト/プロパティを抽出しようとするのではなく、特定の既知のオブジェクトとプロパティのセットを探して、それらを具体的にターゲットにできる可能性があります。
より一般的なケースでは、物事は非常に迅速に困難になります。このようなものを処理するのは非常に難しいです:
<% var u = user; %><%= u.firstName %>
これはばかげた例ですが、それはその特定の氷山の一角にすぎません。 user
はlocals
から読み取られており、対象のオブジェクトですが、u
はほぼすべてであり、firstName
とuser
をu
経由で結ぶ線を簡単に描くことはできません。同様に、配列のforEach
やオブジェクトのfor/in
のようなものは、プロパティを適切なlocals
エントリにリンクすることをすぐに不可能にします。
ただし、私たちにできることは、locals
のエントリ、または少なくともそれに非常に近いものを識別することです。
<%= user.firstName %>
の例を使用すると、識別子user
は3つのもののいずれかを参照できます。まず、それはlocals
のエントリである可能性があります。次に、グローバルオブジェクトのプロパティである可能性があります。第3に、テンプレートのスコープ内で作成された変数である可能性があります(前の例のu
のように)。
最初の2つのケースの違いを実際に区別することはできませんが、グローバルを非常に簡単に分離できる可能性があります。 console
やMath
のようなものは、識別して破棄できます。
3番目のケースはトリッキーなケースで、次の例のように、locals
のエントリとテンプレートの変数の違いを示します。
<% users.forEach(function(user) { %>
<%= user.firstName %>
<% }); %>
この場合、users
はlocals
から直接取得されますが、user
はそうではありません。それを解決するには、IDEに見られるものと同様の可変スコープ分析が必要です。
だからこれが私が試したものです:
locals
オブジェクトのエントリであることを意味します。 with (locals) {...}
内部的には、それがどれであるかを知る方法は実際にはありません。私は想像力を働かせて結果をejsprima
と呼びました。
EJSがサポートするすべてのオプションをサポートしようとはしていません。そのため、カスタム区切り文字または厳密モードを使用している場合は機能しません。 (厳密モードを使用している場合は、とにかくテンプレートにlocals.user.firstName
を明示的に記述する必要があります。これは、代わりにRegExpを介して実行することを求めています)。 include
呼び出しを追跡しようとはしません。
基本的なJS構文の一部を使用しても、どこかにバグが潜んでいないとしたら、非常に驚きますが、考えられるすべての厄介なケースをテストしました。テストケースが含まれています。
メインデモで使用されるEJSは、HTMLの上部にあります。それらがどのように見えるかを示すためだけに、「グローバル書き込み」の無償の例を含めましたが、それらは通常必要なものではないと思います。興味深いのはreads
セクションです。
私はこれをesprima4に対して開発しましたが、私が見つけた最高の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を使用することをお勧めします。
残念ながら、EJSは、テンプレートから変数名を解析および抽出する機能を提供していません。 compile
メソッドがありますが、このメソッドは function を返します。これは、テンプレートごとに文字列をレンダリングするために使用できます。ただし、変数を抽出するには、中間結果を取得する必要があります。
Mustacheテンプレートシステム を使用してそれを行うことができます。
口ひげのデフォルトの区切り文字は{{ }}
。それらを置き換えることができます カスタム区切り文字に 。残念ながら、Mustacheでは複数の区切り文字を定義できません(<%= %>
および<% %>
たとえば)、したがって、複数の区切り文字を含むテンプレートをコンパイルしようとすると、Mustacheはエラーをスローします。そのための可能な解決策は、テンプレートと区切り文字を受け入れ、他のすべての区切り文字をニュートラルなものに置き換える関数を作成することです。そして、区切り文字のペアごとにこの関数を呼び出します。
let vars = [];
vars.concat(parseTemplate(template, ['<%', '%>']));
vars.concat(parseTemplate(template, ['<%=', '%>']));
...
let uniqVars = _.uniq(vars);
1組の区切り文字でのみ機能する単純なバリアントの下:
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']