mirror of
https://github.com/less/less.js.git
synced 2026-01-22 05:37:56 -05:00
Starting in 2.0, stylesheet loading became asynchronous, through the usage of promises for both calculating the list of stylesheets and the initial call to less.refresh(). This resulted in visual issues while loading on some browsers (noticed in Firefox and Safari), along with breakages of any custom JavaScript that depended on the computed style of elements on the page, due to race conditions. This change preserves the promise for initial page loading, in order to retain support for less.pageLoadFinished, but immediately executes the stylesheet scan (through a new less.registerStylesheetsImmediately function) and the less.refresh() call. That resulting behavior matches versions of less prior to 2.0. This unveiled a regression in registering functions, both in the browser and in unit tests, that was not previously noticed due to the asynchronous load. Registered functions would have a 'less' variable set to the less options, and not less itself, when not going through the asynchronous loading mode. This meant that both unit tests and real-world function registration would break when the sync page loading was fixed. Overriding window.less to point to the actual less module and not less.options during bootstrap fixes this. This fixes #2317.
265 lines
9.1 KiB
JavaScript
265 lines
9.1 KiB
JavaScript
//
|
|
// 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);
|
|
|
|
//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;
|
|
|
|
if (!instanceOptions.modifyVars) {
|
|
var css = cache.getCSS(path, webInfo);
|
|
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);
|
|
if (!instanceOptions.modifyVars) {
|
|
cache.setCSS(sheet.href, webInfo.lastModified, 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 <link> 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 <link> 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;
|
|
startTime = endTime = new Date();
|
|
|
|
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');
|
|
if (webInfo.remaining === 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;
|
|
};
|