@kosatyi/ejs 0.0.106 → 0.0.108

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