UNPKG

10.3 kBJavaScriptView Raw
1/*!
2 * Snakeskin
3 * https://github.com/SnakeskinTpl/Snakeskin
4 *
5 * Released under the MIT license
6 * https://github.com/SnakeskinTpl/Snakeskin/blob/master/LICENSE
7 */
8
9/** @type {Snakeskin} */
10module.exports = exports = global['SNAKESKIN_DEBUG'] || require('./dist/snakeskin.min');
11require('core-js/es6/object');
12require('core-js/es6/promise');
13
14var
15 beautify = require('js-beautify'),
16 $C = require('collection.js').$C;
17
18var
19 path = require('path'),
20 fs = require('fs'),
21 exists = require('exists-sync'),
22 cache = {};
23
24/**
25 * Returns true if a template file corresponds to a compiled file by a timestamp
26 *
27 * @param {string} source - path to the template file
28 * @param {string} result - path to the compiled file
29 * @param {(string|boolean|null)=} [opt_key] - key of compile parameters
30 * @param {?boolean=} [opt_includes=false] - if is true, then returns an array of included files
31 * @return {(boolean|!Array)}
32 */
33exports.check = function (source, result, opt_key, opt_includes) {
34 var
35 ctx = module.parent ? path.dirname(module.parent.filename) : '';
36
37 source = path.normalize(path.resolve(ctx, source));
38 result = path.normalize(path.resolve(ctx, result));
39
40 try {
41 var sourceStat = fs.statSync(source);
42
43 if (!sourceStat.isFile() || !fs.statSync(result).isFile()) {
44 return false;
45 }
46
47 } catch (ignore) {
48 return false;
49 }
50
51 var
52 code = fs.readFileSync(result, 'utf8'),
53 label = /label <([\d]+)>/.exec(code);
54
55 if (opt_key === null || !label || sourceStat.mtime.valueOf() != label[1]) {
56 return false;
57 }
58
59 if (opt_key) {
60 var key = /key <(.*?)>/.exec(code);
61
62 if (!key || key[1] != opt_key) {
63 return false;
64 }
65 }
66
67 var includes = /includes <(.*?)>/.exec(code);
68
69 if (!includes) {
70 return false;
71 }
72
73 function test(el) {
74 if (fs.existsSync(el[0])) {
75 if (fs.statSync(el[0]).mtime.valueOf() != el[1]) {
76 return true;
77 }
78
79 } else {
80 return true;
81 }
82 }
83
84 if (includes[1]) {
85 includes = JSON.parse(includes[1]);
86
87 if (includes.some(test)) {
88 return false;
89 }
90
91 if (opt_includes) {
92 return includes.map(function (el) {
93 return el[0];
94 });
95 }
96 }
97
98 return opt_includes ? [] : true;
99};
100
101/**
102 * Compiles a template file and returns a reference to the resulting object
103 * or false if an error occurs during compilation
104 *
105 * @param {string} src - path to the template file
106 *
107 * @see Snakeskin.compile
108 * @param {?$$SnakeskinParams=} [opt_params] - additional parameters
109 *
110 * @return {(!Object|boolean)}
111 */
112exports.compileFile = function (src, opt_params) {
113 var
114 ssrc = path.join(process.cwd(), '.snakeskinrc');
115
116 if (!opt_params && exists(ssrc)) {
117 opt_params = exports.toObj(ssrc);
118 }
119
120 src = path.normalize(path.resolve(module.parent ? path.dirname(module.parent.filename) : '', src));
121
122 var
123 p = Object.assign({}, opt_params),
124 cacheEnabled = p.cache !== false;
125
126 var
127 cacheKey = exports.compile(null, Object.assign({}, p, {getCacheKey: true})),
128 fromCache = cacheEnabled && cache[cacheKey] && cache[cacheKey][src];
129
130 function clone(obj) {
131 return JSON.parse(JSON.stringify(obj));
132 }
133
134 if (fromCache) {
135 var tmp = fromCache;
136
137 if (p.words) {
138 if (!tmp.words) {
139 fromCache = false;
140
141 } else {
142 p.words = clone(tmp.words);
143 }
144 }
145
146 if (p.debug) {
147 if (!tmp.debug) {
148 fromCache = false;
149
150 } else {
151 p.debug = clone(tmp.debug);
152 }
153 }
154
155 if (fromCache) {
156 return tmp.tpls;
157 }
158 }
159
160 var
161 source = fs.readFileSync(src).toString(),
162 resSrc = src + '.js';
163
164 var
165 tpls,
166 res = true;
167
168 function compile() {
169 res = exports.compile(source, p, {file: src});
170
171 if (res !== false) {
172 fs.writeFileSync(resSrc, res);
173 }
174 }
175
176 if (cacheEnabled) {
177 if (!exports.check(src, resSrc, cacheKey)) {
178 compile();
179 }
180
181 } else {
182 compile();
183 }
184
185 if (res !== false) {
186 delete require.cache[require.resolve(resSrc)];
187 tpls = require(resSrc);
188
189 if (cacheKey && (cacheEnabled || cache[cacheKey])) {
190 cache[cacheKey] = cache[cacheKey] || {};
191 cache[cacheKey][src] = {
192 tpls: tpls,
193 debug: p.debug,
194 words: p.words
195 };
196 }
197
198 if (tpls.init) {
199 tpls.init(exports);
200 }
201
202 return tpls;
203 }
204
205 return false;
206};
207
208/**
209 * Returns a reference to the main template
210 *
211 * @param {!Object} tpls - template object
212 * @param {?string=} [opt_src] - path to the template file
213 * @param {?string=} [opt_tplName] - name of the main template
214 * @return {Function}
215 */
216exports.getMainTpl = function (tpls, opt_src, opt_tplName) {
217 var
218 tpl;
219
220 function name(tpls) {
221 return opt_src && tpls[path.basename(opt_src, path.extname(opt_src))] ||
222 tpls.main ||
223 tpls.index ||
224 tpls[Object.keys(tpls).sort()[0]];
225 }
226
227 if (opt_tplName) {
228 tpl = eval('tpls' + (opt_tplName[0] !== '[' ? '.' + opt_tplName : opt_tplName));
229
230 if (tpl && typeof tpls !== 'function') {
231 tpls = tpl
232
233 } else {
234 return tpl || null;
235 }
236 }
237
238 tpl = name(tpls);
239 while (tpl && typeof tpl !== 'function') {
240 tpl = name(tpl);
241 }
242
243 return tpl || null;
244};
245
246/**
247 * Compiles a template file and returns a reference to the main template
248 *
249 * @param {string} src - path to the template file
250 *
251 * @see Snakeskin.compile
252 * @param {?$$SnakeskinParams=} [opt_params] - additional parameters
253 *
254 * @param {?string=} [opt_tplName] - name of the main template
255 * @return {Function}
256 */
257exports.execFile = function (src, opt_params, opt_tplName) {
258 var tpls = exports.compileFile(src, opt_params);
259
260 if (!tpls) {
261 return null;
262 }
263
264 return exports.getMainTpl(tpls, src, opt_tplName);
265};
266
267/**
268 * Compiles a template text and returns a reference to the main template
269 *
270 * @param {string} txt - source text
271 *
272 * @see Snakeskin.compile
273 * @param {?$$SnakeskinParams=} [opt_params] - additional parameters
274 *
275 * @param {?string=} [opt_tplName] - name of the main template
276 * @return {Function}
277 */
278exports.exec = function (txt, opt_params, opt_tplName) {
279 var tpls = {};
280
281 opt_params = opt_params || {};
282 opt_params.context = tpls;
283
284 exports.compile(txt, opt_params);
285 return exports.getMainTpl(tpls, null, opt_tplName);
286};
287
288function testId(id) {
289 try {
290 var obj = {};
291 eval('obj.' + id + '= true');
292 return true;
293
294 } catch (ignore) {
295 return false;
296 }
297}
298
299/**
300 * Executes the specified function and returns the result
301 *
302 * @param {?} fn - source function
303 * @param {?=} [opt_data] - additional parameters
304 * @return {Promise}
305 */
306exports.execTpl = function (fn, opt_data) {
307 var res = typeof fn === 'function' ? fn(opt_data) : fn;
308
309 if (res && res instanceof Object) {
310 if (typeof res === 'function') {
311 return exports.execTpl(res);
312 }
313
314 if (res.then) {
315 return res.then(function (text) {
316 return exports.execTpl(text);
317 });
318 }
319
320 if (res.next) {
321 var
322 iterator = res,
323 pos = iterator.next();
324
325 res = pos.value;
326 while (!pos.done) {
327 pos = iterator.next();
328 res += pos.value;
329 }
330 }
331 }
332
333 return new Promise(function (resolve) {
334 resolve(res);
335 });
336};
337
338/**
339 * Compiles Snakeskin templates as React JSX
340 *
341 * @param {string} txt
342 * @param {{setParams, template, local, importNative, importCJS, importAMD, importGlobal, header, footer}} adapter - adapter of code
343 * @param {?$$SnakeskinParams=} [opt_params] - additional parameters
344 * @param {?$$SnakeskinInfoParams=} [opt_info] - additional parameters for debug
345 * @return {!Promise<(string|boolean|null)>}
346 */
347exports.adapter = function (txt, adapter, opt_params, opt_info) {
348 opt_params = Object.assign({
349 adapterOptions: {},
350 renderMode: 'stringConcat',
351 module: 'umd',
352 moduleId: 'tpls',
353 useStrict: true,
354 eol: '\n'
355 }, opt_params);
356
357 var
358 eol = opt_params.eol,
359 mod = opt_params.module,
360 prettyPrint = opt_params.prettyPrint;
361
362 var
363 nRgxp = /\r?\n|\r/g,
364 tpls = {};
365
366 var p = Object.assign(adapter.setParams(opt_params), {
367 context: tpls,
368 module: 'cjs',
369 prettyPrint: false
370 });
371
372 var
373 useStrict = p.useStrict ? '"useStrict";' : '',
374 opts = p.adapterOptions,
375 res = exports.compile(txt, p, opt_info);
376
377 if (!res) {
378 return res;
379 }
380
381 function compile(tpls, prop) {
382 prop = prop || 'exports';
383 var tasks = [];
384
385 $C(tpls).forEach(function (el, key) {
386 var
387 val,
388 validKey = false;
389
390 if (testId(key)) {
391 val = prop + '.' + key;
392 validKey = true;
393
394 } else {
395 val = prop + '["' + key.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"]';
396 }
397
398 if (typeof el !== 'function') {
399 res +=
400 'if (' + val + ' instanceof Object === false) {' +
401 val + ' = {};' +
402 (validKey && mod === 'native' ? 'export var ' + key + '=' + val + ';' : '') +
403 '}'
404 ;
405
406 return tasks.push(compile(el, val));
407 }
408
409 var
410 decl = /^(async\s+)?(function)[*]?(\s*.*?\)\s*\{)/.exec(el.toString());
411
412 tasks.push(exports.execTpl(el, p.data).then(function (text) {
413 res += adapter.template(val, decl[2] + decl[3], text, p.adapterOptions);
414 }));
415 });
416
417 return Promise.all(tasks);
418 }
419
420 res = /\/\*[\s\S]*?\*\//.exec(res)[0];
421 res = res.replace(
422 /key <.*?>/,
423 'key <' + exports.compile(null, Object.assign({}, opt_params, {getCacheKey: true})) + '>'
424 );
425
426 if (opts.header) {
427 res += opts.header;
428 }
429
430 if (mod === 'native') {
431 res +=
432 useStrict +
433 (adapter.importNative || '') +
434 'var exports = {};' +
435 'export default exports;'
436 ;
437
438 } else {
439 res +=
440 '(function(global, factory) {' +
441 (
442 {cjs: true, umd: true}[mod] ?
443 'if (typeof exports === "object" && typeof module !== "undefined") {' +
444 'factory(exports' + (adapter.importCJS ? ',' + adapter.importCJS : '') + ');' +
445 'return;' +
446 '}' :
447 ''
448 ) +
449
450 (
451 {amd: true, umd: true}[mod] ?
452 'if (typeof define === "function" && define.amd) {' +
453 'define("' + (p.moduleId) + '", ["exports"' + (adapter.importAMD ? ',' + adapter.importAMD : '') + '], factory);' +
454 'return;' +
455 '}' :
456 ''
457 ) +
458
459 (
460 {global: true, umd: true}[mod] ?
461 'factory(global' + (p.moduleName ? '.' + p.moduleName + '= {}' : '') + (adapter.importGlobal ? ',' + adapter.importGlobal : '') + ');' :
462 ''
463 ) +
464
465 '})(this, function (exports' + (adapter.local ? ',' + adapter.local : '') + ') {' +
466 useStrict
467 ;
468 }
469
470 return compile(tpls)
471 .then(function () {
472 if (opts.footer) {
473 res += opts.footer;
474 }
475
476 if (mod !== 'native') {
477 res += '});';
478 }
479
480 if (prettyPrint) {
481 res = beautify.js(res);
482 }
483
484 return res.replace(nRgxp, eol) + eol;
485 });
486};