transformers.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. var dirname = require('path').dirname;
  2. var Transformer = require('./shared');
  3. /**
  4. * minifiers must be first in order to be incorporated inside instances of respective output formats
  5. */
  6. var uglifyJS = require('uglify-js');
  7. exports.uglify = exports.uglifyJS = exports['uglify-js'] = new Transformer({
  8. name: 'uglify-js',
  9. engines: ['.'],
  10. outputFormat: 'js',
  11. isMinifier: true,
  12. sync: function (str, options) {
  13. options.fromString = true;
  14. return this.cache(options) || this.cache(options, uglifyJS.minify(str, options).code);
  15. }
  16. });
  17. var uglifyCSS = require('css');
  18. exports.uglifyCSS = exports['uglify-css'] = new Transformer({
  19. name: 'uglify-css',
  20. engines: ['.'],
  21. outputFormat: 'css',
  22. isMinifier: true,
  23. sync: function (str, options) {
  24. options.compress = options.compress != false && options.beautify != true;
  25. return this.cache(options) || this.cache(options, uglifyCSS.stringify(uglifyCSS.parse(str), options));
  26. }
  27. });
  28. exports.uglifyJSON = exports['uglify-json'] = new Transformer({
  29. name: 'uglify-json',
  30. engines: ['.'],
  31. outputFormat: 'json',
  32. isMinifier: true,
  33. sync: function (str, options) {
  34. return JSON.stringify(JSON.parse(str), null, options.beautify);
  35. }
  36. });
  37. /**
  38. * Syncronous Templating Languages
  39. */
  40. function sync(str, options) {
  41. var tmpl = this.cache(options) || this.cache(options, this.engine.compile(str, options));
  42. return tmpl(options);
  43. }
  44. exports.swig = new Transformer({
  45. name: 'swig',
  46. engines: ['swig'],
  47. outputFormat: 'xml',
  48. sync: sync
  49. });
  50. exports.atpl = new Transformer({
  51. name: 'atpl',
  52. engines: ['atpl'],
  53. outputFormat: 'xml',
  54. sync: function sync(str, options) {
  55. var tmpl = this.cache(options);
  56. if (!tmpl) {
  57. var cInfo = {cache: options.cache, filename: options.filename};
  58. if (options.filename) {
  59. delete options.filename; //atpl can't handle absolute windows file paths properly
  60. }
  61. tmpl = this.cache(cInfo, this.engine.compile(str, options));
  62. }
  63. return tmpl(options);
  64. }
  65. });
  66. exports.dot = new Transformer({
  67. name: 'dot',
  68. engines: ['dot'],
  69. outputFormat: 'xml',
  70. sync: function sync(str, options) {
  71. var tmpl = this.cache(options) || this.cache(options, this.engine.template(str));
  72. return tmpl(options);
  73. }
  74. });
  75. exports.liquor = new Transformer({
  76. name: 'liquor',
  77. engines: ['liquor'],
  78. outputFormat: 'xml',
  79. sync: sync
  80. });
  81. exports.ejs = new Transformer({
  82. name: 'ejs',
  83. engines: ['ejs'],
  84. outputFormat: 'xml',
  85. sync: sync
  86. });
  87. exports.eco = new Transformer({
  88. name: 'eco',
  89. engines: ['eco'],
  90. outputFormat: 'xml',
  91. sync: sync//N.B. eco's internal this.cache isn't quite right but this bypasses it
  92. });
  93. exports.jqtpl = new Transformer({
  94. name: 'jqtpl',
  95. engines: ['jqtpl'],
  96. outputFormat: 'xml',
  97. sync: function (str, options) {
  98. var engine = this.engine;
  99. var key = (options.cache && options.filename) ? options.filename : '@';
  100. engine.compile(str, key);
  101. var res = this.engine.render(key, options);
  102. if (!(options.cache && options.filename)) {
  103. delete engine.cache[key];
  104. }
  105. this.cache(options, true); // caching handled internally
  106. return res;
  107. }
  108. });
  109. exports.haml = new Transformer({
  110. name: 'haml',
  111. engines: ['hamljs'],
  112. outputFormat: 'xml',
  113. sync: sync
  114. });
  115. exports['haml-coffee'] = new Transformer({
  116. name: 'haml-coffee',
  117. engines: ['haml-coffee'],
  118. outputFormat: 'xml',
  119. sync: sync
  120. });
  121. exports.whiskers = new Transformer({
  122. name: 'whiskers',
  123. engines: ['whiskers'],
  124. outputFormat: 'xml',
  125. sync: sync
  126. });
  127. exports.hogan = new Transformer({
  128. name: 'hogan',
  129. engines: ['hogan.js'],
  130. outputFormat: 'xml',
  131. sync: function(str, options){
  132. var tmpl = this.cache(options) || this.cache(options, this.engine.compile(str, options));
  133. return tmpl.render(options, options.partials);
  134. }
  135. });
  136. exports.handlebars = new Transformer({
  137. name: 'handlebars',
  138. engines: ['handlebars'],
  139. outputFormat: 'xml',
  140. sync: function(str, options){
  141. for (var partial in options.partials) {
  142. this.engine.registerPartial(partial, options.partials[partial]);
  143. }
  144. var tmpl = this.cache(options) || this.cache(options, this.engine.compile(str, options));
  145. return tmpl(options);
  146. }
  147. });
  148. exports.underscore = new Transformer({
  149. name: 'underscore',
  150. engines: ['underscore'],
  151. outputFormat: 'xml',
  152. sync: function(str, options){
  153. var tmpl = this.cache(options) || this.cache(options, this.engine.template(str));
  154. return tmpl(options);
  155. }
  156. });
  157. exports.walrus = new Transformer({
  158. name: 'walrus',
  159. engines: ['walrus'],
  160. outputFormat: 'xml',
  161. sync: function(str, options){
  162. var tmpl = this.cache(options) || this.cache(options, this.engine.parse(str));
  163. return tmpl.compile(options);
  164. }
  165. });
  166. exports.mustache = new Transformer({
  167. name: 'mustache',
  168. engines: ['mustache'],
  169. outputFormat: 'xml',
  170. sync: function(str, options){
  171. var tmpl = this.cache(options) || this.cache(options, this.engine.compile(str));
  172. return tmpl(options, options.partials);
  173. }
  174. });
  175. exports.templayed = new Transformer({
  176. name: 'templayed',
  177. engines: ['templayed'],
  178. outputFormat: 'xml',
  179. sync: function(str, options){
  180. var tmpl = this.cache(options) || this.cache(options, this.engine(str));
  181. return tmpl(options);
  182. }
  183. });
  184. exports.plates = new Transformer({
  185. name: 'plates',
  186. engines: ['plates'],
  187. outputFormat: 'xml',
  188. sync: function(str, options){
  189. str = this.cache(options) || this.cache(options, str);
  190. return this.engine.bind(str, options, options.map);
  191. }
  192. });
  193. exports.mote = new Transformer({
  194. name: 'mote',
  195. engines: ['mote'],
  196. outputFormat: 'xml',
  197. sync: sync
  198. });
  199. exports.toffee = new Transformer({
  200. name: 'toffee',
  201. engines: ['toffee'],
  202. outputFormat: 'xml',
  203. sync: function (str, options) {
  204. var View = this.engine.view;
  205. var v = this.cache(options) || this.cache(options, new View(str, options));
  206. var res = v.run(options, require('vm').createContext({}));
  207. if (res[0]) throw res[0];
  208. else return res[1];
  209. }
  210. });
  211. exports.coffeekup = exports.coffeecup = new Transformer({
  212. name: 'coffeecup',
  213. engines: ['coffeecup', 'coffeekup'],
  214. outputFormat: 'xml',
  215. sync: function (str, options) {
  216. var compiled = this.cache(options) || this.cache(options, this.engine.compile(str, options));
  217. return compiled(options);
  218. }
  219. });
  220. /**
  221. * Asyncronous Templating Languages
  222. */
  223. exports.just = new Transformer({
  224. name: 'just',
  225. engines: ['just'],
  226. outputFormat: 'xml',
  227. sudoSync: true,
  228. async: function (str, options, cb) {
  229. var JUST = this.engine;
  230. var tmpl = this.cache(options) || this.cache(options, new JUST({ root: { page: str }}));
  231. tmpl.render('page', options, cb);
  232. }
  233. });
  234. exports.ect = new Transformer({
  235. name: 'ect',
  236. engines: ['ect'],
  237. outputFormat: 'xml',
  238. sudoSync: true, // Always runs syncronously
  239. async: function (str, options, cb) {
  240. var ECT = this.engine;
  241. var tmpl = this.cache(options) || this.cache(options, new ECT({ root: { page: str }}));
  242. tmpl.render('page', options, cb);
  243. }
  244. });
  245. exports.jade = new Transformer({
  246. name: 'jade',
  247. engines: ['jade', 'then-jade'],
  248. outputFormat: 'xml',
  249. sudoSync: 'The jade file FILENAME could not be rendered syncronously. N.B. then-jade does not support syncronous rendering.',
  250. async: function (str, options, cb) {
  251. this.cache(options, true);//jade handles this.cache internally
  252. this.engine.render(str, options, cb);
  253. }
  254. })
  255. exports.dust = new Transformer({
  256. name: 'dust',
  257. engines: ['dust', 'dustjs-linkedin'],
  258. outputFormat: 'xml',
  259. sudoSync: false,
  260. async: function (str, options, cb) {
  261. var ext = 'dust'
  262. , views = '.';
  263. if (options) {
  264. if (options.ext) ext = options.ext;
  265. if (options.views) views = options.views;
  266. if (options.settings && options.settings.views) views = options.settings.views;
  267. }
  268. this.engine.onLoad = function(path, callback){
  269. if ('' == extname(path)) path += '.' + ext;
  270. if ('/' !== path[0]) path = views + '/' + path;
  271. read(path, options, callback);
  272. };
  273. var tmpl = this.cache(options) || this.cache(options, this.engine.compileFn(str));
  274. if (options && !options.cache) this.engine.cache = {};//invalidate dust's internal cache
  275. tmpl(options, cb);
  276. }
  277. });
  278. exports.jazz = new Transformer({
  279. name: 'jazz',
  280. engines: ['jazz'],
  281. outputFormat: 'xml',
  282. sudoSync: true, // except when an async function is passed to locals
  283. async: function (str, options, cb) {
  284. var tmpl = this.cache(options) || this.cache(options, this.engine.compile(str, options));
  285. tmpl.eval(options, function(str){
  286. cb(null, str);
  287. });
  288. }
  289. });
  290. exports.qejs = new Transformer({
  291. name: 'qejs',
  292. engines: ['qejs'],
  293. outputFormat: 'xml',
  294. sudoSync: false,
  295. async: function (str, options, cb) {
  296. var tmpl = this.cache(options) || this.cache(options, this.engine.compile(str, options));
  297. tmpl(options).done(function (result) {
  298. cb(null, result);
  299. }, function (err) {
  300. cb(err);
  301. });
  302. }
  303. });
  304. /**
  305. * Stylsheet Languages
  306. */
  307. exports.less = new Transformer({
  308. name: 'less',
  309. engines: ['less'],
  310. outputFormat: 'css',
  311. sudoSync: 'The less file FILENAME could not be rendered syncronously. This is usually because the file contains `@import url` statements.',
  312. async: function (str, options, cb) {
  313. var self = this;
  314. if (self.cache(options)) return cb(null, self.cache(options));
  315. if (options.filename) {
  316. options.paths = options.paths || [dirname(options.filename)];
  317. }
  318. //If this.cache is enabled, compress by default
  319. if (options.compress !== true && options.compress !== false) {
  320. options.compress = options.cache || false;
  321. }
  322. if (options.sudoSync) {
  323. options.syncImport = true;
  324. }
  325. var parser = new(this.engine.Parser)(options);
  326. parser.parse(str, function (err, tree) {
  327. try {
  328. if (err) throw err;
  329. var res = tree.toCSS(options);
  330. self.cache(options, res);
  331. cb(null, res);
  332. } catch (ex) {
  333. if (ex.constructor.name === 'LessError' && typeof ex === 'object') {
  334. ex.filename = ex.filename || '"Unkown Source"';
  335. var err = new Error(self.engine.formatError(ex, options).replace(/^[^:]+:/, ''), ex.filename, ex.line);
  336. err.name = ex.type;
  337. ex = err;
  338. }
  339. return cb(ex);
  340. }
  341. });
  342. }
  343. });
  344. exports.styl = exports.stylus = new Transformer({
  345. name: 'stylus',
  346. engines: ['stylus'],
  347. outputFormat: 'css',
  348. sudoSync: true,// always runs syncronously
  349. async: function (str, options, cb) {
  350. var self = this;
  351. if (self.cache(options)) return cb(null, self.cache(options));
  352. if (options.filename) {
  353. options.paths = options.paths || [dirname(options.filename)];
  354. }
  355. //If this.cache is enabled, compress by default
  356. if (options.compress !== true && options.compress !== false) {
  357. options.compress = options.cache || false;
  358. }
  359. this.engine.render(str, options, function (err, res) {
  360. if (err) return cb(err);
  361. self.cache(options, res);
  362. cb(null, res);
  363. });
  364. }
  365. })
  366. exports.sass = new Transformer({
  367. name: 'sass',
  368. engines: ['sass'],
  369. outputFormat: 'css',
  370. sync: function (str, options) {
  371. try {
  372. return this.cache(options) || this.cache(options, this.engine.render(str));
  373. } catch (ex) {
  374. if (options.filename) ex.message += ' in ' + options.filename;
  375. throw ex;
  376. }
  377. }
  378. });
  379. /**
  380. * Miscelaneous
  381. */
  382. exports.md = exports.markdown = new Transformer({
  383. name: 'markdown',
  384. engines: ['marked', 'supermarked', 'markdown-js', 'markdown'],
  385. outputFormat: 'html',
  386. sync: function (str, options) {
  387. var arg = options;
  388. if (this.engineName === 'markdown') arg = options.dialect; //even if undefined
  389. return this.cache(options) || this.cache(options, this.engine.parse(str, arg));
  390. }
  391. });
  392. exports.coffee = exports['coffee-script'] = exports.coffeescript = exports.coffeeScript = new Transformer({
  393. name: 'coffee-script',
  394. engines: ['coffee-script'],
  395. outputFormat: 'js',
  396. sync: function (str, options) {
  397. return this.cache(options) || this.cache(options, this.engine.compile(str, options));
  398. }
  399. });
  400. exports.cson = new Transformer({
  401. name: 'cson',
  402. engines: ['cson'],
  403. outputFormat: 'json',
  404. sync: function (str, options) {
  405. //todo: remove once https://github.com/rstacruz/js2coffee/pull/174 accepted & released
  406. if (global.Narcissus) delete global.Narcissus; //prevent global leak
  407. return this.cache(options) || this.cache(options, JSON.stringify(this.engine.parseSync(str)));
  408. }
  409. });
  410. exports.cdata = new Transformer({
  411. name: 'cdata',
  412. engines: ['.'],// `.` means "no dependency"
  413. outputFormat: 'xml',
  414. sync: function (str, options) {
  415. var escaped = str.replace(/\]\]>/g, "]]]]><![CDATA[>");
  416. return this.cache(options) || this.cache(options, '<![CDATA[' + escaped + ']]>');
  417. }
  418. });
  419. exports["cdata-js"] = new Transformer({
  420. name: 'cdata-js',
  421. engines: ['.'],// `.` means "no dependency"
  422. outputFormat: 'xml',
  423. sync: function (str, options) {
  424. var escaped = str.replace(/\]\]>/g, "]]]]><![CDATA[>");
  425. return this.cache(options) || this.cache(options, '//<![CDATA[\n' + escaped + '\n//]]>');
  426. }
  427. });
  428. exports["cdata-css"] = new Transformer({
  429. name: 'cdata-css',
  430. engines: ['.'],// `.` means "no dependency"
  431. outputFormat: 'xml',
  432. sync: function (str, options) {
  433. var escaped = str.replace(/\]\]>/g, "]]]]><![CDATA[>");
  434. return this.cache(options) || this.cache(options, '/*<![CDATA[*/\n' + escaped + '\n/*]]>*/');
  435. }
  436. });
  437. exports.verbatim = new Transformer({
  438. name: 'verbatim',
  439. engines: ['.'],// `.` means "no dependency"
  440. outputFormat: 'xml',
  441. sync: function (str, options) {
  442. return this.cache(options) || this.cache(options, str);
  443. }
  444. });
  445. exports.component = exports['component-js'] = new Transformer({
  446. name: 'component-js',
  447. engines: ['component-builder'],
  448. outputFormat: 'js',
  449. async: function (str, options, cb) {
  450. if (this.cache(options)) return this.cache(options);
  451. var self = this;
  452. var builder = new this.engine(dirname(options.filename));
  453. if (options.development) {
  454. builder.development();
  455. }
  456. if (options.sourceURLs === true || (options.sourceURLs !== false && options.development)) {
  457. builder.addSourceURLs();
  458. }
  459. var path = require('path');
  460. builder.paths = (options.paths || ['components']).map(function (p) {
  461. if (path.resolve(p) === p) {
  462. return p;
  463. } else {
  464. return path.join(dirname(options.filename), p);
  465. }
  466. });
  467. builder.build(function (err, obj) {
  468. if (err) return cb(err);
  469. else return cb(null, self.cache(options, obj.require + obj.js));
  470. });
  471. }
  472. });
  473. exports['component-css'] = new Transformer({
  474. name: 'component-css',
  475. engines: ['component-builder'],
  476. outputFormat: 'css',
  477. async: function (str, options, cb) {
  478. if (this.cache(options)) return this.cache(options);
  479. var self = this;
  480. var builder = new this.engine(dirname(options.filename));
  481. if (options.development) {
  482. builder.development();
  483. }
  484. if (options.sourceURLs === true || (options.sourceURLs !== false && options.development)) {
  485. builder.addSourceURLs();
  486. }
  487. var path = require('path');
  488. builder.paths = (options.paths || ['components']).map(function (p) {
  489. if (path.resolve(p) === p) {
  490. return p;
  491. } else {
  492. return path.join(dirname(options.filename), p);
  493. }
  494. });
  495. builder.build(function (err, obj) {
  496. if (err) return cb(err);
  497. else return cb(null, self.cache(options, obj.css));
  498. });
  499. }
  500. });
  501. exports['html2jade'] = new Transformer({
  502. name: 'html2jade',
  503. engines: ['html2jade'],
  504. outputFormat: 'jade',
  505. async: function (str, options, cb) {
  506. return this.cache(options) || this.cache(options, this.engine.convertHtml(str, options, cb));
  507. }
  508. });
  509. exports['highlight'] = new Transformer({
  510. name: 'highlight',
  511. engines: ['highlight.js'],
  512. outputFormat: 'xml',
  513. sync: function (str, options, cb) {
  514. if (this.cache(options)) return this.cache(options);
  515. if (options.lang) {
  516. try {
  517. return this.cache(options, this.engine.highlight(options.lang, str).value);
  518. } catch (ex) {}
  519. }
  520. if (options.auto || !options.lang) {
  521. try {
  522. return this.cache(options, this.engine.highlightAuto(str).value);
  523. } catch (ex) {}
  524. }
  525. return this.cache(options, str);
  526. }
  527. });
  528. /**
  529. * Marker transformers (they don't actually apply a transformation, but let you declare the 'outputFormat')
  530. */
  531. exports.css = new Transformer({
  532. name: 'css',
  533. engines: ['.'],// `.` means "no dependency"
  534. outputFormat: 'css',
  535. sync: function (str, options) {
  536. return this.cache(options) || this.cache(options, str);
  537. }
  538. });
  539. exports.js = new Transformer({
  540. name: 'js',
  541. engines: ['.'],// `.` means "no dependency"
  542. outputFormat: 'js',
  543. sync: function (str, options) {
  544. return this.cache(options) || this.cache(options, str);
  545. }
  546. });