lib/less/js/lib/less/browser.js in less-2.3.3 vs lib/less/js/lib/less/browser.js in less-2.4.0
- old
+ new
@@ -36,39 +36,41 @@
}
//
// Watch mode
//
-less.watch = function () {
- if (!less.watchMode ){
- less.env = 'development';
- initRunningMode();
- }
- return this.watchMode = true
+less.watch = function () {
+ if (!less.watchMode ){
+ less.env = 'development';
+ initRunningMode();
+ }
+ return this.watchMode = true
};
less.unwatch = function () {clearInterval(less.watchTimer); return this.watchMode = false; };
function initRunningMode(){
- if (less.env === 'development') {
- less.optimization = 0;
- less.watchTimer = setInterval(function () {
- if (less.watchMode) {
- loadStyleSheets(function (e, root, _, sheet, env) {
- if (root) {
- createCSS(root.toCSS(), sheet, env.lastModified);
- }
- });
- }
- }, less.poll);
- } else {
- less.optimization = 3;
- }
+ if (less.env === 'development') {
+ less.optimization = 0;
+ less.watchTimer = setInterval(function () {
+ if (less.watchMode) {
+ loadStyleSheets(function (e, root, _, sheet, env) {
+ if (e) {
+ error(e, sheet.href);
+ } else if (root) {
+ createCSS(root.toCSS(less), sheet, env.lastModified);
+ }
+ });
+ }
+ }, less.poll);
+ } else {
+ less.optimization = 3;
+ }
}
if (/!watch/.test(location.hash)) {
- less.watch();
+ less.watch();
}
var cache = null;
if (less.env != 'development') {
@@ -96,30 +98,37 @@
// With this function, it's possible to alter variables and re-render
// CSS without reloading less-files
//
var session_cache = '';
less.modifyVars = function(record) {
- var str = session_cache;
- for (name in record) {
+ var str = session_cache;
+ for (var name in record) {
str += ((name.slice(0,1) === '@')? '' : '@') + name +': '+
((record[name].slice(-1) === ';')? record[name] : record[name] +';');
}
- new(less.Parser)().parse(str, function (e, root) {
- createCSS(root.toCSS(), less.sheets[less.sheets.length - 1]);
+ new(less.Parser)(new less.tree.parseEnv(less)).parse(str, function (e, root) {
+ if (e) {
+ error(e, "session_cache");
+ } else {
+ createCSS(root.toCSS(less), less.sheets[less.sheets.length - 1]);
+ }
});
};
less.refresh = function (reload) {
var startTime, endTime;
startTime = endTime = new(Date);
loadStyleSheets(function (e, root, _, sheet, env) {
+ if (e) {
+ return error(e, sheet.href);
+ }
if (env.local) {
log("loading " + sheet.href + " from cache.");
} else {
log("parsed " + sheet.href + " successfully.");
- createCSS(root.toCSS(), sheet, env.lastModified);
+ createCSS(root.toCSS(less), sheet, env.lastModified);
}
log("css for " + sheet.href + " generated in " + (new(Date) - endTime) + 'ms');
(env.remaining === 0) && log("css generated in " + (new(Date) - startTime) + 'ms');
endTime = new(Date);
}, reload);
@@ -132,15 +141,18 @@
function loadStyles() {
var styles = document.getElementsByTagName('style');
for (var i = 0; i < styles.length; i++) {
if (styles[i].type.match(typePattern)) {
- new(less.Parser)({
- filename: document.location.href.replace(/#.*$/, ''),
- dumpLineNumbers: less.dumpLineNumbers
- }).parse(styles[i].innerHTML || '', function (e, tree) {
- var css = tree.toCSS();
+ var env = new less.tree.parseEnv(less);
+ env.filename = document.location.href.replace(/#.*$/, '');
+
+ new(less.Parser)(env).parse(styles[i].innerHTML || '', function (e, cssAST) {
+ if (e) {
+ return error(e, "inline");
+ }
+ var css = cssAST.toCSS(less);
var style = styles[i];
style.type = 'text/css';
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
@@ -186,11 +198,11 @@
// urlParts[2] = / if path relative to host base
// urlParts[3] = directories
// urlParts[4] = filename
// urlParts[5] = parameters
- var urlPartsRegex = /^((?:[a-z-]+:)?\/\/(?:[^\/\?#]+\/)|([\/\\]))?((?:[^\/\\\?#]+[\/\\])*)([^\/\\\?#]*)([#\?].*)?$/,
+ var urlPartsRegex = /^((?:[a-z-]+:)?\/+?(?:[^\/\?#]*\/)|([\/\\]))?((?:[^\/\\\?#]*[\/\\])*)([^\/\\\?#]*)([#\?].*)?$/i,
urlParts = url.match(urlPartsRegex),
returner = {}, directories = [], i, baseUrlParts;
if (!urlParts) {
throw new Error("Could not parse sheet href - '"+url+"'");
@@ -200,20 +212,28 @@
if (!urlParts[1] || urlParts[2]) {
baseUrlParts = baseUrl.match(urlPartsRegex);
if (!baseUrlParts) {
throw new Error("Could not parse page url - '"+baseUrl+"'");
}
- urlParts[1] = baseUrlParts[1];
+ urlParts[1] = urlParts[1] || baseUrlParts[1] || "";
if (!urlParts[2]) {
urlParts[3] = baseUrlParts[3] + urlParts[3];
}
}
if (urlParts[3]) {
- directories = urlParts[3].replace("\\", "/").split("/");
+ directories = urlParts[3].replace(/\\/g, "/").split("/");
+ // extract out . before .. so .. doesn't absorb a non-directory
for(i = 0; i < directories.length; i++) {
+ if (directories[i] === ".") {
+ directories.splice(i, 1);
+ i -= 1;
+ }
+ }
+
+ for(i = 0; i < directories.length; i++) {
if (directories[i] === ".." && i > 0) {
directories.splice(i-1, 2);
i -= 2;
}
}
@@ -226,41 +246,46 @@
returner.url = returner.fileUrl + (urlParts[5] || "");
return returner;
}
function loadStyleSheet(sheet, callback, reload, remaining) {
+
// sheet may be set to the stylesheet for the initial load or a collection of properties including
// some env variables for imports
- var contents = sheet.contents || {};
- var files = sheet.files || {};
var hrefParts = extractUrlParts(sheet.href, window.location.href);
var href = hrefParts.url;
var css = cache && cache.getItem(href);
var timestamp = cache && cache.getItem(href + ':timestamp');
var styles = { css: css, timestamp: timestamp };
- var rootpath;
+ var env;
+ var newFileInfo = {
+ relativeUrls: less.relativeUrls,
+ currentDirectory: hrefParts.path,
+ filename: href
+ };
- if (less.relativeUrls) {
+ if (sheet instanceof less.tree.parseEnv) {
+ env = new less.tree.parseEnv(sheet);
+ newFileInfo.entryPath = env.currentFileInfo.entryPath;
+ newFileInfo.rootpath = env.currentFileInfo.rootpath;
+ newFileInfo.rootFilename = env.currentFileInfo.rootFilename;
+ } else {
+ env = new less.tree.parseEnv(less);
+ env.mime = sheet.type;
+ newFileInfo.entryPath = hrefParts.path;
+ newFileInfo.rootpath = less.rootpath || hrefParts.path;
+ newFileInfo.rootFilename = href;
+ }
+
+ if (env.relativeUrls) {
+ //todo - this relies on option being set on less object rather than being passed in as an option
+ // - need an originalRootpath
if (less.rootpath) {
- if (sheet.entryPath) {
- rootpath = extractUrlParts(less.rootpath + pathDiff(hrefParts.path, sheet.entryPath)).path;
- } else {
- rootpath = less.rootpath;
- }
+ newFileInfo.rootpath = extractUrlParts(less.rootpath + pathDiff(hrefParts.path, newFileInfo.entryPath)).path;
} else {
- rootpath = hrefParts.path;
+ newFileInfo.rootpath = hrefParts.path;
}
- } else {
- if (less.rootpath) {
- rootpath = less.rootpath;
- } else {
- if (sheet.entryPath) {
- rootpath = sheet.entryPath;
- } else {
- rootpath = hrefParts.path;
- }
- }
}
xhr(href, sheet.type, function (data, lastModified) {
// Store data this session
session_cache += data.replace(/@import .+?;/ig, '');
@@ -272,85 +297,90 @@
createCSS(styles.css, sheet);
callback(null, null, data, sheet, { local: true, remaining: remaining }, href);
} else {
// Use remote copy (re-parse)
try {
- contents[href] = data; // Updating top importing parser content cache
- new(less.Parser)({
- optimization: less.optimization,
- paths: [hrefParts.path],
- entryPath: sheet.entryPath || hrefParts.path,
- mime: sheet.type,
- filename: href,
- rootpath: rootpath,
- relativeUrls: sheet.relativeUrls,
- contents: contents, // Passing top importing parser content cache ref down.
- files: files,
- dumpLineNumbers: less.dumpLineNumbers
- }).parse(data, function (e, root) {
- if (e) { return error(e, href) }
+ env.contents[href] = data; // Updating content cache
+ env.paths = [hrefParts.path];
+ env.currentFileInfo = newFileInfo;
+
+ new(less.Parser)(env).parse(data, function (e, root) {
+ if (e) { return callback(e, null, null, sheet); }
try {
callback(e, root, data, sheet, { local: false, lastModified: lastModified, remaining: remaining }, href);
- removeNode(document.getElementById('less-error-message:' + extractId(href)));
+ //TODO - there must be a better way? A generic less-to-css function that can both call error
+ //and removeNode where appropriate
+ //should also add tests
+ if (env.currentFileInfo.rootFilename === href) {
+ removeNode(document.getElementById('less-error-message:' + extractId(href)));
+ }
} catch (e) {
- error(e, href);
+ callback(e, null, null, sheet);
}
});
} catch (e) {
- error(e, href);
+ callback(e, null, null, sheet);
}
}
}, function (status, url) {
- throw new(Error)("Couldn't load " + url + " (" + status + ")");
+ callback({ type: 'File', message: "'" + url + "' wasn't found (" + status + ")" }, null, null, sheet);
});
}
function extractId(href) {
- return href.replace(/^[a-z]+:\/\/?[^\/]+/, '' ) // Remove protocol & domain
+ return href.replace(/^[a-z-]+:\/+?[^\/]+/, '' ) // Remove protocol & domain
.replace(/^\//, '' ) // Remove root /
.replace(/\.[a-zA-Z]+$/, '' ) // Remove simple extension
.replace(/[^\.\w-]+/g, '-') // Replace illegal characters
.replace(/\./g, ':'); // Replace dots with colons(for valid id)
}
function createCSS(styles, sheet, lastModified) {
- var css;
-
// Strip the query-string
var href = sheet.href || '';
// If there is no title set, use the filename, minus the extension
var id = 'less:' + (sheet.title || extractId(href));
- // If the stylesheet doesn't exist, create a new node
- if ((css = document.getElementById(id)) === null) {
- css = document.createElement('style');
- css.type = 'text/css';
- if( sheet.media ){ css.media = sheet.media; }
- css.id = id;
- var nextEl = sheet && sheet.nextSibling || null;
- (nextEl || document.getElementsByTagName('head')[0]).parentNode.insertBefore(css, nextEl);
+ // If this has already been inserted into the DOM, we may need to replace it
+ var oldCss = document.getElementById(id);
+ var keepOldCss = false;
+
+ // Create a new stylesheet node for insertion or (if necessary) replacement
+ var css = document.createElement('style');
+ css.setAttribute('type', 'text/css');
+ if (sheet.media) {
+ css.setAttribute('media', sheet.media);
}
+ css.id = id;
if (css.styleSheet) { // IE
try {
css.styleSheet.cssText = styles;
} catch (e) {
throw new(Error)("Couldn't reassign styleSheet.cssText.");
}
} else {
- (function (node) {
- if (css.childNodes.length > 0) {
- if (css.firstChild.nodeValue !== node.nodeValue) {
- css.replaceChild(node, css.firstChild);
- }
- } else {
- css.appendChild(node);
- }
- })(document.createTextNode(styles));
+ css.appendChild(document.createTextNode(styles));
+
+ // If new contents match contents of oldCss, don't replace oldCss
+ keepOldCss = (oldCss !== null && oldCss.childNodes.length > 0 && css.childNodes.length > 0 &&
+ oldCss.firstChild.nodeValue === css.firstChild.nodeValue);
}
+ var head = document.getElementsByTagName('head')[0];
+
+ // If there is no oldCss, just append; otherwise, only append if we need
+ // to replace oldCss with an updated stylesheet
+ if (oldCss == null || keepOldCss === false) {
+ var nextEl = sheet && sheet.nextSibling || null;
+ (nextEl || document.getElementsByTagName('head')[0]).parentNode.insertBefore(css, nextEl);
+ }
+ if (oldCss && keepOldCss === false) {
+ head.removeChild(oldCss);
+ }
+
// Don't update the local store if the file wasn't modified
if (lastModified && cache) {
log('saving ' + href + ' to cache.');
try {
cache.setItem(href, styles);
@@ -418,38 +448,38 @@
function log(str) {
if (less.env == 'development' && typeof(console) !== "undefined") { console.log('less: ' + str) }
}
-function error(e, href) {
- var id = 'less-error-message:' + extractId(href);
+function error(e, rootHref) {
+ var id = 'less-error-message:' + extractId(rootHref || "");
var template = '<li><label>{line}</label><pre class="{class}">{content}</pre></li>';
var elem = document.createElement('div'), timer, content, error = [];
- var filename = e.filename || href;
+ var filename = e.filename || rootHref;
var filenameNoPath = filename.match(/([^\/]+(\?.*)?)$/)[1];
elem.id = id;
elem.className = "less-error-message";
- content = '<h3>' + (e.message || 'There is an error in your .less file') +
+ content = '<h3>' + (e.type || "Syntax") + "Error: " + (e.message || 'There is an error in your .less file') +
'</h3>' + '<p>in <a href="' + filename + '">' + filenameNoPath + "</a> ";
var errorline = function (e, i, classname) {
- if (e.extract[i]) {
- error.push(template.replace(/\{line\}/, parseInt(e.line) + (i - 1))
+ if (e.extract[i] != undefined) {
+ error.push(template.replace(/\{line\}/, (parseInt(e.line) || 0) + (i - 1))
.replace(/\{class\}/, classname)
.replace(/\{content\}/, e.extract[i]));
}
};
- if (e.stack) {
- content += '<br/>' + e.stack.split('\n').slice(1).join('<br/>');
- } else if (e.extract) {
+ if (e.extract) {
errorline(e, 0, '');
errorline(e, 1, 'line');
errorline(e, 2, '');
content += 'on line ' + e.line + ', column ' + (e.column + 1) + ':</p>' +
'<ul>' + error.join('') + '</ul>';
+ } else if (e.stack) {
+ content += '<br/>' + e.stack.split('\n').slice(1).join('<br/>');
}
elem.innerHTML = content;
// CSS for error messages
createCSS([