Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80742 views
1
var assignOwnDefaults = require('../internal/assignOwnDefaults'),
2
assignWith = require('../internal/assignWith'),
3
attempt = require('../utility/attempt'),
4
baseAssign = require('../internal/baseAssign'),
5
baseToString = require('../internal/baseToString'),
6
baseValues = require('../internal/baseValues'),
7
escapeStringChar = require('../internal/escapeStringChar'),
8
isError = require('../lang/isError'),
9
isIterateeCall = require('../internal/isIterateeCall'),
10
keys = require('../object/keys'),
11
reInterpolate = require('../internal/reInterpolate'),
12
templateSettings = require('./templateSettings');
13
14
/** Used to match empty string literals in compiled template source. */
15
var reEmptyStringLeading = /\b__p \+= '';/g,
16
reEmptyStringMiddle = /\b(__p \+=) '' \+/g,
17
reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g;
18
19
/** Used to match [ES template delimiters](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-template-literal-lexical-components). */
20
var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;
21
22
/** Used to ensure capturing order of template delimiters. */
23
var reNoMatch = /($^)/;
24
25
/** Used to match unescaped characters in compiled string literals. */
26
var reUnescapedString = /['\n\r\u2028\u2029\\]/g;
27
28
/**
29
* Creates a compiled template function that can interpolate data properties
30
* in "interpolate" delimiters, HTML-escape interpolated data properties in
31
* "escape" delimiters, and execute JavaScript in "evaluate" delimiters. Data
32
* properties may be accessed as free variables in the template. If a setting
33
* object is provided it takes precedence over `_.templateSettings` values.
34
*
35
* **Note:** In the development build `_.template` utilizes
36
* [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl)
37
* for easier debugging.
38
*
39
* For more information on precompiling templates see
40
* [lodash's custom builds documentation](https://lodash.com/custom-builds).
41
*
42
* For more information on Chrome extension sandboxes see
43
* [Chrome's extensions documentation](https://developer.chrome.com/extensions/sandboxingEval).
44
*
45
* @static
46
* @memberOf _
47
* @category String
48
* @param {string} [string=''] The template string.
49
* @param {Object} [options] The options object.
50
* @param {RegExp} [options.escape] The HTML "escape" delimiter.
51
* @param {RegExp} [options.evaluate] The "evaluate" delimiter.
52
* @param {Object} [options.imports] An object to import into the template as free variables.
53
* @param {RegExp} [options.interpolate] The "interpolate" delimiter.
54
* @param {string} [options.sourceURL] The sourceURL of the template's compiled source.
55
* @param {string} [options.variable] The data object variable name.
56
* @param- {Object} [otherOptions] Enables the legacy `options` param signature.
57
* @returns {Function} Returns the compiled template function.
58
* @example
59
*
60
* // using the "interpolate" delimiter to create a compiled template
61
* var compiled = _.template('hello <%= user %>!');
62
* compiled({ 'user': 'fred' });
63
* // => 'hello fred!'
64
*
65
* // using the HTML "escape" delimiter to escape data property values
66
* var compiled = _.template('<b><%- value %></b>');
67
* compiled({ 'value': '<script>' });
68
* // => '<b>&lt;script&gt;</b>'
69
*
70
* // using the "evaluate" delimiter to execute JavaScript and generate HTML
71
* var compiled = _.template('<% _.forEach(users, function(user) { %><li><%- user %></li><% }); %>');
72
* compiled({ 'users': ['fred', 'barney'] });
73
* // => '<li>fred</li><li>barney</li>'
74
*
75
* // using the internal `print` function in "evaluate" delimiters
76
* var compiled = _.template('<% print("hello " + user); %>!');
77
* compiled({ 'user': 'barney' });
78
* // => 'hello barney!'
79
*
80
* // using the ES delimiter as an alternative to the default "interpolate" delimiter
81
* var compiled = _.template('hello ${ user }!');
82
* compiled({ 'user': 'pebbles' });
83
* // => 'hello pebbles!'
84
*
85
* // using custom template delimiters
86
* _.templateSettings.interpolate = /{{([\s\S]+?)}}/g;
87
* var compiled = _.template('hello {{ user }}!');
88
* compiled({ 'user': 'mustache' });
89
* // => 'hello mustache!'
90
*
91
* // using backslashes to treat delimiters as plain text
92
* var compiled = _.template('<%= "\\<%- value %\\>" %>');
93
* compiled({ 'value': 'ignored' });
94
* // => '<%- value %>'
95
*
96
* // using the `imports` option to import `jQuery` as `jq`
97
* var text = '<% jq.each(users, function(user) { %><li><%- user %></li><% }); %>';
98
* var compiled = _.template(text, { 'imports': { 'jq': jQuery } });
99
* compiled({ 'users': ['fred', 'barney'] });
100
* // => '<li>fred</li><li>barney</li>'
101
*
102
* // using the `sourceURL` option to specify a custom sourceURL for the template
103
* var compiled = _.template('hello <%= user %>!', { 'sourceURL': '/basic/greeting.jst' });
104
* compiled(data);
105
* // => find the source of "greeting.jst" under the Sources tab or Resources panel of the web inspector
106
*
107
* // using the `variable` option to ensure a with-statement isn't used in the compiled template
108
* var compiled = _.template('hi <%= data.user %>!', { 'variable': 'data' });
109
* compiled.source;
110
* // => function(data) {
111
* // var __t, __p = '';
112
* // __p += 'hi ' + ((__t = ( data.user )) == null ? '' : __t) + '!';
113
* // return __p;
114
* // }
115
*
116
* // using the `source` property to inline compiled templates for meaningful
117
* // line numbers in error messages and a stack trace
118
* fs.writeFileSync(path.join(cwd, 'jst.js'), '\
119
* var JST = {\
120
* "main": ' + _.template(mainText).source + '\
121
* };\
122
* ');
123
*/
124
function template(string, options, otherOptions) {
125
// Based on John Resig's `tmpl` implementation (http://ejohn.org/blog/javascript-micro-templating/)
126
// and Laura Doktorova's doT.js (https://github.com/olado/doT).
127
var settings = templateSettings.imports._.templateSettings || templateSettings;
128
129
if (otherOptions && isIterateeCall(string, options, otherOptions)) {
130
options = otherOptions = null;
131
}
132
string = baseToString(string);
133
options = assignWith(baseAssign({}, otherOptions || options), settings, assignOwnDefaults);
134
135
var imports = assignWith(baseAssign({}, options.imports), settings.imports, assignOwnDefaults),
136
importsKeys = keys(imports),
137
importsValues = baseValues(imports, importsKeys);
138
139
var isEscaping,
140
isEvaluating,
141
index = 0,
142
interpolate = options.interpolate || reNoMatch,
143
source = "__p += '";
144
145
// Compile the regexp to match each delimiter.
146
var reDelimiters = RegExp(
147
(options.escape || reNoMatch).source + '|' +
148
interpolate.source + '|' +
149
(interpolate === reInterpolate ? reEsTemplate : reNoMatch).source + '|' +
150
(options.evaluate || reNoMatch).source + '|$'
151
, 'g');
152
153
// Use a sourceURL for easier debugging.
154
var sourceURL = 'sourceURL' in options ? '//# sourceURL=' + options.sourceURL + '\n' : '';
155
156
string.replace(reDelimiters, function(match, escapeValue, interpolateValue, esTemplateValue, evaluateValue, offset) {
157
interpolateValue || (interpolateValue = esTemplateValue);
158
159
// Escape characters that can't be included in string literals.
160
source += string.slice(index, offset).replace(reUnescapedString, escapeStringChar);
161
162
// Replace delimiters with snippets.
163
if (escapeValue) {
164
isEscaping = true;
165
source += "' +\n__e(" + escapeValue + ") +\n'";
166
}
167
if (evaluateValue) {
168
isEvaluating = true;
169
source += "';\n" + evaluateValue + ";\n__p += '";
170
}
171
if (interpolateValue) {
172
source += "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'";
173
}
174
index = offset + match.length;
175
176
// The JS engine embedded in Adobe products requires returning the `match`
177
// string in order to produce the correct `offset` value.
178
return match;
179
});
180
181
source += "';\n";
182
183
// If `variable` is not specified wrap a with-statement around the generated
184
// code to add the data object to the top of the scope chain.
185
var variable = options.variable;
186
if (!variable) {
187
source = 'with (obj) {\n' + source + '\n}\n';
188
}
189
// Cleanup code by stripping empty strings.
190
source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source)
191
.replace(reEmptyStringMiddle, '$1')
192
.replace(reEmptyStringTrailing, '$1;');
193
194
// Frame code as the function body.
195
source = 'function(' + (variable || 'obj') + ') {\n' +
196
(variable
197
? ''
198
: 'obj || (obj = {});\n'
199
) +
200
"var __t, __p = ''" +
201
(isEscaping
202
? ', __e = _.escape'
203
: ''
204
) +
205
(isEvaluating
206
? ', __j = Array.prototype.join;\n' +
207
"function print() { __p += __j.call(arguments, '') }\n"
208
: ';\n'
209
) +
210
source +
211
'return __p\n}';
212
213
var result = attempt(function() {
214
return Function(importsKeys, sourceURL + 'return ' + source).apply(undefined, importsValues);
215
});
216
217
// Provide the compiled function's source by its `toString` method or
218
// the `source` property as a convenience for inlining compiled templates.
219
result.source = source;
220
if (isError(result)) {
221
throw result;
222
}
223
return result;
224
}
225
226
module.exports = template;
227
228