Start abstracting re-organising logic into a visitor before css output. Will allow nodes to just be 'read' and debugInfo written into a sourcemap. part 1.

This commit is contained in:
Luke Page
2013-07-04 21:30:29 +01:00
parent 8ea150d4cb
commit 800b4218d5
11 changed files with 130 additions and 58 deletions

View File

@@ -44,6 +44,7 @@ less:
${SRC}/visitor.js\
${SRC}/import-visitor.js\
${SRC}/join-selector-visitor.js\
${SRC}/to-css-visitor.js\
${SRC}/extend-visitor.js\
${SRC}/browser.js\
build/amd.js >> ${DIST}
@@ -68,6 +69,7 @@ rhino:
${SRC}/visitor.js\
${SRC}/import-visitor.js\
${SRC}/join-selector-visitor.js\
${SRC}/to-css-visitor.js\
${SRC}/extend-visitor.js\
${SRC}/functions.js\
${SRC}/colors.js\

View File

@@ -214,5 +214,6 @@ require('./visitor.js');
require('./import-visitor.js');
require('./extend-visitor.js');
require('./join-selector-visitor.js');
require('./to-css-visitor.js');
for (var k in less) { exports[k] = less[k]; }

View File

@@ -30,7 +30,7 @@
},
visitMedia: function (mediaNode, visitArgs) {
var context = this.contexts[this.contexts.length - 1];
mediaNode.ruleset.root = (context.length === 0 || context[0].multiMedia);
mediaNode.rules[0].root = (context.length === 0 || context[0].multiMedia);
}
};

View File

@@ -435,6 +435,9 @@ less.Parser = function Parser(env) {
new(tree.processExtendsVisitor)()
.run(evaldRoot);
new(tree.toCSSVisitor)()
.run(evaldRoot);
var css = evaldRoot.toCSS({
compress: Boolean(options.compress),
dumpLineNumbers: env.dumpLineNumbers,

View File

@@ -0,0 +1,60 @@
(function (tree) {
tree.toCSSVisitor = function() {
this._visitor = new tree.visitor(this);
};
tree.toCSSVisitor.prototype = {
isReplacing: true,
run: function (root) {
return this._visitor.visit(root);
},
visitRule: function (ruleNode, visitArgs) {
visitArgs.visitDeeper = false;
return ruleNode;
},
visitDirective: function(directiveNode, visitArgs) {
if (directiveNode.name === "@charset") {
// Only output the debug info together with subsequent @charset definitions
// a comment (or @media statement) before the actual @charset directive would
// be considered illegal css as it has to be on the first line
if (this.charset) {
if (directiveNode.debugInfo) {
var comment = new tree.Comment("/* " + directiveNode.toCSS({}).replace(/\n/g, "")+" */\n");
comment.debugInfo = directiveNode.debugInfo;
return this._visitor.visit(comment);
}
return [];
}
this.charset = true;
}
return directiveNode;
},
visitRuleset: function (rulesetNode, visitArgs) {
var rule, rulesets = [];
if (! rulesetNode.root) {
// Compile rules and rulesets
for (var i = 0; i < rulesetNode.rules.length; i++) {
rule = rulesetNode.rules[i];
if (rule.rules) {
rulesets.push(this._visitor.visit(rule));
rulesetNode.rules.splice(i, 1);
i--;
continue;
}
}
if (rulesets.length > 0 && rulesetNode.rules.length > 0) {
rulesets.splice(0, 0, rulesetNode);
}
}
if (rulesets.length === 0) {
rulesets = rulesetNode;
}
return rulesets;
}
};
})(require('./tree'));

View File

@@ -9,7 +9,7 @@ tree.Alpha.prototype = {
this.value = visitor.visit(this.value);
},
eval: function (env) {
if (this.value.eval) { this.value = this.value.eval(env) }
if (this.value.eval) { this.value = this.value.eval(env); }
return this;
},
toCSS: function () {

View File

@@ -8,9 +8,13 @@ tree.Comment = function (value, silent, index, currentFileInfo) {
tree.Comment.prototype = {
type: "Comment",
toCSS: function (env) {
var debugInfo = "";
if (this.debugInfo) {
debugInfo = tree.debugInfo(env, this);
}
var isReference = (this.currentFileInfo && this.currentFileInfo.reference && !this.isReferenced),
isCompressed = env.compress && !this.value.match(/^\/\*!/);
return (isReference || isCompressed) ? '' : this.value;
return (isReference || isCompressed) ? '' : (debugInfo + this.value);
},
eval: function () { return this; },
markReferenced: function () {

View File

@@ -4,8 +4,8 @@ tree.Directive = function (name, value, index, currentFileInfo) {
this.name = name;
if (Array.isArray(value)) {
this.ruleset = new(tree.Ruleset)([], value);
this.ruleset.allowImports = true;
this.rules = [new(tree.Ruleset)([], value)];
this.rules[0].allowImports = true;
} else {
this.value = value;
}
@@ -14,7 +14,7 @@ tree.Directive = function (name, value, index, currentFileInfo) {
tree.Directive.prototype = {
type: "Directive",
accept: function (visitor) {
this.ruleset = visitor.visit(this.ruleset);
this.rules = visitor.visit(this.rules);
this.value = visitor.visit(this.value);
},
toCSS: function (env) {
@@ -23,36 +23,40 @@ tree.Directive.prototype = {
return "";
}
if (this.ruleset) {
this.ruleset.root = true;
return this.name + (env.compress ? '{' : ' {\n ') +
this.ruleset.toCSS(env).trim().replace(/\n/g, '\n ') +
(env.compress ? '}': '\n}\n');
if (this.rules) {
var css = "";
for(var i = 0; i < this.rules.length; i++) {
//this.rules[i].root = true;
css += this.rules[i].toCSS(env).trim() + "\n";
}
css = css.trim().replace(/\n/g, '\n ');
return this.name + (env.compress ? '{' : ' {\n ') + css + (env.compress ? '}': '\n}\n');
} else {
return this.name + ' ' + this.value.toCSS() + ';\n';
}
},
eval: function (env) {
var evaldDirective = this;
if (this.ruleset) {
if (this.rules) {
env.frames.unshift(this);
evaldDirective = new(tree.Directive)(this.name, null, this.index, this.currentFileInfo);
evaldDirective.ruleset = this.ruleset.eval(env);
evaldDirective.rules = [this.rules[0].eval(env)];
evaldDirective.rules[0].root = true;
env.frames.shift();
}
return evaldDirective;
},
variable: function (name) { return tree.Ruleset.prototype.variable.call(this.ruleset, name) },
find: function () { return tree.Ruleset.prototype.find.apply(this.ruleset, arguments) },
rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.ruleset) },
variable: function (name) { return tree.Ruleset.prototype.variable.call(this.rules[0], name); },
find: function () { return tree.Ruleset.prototype.find.apply(this.rules[0], arguments); },
rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.rules[0]); },
markReferenced: function () {
var rule, i;
var i, rules;
this.isReferenced = true;
if (this.ruleset) {
for (i = 0; i < this.ruleset.rules.length; i++) {
rule = this.ruleset.rules[i];
if (rule.markReferenced) {
rule.markReferenced();
if (this.rules) {
rules = this.rules[0].rules;
for (i = 0; i < rules.length; i++) {
if (rules[i].markReferenced) {
rules[i].markReferenced();
}
}
}

View File

@@ -7,19 +7,25 @@ tree.Media = function (value, features, index, currentFileInfo) {
var selectors = this.emptySelectors();
this.features = new(tree.Value)(features);
this.ruleset = new(tree.Ruleset)(selectors, value);
this.ruleset.allowImports = true;
this.rules = [new(tree.Ruleset)(selectors, value)];
this.rules[0].allowImports = true;
};
tree.Media.prototype = {
type: "Media",
accept: function (visitor) {
this.features = visitor.visit(this.features);
this.ruleset = visitor.visit(this.ruleset);
this.rules = visitor.visit(this.rules);
},
toCSS: function (env) {
var features = this.features.toCSS(env);
var content = this.ruleset.toCSS(env).trim().replace(/\n/g, '\n ');
var content = "";
for(var i = 0; i < this.rules.length; i++) {
content += this.rules[i].toCSS(env).trim() + "\n";
}
content = content.trim().replace(/\n/g, '\n ');
if (content.match(/\S/)) {
return '@media ' + features + (env.compress ? '{' : ' {\n ') + content +
@@ -36,7 +42,7 @@ tree.Media.prototype = {
var media = new(tree.Media)([], [], this.index, this.currentFileInfo);
if(this.debugInfo) {
this.ruleset.debugInfo = this.debugInfo;
this.rules[0].debugInfo = this.debugInfo;
media.debugInfo = this.debugInfo;
}
var strictMathBypass = false;
@@ -56,8 +62,8 @@ tree.Media.prototype = {
env.mediaPath.push(media);
env.mediaBlocks.push(media);
env.frames.unshift(this.ruleset);
media.ruleset = this.ruleset.eval(env);
env.frames.unshift(this.rules[0]);
media.rules = [this.rules[0].eval(env)];
env.frames.shift();
env.mediaPath.pop();
@@ -65,20 +71,19 @@ tree.Media.prototype = {
return env.mediaPath.length === 0 ? media.evalTop(env) :
media.evalNested(env)
},
variable: function (name) { return tree.Ruleset.prototype.variable.call(this.ruleset, name) },
find: function () { return tree.Ruleset.prototype.find.apply(this.ruleset, arguments) },
rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.ruleset) },
variable: function (name) { return tree.Ruleset.prototype.variable.call(this.rules[0], name); },
find: function () { return tree.Ruleset.prototype.find.apply(this.rules[0], arguments); },
rulesets: function () { return tree.Ruleset.prototype.rulesets.apply(this.rules[0]); },
emptySelectors: function() {
var el = new(tree.Element)('', '&', 0);
return [new(tree.Selector)([el], null, null, this.index, this.currentFileInfo)];
},
markReferenced: function () {
var rule, i;
var i, rules = this.rules[0].rules;
this.isReferenced = true;
for (i = 0; i < this.ruleset.rules.length; i++) {
rule = this.ruleset.rules[i];
if (rule.markReferenced) {
rule.markReferenced();
for (i = 0; i < rules.length; i++) {
if (rules[i].markReferenced) {
rules[i].markReferenced();
}
}
},
@@ -148,7 +153,7 @@ tree.Media.prototype = {
}
},
bubbleSelectors: function (selectors) {
this.ruleset = new(tree.Ruleset)(selectors.slice(0), [this.ruleset]);
this.rules = [new(tree.Ruleset)(selectors.slice(0), [this.rules[0]])];
}
};

View File

@@ -194,26 +194,8 @@ tree.Ruleset.prototype = {
for (var i = 0; i < this.rules.length; i++) {
rule = this.rules[i];
if (rule.rules || (rule instanceof tree.Media)) {
if (rule.rules || (rule instanceof tree.Media) || rule instanceof tree.Directive) {
rulesets.push(rule.toCSS(env));
} else if (rule instanceof tree.Directive) {
var cssValue = rule.toCSS(env);
// Output only the first @charset definition as such - convert the others
// to comments in case debug is enabled
if (rule.name === "@charset") {
// Only output the debug info together with subsequent @charset definitions
// a comment (or @media statement) before the actual @charset directive would
// be considered illegal css as it has to be on the first line
if (env.charset) {
if (rule.debugInfo) {
rulesets.push(tree.debugInfo(env, rule));
rulesets.push(new tree.Comment("/* "+cssValue.replace(/\n/g, "")+" */\n").toCSS(env));
}
continue;
}
env.charset = true;
}
rulesets.push(cssValue);
} else if (rule instanceof tree.Comment) {
if (!rule.silent) {
if (this.root) {

View File

@@ -39,7 +39,7 @@
for(i = 0; i < nodes.length; i++) {
var evald = this.visit(nodes[i]);
if (evald instanceof Array) {
newNodes = newNodes.concat(evald);
newNodes = newNodes.concat(this.flatten(evald));
} else {
newNodes.push(evald);
}
@@ -48,6 +48,17 @@
return newNodes;
}
return nodes;
},
flatten: function(arr, master) {
return arr.reduce(this.flattenReduce.bind(this), master || []);
},
flattenReduce: function(sum, element) {
if (element instanceof Array) {
sum = this.flatten(element, sum);
} else {
sum.push(element);
}
return sum;
}
};