index.js 9.7 KB

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