Relative URLs in LESS files should be relative to the file that defines them.

It is up to the parser and compiler to rewrite them when those files are
imported by another LESS file.

- Modified and added test cases for import and import-once rules
- Fixed difference between client side and server side handling of relative urls
- Added a -rootpath option to lessc to specify another base path for the url
  rewriting. By default, rootpath=''
This commit is contained in:
Salim Bensiali
2012-10-10 15:50:06 +11:00
committed by Luke Page
parent 22a4a0c874
commit e59a93b5fd
13 changed files with 81 additions and 15 deletions

View File

@@ -14,7 +14,8 @@ var options = {
silent: false,
paths: [],
color: true,
strictImports: false
strictImports: false,
rootpath: ''
};
var continueProcessing = true,
currentErrorcode;
@@ -77,6 +78,10 @@ args = args.filter(function (arg) {
case 'line-numbers':
options.dumpLineNumbers = match[2];
break;
case 'rp':
case 'rootpath':
options.rootpath = path.normalize(match[2] + '/');
break;
}
});
@@ -120,6 +125,7 @@ var parseLessFile = function (e, data) {
paths: [path.dirname(input)].concat(options.paths),
optimization: options.optimization,
filename: input,
rootpath: options.rootpath,
strictImports: options.strictImports,
dumpLineNumbers: options.dumpLineNumbers
}).parse(data, function (err, tree) {

View File

@@ -211,6 +211,7 @@ function loadStyleSheet(sheet, callback, reload, remaining) {
var css = cache && cache.getItem(href);
var timestamp = cache && cache.getItem(href + ':timestamp');
var styles = { css: css, timestamp: timestamp };
var rootpath = sheet.rootpath || hrefParts.path;
xhr(href, sheet.type, function (data, lastModified) {
// Store data this session
@@ -231,6 +232,7 @@ function loadStyleSheet(sheet, callback, reload, remaining) {
paths: [hrefParts.path],
mime: sheet.type,
filename: href,
rootpath: rootpath,
contents: contents, // Passing top importing parser content cache ref down.
files: files,
dumpLineNumbers: less.dumpLineNumbers

View File

@@ -104,6 +104,21 @@ less.Parser.importer = function (file, paths, callback, env) {
function parseFile(e, data) {
if (e) return callback(e);
var rootpath = env.rootpath,
j = file.lastIndexOf('/');
// Pass on an updated rootpath if path of imported file is relative and file
// is in a (sub|sup) directory
//
// Examples:
// - If path of imported file is 'module/nav/nav.less' and rootpath is 'less/',
// then rootpath should become 'less/module/nav/'
// - If path of imported file is '../mixins.less' and rootpath is 'less/',
// then rootpath should become 'less/../'
if(!/^(?:[a-z-]+:|\/)/.test(file) && j != -1) {
rootpath = rootpath + file.slice(0, j+1); // append (sub|sup) directory path of imported file
}
env.contents[pathname] = data; // Updating top importing parser content cache.
new(less.Parser)({
@@ -112,6 +127,7 @@ less.Parser.importer = function (file, paths, callback, env) {
contents: env.contents,
files: env.files,
syncImport: env.syncImport,
rootpath: rootpath,
dumpLineNumbers: env.dumpLineNumbers
}).parse(data, function (e, root) {
callback(e, root, pathname);
@@ -169,6 +185,7 @@ less.Parser.importer = function (file, paths, callback, env) {
paths = paths.slice(0, paths.length - 1);
if (!pathname) {
if (typeof(env.errback) === "function") {
env.errback(file, paths, callback);
} else {

View File

@@ -70,6 +70,7 @@ less.Parser = function Parser(env) {
var env = env || { };
// env.contents and files must be passed arround with top env
if (!env.contents) { env.contents = {}; }
env.rootpath = env.rootpath || ''; // env.rootpath must be initialized to '' if not provided
if (!env.files) { env.files = {}; }
// This function is called after all files
@@ -670,7 +671,7 @@ less.Parser = function Parser(env) {
expect(')');
return new(tree.URL)((value.value != null || value instanceof tree.Variable)
? value : new(tree.Anonymous)(value), imports.paths);
? value : new(tree.Anonymous)(value), env.rootpath);
},
//
@@ -1198,7 +1199,7 @@ less.Parser = function Parser(env) {
if (dir && (path = $(this.entities.quoted) || $(this.entities.url))) {
features = $(this.mediaFeatures);
if ($(';')) {
return new(tree.Import)(path, imports, features, (dir[1] === 'once'), index);
return new(tree.Import)(path, imports, features, (dir[1] === 'once'), index, env.rootpath);
}
}
@@ -1500,7 +1501,7 @@ if (less.mode === 'browser' || less.mode === 'rhino') {
// This is so we can get the syntax tree as opposed to just the CSS output,
// as we need this to evaluate the current stylesheet.
// __ Now using the hack of passing a ref to top parser's content cache in the 1st arg. __
loadStyleSheet({ href: path, title: path, type: env.mime, contents: env.contents, files: env.files }, function (e, root, data, sheet, _, path) {
loadStyleSheet({ href: path, title: path, type: env.mime, contents: env.contents, files: env.files, rootpath: env.rootpath }, function (e, root, data, sheet, _, path) {
if (e && typeof(env.errback) === "function") {
env.errback.call(null, path, paths, callback, env);
} else {

View File

@@ -11,14 +11,15 @@
// `import,push`, we also pass it a callback, which it'll call once
// the file has been fetched, and parsed.
//
tree.Import = function (path, imports, features, once, index) {
tree.Import = function (path, imports, features, once, index, rootpath) {
var that = this;
this.once = once;
this.index = index;
this._path = path;
this.features = features && new(tree.Value)(features);
this.rootpath = rootpath;
// The '.less' extension is optional
if (path instanceof tree.Quoted) {
this.path = /\.(le?|c)ss(\?.*)?$/.test(path.value) ? path.value : path.value + '.less';
@@ -52,6 +53,10 @@ tree.Import.prototype = {
var features = this.features ? ' ' + this.features.toCSS(env) : '';
if (this.css) {
// Add the base path if the import is relative
if (typeof this._path.value === "string" && !/^(?:[a-z-]+:|\/)/.test(this._path.value)) {
this._path.value = this.rootpath + this._path.value;
}
return "@import " + this._path.toCSS() + features + ';\n';
} else {
return "";

View File

@@ -1,8 +1,8 @@
(function (tree) {
tree.URL = function (val, paths) {
tree.URL = function (val, rootpath) {
this.value = val;
this.paths = paths;
this.rootpath = rootpath;
};
tree.URL.prototype = {
toCSS: function () {
@@ -11,12 +11,12 @@ tree.URL.prototype = {
eval: function (ctx) {
var val = this.value.eval(ctx);
// Add the base path if the URL is relative and we are in the browser
if (typeof window !== 'undefined' && typeof val.value === "string" && !/^(?:[a-z-]+:|\/)/.test(val.value) && this.paths.length > 0) {
val.value = this.paths[0] + (val.value.charAt(0) === '/' ? val.value.slice(1) : val.value);
// Add the base path if the URL is relative
if (typeof val.value === "string" && !/^(?:[a-z-]+:|\/)/.test(val.value)) {
val.value = this.rootpath + val.value;
}
return new(tree.URL)(val, this.paths);
return new(tree.URL)(val, this.rootpath);
}
};

View File

@@ -1,6 +1,6 @@
@import "import-test-d.css";
@import "import/import-test-d.css";
@import "../import-test-d.css";
@import "import/deeper/../import-test-d.css";
#import {
color: #ff0000;
}

16
test/css/import.css vendored
View File

@@ -1,8 +1,10 @@
@import "import-test-d.css";
@import "import/import-test-d.css";
@import url(http://fonts.googleapis.com/css?family=Open+Sans);
@import url(something.css) screen and (color) and (max-width: 600px);
@import "import/../css/background.css";
#import {
color: #ff0000;
}
@@ -21,3 +23,15 @@
width: 100%;
}
}
#logo {
width: 100px;
height: 100px;
background: url('import/imports/../assets/logo.png');
}
@font-face {
font-family: xecret;
src: url('import/imports/../assets/xecret.ttf');
}
#secret {
font-family: xecret, sans-serif;
}

View File

@@ -1,3 +1,4 @@
@import "import/import-once-test-c";
@import-once "import/import-once-test-c";
@import-once "import/import-once-test-c.less";
@import-once "import/deeper/import-once-test-a";

View File

@@ -9,3 +9,5 @@
height: @a + 10%;
}
@import "import/import-test-e" screen and (max-width: 600px);
@import "import/import-and-relative-paths-test";

View File

@@ -0,0 +1,5 @@
@import "../css/background.css";
@import "imports/logo";
@import "imports/font";

View File

@@ -0,0 +1,8 @@
@font-face {
font-family: xecret;
src: url('../assets/xecret.ttf');
}
#secret {
font-family: xecret, sans-serif;
}

View File

@@ -0,0 +1,5 @@
#logo {
width: 100px;
height: 100px;
background: url('../assets/logo.png');
}