index.js 10.0 KB

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