index.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. //
  2. // index.js
  3. // Should expose the additional browser functions on to the less object
  4. //
  5. var addDataAttr = require("./utils").addDataAttr,
  6. browser = require("./browser");
  7. module.exports = function(window, options) {
  8. var document = window.document;
  9. var less = require('../less')();
  10. less.options = options;
  11. var environment = less.environment,
  12. FileManager = require("./file-manager")(options, less.logger),
  13. fileManager = new FileManager();
  14. environment.addFileManager(fileManager);
  15. less.FileManager = FileManager;
  16. less.PluginLoader = require("./plugin-loader");
  17. require("./log-listener")(less, options);
  18. var errors = require("./error-reporting")(window, less, options);
  19. var cache = less.cache = options.cache || require("./cache")(window, options, less.logger);
  20. require('./image-size')(less.environment);
  21. // Setup user functions - Deprecate?
  22. if (options.functions) {
  23. less.functions.functionRegistry.addMultiple(options.functions);
  24. }
  25. var typePattern = /^text\/(x-)?less$/;
  26. function clone(obj) {
  27. return JSON.parse(JSON.stringify(obj || {}));
  28. }
  29. // only really needed for phantom
  30. function bind(func, thisArg) {
  31. var curryArgs = Array.prototype.slice.call(arguments, 2);
  32. return function() {
  33. var args = curryArgs.concat(Array.prototype.slice.call(arguments, 0));
  34. return func.apply(thisArg, args);
  35. };
  36. }
  37. function loadStyles(modifyVars) {
  38. var styles = document.getElementsByTagName('style'),
  39. style;
  40. for (var i = 0; i < styles.length; i++) {
  41. style = styles[i];
  42. if (style.type.match(typePattern)) {
  43. var instanceOptions = clone(options);
  44. instanceOptions.modifyVars = modifyVars;
  45. var lessText = style.innerHTML || '';
  46. instanceOptions.filename = document.location.href.replace(/#.*$/, '');
  47. /* jshint loopfunc:true */
  48. // use closure to store current style
  49. less.render(lessText, instanceOptions,
  50. bind(function(style, e, result) {
  51. if (e) {
  52. errors.add(e, "inline");
  53. } else {
  54. style.type = 'text/css';
  55. if (style.styleSheet) {
  56. style.styleSheet.cssText = result.css;
  57. } else {
  58. style.innerHTML = result.css;
  59. }
  60. }
  61. }, null, style));
  62. }
  63. }
  64. }
  65. function loadStyleSheet(sheet, callback, reload, remaining, modifyVars) {
  66. var instanceOptions = clone(options);
  67. addDataAttr(instanceOptions, sheet);
  68. instanceOptions.mime = sheet.type;
  69. if (modifyVars) {
  70. instanceOptions.modifyVars = modifyVars;
  71. }
  72. function loadInitialFileCallback(loadedFile) {
  73. var data = loadedFile.contents,
  74. path = loadedFile.filename,
  75. webInfo = loadedFile.webInfo;
  76. var newFileInfo = {
  77. currentDirectory: fileManager.getPath(path),
  78. filename: path,
  79. rootFilename: path,
  80. relativeUrls: instanceOptions.relativeUrls};
  81. newFileInfo.entryPath = newFileInfo.currentDirectory;
  82. newFileInfo.rootpath = instanceOptions.rootpath || newFileInfo.currentDirectory;
  83. if (webInfo) {
  84. webInfo.remaining = remaining;
  85. var css = cache.getCSS(path, webInfo, instanceOptions.modifyVars);
  86. if (!reload && css) {
  87. webInfo.local = true;
  88. callback(null, css, data, sheet, webInfo, path);
  89. return;
  90. }
  91. }
  92. // TODO add tests around how this behaves when reloading
  93. errors.remove(path);
  94. instanceOptions.rootFileInfo = newFileInfo;
  95. less.render(data, instanceOptions, function(e, result) {
  96. if (e) {
  97. e.href = path;
  98. callback(e);
  99. } else {
  100. cache.setCSS(sheet.href, webInfo.lastModified, instanceOptions.modifyVars, result.css);
  101. callback(null, result.css, data, sheet, webInfo, path);
  102. }
  103. });
  104. }
  105. fileManager.loadFile(sheet.href, null, instanceOptions, environment)
  106. .then(function(loadedFile) {
  107. loadInitialFileCallback(loadedFile);
  108. }).catch(function(err) {
  109. console.log(err);
  110. callback(err);
  111. });
  112. }
  113. function loadStyleSheets(callback, reload, modifyVars) {
  114. for (var i = 0; i < less.sheets.length; i++) {
  115. loadStyleSheet(less.sheets[i], callback, reload, less.sheets.length - (i + 1), modifyVars);
  116. }
  117. }
  118. function initRunningMode() {
  119. if (less.env === 'development') {
  120. less.watchTimer = setInterval(function () {
  121. if (less.watchMode) {
  122. fileManager.clearFileCache();
  123. loadStyleSheets(function (e, css, _, sheet, webInfo) {
  124. if (e) {
  125. errors.add(e, e.href || sheet.href);
  126. } else if (css) {
  127. browser.createCSS(window.document, css, sheet);
  128. }
  129. });
  130. }
  131. }, options.poll);
  132. }
  133. }
  134. //
  135. // Watch mode
  136. //
  137. less.watch = function () {
  138. if (!less.watchMode ) {
  139. less.env = 'development';
  140. initRunningMode();
  141. }
  142. this.watchMode = true;
  143. return true;
  144. };
  145. less.unwatch = function () {clearInterval(less.watchTimer); this.watchMode = false; return false; };
  146. //
  147. // Synchronously get all <link> tags with the 'rel' attribute set to
  148. // "stylesheet/less".
  149. //
  150. less.registerStylesheetsImmediately = function() {
  151. var links = document.getElementsByTagName('link');
  152. less.sheets = [];
  153. for (var i = 0; i < links.length; i++) {
  154. if (links[i].rel === 'stylesheet/less' || (links[i].rel.match(/stylesheet/) &&
  155. (links[i].type.match(typePattern)))) {
  156. less.sheets.push(links[i]);
  157. }
  158. }
  159. };
  160. //
  161. // Asynchronously get all <link> tags with the 'rel' attribute set to
  162. // "stylesheet/less", returning a Promise.
  163. //
  164. less.registerStylesheets = function() {
  165. return new Promise(function(resolve, reject) {
  166. less.registerStylesheetsImmediately();
  167. resolve();
  168. });
  169. };
  170. //
  171. // With this function, it's possible to alter variables and re-render
  172. // CSS without reloading less-files
  173. //
  174. less.modifyVars = function(record) {
  175. return less.refresh(true, record, false);
  176. };
  177. less.refresh = function (reload, modifyVars, clearFileCache) {
  178. if ((reload || clearFileCache) && clearFileCache !== false) {
  179. fileManager.clearFileCache();
  180. }
  181. return new Promise(function (resolve, reject) {
  182. var startTime, endTime, totalMilliseconds, remainingSheets;
  183. startTime = endTime = new Date();
  184. // Set counter for remaining unprocessed sheets
  185. remainingSheets = less.sheets.length;
  186. if (remainingSheets === 0) {
  187. endTime = new Date();
  188. totalMilliseconds = endTime - startTime;
  189. less.logger.info("Less has finished and no sheets were loaded.");
  190. resolve({
  191. startTime: startTime,
  192. endTime: endTime,
  193. totalMilliseconds: totalMilliseconds,
  194. sheets: less.sheets.length
  195. });
  196. } else {
  197. // Relies on less.sheets array, callback seems to be guaranteed to be called for every element of the array
  198. loadStyleSheets(function (e, css, _, sheet, webInfo) {
  199. if (e) {
  200. errors.add(e, e.href || sheet.href);
  201. reject(e);
  202. return;
  203. }
  204. if (webInfo.local) {
  205. less.logger.info("Loading " + sheet.href + " from cache.");
  206. } else {
  207. less.logger.info("Rendered " + sheet.href + " successfully.");
  208. }
  209. browser.createCSS(window.document, css, sheet);
  210. less.logger.info("CSS for " + sheet.href + " generated in " + (new Date() - endTime) + 'ms');
  211. // Count completed sheet
  212. remainingSheets--;
  213. // Check if the last remaining sheet was processed and then call the promise
  214. if (remainingSheets === 0) {
  215. totalMilliseconds = new Date() - startTime;
  216. less.logger.info("Less has finished. CSS generated in " + totalMilliseconds + 'ms');
  217. resolve({
  218. startTime: startTime,
  219. endTime: endTime,
  220. totalMilliseconds: totalMilliseconds,
  221. sheets: less.sheets.length
  222. });
  223. }
  224. endTime = new Date();
  225. }, reload, modifyVars);
  226. }
  227. loadStyles(modifyVars);
  228. });
  229. };
  230. less.refreshStyles = loadStyles;
  231. return less;
  232. };