art-template 4.13.3 → 4.13.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,470 @@
1
+ 'use strict';
2
+
3
+ var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
4
+
5
+ function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
6
+
7
+ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
8
+
9
+ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
10
+
11
+ var esTokenizer = require('./es-tokenizer');
12
+ var tplTokenizer = require('./tpl-tokenizer');
13
+
14
+ /** 传递给模板的数据引用 */
15
+ var DATA = '$data';
16
+
17
+ /** 外部导入的所有全局变量引用 */
18
+ var IMPORTS = '$imports';
19
+
20
+ /** $imports.$escape */
21
+ var ESCAPE = '$escape';
22
+
23
+ /** $imports.$each */
24
+ var EACH = '$each';
25
+
26
+ /** 文本输出函数 */
27
+ var PRINT = 'print';
28
+
29
+ /** 包含子模板函数 */
30
+ var INCLUDE = 'include';
31
+
32
+ /** 继承布局模板函数 */
33
+ var EXTEND = 'extend';
34
+
35
+ /** “模板块”读写函数 */
36
+ var BLOCK = 'block';
37
+
38
+ /** 字符串拼接变量 */
39
+ var OUT = '$$out';
40
+
41
+ /** 运行时逐行调试记录变量 [line, start, source] */
42
+ var LINE = '$$line';
43
+
44
+ /** 所有“模板块”变量 */
45
+ var BLOCKS = '$$blocks';
46
+
47
+ /** 截取模版输出“流”的函数 */
48
+ var SLICE = '$$slice';
49
+
50
+ /** 继承的布局模板的文件地址变量 */
51
+ var FROM = '$$from';
52
+
53
+ /** 编译设置变量 */
54
+ var OPTIONS = '$$options';
55
+
56
+ var has = function has(object, key) {
57
+ return Object.hasOwnProperty.call(object, key);
58
+ };
59
+ var stringify = JSON.stringify;
60
+
61
+ var Compiler = function () {
62
+ /**
63
+ * 模板编译器
64
+ * @param {Object} options
65
+ */
66
+ function Compiler(options) {
67
+ var _internal,
68
+ _dependencies,
69
+ _this = this;
70
+
71
+ _classCallCheck(this, Compiler);
72
+
73
+ var source = options.source;
74
+ var minimize = options.minimize;
75
+ var htmlMinifier = options.htmlMinifier;
76
+
77
+ // 编译选项
78
+ this.options = options;
79
+
80
+ // 所有语句堆栈
81
+ this.stacks = [];
82
+
83
+ // 运行时注入的上下文
84
+ this.context = [];
85
+
86
+ // 模板语句编译后的代码
87
+ this.scripts = [];
88
+
89
+ // context map
90
+ this.CONTEXT_MAP = {};
91
+
92
+ // 忽略的变量名单
93
+ this.ignore = [DATA, IMPORTS, OPTIONS].concat(_toConsumableArray(options.ignore));
94
+
95
+ // 按需编译到模板渲染函数的内置变量
96
+ this.internal = (_internal = {}, _defineProperty(_internal, OUT, '\'\''), _defineProperty(_internal, LINE, '[0,0]'), _defineProperty(_internal, BLOCKS, 'arguments[1]||{}'), _defineProperty(_internal, FROM, 'null'), _defineProperty(_internal, PRINT, 'function(){var s=\'\'.concat.apply(\'\',arguments);' + OUT + '+=s;return s}'), _defineProperty(_internal, INCLUDE, 'function(src,data){var s=' + OPTIONS + '.include(src,data||' + DATA + ',arguments[2]||' + BLOCKS + ',' + OPTIONS + ');' + OUT + '+=s;return s}'), _defineProperty(_internal, EXTEND, 'function(from){' + FROM + '=from}'), _defineProperty(_internal, SLICE, 'function(c,p,s){p=' + OUT + ';' + OUT + '=\'\';c();s=' + OUT + ';' + OUT + '=p+s;return s}'), _defineProperty(_internal, BLOCK, 'function(){var a=arguments,s;if(typeof a[0]===\'function\'){return ' + SLICE + '(a[0])}else if(' + FROM + '){if(!' + BLOCKS + '[a[0]]){' + BLOCKS + '[a[0]]=' + SLICE + '(a[1])}else{' + OUT + '+=' + BLOCKS + '[a[0]]}}else{s=' + BLOCKS + '[a[0]];if(typeof s===\'string\'){' + OUT + '+=s}else{s=' + SLICE + '(a[1])}return s}}'), _internal);
97
+
98
+ // 内置函数依赖关系声明
99
+ this.dependencies = (_dependencies = {}, _defineProperty(_dependencies, PRINT, [OUT]), _defineProperty(_dependencies, INCLUDE, [OUT, OPTIONS, DATA, BLOCKS]), _defineProperty(_dependencies, EXTEND, [FROM, /*[*/INCLUDE /*]*/]), _defineProperty(_dependencies, BLOCK, [SLICE, FROM, OUT, BLOCKS]), _dependencies);
100
+
101
+ this.importContext(OUT);
102
+
103
+ if (options.compileDebug) {
104
+ this.importContext(LINE);
105
+ }
106
+
107
+ if (minimize) {
108
+ try {
109
+ source = htmlMinifier(source, options);
110
+ } catch (error) {}
111
+ }
112
+
113
+ this.source = source;
114
+ this.getTplTokens(source, options.rules, this).forEach(function (tokens) {
115
+ if (tokens.type === tplTokenizer.TYPE_STRING) {
116
+ _this.parseString(tokens);
117
+ } else {
118
+ _this.parseExpression(tokens);
119
+ }
120
+ });
121
+ }
122
+
123
+ /**
124
+ * 将模板代码转换成 tplToken 数组
125
+ * @param {string} source
126
+ * @return {Object[]}
127
+ */
128
+
129
+
130
+ _createClass(Compiler, [{
131
+ key: 'getTplTokens',
132
+ value: function getTplTokens() {
133
+ return tplTokenizer.apply(undefined, arguments);
134
+ }
135
+
136
+ /**
137
+ * 将模板表达式转换成 esToken 数组
138
+ * @param {string} source
139
+ * @return {Object[]}
140
+ */
141
+
142
+ }, {
143
+ key: 'getEsTokens',
144
+ value: function getEsTokens(source) {
145
+ return esTokenizer(source);
146
+ }
147
+
148
+ /**
149
+ * 获取变量列表
150
+ * @param {Object[]} esTokens
151
+ * @return {string[]}
152
+ */
153
+
154
+ }, {
155
+ key: 'getVariables',
156
+ value: function getVariables(esTokens) {
157
+ var ignore = false;
158
+ return esTokens.filter(function (esToken) {
159
+ return esToken.type !== 'whitespace' && esToken.type !== 'comment';
160
+ }).filter(function (esToken) {
161
+ if (esToken.type === 'name' && !ignore) {
162
+ return true;
163
+ }
164
+
165
+ ignore = esToken.type === 'punctuator' && esToken.value === '.';
166
+
167
+ return false;
168
+ }).map(function (tooken) {
169
+ return tooken.value;
170
+ });
171
+ }
172
+
173
+ /**
174
+ * 导入模板上下文
175
+ * @param {string} name
176
+ */
177
+
178
+ }, {
179
+ key: 'importContext',
180
+ value: function importContext(name) {
181
+ var _this2 = this;
182
+
183
+ var value = '';
184
+ var internal = this.internal;
185
+ var dependencies = this.dependencies;
186
+ var ignore = this.ignore;
187
+ var context = this.context;
188
+ var options = this.options;
189
+ var imports = options.imports;
190
+ var contextMap = this.CONTEXT_MAP;
191
+
192
+ if (!has(contextMap, name) && ignore.indexOf(name) === -1) {
193
+ if (has(internal, name)) {
194
+ value = internal[name];
195
+
196
+ if (has(dependencies, name)) {
197
+ dependencies[name].forEach(function (name) {
198
+ return _this2.importContext(name);
199
+ });
200
+ }
201
+
202
+ // imports 继承了 Global,但是继承的属性不分配到顶级变量中,避免占用了模板内部的变量名称
203
+ } else if (name === ESCAPE || name === EACH || has(imports, name)) {
204
+ value = IMPORTS + '.' + name;
205
+ } else {
206
+ value = DATA + '.' + name;
207
+ }
208
+
209
+ contextMap[name] = value;
210
+ context.push({
211
+ name: name,
212
+ value: value
213
+ });
214
+ }
215
+ }
216
+
217
+ /**
218
+ * 解析字符串(HTML)直接输出语句
219
+ * @param {Object} tplToken
220
+ */
221
+
222
+ }, {
223
+ key: 'parseString',
224
+ value: function parseString(tplToken) {
225
+ var source = tplToken.value;
226
+
227
+ if (!source) {
228
+ return;
229
+ }
230
+
231
+ var code = OUT + '+=' + stringify(source);
232
+ this.scripts.push({
233
+ source: source,
234
+ tplToken: tplToken,
235
+ code: code
236
+ });
237
+ }
238
+
239
+ /**
240
+ * 解析逻辑表达式语句
241
+ * @param {Object} tplToken
242
+ */
243
+
244
+ }, {
245
+ key: 'parseExpression',
246
+ value: function parseExpression(tplToken) {
247
+ var _this3 = this;
248
+
249
+ var source = tplToken.value;
250
+ var script = tplToken.script;
251
+ var output = script.output;
252
+ var escape = this.options.escape;
253
+ var code = script.code;
254
+
255
+ if (output) {
256
+ if (escape === false || output === tplTokenizer.TYPE_RAW) {
257
+ code = OUT + '+=' + script.code;
258
+ } else {
259
+ code = OUT + '+=' + ESCAPE + '(' + script.code + ')';
260
+ }
261
+ }
262
+
263
+ var esToken = this.getEsTokens(code);
264
+ this.getVariables(esToken).forEach(function (name) {
265
+ return _this3.importContext(name);
266
+ });
267
+
268
+ this.scripts.push({
269
+ source: source,
270
+ tplToken: tplToken,
271
+ code: code
272
+ });
273
+ }
274
+
275
+ /**
276
+ * 检查解析后的模板语句是否存在语法错误
277
+ * @param {string} script
278
+ * @return {boolean}
279
+ */
280
+
281
+ }, {
282
+ key: 'checkExpression',
283
+ value: function checkExpression(script) {
284
+ // 没有闭合的块级模板语句规则
285
+ // 基于正则规则来补全语法不能保证 100% 准确,
286
+ // 但是在绝大多数情况下足以满足辅助开发调试的需要
287
+ var rules = [
288
+ // <% } %>
289
+ // <% }else{ %>
290
+ // <% }else if(a){ %>
291
+ [/^\s*}[\w\W]*?{?[\s;]*$/, ''],
292
+
293
+ // <% fn(c,function(a,b){ %>
294
+ // <% fn(c, a=>{ %>
295
+ // <% fn(c,(a,b)=>{ %>
296
+ [/(^[\w\W]*?\([\w\W]*?(?:=>|\([\w\W]*?\))\s*{[\s;]*$)/, '$1})'],
297
+
298
+ // <% if(a){ %>
299
+ // <% for(var i in d){ %>
300
+ [/(^[\w\W]*?\([\w\W]*?\)\s*{[\s;]*$)/, '$1}']];
301
+
302
+ var index = 0;
303
+ while (index < rules.length) {
304
+ if (rules[index][0].test(script)) {
305
+ var _script;
306
+
307
+ script = (_script = script).replace.apply(_script, _toConsumableArray(rules[index]));
308
+ break;
309
+ }
310
+ index++;
311
+ }
312
+
313
+ try {
314
+ new Function(script);
315
+ return true;
316
+ } catch (e) {
317
+ return false;
318
+ }
319
+ }
320
+
321
+ /**
322
+ * 编译
323
+ * @return {function}
324
+ */
325
+
326
+ }, {
327
+ key: 'build',
328
+ value: function build() {
329
+ var options = this.options;
330
+ var context = this.context;
331
+ var scripts = this.scripts;
332
+ var stacks = this.stacks;
333
+ var source = this.source;
334
+ var filename = options.filename;
335
+ var imports = options.imports;
336
+ var mappings = [];
337
+ var extendMode = has(this.CONTEXT_MAP, EXTEND);
338
+
339
+ var offsetLine = 0;
340
+
341
+ // Create SourceMap: mapping
342
+ var mapping = function mapping(code, _ref) {
343
+ var line = _ref.line,
344
+ start = _ref.start;
345
+
346
+ var node = {
347
+ generated: {
348
+ line: stacks.length + offsetLine + 1,
349
+ column: 1
350
+ },
351
+ original: {
352
+ line: line + 1,
353
+ column: start + 1
354
+ }
355
+ };
356
+
357
+ offsetLine += code.split(/\n/).length - 1;
358
+ return node;
359
+ };
360
+
361
+ // Trim code
362
+ var trim = function trim(code) {
363
+ return code.replace(/^[\t ]+|[\t ]$/g, '');
364
+ };
365
+
366
+ stacks.push('function(' + DATA + '){');
367
+ stacks.push('\'use strict\'');
368
+ stacks.push(DATA + '=' + DATA + '||{}');
369
+ stacks.push('var ' + context.map(function (_ref2) {
370
+ var name = _ref2.name,
371
+ value = _ref2.value;
372
+ return name + '=' + value;
373
+ }).join(','));
374
+
375
+ if (options.compileDebug) {
376
+ stacks.push('try{');
377
+
378
+ scripts.forEach(function (script) {
379
+ if (script.tplToken.type === tplTokenizer.TYPE_EXPRESSION) {
380
+ stacks.push(LINE + '=[' + [script.tplToken.line, script.tplToken.start].join(',') + ']');
381
+ }
382
+
383
+ mappings.push(mapping(script.code, script.tplToken));
384
+ stacks.push(trim(script.code));
385
+ });
386
+
387
+ stacks.push('}catch(error){');
388
+
389
+ stacks.push('throw {' + ['name:\'RuntimeError\'', 'path:' + stringify(filename), 'message:error.message', 'line:' + LINE + '[0]+1', 'column:' + LINE + '[1]+1', 'source:' + stringify(source), 'stack:error.stack'].join(',') + '}');
390
+
391
+ stacks.push('}');
392
+ } else {
393
+ scripts.forEach(function (script) {
394
+ mappings.push(mapping(script.code, script.tplToken));
395
+ stacks.push(trim(script.code));
396
+ });
397
+ }
398
+
399
+ if (extendMode) {
400
+ stacks.push(OUT + '=\'\'');
401
+ stacks.push(INCLUDE + '(' + FROM + ',' + DATA + ',' + BLOCKS + ')');
402
+ }
403
+
404
+ stacks.push('return ' + OUT);
405
+ stacks.push('}');
406
+
407
+ var renderCode = stacks.join('\n');
408
+
409
+ try {
410
+ var result = new Function(IMPORTS, OPTIONS, 'return ' + renderCode)(imports, options);
411
+ result.mappings = mappings;
412
+ result.sourcesContent = [source];
413
+ return result;
414
+ } catch (error) {
415
+ var index = 0;
416
+ var line = 0;
417
+ var start = 0;
418
+ var generated = void 0;
419
+
420
+ while (index < scripts.length) {
421
+ var current = scripts[index];
422
+ if (!this.checkExpression(current.code)) {
423
+ line = current.tplToken.line;
424
+ start = current.tplToken.start;
425
+ generated = current.code;
426
+ break;
427
+ }
428
+ index++;
429
+ }
430
+
431
+ throw {
432
+ name: 'CompileError',
433
+ path: filename,
434
+ message: error.message,
435
+ line: line + 1,
436
+ column: start + 1,
437
+ source: source,
438
+ generated: generated,
439
+ stack: error.stack
440
+ };
441
+ }
442
+ }
443
+ }]);
444
+
445
+ return Compiler;
446
+ }();
447
+
448
+ /**
449
+ * 模板内置常量
450
+ */
451
+
452
+
453
+ Compiler.CONSTS = {
454
+ DATA: DATA,
455
+ IMPORTS: IMPORTS,
456
+ PRINT: PRINT,
457
+ INCLUDE: INCLUDE,
458
+ EXTEND: EXTEND,
459
+ BLOCK: BLOCK,
460
+ OPTIONS: OPTIONS,
461
+ OUT: OUT,
462
+ LINE: LINE,
463
+ BLOCKS: BLOCKS,
464
+ SLICE: SLICE,
465
+ FROM: FROM,
466
+ ESCAPE: ESCAPE,
467
+ EACH: EACH
468
+ };
469
+
470
+ module.exports = Compiler;
@@ -0,0 +1,95 @@
1
+ 'use strict';
2
+
3
+ var runtime = require('./runtime');
4
+ var extend = require('./adapter/extend');
5
+ var include = require('./adapter/include');
6
+ var onerror = require('./adapter/onerror');
7
+ var caches = require('./adapter/caches');
8
+ var loader = require('./adapter/loader');
9
+ var artRule = require('./adapter/rule.art');
10
+ var nativeRule = require('./adapter/rule.native');
11
+ var htmlMinifier = require('./adapter/html-minifier');
12
+ var resolveFilename = require('./adapter/resolve-filename');
13
+
14
+ var detectNode = typeof window === 'undefined';
15
+
16
+ /** 模板编译器默认配置 */
17
+ var settings = {
18
+ // 模板内容。如果没有此字段,则会根据 filename 来加载模板内容
19
+ source: null,
20
+
21
+ // 模板名
22
+ filename: null,
23
+
24
+ // 模板语法规则列表
25
+ rules: [nativeRule, artRule],
26
+
27
+ // 是否开启对模板输出语句自动编码功能。为 false 则关闭编码输出功能
28
+ // escape 可以防范 XSS 攻击
29
+ escape: true,
30
+
31
+ // 启动模板引擎调试模式。如果为 true: {cache:false, minimize:false, compileDebug:true}
32
+ debug: detectNode ? process.env.NODE_ENV !== 'production' : false,
33
+
34
+ // bail 如果为 true,编译错误与运行时错误都会抛出异常
35
+ bail: true,
36
+
37
+ // 是否开启缓存
38
+ cache: true,
39
+
40
+ // 是否开启压缩。它会运行 htmlMinifier,将页面 HTML、CSS、CSS 进行压缩输出
41
+ // 如果模板包含没有闭合的 HTML 标签,请不要打开 minimize,否则可能被 htmlMinifier 修复或过滤
42
+ minimize: true,
43
+
44
+ // 是否编译调试版
45
+ compileDebug: false,
46
+
47
+ // 模板路径转换器
48
+ resolveFilename: resolveFilename,
49
+
50
+ // 子模板编译适配器
51
+ include: include,
52
+
53
+ // HTML 压缩器。仅在 NodeJS 环境下有效
54
+ htmlMinifier: htmlMinifier,
55
+
56
+ // HTML 压缩器配置。参见 https://github.com/kangax/html-minifier
57
+ htmlMinifierOptions: {
58
+ collapseWhitespace: true,
59
+ minifyCSS: true,
60
+ minifyJS: true,
61
+ // 运行时自动合并:rules.map(rule => rule.test)
62
+ ignoreCustomFragments: []
63
+ },
64
+
65
+ // 错误事件。仅在 bail 为 false 时生效
66
+ onerror: onerror,
67
+
68
+ // 模板文件加载器
69
+ loader: loader,
70
+
71
+ // 缓存中心适配器(依赖 filename 字段)
72
+ caches: caches,
73
+
74
+ // 模板根目录。如果 filename 字段不是本地路径,则在 root 查找模板
75
+ root: '/',
76
+
77
+ // 默认后缀名。如果没有后缀名,则会自动添加 extname
78
+ extname: '.art',
79
+
80
+ // 忽略的变量。被模板编译器忽略的模板变量列表
81
+ ignore: [],
82
+
83
+ // 导入的模板变量
84
+ imports: runtime
85
+ };
86
+
87
+ function Defaults() {
88
+ this.$extend = function (options) {
89
+ options = options || {};
90
+ return extend(options, options instanceof Defaults ? options : this);
91
+ };
92
+ }
93
+ Defaults.prototype = settings;
94
+
95
+ module.exports = new Defaults();
@@ -0,0 +1,60 @@
1
+ 'use strict';
2
+
3
+ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
4
+
5
+ function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
6
+
7
+ function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
8
+
9
+ /**
10
+ * 模板错误处理类
11
+ * @param {Object} options
12
+ */
13
+ var TemplateError = function (_Error) {
14
+ _inherits(TemplateError, _Error);
15
+
16
+ function TemplateError(options) {
17
+ _classCallCheck(this, TemplateError);
18
+
19
+ var _this = _possibleConstructorReturn(this, (TemplateError.__proto__ || Object.getPrototypeOf(TemplateError)).call(this, options.message));
20
+
21
+ _this.name = 'TemplateError';
22
+ _this.message = formatMessage(options);
23
+ if (Error.captureStackTrace) {
24
+ Error.captureStackTrace(_this, _this.constructor);
25
+ }
26
+ return _this;
27
+ }
28
+
29
+ return TemplateError;
30
+ }(Error);
31
+
32
+ function formatMessage(_ref) {
33
+ var name = _ref.name,
34
+ source = _ref.source,
35
+ path = _ref.path,
36
+ line = _ref.line,
37
+ column = _ref.column,
38
+ generated = _ref.generated,
39
+ message = _ref.message;
40
+
41
+ if (!source) {
42
+ return message;
43
+ }
44
+
45
+ var lines = source.split(/\n/);
46
+ var start = Math.max(line - 3, 0);
47
+ var end = Math.min(lines.length, line + 3);
48
+
49
+ // Error context
50
+ var context = lines.slice(start, end).map(function (code, index) {
51
+ var number = index + start + 1;
52
+ var left = number === line ? ' >> ' : ' ';
53
+ return '' + left + number + '| ' + code;
54
+ }).join('\n');
55
+
56
+ // Alter exception message
57
+ return (path || 'anonymous') + ':' + line + ':' + column + '\n' + (context + '\n\n') + (name + ': ' + message) + (generated ? '\n generated: ' + generated : '');
58
+ }
59
+
60
+ module.exports = TemplateError;
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ var isKeyword = require('is-keyword-js');
4
+ var jsTokens = require('js-tokens').default;
5
+ var matchToToken = require('js-tokens').matchToToken;
6
+
7
+ /**
8
+ * 将逻辑表达式解释为 Tokens
9
+ * @param {string} code
10
+ * @return {Object[]}
11
+ */
12
+ var esTokenizer = function esTokenizer(code) {
13
+ var tokens = code.match(jsTokens).map(function (value) {
14
+ jsTokens.lastIndex = 0;
15
+ return matchToToken(jsTokens.exec(value));
16
+ }).map(function (token) {
17
+ if (token.type === 'name' && isKeyword(token.value)) {
18
+ token.type = 'keyword';
19
+ }
20
+ return token;
21
+ });
22
+
23
+ return tokens;
24
+ };
25
+
26
+ module.exports = esTokenizer;