// // index.js // Should expose the additional browser functions on to the less object // var addDataAttr = require("./utils").addDataAttr, browser = require("./browser"); module.exports = function(window, options) { var document = window.document; var less = require('../less')(); //module.exports = less; less.options = options; var environment = less.environment, FileManager = require("./file-manager")(options, less.logger), fileManager = new FileManager(); environment.addFileManager(fileManager); less.FileManager = FileManager; require("./log-listener")(less, options); var errors = require("./error-reporting")(window, less, options); var cache = less.cache = options.cache || require("./cache")(window, options, less.logger); require('./image-size')(less.environment); //Setup user functions if (options.functions) { less.functions.functionRegistry.addMultiple(options.functions); } var typePattern = /^text\/(x-)?less$/; function postProcessCSS(styles) { // deprecated, use a plugin for postprocesstasks if (options.postProcessor && typeof options.postProcessor === 'function') { styles = options.postProcessor.call(styles, styles) || styles; } return styles; } function clone(obj) { var cloned = {}; for (var prop in obj) { if (obj.hasOwnProperty(prop)) { cloned[prop] = obj[prop]; } } return cloned; } // only really needed for phantom function bind(func, thisArg) { var curryArgs = Array.prototype.slice.call(arguments, 2); return function() { var args = curryArgs.concat(Array.prototype.slice.call(arguments, 0)); return func.apply(thisArg, args); }; } function loadStyles(modifyVars) { var styles = document.getElementsByTagName('style'), style; for (var i = 0; i < styles.length; i++) { style = styles[i]; if (style.type.match(typePattern)) { var instanceOptions = clone(options); instanceOptions.modifyVars = modifyVars; var lessText = style.innerHTML || ''; instanceOptions.filename = document.location.href.replace(/#.*$/, ''); /*jshint loopfunc:true */ // use closure to store current style less.render(lessText, instanceOptions, bind(function(style, e, result) { if (e) { errors.add(e, "inline"); } else { style.type = 'text/css'; if (style.styleSheet) { style.styleSheet.cssText = result.css; } else { style.innerHTML = result.css; } } }, null, style)); } } } function loadStyleSheet(sheet, callback, reload, remaining, modifyVars) { var instanceOptions = clone(options); addDataAttr(instanceOptions, sheet); instanceOptions.mime = sheet.type; if (modifyVars) { instanceOptions.modifyVars = modifyVars; } function loadInitialFileCallback(loadedFile) { var data = loadedFile.contents, path = loadedFile.filename, webInfo = loadedFile.webInfo; var newFileInfo = { currentDirectory: fileManager.getPath(path), filename: path, rootFilename: path, relativeUrls: instanceOptions.relativeUrls}; newFileInfo.entryPath = newFileInfo.currentDirectory; newFileInfo.rootpath = instanceOptions.rootpath || newFileInfo.currentDirectory; if (webInfo) { webInfo.remaining = remaining; var css = cache.getCSS(path, webInfo, instanceOptions.modifyVars); if (!reload && css) { webInfo.local = true; callback(null, css, data, sheet, webInfo, path); return; } } //TODO add tests around how this behaves when reloading errors.remove(path); instanceOptions.rootFileInfo = newFileInfo; less.render(data, instanceOptions, function(e, result) { if (e) { e.href = path; callback(e); } else { result.css = postProcessCSS(result.css); cache.setCSS(sheet.href, webInfo.lastModified, instanceOptions.modifyVars, result.css); callback(null, result.css, data, sheet, webInfo, path); } }); } fileManager.loadFile(sheet.href, null, instanceOptions, environment, function(e, loadedFile) { if (e) { callback(e); return; } loadInitialFileCallback(loadedFile); }); } function loadStyleSheets(callback, reload, modifyVars) { for (var i = 0; i < less.sheets.length; i++) { loadStyleSheet(less.sheets[i], callback, reload, less.sheets.length - (i + 1), modifyVars); } } function initRunningMode() { if (less.env === 'development') { less.watchTimer = setInterval(function () { if (less.watchMode) { fileManager.clearFileCache(); loadStyleSheets(function (e, css, _, sheet, webInfo) { if (e) { errors.add(e, e.href || sheet.href); } else if (css) { browser.createCSS(window.document, css, sheet); } }); } }, options.poll); } } // // Watch mode // less.watch = function () { if (!less.watchMode ) { less.env = 'development'; initRunningMode(); } this.watchMode = true; return true; }; less.unwatch = function () {clearInterval(less.watchTimer); this.watchMode = false; return false; }; // // Synchronously get all tags with the 'rel' attribute set to // "stylesheet/less". // less.registerStylesheetsImmediately = function() { var links = document.getElementsByTagName('link'); less.sheets = []; for (var i = 0; i < links.length; i++) { if (links[i].rel === 'stylesheet/less' || (links[i].rel.match(/stylesheet/) && (links[i].type.match(typePattern)))) { less.sheets.push(links[i]); } } }; // // Asynchronously get all tags with the 'rel' attribute set to // "stylesheet/less", returning a Promise. // less.registerStylesheets = function() { return new Promise(function(resolve, reject) { less.registerStylesheetsImmediately(); resolve(); }); }; // // With this function, it's possible to alter variables and re-render // CSS without reloading less-files // less.modifyVars = function(record) { return less.refresh(true, record, false); }; less.refresh = function (reload, modifyVars, clearFileCache) { if ((reload || clearFileCache) && clearFileCache !== false) { fileManager.clearFileCache(); } return new Promise(function (resolve, reject) { var startTime, endTime, totalMilliseconds, remainingSheets; startTime = endTime = new Date(); // Set counter for remaining unprocessed sheets remainingSheets = less.sheets.length; if (remainingSheets === 0) { endTime = new Date(); totalMilliseconds = endTime - startTime; less.logger.info("Less has finished and no sheets were loaded."); resolve({ startTime: startTime, endTime: endTime, totalMilliseconds: totalMilliseconds, sheets: less.sheets.length }); } else { // Relies on less.sheets array, callback seems to be guaranteed to be called for every element of the array loadStyleSheets(function (e, css, _, sheet, webInfo) { if (e) { errors.add(e, e.href || sheet.href); reject(e); return; } if (webInfo.local) { less.logger.info("Loading " + sheet.href + " from cache."); } else { less.logger.info("Rendered " + sheet.href + " successfully."); } browser.createCSS(window.document, css, sheet); less.logger.info("CSS for " + sheet.href + " generated in " + (new Date() - endTime) + 'ms'); // Count completed sheet remainingSheets--; // Check if the last remaining sheet was processed and then call the promise if (remainingSheets === 0) { totalMilliseconds = new Date() - startTime; less.logger.info("Less has finished. CSS generated in " + totalMilliseconds + 'ms'); resolve({ startTime: startTime, endTime: endTime, totalMilliseconds: totalMilliseconds, sheets: less.sheets.length }); } endTime = new Date(); }, reload, modifyVars); } loadStyles(modifyVars); }); }; less.refreshStyles = loadStyles; return less; };