@kosatyi/ejs 0.0.107 → 0.0.109

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.
package/dist/esm/index.js CHANGED
@@ -1,23 +1,117 @@
1
- import fs from 'node:fs';
2
1
  import path from 'node:path';
2
+ import fs from 'node:fs/promises';
3
+
4
+ /**
5
+ * @type {EjsConfig}
6
+ */
7
+ const ejsDefaults = {
8
+ precompiled: 'ejsPrecompiled',
9
+ cache: true,
10
+ path: 'views',
11
+ extension: 'ejs',
12
+ rmWhitespace: true,
13
+ strict: true,
14
+ resolver: (path, template) => {
15
+ return Promise.resolve(
16
+ ['resolver is not defined', path, template].join(' '),
17
+ )
18
+ },
19
+ globals: [],
20
+ vars: {
21
+ SCOPE: 'ejs',
22
+ COMPONENT: 'ui',
23
+ ELEMENT: 'el',
24
+ EXTEND: '$$e',
25
+ BUFFER: '$$a',
26
+ LAYOUT: '$$l',
27
+ BLOCKS: '$$b',
28
+ MACRO: '$$m',
29
+ SAFE: '$$v',
30
+ },
31
+ token: {
32
+ start: '<%',
33
+ end: '%>',
34
+ regex: '([\\s\\S]+?)',
35
+ },
36
+ };
37
+
38
+ const jsVariableName = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/;
3
39
 
4
40
  const typeProp = function () {
5
41
  const args = [].slice.call(arguments);
6
42
  const callback = args.shift();
7
43
  return args.filter(callback).pop()
8
44
  };
9
- const isArray = (v) => Array.isArray(v);
10
- const isFunction = (v) => typeof v === 'function';
11
- const isString = (v) => typeof v === 'string';
12
- const isBoolean = (v) => typeof v === 'boolean';
13
- const isUndefined = (v) => typeof v === 'undefined';
14
-
15
- const isNodeEnv =
16
- Object.prototype.toString.call(
17
- typeof process !== 'undefined' ? process : 0,
18
- ) === '[object process]';
45
+ const isArray = (value) => Array.isArray(value);
46
+ const isFunction = (value) => typeof value === 'function';
47
+ const isString = (value) => typeof value === 'string';
48
+ const isBoolean = (value) => typeof value === 'boolean';
49
+ const isArrayOfVariables = (value) => {
50
+ if (!isArray(value)) return false
51
+ return value.filter((name) => {
52
+ const valid = jsVariableName.test(name);
53
+ if (valid === false)
54
+ console.log(
55
+ `ejsConfig.globals: expected '${name}' to be valid variable name --> skipped`,
56
+ );
57
+ return valid
58
+ })
59
+ };
19
60
 
20
- const isNode = () => isNodeEnv;
61
+ const configSchema = (config, options) => {
62
+ return Object.assign(config, {
63
+ path: typeProp(isString, ejsDefaults.path, config.path, options.path),
64
+ precompiled: typeProp(
65
+ isString,
66
+ ejsDefaults.precompiled,
67
+ config.export,
68
+ options.export,
69
+ ),
70
+ resolver: typeProp(
71
+ isFunction,
72
+ ejsDefaults.resolver,
73
+ config.resolver,
74
+ options.resolver,
75
+ ),
76
+ extension: typeProp(
77
+ isString,
78
+ ejsDefaults.extension,
79
+ config.extension,
80
+ options.extension,
81
+ ),
82
+ strict: typeProp(
83
+ isBoolean,
84
+ ejsDefaults.strict,
85
+ config.strict,
86
+ options.strict,
87
+ ),
88
+ rmWhitespace: typeProp(
89
+ isBoolean,
90
+ ejsDefaults.rmWhitespace,
91
+ config.rmWhitespace,
92
+ options.rmWhitespace,
93
+ ),
94
+ cache: typeProp(
95
+ isBoolean,
96
+ ejsDefaults.cache,
97
+ config.cache,
98
+ options.cache,
99
+ ),
100
+ globals: typeProp(
101
+ isArray,
102
+ ejsDefaults.globals,
103
+ config.globals,
104
+ isArrayOfVariables(options.globals),
105
+ ),
106
+ token: Object.assign(
107
+ {},
108
+ ejsDefaults.token,
109
+ config.token,
110
+ options.token,
111
+ ),
112
+ vars: Object.assign({}, ejsDefaults.vars, config.vars, options.vars),
113
+ })
114
+ };
21
115
 
22
116
  const symbolEntities = {
23
117
  "'": "'",
@@ -88,25 +182,6 @@ const getPath = (context, name, strict) => {
88
182
  return [data, prop]
89
183
  };
90
184
 
91
- const ext = (path, defaults) => {
92
- const ext = path.split('.').pop();
93
- if (ext !== defaults) {
94
- path = [path, defaults].join('.');
95
- }
96
- return path
97
- };
98
-
99
- /**
100
- * @type {<T extends {}, U, V, W,Y>(T,U,V,W,Y)=> T & U & V & W & Y}
101
- */
102
- const extend = (target, ...args) => {
103
- return args
104
- .filter((source) => source)
105
- .reduce((target, source) => Object.assign(target, source), target)
106
- };
107
-
108
- const noop = () => {};
109
-
110
185
  const each = (object, callback) => {
111
186
  let prop;
112
187
  for (prop in object) {
@@ -116,53 +191,16 @@ const each = (object, callback) => {
116
191
  }
117
192
  };
118
193
 
119
- const map = (object, callback, context) => {
120
- const result = [];
121
- each(
122
- object,
123
- (value, key, object) => {
124
- let item = callback(value, key, object);
125
- if (isUndefined(item) === false) {
126
- result.push(item);
127
- }
128
- });
129
- return result
130
- };
131
-
132
- const filter = (object, callback, context) => {
133
- const isArray = object instanceof Array;
134
- const result = isArray ? [] : {};
135
- each(
136
- object,
137
- (value, key, object) => {
138
- let item = callback(value, key, object);
139
- if (isUndefined(item) === false) {
140
- if (isArray) {
141
- result.push(item);
142
- } else {
143
- result[key] = item;
144
- }
145
- }
146
- });
147
- return result
148
- };
149
-
150
194
  const omit = (object, list) => {
151
- return filter(object, (value, key) => {
152
- if (list.indexOf(key) === -1) {
153
- return value
154
- }
155
- })
195
+ const result = { ...object };
196
+ for (const key of list) {
197
+ delete result[key];
198
+ }
199
+ return result
156
200
  };
157
201
 
158
- /**
159
- *
160
- * @param object
161
- * @param prop
162
- * @return {boolean}
163
- */
164
202
  const hasProp = (object, prop) => {
165
- return object && object.hasOwnProperty(prop)
203
+ return object && Object.hasOwn(object, prop)
166
204
  };
167
205
 
168
206
  const joinPath = (path, template) => {
@@ -171,9 +209,68 @@ const joinPath = (path, template) => {
171
209
  return template
172
210
  };
173
211
 
174
- const matchTokens = (regex, text, callback) => {
212
+ const bindContext = (object, methods = []) => {
213
+ for (let i = 0, len = methods.length; i < len; i++) {
214
+ const name = methods[i];
215
+ if (name in object) {
216
+ object[name] = object[name].bind(object);
217
+ }
218
+ }
219
+ };
220
+
221
+ class Template {
222
+ #path
223
+ #resolver
224
+ #cache
225
+ #compiler
226
+ static exports = ['configure', 'get', 'compile']
227
+ constructor(options, cache, compiler) {
228
+ bindContext(this, this.constructor.exports);
229
+ this.#cache = cache;
230
+ this.#compiler = compiler;
231
+ this.configure(options ?? {});
232
+ }
233
+ configure(options) {
234
+ this.#path = options.path;
235
+ if (isFunction(options.resolver)) {
236
+ this.#resolver = options.resolver;
237
+ }
238
+ }
239
+ #resolve(template) {
240
+ const cached = this.#cache.get(template);
241
+ if (cached instanceof Promise) return cached
242
+ const result = Promise.resolve(this.#resolver(this.#path, template));
243
+ this.#cache.set(template, result);
244
+ return result
245
+ }
246
+ compile(content, template) {
247
+ const cached = this.#cache.get(template);
248
+ if (typeof cached === 'function') return cached
249
+ if (typeof content === 'string') {
250
+ content = this.#compiler.compile(content, template);
251
+ }
252
+ if (typeof content === 'function') {
253
+ this.#cache.set(template, content);
254
+ return content
255
+ }
256
+ }
257
+ get(template) {
258
+ return this.#resolve(template).then((content) =>
259
+ this.compile(content, template),
260
+ )
261
+ }
262
+ }
263
+
264
+ const tokenList = [
265
+ ['-', (v, b, s) => `')\n${b}(${s}(${v},1))\n${b}('`],
266
+ ['=', (v, b, s) => `')\n${b}(${s}(${v}))\n${b}('`],
267
+ ['#', (v, b) => `')\n/**${v}**/\n${b}('`],
268
+ ['', (v, b) => `')\n${v}\n${b}('`],
269
+ ];
270
+
271
+ const tokensMatch = (regex, content, callback) => {
175
272
  let index = 0;
176
- text.replace(regex, function () {
273
+ content.replace(regex, function () {
177
274
  const params = [].slice.call(arguments, 0, -1);
178
275
  const offset = params.pop();
179
276
  const match = params.shift();
@@ -183,284 +280,167 @@ const matchTokens = (regex, text, callback) => {
183
280
  });
184
281
  };
185
282
 
186
- const defaults = {};
283
+ class Compiler {
284
+ #config = {}
285
+ static exports = ['compile']
187
286
 
188
- defaults.export = 'ejsPrecompiled';
189
- defaults.cache = true;
190
- defaults.chokidar = null;
191
- defaults.path = 'views';
192
- defaults.resolver = function (path, template) {
193
- return Promise.resolve(
194
- ['resolver is not defined', path, template].join(' '),
195
- )
196
- };
197
- defaults.extension = 'ejs';
198
- defaults.rmWhitespace = true;
199
- defaults.withObject = true;
200
- defaults.globalHelpers = [];
201
- defaults.vars = {
202
- SCOPE: 'ejs',
203
- COMPONENT: 'ui',
204
- ELEMENT: 'el',
205
- EXTEND: '$$e',
206
- BUFFER: '$$a',
207
- LAYOUT: '$$l',
208
- BLOCKS: '$$b',
209
- MACRO: '$$m',
210
- SAFE: '$$v',
211
- };
212
- defaults.token = {
213
- start: '<%',
214
- end: '%>',
215
- regex: '([\\s\\S]+?)',
216
- };
217
-
218
- const configSchema = (config, options) => {
219
- return extend(config, {
220
- path: typeProp(isString, defaults.path, config.path, options.path),
221
- export: typeProp(
222
- isString,
223
- defaults.export,
224
- config.export,
225
- options.export,
226
- ),
227
- resolver: typeProp(
228
- isFunction,
229
- defaults.resolver,
230
- config.resolver,
231
- options.resolver,
232
- ),
233
- extension: typeProp(
234
- isString,
235
- defaults.extension,
236
- config.extension,
237
- options.extension,
238
- ),
239
- withObject: typeProp(
240
- isBoolean,
241
- defaults.withObject,
242
- config.withObject,
243
- options.withObject,
244
- ),
245
- rmWhitespace: typeProp(
246
- isBoolean,
247
- defaults.rmWhitespace,
248
- config.rmWhitespace,
249
- options.rmWhitespace,
250
- ),
251
- cache: typeProp(isBoolean, defaults.cache, config.cache, options.cache),
252
- token: extend({}, defaults.token, config.token, options.token),
253
- vars: extend({}, defaults.vars, config.vars, options.vars),
254
- globalHelpers: typeProp(
255
- isArray,
256
- defaults.globalHelpers,
257
- config.globalHelpers,
258
- options.globalHelpers,
259
- ),
260
- })
261
- };
262
-
263
- const Template = (options, cache, compiler) => {
264
- const config = {
265
- path: null,
266
- resolver: null,
267
- };
268
- const resolve = (path) => {
269
- return config.resolver(config.path, path)
270
- };
271
- const result = (template, content) => {
272
- cache.set(template, content);
273
- return content
274
- };
275
- const compile = (content, template) => {
276
- if (isFunction(content)) {
277
- return content
278
- } else {
279
- return compiler.compile(content, template)
280
- }
281
- };
282
- const get = (template) => {
283
- if (cache.exist(template)) {
284
- return cache.resolve(template)
285
- }
286
- return resolve(template).then((content) =>
287
- result(template, compile(content, template)),
288
- )
289
- };
290
- const configure = (options) => {
291
- config.path = options.path;
292
- if (isFunction(options.resolver)) {
293
- config.resolver = options.resolver;
294
- }
295
- };
296
- configure(options);
297
- return {
298
- get,
299
- configure,
300
- compile,
287
+ constructor(options) {
288
+ bindContext(this, this.constructor.exports);
289
+ this.configure(options);
301
290
  }
302
- };
303
-
304
- const configSymbols = [
305
- {
306
- symbol: '-',
307
- format(value) {
308
- return `')\n${this.BUFFER}(${this.SAFE}(${value},1))\n${this.BUFFER}('`
309
- },
310
- },
311
- {
312
- symbol: '=',
313
- format(value) {
314
- return `')\n${this.BUFFER}(${this.SAFE}(${value}))\n${this.BUFFER}('`
315
- },
316
- },
317
- {
318
- symbol: '#',
319
- format(value) {
320
- return `')\n/**${value}**/\n${this.BUFFER}('`
321
- },
322
- },
323
- {
324
- symbol: '',
325
- format(value) {
326
- return `')\n${value.trim()}\n${this.BUFFER}('`
327
- },
328
- },
329
- ];
330
291
 
331
- const Compiler = (options) => {
332
- const config = {};
333
- const configure = (options) => {
334
- config.withObject = options.withObject;
335
- config.rmWhitespace = options.rmWhitespace;
336
- config.token = options.token;
337
- config.vars = options.vars;
338
- config.globalHelpers = options.globalHelpers;
339
- config.matches = [];
340
- config.formats = [];
341
- config.slurp = {
292
+ configure(options) {
293
+ this.#config.strict = options.strict;
294
+ this.#config.rmWhitespace = options.rmWhitespace;
295
+ this.#config.token = options.token;
296
+ this.#config.vars = options.vars;
297
+ this.#config.globals = options.globals;
298
+ this.#config.legacy = options.legacy ?? true;
299
+ this.#config.slurp = {
342
300
  match: '[s\t\n]*',
343
- start: [config.token.start, '_'],
344
- end: ['_', config.token.end],
301
+ start: [this.#config.token.start, '_'],
302
+ end: ['_', this.#config.token.end],
345
303
  };
346
- configSymbols.forEach((item) => {
347
- config.matches.push(
348
- config.token.start
349
- .concat(item.symbol)
350
- .concat(config.token.regex)
351
- .concat(config.token.end),
304
+ this.#config.matches = [];
305
+ this.#config.formats = [];
306
+ for (const [symbol, format] of tokenList) {
307
+ this.#config.matches.push(
308
+ this.#config.token.start
309
+ .concat(symbol)
310
+ .concat(this.#config.token.regex)
311
+ .concat(this.#config.token.end),
352
312
  );
353
- config.formats.push(item.format.bind(config.vars));
354
- });
355
- config.regex = new RegExp(config.matches.join('|').concat('|$'), 'g');
356
- config.slurpStart = new RegExp(
357
- [config.slurp.match, config.slurp.start.join('')].join(''),
313
+ this.#config.formats.push(format);
314
+ }
315
+ this.#config.regex = new RegExp(
316
+ this.#config.matches.join('|').concat('|$'),
317
+ 'g',
318
+ );
319
+ this.#config.slurpStart = new RegExp(
320
+ [this.#config.slurp.match, this.#config.slurp.start.join('')].join(
321
+ '',
322
+ ),
358
323
  'gm',
359
324
  );
360
- config.slurpEnd = new RegExp(
361
- [config.slurp.end.join(''), config.slurp.match].join(''),
325
+ this.#config.slurpEnd = new RegExp(
326
+ [this.#config.slurp.end.join(''), this.#config.slurp.match].join(
327
+ '',
328
+ ),
362
329
  'gm',
363
330
  );
364
- };
365
- const compile = (content, path) => {
366
- const { SCOPE, SAFE, BUFFER, COMPONENT, ELEMENT } = config.vars;
367
- const GLOBALS = config.globalHelpers;
368
- if (config.rmWhitespace) {
331
+ if (this.#config.globals.length) {
332
+ if (this.#config.legacy) {
333
+ this.#config.globalVariables = `const ${this.#config.globals
334
+ .map((name) => `${name}=${this.#config.vars.SCOPE}.${name}`)
335
+ .join(',')};`;
336
+ } else {
337
+ this.#config.globalVariables = `const {${this.#config.globals.join(',')}} = ${this.#config.vars.SCOPE};`;
338
+ }
339
+ }
340
+ }
341
+ compile(content, path) {
342
+ const GLOBALS = this.#config.globalVariables;
343
+ const { SCOPE, SAFE, BUFFER, COMPONENT, ELEMENT } = this.#config.vars;
344
+ if (this.#config.rmWhitespace) {
369
345
  content = String(content)
370
346
  .replace(/[\r\n]+/g, '\n')
371
347
  .replace(/^\s+|\s+$/gm, '');
372
348
  }
373
349
  content = String(content)
374
- .replace(config.slurpStart, config.token.start)
375
- .replace(config.slurpEnd, config.token.end);
376
- let source = `${BUFFER}('`;
377
- matchTokens(config.regex, content, (params, index, offset) => {
378
- source += symbols(content.slice(index, offset));
350
+ .replace(this.#config.slurpStart, this.#config.token.start)
351
+ .replace(this.#config.slurpEnd, this.#config.token.end);
352
+ let OUTPUT = `${BUFFER}('`;
353
+ tokensMatch(this.#config.regex, content, (params, index, offset) => {
354
+ OUTPUT += symbols(content.slice(index, offset));
379
355
  params.forEach((value, index) => {
380
356
  if (value) {
381
- source += config.formats[index](value);
357
+ OUTPUT += this.#config.formats[index](
358
+ value.trim(),
359
+ BUFFER,
360
+ SAFE,
361
+ );
382
362
  }
383
363
  });
384
364
  });
385
- source += `');`;
386
- source = `try{${source}}catch(e){return ${BUFFER}.error(e)}`;
387
- if (config.withObject) {
388
- source = `with(${SCOPE}){${source}}`;
365
+ OUTPUT += `');`;
366
+ OUTPUT = `try{${OUTPUT}}catch(e){return ${BUFFER}.error(e,'${path}')}`;
367
+ if (this.#config.strict === false) {
368
+ OUTPUT = `with(${SCOPE}){${OUTPUT}}`;
369
+ }
370
+ OUTPUT = `${BUFFER}.start();${OUTPUT}return ${BUFFER}.end();`;
371
+ OUTPUT += `\n//# sourceURL=${path}`;
372
+ if (GLOBALS) {
373
+ OUTPUT = `${GLOBALS}\n${OUTPUT}`;
389
374
  }
390
- source = `${BUFFER}.start();${source}return ${BUFFER}.end();`;
391
- source += `\n//# sourceURL=${path}`;
392
- let result = null;
393
- let params = [SCOPE, COMPONENT, ELEMENT, BUFFER, SAFE].concat(GLOBALS);
394
375
  try {
395
- result = Function.apply(null, params.concat(source));
396
- result.source = `(function(${params.join(',')}){\n${source}\n});`;
397
- } catch (e) {
398
- e.filename = path;
399
- e.source = source;
400
- throw e
376
+ const params = [SCOPE, COMPONENT, ELEMENT, BUFFER, SAFE];
377
+ const result = Function.apply(null, params.concat(OUTPUT));
378
+ result.source = `(function(${params.join(',')}){\n${OUTPUT}\n});`;
379
+ return result
380
+ } catch (error) {
381
+ error.filename = path;
382
+ error.source = OUTPUT;
383
+ throw error
401
384
  }
402
- return result
403
- };
404
- configure(options);
405
- return {
406
- configure,
407
- compile,
408
385
  }
409
- };
410
-
411
- const global = typeof globalThis !== 'undefined' ? globalThis : window || self;
386
+ }
412
387
 
413
- const Cache = (options = {}) => {
414
- const config = {};
415
- const list = {};
416
- const load = (data) => {
417
- if (config.enabled) {
418
- extend(list, data || {});
388
+ class Cache {
389
+ static exports = [
390
+ 'load',
391
+ 'set',
392
+ 'get',
393
+ 'exist',
394
+ 'clear',
395
+ 'remove',
396
+ 'resolve',
397
+ ]
398
+ #cache = true
399
+ #precompiled
400
+ #list = {}
401
+ constructor(options) {
402
+ bindContext(this, this.constructor.exports);
403
+ this.configure(options);
404
+ }
405
+ get(key) {
406
+ if (this.#cache) {
407
+ return this.#list[key]
419
408
  }
420
- };
421
- const get = (key) => {
422
- if (config.enabled) {
423
- return list[key]
409
+ }
410
+ set(key, value) {
411
+ if (this.#cache) {
412
+ this.#list[key] = value;
424
413
  }
425
- };
426
- const set = (key, value) => {
427
- if (config.enabled) {
428
- list[key] = value;
414
+ }
415
+ exist(key) {
416
+ if (this.#cache) {
417
+ return this.#list.hasOwnProperty(key)
429
418
  }
430
- };
431
- const exist = (key) => {
432
- if (config.enabled) {
433
- return hasProp(list, key)
419
+ }
420
+ clear() {
421
+ Object.keys(this.#list).forEach(this.remove);
422
+ }
423
+ remove(key) {
424
+ delete this.#list[key];
425
+ }
426
+ resolve(key) {
427
+ return Promise.resolve(this.get(key))
428
+ }
429
+
430
+ load(data) {
431
+ if (this.#cache) {
432
+ Object.assign(this.#list, data || {});
434
433
  }
435
- };
436
- const clear = () => {
437
- Object.keys(list).forEach(remove);
438
- };
439
- const remove = (key) => {
440
- delete list[key];
441
- };
442
- const resolve = (key) => {
443
- return Promise.resolve(get(key))
444
- };
445
- const configure = (options = {}) => {
446
- config.enabled = options.cache;
447
- config.export = options.export;
448
- if (isNode() === false) {
449
- load(global[config.export]);
434
+ }
435
+
436
+ configure(options) {
437
+ this.#cache = options.cache;
438
+ this.#precompiled = options.precompiled;
439
+ if (typeof window === 'object') {
440
+ this.load(window[this.#precompiled]);
450
441
  }
451
- };
452
- configure(options);
453
- return {
454
- configure,
455
- load,
456
- set,
457
- get,
458
- exist,
459
- clear,
460
- remove,
461
- resolve,
462
442
  }
463
- };
443
+ }
464
444
 
465
445
  const selfClosed = [
466
446
  'area',
@@ -486,20 +466,24 @@ const slash = '/';
486
466
  const lt = '<';
487
467
  const gt = '>';
488
468
 
469
+ const eachAttribute = ([key, value]) => {
470
+ if (value !== null && value !== undefined) {
471
+ return [entities(key), [quote, entities(value), quote].join('')].join(
472
+ equal,
473
+ )
474
+ }
475
+ };
476
+
489
477
  const element = (tag, attrs, content) => {
490
478
  const result = [];
491
479
  const hasClosedTag = selfClosed.indexOf(tag) === -1;
492
- const attributes = map(attrs, (value, key) => {
493
- if (value !== null && value !== undefined) {
494
- return [
495
- entities(key),
496
- [quote, entities(value), quote].join(''),
497
- ].join(equal)
498
- }
499
- }).join(space);
480
+ const attributes = Object.entries(attrs ?? {})
481
+ .map(eachAttribute)
482
+ .filter((e) => e)
483
+ .join(space);
500
484
  result.push([lt, tag, space, attributes, gt].join(''));
501
485
  if (content && hasClosedTag) {
502
- result.push(content instanceof Array ? content.join('') : content);
486
+ result.push(Array.isArray(content) ? content.join('') : content);
503
487
  }
504
488
  if (hasClosedTag) {
505
489
  result.push([lt, slash, tag, gt].join(''));
@@ -507,93 +491,73 @@ const element = (tag, attrs, content) => {
507
491
  return result.join('')
508
492
  };
509
493
 
510
- /**
511
- *
512
- * @constructor
513
- */
514
- function TemplateError() {
515
- TemplateError.call(this);
494
+ class TemplateError extends Error {
495
+ name = 'TemplateError'
496
+ constructor(error) {
497
+ super(error);
498
+ if (error instanceof Error) {
499
+ this.stack = error.stack;
500
+ this.filename = error.filename;
501
+ this.lineno = error.lineno;
502
+ }
503
+ }
516
504
  }
517
- Object.setPrototypeOf(TemplateError.prototype, Error.prototype);
518
- Object.assign(TemplateError.prototype, {
519
- code: 0,
520
- getCode() {
521
- return this.code
522
- },
523
- getMessage() {
524
- return this.message
525
- },
526
- toString() {
527
- return this.getMessage()
528
- },
529
- });
530
505
 
531
- /**
532
- *
533
- * @constructor
534
- */
535
- function TemplateNotFound() {
536
- TemplateError.call(this);
506
+ class TemplateNotFound extends TemplateError {
507
+ name = 'TemplateNotFound'
508
+ code = 404
537
509
  }
538
- Object.setPrototypeOf(TemplateNotFound.prototype, TemplateError.prototype);
539
- Object.assign(TemplateNotFound.prototype, { code: 404 });
540
510
 
541
- /**
542
- *
543
- * @constructor
544
- */
545
- function TemplateSyntaxError() {
546
- TemplateError.call(this);
511
+ class TemplateSyntaxError extends TemplateError {
512
+ name = 'TemplateSyntaxError'
513
+ code = 500
547
514
  }
548
- Object.setPrototypeOf(TemplateSyntaxError.prototype, TemplateError.prototype);
549
- Object.assign(TemplateSyntaxError.prototype, { code: 500 });
550
515
 
551
- function resolve(list) {
516
+ const resolve = (list) => {
552
517
  return Promise.all(list || [])
553
518
  .then((list) => list.join(''))
554
519
  .catch((e) => e)
555
- }
556
-
557
- function reject(error) {
558
- return Promise.reject(new TemplateSyntaxError(error.message))
559
- }
520
+ };
560
521
 
561
- /**
562
- *
563
- * @return {buffer}
564
- */
565
- function createBuffer() {
566
- let store = [],
567
- array = [];
522
+ const reject = (error) => {
523
+ return Promise.reject(new TemplateSyntaxError(error))
524
+ };
568
525
 
569
- const buffer = (value) => {
526
+ const EjsBuffer = () => {
527
+ let store = [];
528
+ let array = [];
529
+ /**
530
+ *
531
+ * @param value
532
+ * @constructor
533
+ */
534
+ const EjsBuffer = (value) => {
570
535
  array.push(value);
571
536
  };
572
-
573
- buffer.start = () => {
537
+ EjsBuffer.start = () => {
574
538
  array = [];
575
539
  };
576
- buffer.backup = () => {
540
+ EjsBuffer.backup = () => {
577
541
  store.push(array.concat());
578
542
  array = [];
579
543
  };
580
- buffer.restore = () => {
544
+ EjsBuffer.restore = () => {
581
545
  const result = array.concat();
582
546
  array = store.pop();
583
547
  return resolve(result)
584
548
  };
585
- buffer.error = (e) => {
549
+ EjsBuffer.error = (e, filename) => {
586
550
  return reject(e)
587
551
  };
588
- buffer.end = () => {
552
+ EjsBuffer.end = () => {
589
553
  return resolve(array)
590
554
  };
591
- return buffer
592
- }
555
+ return EjsBuffer
556
+ };
593
557
 
594
- const PARENT = Symbol('ContextScope.parentTemplate');
558
+ const PARENT = Symbol('EjsContext.parentTemplate');
595
559
 
596
- const createContextScope = (config, methods) => {
560
+ const createContext$1 = (config, methods) => {
597
561
  const {
598
562
  BLOCKS,
599
563
  MACRO,
@@ -605,17 +569,8 @@ const createContextScope = (config, methods) => {
605
569
  COMPONENT,
606
570
  ELEMENT,
607
571
  } = config.vars;
608
- /**
609
- *
610
- * @type {symbol}
611
- */
612
-
613
- /**
614
- * @name ContextScope
615
- * @param data
616
- * @constructor
617
- */
618
- function ContextScope(data) {
572
+ const globals = config.globals || [];
573
+ function EjsContext(data) {
619
574
  this[PARENT] = null;
620
575
  this[BLOCKS] = {};
621
576
  this[MACRO] = {};
@@ -624,32 +579,36 @@ const createContextScope = (config, methods) => {
624
579
  omit(data, [SCOPE, BUFFER, SAFE, COMPONENT, ELEMENT]),
625
580
  );
626
581
  }
627
-
628
- Object.assign(ContextScope.prototype, methods);
629
- Object.defineProperty(ContextScope.prototype, BUFFER, {
630
- value: createBuffer(),
582
+ Object.entries(methods).forEach(([name, value]) => {
583
+ if (isFunction(value) && globals.includes(name)) {
584
+ value = value.bind(EjsContext.prototype);
585
+ }
586
+ EjsContext.prototype[name] = value;
631
587
  });
632
- Object.defineProperty(ContextScope.prototype, BLOCKS, {
588
+ Object.defineProperty(EjsContext.prototype, BUFFER, {
589
+ value: EjsBuffer(),
590
+ });
591
+ Object.defineProperty(EjsContext.prototype, BLOCKS, {
633
592
  value: {},
634
593
  writable: true,
635
594
  });
636
- Object.defineProperty(ContextScope.prototype, MACRO, {
595
+ Object.defineProperty(EjsContext.prototype, MACRO, {
637
596
  value: {},
638
597
  writable: true,
639
598
  });
640
- Object.defineProperty(ContextScope.prototype, LAYOUT, {
599
+ Object.defineProperty(EjsContext.prototype, LAYOUT, {
641
600
  value: false,
642
601
  writable: true,
643
602
  });
644
- Object.defineProperty(ContextScope.prototype, EXTEND, {
603
+ Object.defineProperty(EjsContext.prototype, EXTEND, {
645
604
  value: false,
646
605
  writable: true,
647
606
  });
648
- Object.defineProperty(ContextScope.prototype, PARENT, {
607
+ Object.defineProperty(EjsContext.prototype, PARENT, {
649
608
  value: null,
650
609
  writable: true,
651
610
  });
652
- Object.defineProperties(ContextScope.prototype, {
611
+ Object.defineProperties(EjsContext.prototype, {
653
612
  /** @type {function} */
654
613
  setParentTemplate: {
655
614
  value(value) {
@@ -691,16 +650,16 @@ const createContextScope = (config, methods) => {
691
650
  }
692
651
  },
693
652
  },
694
- /** @type {()=>this[MACRO]} */
695
- getMacro: {
696
- value() {
697
- return this[MACRO]
653
+ /** @type {function} */
654
+ useBuffer: {
655
+ get() {
656
+ return this[BUFFER]
698
657
  },
699
658
  },
700
659
  /** @type {function} */
701
- getBuffer: {
660
+ getMacro: {
702
661
  value() {
703
- return this[BUFFER]
662
+ return this[MACRO]
704
663
  },
705
664
  },
706
665
  /** @type {function} */
@@ -754,22 +713,18 @@ const createContextScope = (config, methods) => {
754
713
  },
755
714
  /** @type {function} */
756
715
  echo: {
757
- value(layout) {
758
- const buffer = this.getBuffer();
759
- const params = [].slice.call(arguments);
760
- params.forEach(buffer);
716
+ value() {
717
+ return [].slice.call(arguments).forEach(this.useBuffer)
761
718
  },
762
719
  },
763
720
  /** @type {function} */
764
721
  fn: {
765
722
  value(callback) {
766
- const buffer = this.getBuffer();
767
- const context = this;
768
- return function () {
723
+ return () => {
769
724
  if (isFunction(callback)) {
770
- buffer.backup();
771
- buffer(callback.apply(context, arguments));
772
- return buffer.restore()
725
+ this.useBuffer.backup();
726
+ this.useBuffer(callback.apply(this, arguments));
727
+ return this.useBuffer.restore()
773
728
  }
774
729
  }
775
730
  },
@@ -804,20 +759,18 @@ const createContextScope = (config, methods) => {
804
759
  blocks[name].push(this.fn(callback));
805
760
  if (this.getExtend()) return
806
761
  const list = Object.assign([], blocks[name]);
807
- const current = () => {
808
- return list.shift()
809
- };
762
+ const shift = () => list.shift();
810
763
  const next = () => {
811
- const parent = current();
764
+ const parent = shift();
812
765
  if (parent) {
813
766
  return () => {
814
767
  this.echo(parent(next()));
815
768
  }
816
769
  } else {
817
- return noop
770
+ return () => {}
818
771
  }
819
772
  };
820
- this.echo(current()(next()));
773
+ this.echo(shift()(next()));
821
774
  },
822
775
  },
823
776
  /** @type {function} */
@@ -830,7 +783,7 @@ const createContextScope = (config, methods) => {
830
783
  include: {
831
784
  value(path, data, cx) {
832
785
  const context = cx === false ? {} : this.clone(true);
833
- const params = extend(context, data || {});
786
+ const params = Object.assign(context, data || {});
834
787
  const promise = this.render(path, params);
835
788
  this.echo(promise);
836
789
  },
@@ -839,9 +792,9 @@ const createContextScope = (config, methods) => {
839
792
  use: {
840
793
  value(path, namespace) {
841
794
  this.echo(
842
- Promise.resolve(this.require(path)).then((exports) => {
795
+ Promise.resolve(this.require(path)).then((exports$1) => {
843
796
  const list = this.getMacro();
844
- each(exports, function (macro, name) {
797
+ each(exports$1, (macro, name) => {
845
798
  list[[namespace, name].join('.')] = macro;
846
799
  });
847
800
  }),
@@ -877,7 +830,7 @@ const createContextScope = (config, methods) => {
877
830
  },
878
831
  /** @type {function} */
879
832
  each: {
880
- value: function (object, callback) {
833
+ value(object, callback) {
881
834
  if (isString(object)) {
882
835
  object = this.get(object, []);
883
836
  }
@@ -903,127 +856,126 @@ const createContextScope = (config, methods) => {
903
856
  writable: true,
904
857
  },
905
858
  });
906
- return ContextScope
859
+ return EjsContext
907
860
  };
908
861
 
909
- const Context = (options, methods) => {
910
- const config = {
911
- Scope: null,
912
- };
913
- /**
914
- * @return {ContextScope}
915
- */
916
- const create = (data) => {
917
- return new config.Scope(data)
918
- };
919
- const helpers = (methods) => {
920
- extend(config.Scope.prototype, methods || {});
921
- };
922
- const configure = (options, methods) => {
923
- config.Scope = createContextScope(options, methods);
924
- };
925
- configure(options, methods);
926
- return {
927
- configure,
928
- create,
929
- helpers,
862
+ class Context {
863
+ #context
864
+ static exports = ['create', 'globals', 'helpers']
865
+ constructor(options, methods) {
866
+ bindContext(this, this.constructor.exports);
867
+ this.configure(options, methods);
930
868
  }
931
- };
869
+ create(data) {
870
+ return new this.#context(data)
871
+ }
872
+ helpers(methods) {
873
+ Object.assign(this.#context.prototype, methods);
874
+ }
875
+ configure(options, methods) {
876
+ this.#context = createContext$1(options, methods);
877
+ }
878
+ }
932
879
 
933
- const EJS = (options = {}) => {
934
- const config = configSchema({}, options);
935
- const methods = {};
936
- const context = Context(config, methods);
937
- const compiler = Compiler(config);
938
- const cache = Cache(config);
939
- const template = Template(config, cache, compiler);
940
- const configure = (options = {}) => {
941
- configSchema(config, options || {});
942
- context.configure(config, methods);
943
- compiler.configure(config);
944
- cache.configure(config);
945
- template.configure(config);
946
- return config
947
- };
948
- const filePath = (name) => {
949
- return ext(name, config.extension)
950
- };
951
- const require = (name) => {
952
- const scope = createContext({});
953
- return output(filePath(name), scope).then(() => scope.getMacro())
954
- };
955
- const render = (name, data) => {
956
- const scope = createContext(data);
957
- return output(filePath(name), scope).then(outputContent(name, scope))
958
- };
959
- const outputContent = (name, scope) => {
880
+ class EjsInstance {
881
+ #cache
882
+ #context
883
+ #compiler
884
+ #template
885
+ #config = {}
886
+ #methods = {}
887
+ static exports = [
888
+ 'configure',
889
+ 'create',
890
+ 'createContext',
891
+ 'render',
892
+ 'require',
893
+ 'preload',
894
+ 'compile',
895
+ 'helpers',
896
+ ]
897
+ constructor(options = {}) {
898
+ bindContext(this, this.constructor.exports);
899
+ this.#methods = {};
900
+ this.#config = configSchema({}, options);
901
+ this.#context = new Context(this.#config, this.#methods);
902
+ this.#compiler = new Compiler(this.#config);
903
+ this.#cache = new Cache(this.#config);
904
+ this.#template = new Template(this.#config, this.#cache, this.#compiler);
905
+ this.helpers({ render: this.render, require: this.require });
906
+ }
907
+ create(options) {
908
+ return new this.constructor(options)
909
+ }
910
+ configure(options) {
911
+ if (options) {
912
+ configSchema(this.#config, options);
913
+ this.#context.configure(this.#config, this.#methods);
914
+ this.#compiler.configure(this.#config);
915
+ this.#cache.configure(this.#config);
916
+ this.#template.configure(this.#config);
917
+ }
918
+ return this.#config
919
+ }
920
+ createContext(data) {
921
+ return this.#context.create(data)
922
+ }
923
+ preload(list) {
924
+ return this.#cache.load(list || {})
925
+ }
926
+ compile(content, path) {
927
+ return this.#compiler.compile(content, path)
928
+ }
929
+ helpers(methods) {
930
+ this.#context.helpers(Object.assign(this.#methods, methods));
931
+ }
932
+ async render(name, params) {
933
+ const data = this.createContext(params);
934
+ return this.#output(this.#path(name), data).then(
935
+ this.#outputContent(name, data),
936
+ )
937
+ }
938
+ async require(name) {
939
+ const data = this.createContext({});
940
+ return this.#output(this.#path(name), data).then(() => data.getMacro())
941
+ }
942
+ #path(name) {
943
+ const ext = name.split('.').pop();
944
+ if (ext !== this.#config.extension) {
945
+ name = [name, this.#config.extension].join('.');
946
+ }
947
+ return name
948
+ }
949
+ #output(path, data) {
950
+ return this.#template
951
+ .get(path)
952
+ .then((callback) =>
953
+ callback.apply(data, [
954
+ data,
955
+ data.useComponent,
956
+ data.useElement,
957
+ data.useBuffer,
958
+ data.useSafeValue,
959
+ ]),
960
+ )
961
+ }
962
+ #renderLayout(name, params, parentTemplate) {
963
+ const data = this.createContext(params);
964
+ if (parentTemplate) data.setParentTemplate(parentTemplate);
965
+ return this.#output(this.#path(name), data).then(
966
+ this.#outputContent(name, data),
967
+ )
968
+ }
969
+ #outputContent(name, data) {
960
970
  return (content) => {
961
- if (scope.getExtend()) {
962
- scope.setExtend(false);
963
- return renderLayout(scope.getLayout(), scope, name)
971
+ if (data.getExtend()) {
972
+ data.setExtend(false);
973
+ return this.#renderLayout(data.getLayout(), data, name)
964
974
  }
965
975
  return content
966
976
  }
967
- };
968
- const renderLayout = (name, data, parent) => {
969
- const scope = createContext(data);
970
- if (parent) scope.setParentTemplate(parent);
971
- return output(filePath(name), scope).then(outputContent(name, scope))
972
- };
973
- const helpers = (extendMethods) => {
974
- context.helpers(extend(methods, extendMethods));
975
- };
976
- const createContext = (data) => {
977
- return context.create(data)
978
- };
979
- const compile = (content, path) => {
980
- return compiler.compile(content, path)
981
- };
982
- const preload = (list) => {
983
- return cache.load(list || {})
984
- };
985
- const create = (config) => {
986
- return EJS(config)
987
- };
988
- const output = (path, scope) => {
989
- const params = [
990
- scope,
991
- scope.useComponent,
992
- scope.useElement,
993
- scope.getBuffer(),
994
- scope.useSafeValue,
995
- ];
996
- const globals = config.globalHelpers
997
- .filter((name) => isFunction(scope[name]))
998
- .map((name) => scope[name].bind(scope));
999
- return template
1000
- .get(path)
1001
- .then((callback) => callback.apply(scope, params.concat(globals)))
1002
- };
1003
- helpers({ render, require });
1004
- return {
1005
- configure,
1006
- create,
1007
- createContext,
1008
- render,
1009
- require,
1010
- preload,
1011
- compile,
1012
- helpers,
1013
977
  }
1014
- };
1015
-
1016
- const readFile = (path, template) => {
1017
- return new Promise((resolve, reject) => {
1018
- fs.readFile(joinPath(path, template), (error, data) => {
1019
- if (error) {
1020
- reject(new TemplateError());
1021
- } else {
1022
- resolve(data.toString());
1023
- }
1024
- });
1025
- })
1026
- };
978
+ }
1027
979
 
1028
980
  /**
1029
981
  *
@@ -1038,14 +990,14 @@ const expressRenderer = (configure, render) => {
1038
990
  options = {};
1039
991
  }
1040
992
  options = options || {};
1041
- const settings = extend({}, options.settings);
1042
- const viewPath = typeProp(isString, defaults.path, settings['views']);
993
+ const settings = Object.assign({}, options.settings);
994
+ const viewPath = typeProp(isString, ejsDefaults.path, settings['views']);
1043
995
  const viewCache = typeProp(
1044
996
  isBoolean,
1045
- defaults.cache,
997
+ ejsDefaults.cache,
1046
998
  settings['view cache'],
1047
999
  );
1048
- const viewOptions = extend({}, settings['view options']);
1000
+ const viewOptions = Object.assign({}, settings['view options']);
1049
1001
  const filename = path.relative(viewPath, name);
1050
1002
  viewOptions.path = viewPath;
1051
1003
  viewOptions.cache = viewCache;
@@ -1060,6 +1012,13 @@ const expressRenderer = (configure, render) => {
1060
1012
  }
1061
1013
  };
1062
1014
 
1015
+ const readFile = (path, template) => {
1016
+ return fs
1017
+ .readFile(joinPath(path, template))
1018
+ .then((contents) => contents.toString())
1019
+ .then((text) => String(text))
1020
+ };
1021
+
1063
1022
  const {
1064
1023
  render,
1065
1024
  createContext,
@@ -1068,7 +1027,7 @@ const {
1068
1027
  preload,
1069
1028
  configure,
1070
1029
  create,
1071
- } = EJS({
1030
+ } = new EjsInstance({
1072
1031
  resolver: readFile,
1073
1032
  });
1074
1033