shared.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. var Promise = require('promise');
  2. var fs = require('fs');
  3. var path = require('path');
  4. var normalize = path.normalize;
  5. Promise.prototype.nodeify = function (cb) {
  6. if (typeof cb === 'function') {
  7. this.then(function (res) { process.nextTick(function () { cb(null, res); }); },
  8. function (err) { process.nextTick(function () { cb(err); }); });
  9. return undefined;
  10. } else {
  11. return this;
  12. }
  13. }
  14. var minifiers = {};
  15. module.exports = Transformer;
  16. function Transformer(obj) {
  17. this.name = obj.name;
  18. this.engines = obj.engines;
  19. this.isBinary = obj.isBinary || false;
  20. this.isMinifier = obj.isMinifier || false;
  21. this.outputFormat = obj.outputFormat;
  22. this._cache = {};
  23. if (typeof obj.async === 'function') {
  24. this._renderAsync = obj.async;
  25. this.sudoSync = obj.sudoSync || false;
  26. }
  27. if (typeof obj.sync === 'function') {
  28. this._renderSync = obj.sync;
  29. this.sync = true;
  30. } else {
  31. this.sync = obj.sudoSync || false;
  32. }
  33. if (this.isMinifier)
  34. minifiers[this.outputFormat] = this;
  35. else {
  36. var minifier = minifiers[this.outputFormat];
  37. if (minifier) {
  38. this.minify = function(str, options) {
  39. if (options && options.minify)
  40. return minifier.renderSync(str, typeof options.minify === 'object' && options.minify || {});
  41. return str;
  42. }
  43. }
  44. }
  45. }
  46. Transformer.prototype.cache = function (options, data) {
  47. if (options.cache && options.filename) {
  48. if (data) return this.cache[options.filename] = data;
  49. else return this.cache[options.filename];
  50. } else {
  51. return data;
  52. }
  53. };
  54. Transformer.prototype.loadModule = function () {
  55. if (this.engine) return this.engine;
  56. for (var i = 0; i < this.engines.length; i++) {
  57. try {
  58. var res = this.engines[i] === '.' ? null : (this.engine = require(this.engines[i]));
  59. this.engineName = this.engines[i];
  60. return res;
  61. } catch (ex) {
  62. if (this.engines.length === 1) {
  63. throw ex;
  64. }
  65. }
  66. }
  67. throw new Error('In order to apply the transform ' + this.name + ' you must install one of ' + this.engines.map(function (e) { return '"' + e + '"'; }).join());
  68. };
  69. Transformer.prototype.minify = function(str, options) {
  70. return str;
  71. }
  72. Transformer.prototype.renderSync = function (str, options) {
  73. options = options || {};
  74. options = clone(options);
  75. this.loadModule();
  76. if (this._renderSync) {
  77. return this.minify(this._renderSync((this.isBinary ? str : fixString(str)), options), options);
  78. } else if (this.sudoSync) {
  79. options.sudoSync = true;
  80. var res, err;
  81. this._renderAsync((this.isBinary ? str : fixString(str)), options, function (e, val) {
  82. if (e) err = e;
  83. else res = val;
  84. });
  85. if (err) throw err;
  86. else if (res != undefined) return this.minify(res, options);
  87. else if (typeof this.sudoSync === 'string') throw new Error(this.sudoSync.replace(/FILENAME/g, options.filename || ''));
  88. else throw new Error('There was a problem transforming ' + (options.filename || '') + ' syncronously using ' + this.name);
  89. } else {
  90. throw new Error(this.name + ' does not support transforming syncronously.');
  91. }
  92. };
  93. Transformer.prototype.render = function (str, options, cb) {
  94. options = options || {};
  95. var self = this;
  96. return new Promise(function (resolve, reject) {
  97. self.loadModule();
  98. if (self._renderAsync) {
  99. self._renderAsync((self.isBinary ? str : fixString(str)), clone(options), function (err, val) {
  100. if (err) reject(err);
  101. else resolve(self.minify(val, options));
  102. })
  103. } else {
  104. resolve(self.renderSync(str, options));
  105. }
  106. })
  107. .nodeify(cb);
  108. };
  109. Transformer.prototype.renderFile = function (path, options, cb) {
  110. options = options || {};
  111. var self = this;
  112. return new Promise(function (resolve, reject) {
  113. options.filename = (path = normalize(path));
  114. if (self._cache[path])
  115. resolve(null);
  116. else
  117. fs.readFile(path, function (err, data) {
  118. if (err) reject(err);
  119. else resolve(data);
  120. })
  121. })
  122. .then(function (str) {
  123. return self.render(str, options);
  124. })
  125. .nodeify(cb);
  126. };
  127. Transformer.prototype.renderFileSync = function (path, options) {
  128. options = options || {};
  129. options.filename = (path = normalize(path));
  130. return this.renderSync((this._cache[path] ? null : fs.readFileSync(path)), options);
  131. };
  132. function fixString(str) {
  133. if (str == null) return str;
  134. //convert buffer to string
  135. str = str.toString();
  136. // Strip UTF-8 BOM if it exists
  137. str = (0xFEFF == str.charCodeAt(0)
  138. ? str.substring(1)
  139. : str);
  140. //remove `\r` added by windows
  141. return str.replace(/\r/g, '');
  142. }
  143. function clone(obj) {
  144. if (Array.isArray(obj)) {
  145. return obj.map(clone);
  146. } else if (obj && typeof obj === 'object') {
  147. var res = {};
  148. for (var key in obj) {
  149. res[key] = clone(obj[key]);
  150. }
  151. return res;
  152. } else {
  153. return obj;
  154. }
  155. }