Comparing version 0.1.0 to 0.2.0
@@ -35,15 +35,18 @@ (function() { | ||
AtRule.prototype.toString = function(last) { | ||
var name, semicolon; | ||
name = (this.before || '') + '@' + this.name; | ||
AtRule.prototype.stringify = function(builder, last) { | ||
var params, semicolon; | ||
if (this.rules || this.decls) { | ||
return name + this._params.stringify({ | ||
params = this._params.stringify({ | ||
before: ' ', | ||
after: ' ' | ||
}) + this.stringifyContent(); | ||
}); | ||
return this.stringifyBlock(builder, '@' + this.name + params + '{'); | ||
} else { | ||
if (this.before) { | ||
builder(this.before); | ||
} | ||
semicolon = !last || this.semicolon ? ';' : ''; | ||
return name + this._params.stringify({ | ||
return builder('@' + this.name + this._params.stringify({ | ||
before: ' ' | ||
}) + semicolon; | ||
}) + semicolon, this); | ||
} | ||
@@ -50,0 +53,0 @@ }; |
@@ -18,24 +18,31 @@ (function() { | ||
Container.prototype.stringifyContent = function(brackets) { | ||
var inside, | ||
Container.prototype.stringifyContent = function(builder) { | ||
var last, | ||
_this = this; | ||
if (brackets == null) { | ||
brackets = true; | ||
} | ||
if (!this.rules && !this.decls) { | ||
return; | ||
} | ||
inside = this.rules ? this.rules.map(function(rule, i) { | ||
return rule.toString(_this.rules.length - 1 === i); | ||
}).join('') : this.decls ? this.decls.map(function(i) { | ||
return i.toString(); | ||
}).join(';') + (this.semicolon ? ';' : '') : void 0; | ||
if (this.after != null) { | ||
inside += this.after; | ||
if (this.rules) { | ||
last = this.rules.length - 1; | ||
return this.rules.map(function(rule, i) { | ||
return rule.stringify(builder, last === i); | ||
}); | ||
} else if (this.decls) { | ||
last = this.decls.length - 1; | ||
return this.decls.map(function(decl, i) { | ||
return decl.stringify(builder, last !== i || _this.semicolon); | ||
}); | ||
} | ||
if (brackets) { | ||
return '{' + inside + '}'; | ||
} else { | ||
return inside; | ||
}; | ||
Container.prototype.stringifyBlock = function(builder, start) { | ||
if (this.before) { | ||
builder(this.before); | ||
} | ||
builder(start, this, 'start'); | ||
this.stringifyContent(builder); | ||
if (this.after) { | ||
builder(this.after); | ||
} | ||
return builder('}', this, 'end'); | ||
}; | ||
@@ -42,0 +49,0 @@ |
@@ -20,8 +20,24 @@ (function() { | ||
Declaration.prototype.toString = function() { | ||
return (this.before || '') + this.prop + (this.between || '') + ':' + this._value.stringify({ | ||
Declaration.prototype.stringify = function(builder, semicolon) { | ||
var string; | ||
if (this.before) { | ||
builder(this.before); | ||
} | ||
string = this.prop + (this.between || '') + ':' + this._value.stringify({ | ||
before: ' ' | ||
}); | ||
if (semicolon) { | ||
string += ';'; | ||
} | ||
return builder(string, this); | ||
}; | ||
Declaration.prototype.removeSelf = function() { | ||
if (!this.parent) { | ||
return; | ||
} | ||
this.parent.remove(this); | ||
return this; | ||
}; | ||
Declaration.prototype.clone = function(obj) { | ||
@@ -28,0 +44,0 @@ var cloned; |
@@ -69,3 +69,3 @@ (function() { | ||
Node.prototype.remove = function() { | ||
Node.prototype.removeSelf = function() { | ||
if (!this.parent) { | ||
@@ -78,2 +78,12 @@ return; | ||
Node.prototype.toString = function() { | ||
var builder, result; | ||
result = ''; | ||
builder = function(str) { | ||
return result += str; | ||
}; | ||
this.stringify(builder); | ||
return result; | ||
}; | ||
Node.prototype.clone = function(overrides) { | ||
@@ -80,0 +90,0 @@ var cloned, name, value; |
113
lib/parse.js
@@ -17,4 +17,4 @@ (function() { | ||
Parser = (function() { | ||
function Parser(source, options) { | ||
this.options = options; | ||
function Parser(source, opts) { | ||
this.opts = opts; | ||
this.source = source.toString(); | ||
@@ -28,2 +28,3 @@ this.root = new Root(); | ||
this.line = 1; | ||
this.lines = []; | ||
this.column = 0; | ||
@@ -112,3 +113,3 @@ this.buffer = ''; | ||
Parser.prototype.inAtrule = function(finish) { | ||
Parser.prototype.inAtrule = function(close) { | ||
if (this.inside('atrule-name')) { | ||
@@ -120,5 +121,5 @@ if (this.space()) { | ||
this.setType('atrule-param'); | ||
} else if (this.letter === ';' || this.letter === '{' || finish) { | ||
} else if (this.letter === ';' || this.letter === '{' || close) { | ||
this.checkAtruleName(); | ||
this.endAtruleParams(finish); | ||
this.endAtruleParams(); | ||
} else { | ||
@@ -129,5 +130,5 @@ this.current.name += this.letter; | ||
} else if (this.inside('atrule-param')) { | ||
if (this.letter === ';' || this.letter === '{' || finish) { | ||
if (this.letter === ';' || this.letter === '{' || close) { | ||
this.current.params = new Raw(this.prevBuffer(), this.trim(this.trimmed)); | ||
this.endAtruleParams(finish); | ||
this.endAtruleParams(); | ||
} else { | ||
@@ -172,3 +173,2 @@ this.trimmed += this.letter; | ||
Parser.prototype.isBlockEnd = function() { | ||
var after, start; | ||
if (this.letter === '}') { | ||
@@ -179,7 +179,5 @@ if (this.parents.length === 1) { | ||
if (this.inside('value')) { | ||
start = this.buffer.search(/\s*\}$/); | ||
after = this.buffer.slice(start, -1); | ||
this.buffer = this.buffer.slice(0, +start + 1 || 9e9); | ||
this.inValue(true); | ||
this.current.after = after; | ||
this.fixEnd(function() { | ||
return this.inValue('close'); | ||
}); | ||
} else { | ||
@@ -230,3 +228,3 @@ if (this.semicolon) { | ||
Parser.prototype.inValue = function(finish) { | ||
Parser.prototype.inValue = function(close) { | ||
if (this.inside('value')) { | ||
@@ -238,3 +236,3 @@ if (this.letter === '(') { | ||
} | ||
if ((this.letter === ';' && !this.inBrackets) || finish) { | ||
if ((this.letter === ';' && !this.inBrackets) || close) { | ||
if (this.letter === ';') { | ||
@@ -260,10 +258,12 @@ this.semicolon = true; | ||
if (this.inside('atrule-param') || this.inside('atrule-name')) { | ||
this.inAtrule(true); | ||
this.fixEnd(function() { | ||
return this.inAtrule('close'); | ||
}); | ||
} | ||
if (this.parents.length > 1) { | ||
return this.error('Unclosed block', this.current.line, this.current.column); | ||
return this.error('Unclosed block', this.current.source.start); | ||
} else if (this.inside('comment')) { | ||
return this.error('Unclosed comment', this.commentPos.line, this.commentPos.column); | ||
return this.error('Unclosed comment', this.commentPos); | ||
} else if (this.quote) { | ||
return this.error('Unclosed quote', this.quotePos.line, this.quotePos.column); | ||
return this.error('Unclosed quote', this.quotePos); | ||
} else { | ||
@@ -274,10 +274,10 @@ return this.root.after = this.buffer; | ||
Parser.prototype.error = function(message, line, column) { | ||
if (line == null) { | ||
line = this.line; | ||
Parser.prototype.error = function(message, position) { | ||
if (position == null) { | ||
position = { | ||
line: this.line, | ||
column: this.column | ||
}; | ||
} | ||
if (column == null) { | ||
column = this.column; | ||
} | ||
throw new SyntexError(message, this.source, line, column, this.options.file); | ||
throw new SyntexError(message, this.source, position, this.opts.from); | ||
}; | ||
@@ -291,2 +291,3 @@ | ||
if (this.letter === "\n") { | ||
this.lines[this.line] = this.column - 1; | ||
this.line += 1; | ||
@@ -317,9 +318,48 @@ return this.column = 0; | ||
this.current = node; | ||
node.line = this.line; | ||
node.column = this.column; | ||
node.before = this.buffer.slice(0, -1); | ||
this.current.source = { | ||
start: { | ||
line: this.line, | ||
column: this.column | ||
} | ||
}; | ||
if (this.opts.from) { | ||
this.current.source.file = this.opts.from; | ||
} | ||
this.current.before = this.buffer.slice(0, -1); | ||
return this.buffer = ''; | ||
}; | ||
Parser.prototype.fixEnd = function(callback) { | ||
var after, all, el, last, lines, start; | ||
if (this.letter === '}') { | ||
start = this.buffer.search(/\s*\}$/); | ||
after = this.buffer.slice(start, -1); | ||
} else { | ||
start = this.buffer.search(/\s*$/); | ||
after = this.buffer.slice(start); | ||
} | ||
this.buffer = this.buffer.slice(0, +start + 1 || 9e9); | ||
el = this.current; | ||
callback.apply(this); | ||
lines = after.match(/\n/g); | ||
if (lines) { | ||
el.source.end.line -= lines.length; | ||
all = this.lines[el.source.end.line]; | ||
last = after.indexOf("\n"); | ||
if (last === -1) { | ||
last = after.length; | ||
} | ||
el.source.end.column = all - last; | ||
} else { | ||
el.source.end.column -= after.length; | ||
} | ||
this.current.after = after; | ||
return this.buffer = after; | ||
}; | ||
Parser.prototype.pop = function() { | ||
this.current.source.end = { | ||
line: this.line, | ||
column: this.column | ||
}; | ||
this.popType(); | ||
@@ -356,3 +396,3 @@ this.parents.pop(); | ||
Parser.prototype.endAtruleParams = function(finish) { | ||
Parser.prototype.endAtruleParams = function() { | ||
var type; | ||
@@ -368,6 +408,3 @@ if (this.letter === '{') { | ||
} | ||
this.pop(); | ||
if (this.letter !== ';') { | ||
return this.buffer = this.letter; | ||
} | ||
return this.pop(); | ||
} | ||
@@ -390,8 +427,8 @@ }; | ||
module.exports = function(source, options) { | ||
module.exports = function(source, opts) { | ||
var parser; | ||
if (options == null) { | ||
options = {}; | ||
if (opts == null) { | ||
opts = {}; | ||
} | ||
parser = new Parser(source, options); | ||
parser = new Parser(source, opts); | ||
parser.loop(); | ||
@@ -398,0 +435,0 @@ return parser.root; |
(function() { | ||
var PostCSS, Root, postcss, | ||
var AtRule, Declaration, PostCSS, Result, Root, Rule, generateMap, postcss, | ||
__slice = [].slice; | ||
generateMap = require('./generate-map'); | ||
Declaration = require('./declaration'); | ||
AtRule = require('./at-rule'); | ||
Result = require('./result'); | ||
Rule = require('./rule'); | ||
Root = require('./root'); | ||
@@ -17,8 +27,8 @@ | ||
PostCSS.prototype.process = function(css, options) { | ||
PostCSS.prototype.process = function(css, opts) { | ||
var parsed, processor, returned, _i, _len, _ref; | ||
if (options == null) { | ||
options = {}; | ||
if (opts == null) { | ||
opts = {}; | ||
} | ||
parsed = postcss.parse(css, options); | ||
parsed = postcss.parse(css, opts); | ||
_ref = this.processors; | ||
@@ -32,3 +42,7 @@ for (_i = 0, _len = _ref.length; _i < _len; _i++) { | ||
} | ||
return parsed.toString(); | ||
if (opts.map) { | ||
return generateMap(parsed, opts); | ||
} else { | ||
return new Result(parsed, parsed.toString()); | ||
} | ||
}; | ||
@@ -48,4 +62,20 @@ | ||
postcss.decl = function(defaults) { | ||
return new Declaration(defaults); | ||
}; | ||
postcss.atRule = function(defaults) { | ||
return new AtRule(defaults); | ||
}; | ||
postcss.rule = function(defaults) { | ||
return new Rule(defaults); | ||
}; | ||
postcss.root = function(defaults) { | ||
return new Root(defaults); | ||
}; | ||
module.exports = postcss; | ||
}).call(this); |
@@ -25,4 +25,7 @@ (function() { | ||
Root.prototype.toString = function() { | ||
return this.stringifyContent(false); | ||
Root.prototype.stringify = function(builder) { | ||
this.stringifyContent(builder); | ||
if (this.after) { | ||
return builder(this.after); | ||
} | ||
}; | ||
@@ -29,0 +32,0 @@ |
@@ -20,6 +20,6 @@ (function() { | ||
Rule.prototype.toString = function() { | ||
return (this.before || '') + this._selector.stringify({ | ||
Rule.prototype.stringify = function(builder) { | ||
return this.stringifyBlock(builder, this._selector.stringify({ | ||
after: ' ' | ||
}) + this.stringifyContent(); | ||
}) + '{'); | ||
}; | ||
@@ -26,0 +26,0 @@ |
@@ -9,8 +9,9 @@ (function() { | ||
function SyntaxError(text, source, line, column, file) { | ||
function SyntaxError(text, source, pos, file) { | ||
this.source = source; | ||
this.line = line; | ||
this.column = column; | ||
this.file = file; | ||
this.message = "Can't parse CSS: " + text + " at line " + this.line + ":" + this.column; | ||
this.line = pos.line; | ||
this.column = pos.column; | ||
this.message = "Can't parse CSS: " + text; | ||
this.message += " at line " + pos.line + ":" + pos.column; | ||
if (this.file) { | ||
@@ -17,0 +18,0 @@ this.message += " in " + this.file; |
{ | ||
"name": "postcss", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"description": "Framework for CSS postprocessors", | ||
@@ -13,2 +13,3 @@ "keywords": ["css", "parser", "postproccessor"], | ||
"dependencies": { | ||
"source-map": "*" | ||
}, | ||
@@ -18,4 +19,4 @@ "devDependencies": { | ||
"fs-extra": "0.8.1", | ||
"should": "2.0.2", | ||
"mocha": "1.14.0" | ||
"should": "2.1.1", | ||
"mocha": "1.15.1" | ||
}, | ||
@@ -22,0 +23,0 @@ "main": "lib/postcss", |
550
README.md
# PostCSS | ||
PostCSS is a framework for CSS postprocessors. You get a custom JS function | ||
to modify CSS, and PostCSS parses CSS, gives you usable JS API to edit CSS node | ||
tree and then save modified node tree to new CSS. | ||
PostCSS is a framework for CSS postprocessors, | ||
to modify CSS by your JS function. | ||
For example, let's fix forgotten `content` propery in `::before` and `::after`: | ||
It takes care of most common CSS tool tasks: | ||
1. parses CSS; | ||
2. gives you usable JS API to edit CSS node tree; | ||
3. saves modified node tree to new CSS; | ||
4. generates (or modifies existent) source map for your changes; | ||
You can use this framework to write you own: | ||
* CSS minifier or beautifizer. | ||
* Grunt plugin to generate sprites, include `data-uri` images | ||
or any other works. | ||
* Text editor plugin to automate CSS routine. | ||
* Command-line CSS tool. | ||
Sponsored by [Evil Martians](http://evilmartians.com/). | ||
## Build with PostCSS | ||
* [Autoprefixer](https://github.com/ai/autoprefixer) | ||
* [grunt-pixrem](https://github.com/robwierzbowski/grunt-pixrem) | ||
## Quick Example | ||
Let’s fix forgotten `content` property in `::before` and `::after`: | ||
```js | ||
var postcss = require('postcss'); | ||
var postprocessor = postcss(function (css) { | ||
var contenter = postcss(function (css) { | ||
css.eachRule(function (rule) { | ||
if ( rule.selector.match(/::(before|after)/) ) { | ||
// In every ::before/::after rule | ||
var good = rule.some(function (i) { | ||
return i.prop == 'content'; | ||
}); | ||
// Did we forget content property? | ||
var good = rule.some(function (i) { return i.prop == 'content'; }); | ||
if ( !good ) { | ||
// Add content: '' if we forget it | ||
rule.prepend({ prop: 'content', value: '""' }); | ||
@@ -33,11 +58,10 @@ } | ||
width: 10px; | ||
height: 10px; | ||
background: black | ||
height: 10px | ||
} | ||
``` | ||
will be fixed by our new `postprocessor`: | ||
will be fixed by our new `contenter`: | ||
```js | ||
var fixed = postprocessor.process(css); | ||
var fixed = contenter.process(css).css; | ||
``` | ||
@@ -51,31 +75,499 @@ | ||
width: 10px; | ||
height: 10px; | ||
background: black | ||
height: 10px | ||
} | ||
``` | ||
Sponsored by [Evil Martians](http://evilmartians.com/). | ||
## Features | ||
### Source Map | ||
PostCSS generates source map for its transformations: | ||
```js | ||
result = processor.process(css, { map: true, from: 'from.css', to: 'to.css' }); | ||
result.css // String with processed CSS | ||
result.map // Source map | ||
``` | ||
And modifies source map from previous step (like Sass preprocessor): | ||
```js | ||
var sassMap = fs.readFileSync('from.sass.map'); | ||
processor.process(css, { map: sassMap, from: 'from.sass.css', to: 'to.css' }); | ||
``` | ||
### Preserves code formatting and indentations | ||
PostCSS saves all spaces if you don’t change CSS node and try to copy your | ||
coding style if you modify it. | ||
PostCSS will not change any byte of rule if you don't modify node: | ||
### Parses everything | ||
```js | ||
postcss(function (css) { }).process(css).css == css; | ||
``` | ||
In addition to the unit tests, PostCSS has integration tests to check | ||
CSS parser on real-world sites. Right now parser is tested on GitHub, Twitter, | ||
Bootstrap and Habrahabr styles. | ||
And when you modify CSS nodes, PostCSS will try to copy coding style: | ||
Also PostCSS parser is very flexible and, for example, can parse any custom | ||
or future at-rules, instead of built-in list. | ||
```js | ||
contenter.process("a::before{color: black}") | ||
// a::before{content: '';color: black} | ||
### High-level API | ||
contenter.process("a::before {\n color: black;\n }") | ||
// a::before { | ||
// content: ''; | ||
// color: black; | ||
// } | ||
``` | ||
PostCSS is not only parser and stringifier. It contains useful tools, which | ||
can be used in most of postprocessor: | ||
## Why PostCSS Better Than … | ||
1. Safe iterator, which allow to change list inside iteration. | ||
2. Module to split value list by spaces or commas. | ||
### Preprocessors | ||
Preprocessors (like Sass or Stylus) give us special language with variables, | ||
mixins, statements and compile it to CSS. Compass, nib and other mixins | ||
libraries use this languages to work with prefixes, sprites and inline images. | ||
But Sass and Stylus languages were created to be syntax-sugar for CSS. | ||
Writing really complicated programs using preporcessor languages is very difficult. | ||
[Autoprefixer] is absolutely impossible on Sass. | ||
PostCSS gives you comfort and power of JS or CoffeeScript to working with CSS. | ||
You can do really magic things with wide range of [npm] libraries. | ||
But postprocessors are not enemies for preprocessors. Sass and Stylus is still | ||
the best way to improve readability and add some syntax sugar to CSS. You can easily combine preprocessors and postprocessors. | ||
[Autoprefixer]: https://github.com/ai/autoprefixer | ||
[npm]: https://npmjs.org/ | ||
### RegExp | ||
Some Grunt plugins modify CSS with regular expressions. But CSS parser and | ||
node tree are much safer way to edit CSS. Also regexps will break source maps | ||
generated by preprocessors. | ||
### CSS Parsers | ||
There are a lot of good CSS parsers, like [Gonzales]. But they help you only | ||
with first step. | ||
Unlike them PostCSS gives you useful high level API (for example, | ||
safe iterators) and changes source map generator (or modifier for existing | ||
source map from preprocessors). | ||
[Gonzales]: https://github.com/css/gonzales | ||
### Rework | ||
[Rework] was a first CSS postprocessors framework. PostCSS is very similar | ||
to it. | ||
But Rework has no high level API and rewrite your CSS code style | ||
and indentations. So it can’t be used in text editor plugins. | ||
Unlike it PostCSS preserves all spaces and code formatting. | ||
If you don't change rule, output will be byte‑to‑byte equal. | ||
[Rework]: https://github.com/visionmedia/rework | ||
## Usage | ||
### Processor | ||
Function `postcss(fn)` creates processor by your function: | ||
```js | ||
var postcss = require('postcss'); | ||
var processor = postcss(function (css) { | ||
// Code to modify CSS | ||
}); | ||
``` | ||
If you want to combine multiple processors (and parse CSS only once), | ||
you can create empty processor and add several functions by `use(fn)` method: | ||
```js | ||
var all = postcss(). | ||
use(prefixer). | ||
use(minifing); | ||
``` | ||
Processor function can just change current CSS node tree: | ||
```js | ||
postcss(function (css) { | ||
css.append( /* new rule */ ) | ||
}); | ||
``` | ||
or create totally new CSS root and return it: | ||
```js | ||
postcss(function (css) { | ||
var newCSS = postcss.root() | ||
// Add rules and declarations | ||
return newCSS; | ||
}); | ||
``` | ||
Processor will transform some CSS by `process(css, opts)` method: | ||
```js | ||
var doubler = postcss(function (css) { | ||
// Clone each declaration | ||
css.eachDecl(function (decl) { | ||
decl.parent.prepend( decl.clone() ); | ||
}); | ||
}); | ||
var css = "a { color: black; }"; | ||
var result = processor.process(css); | ||
result.css //=> "a { color: black; color: black; }" | ||
``` | ||
You can set original CSS filename by `from` options and make syntax error | ||
messages much more helpful: | ||
```js | ||
var wrong = "a {"; | ||
processor.process(wrong, { from: 'main.css' }); | ||
//=> Can't parse CSS: Unclosed block at line 1:1 in main.css | ||
``` | ||
### Source Map | ||
PostCSS will generate source map, if you set `map` option to `true` | ||
in `process(css, opts)` method. | ||
You must set input and output CSS files paths (by `from` and `to` options) | ||
to generate correct map. | ||
```js | ||
var result = processor.process(css, { | ||
map: true, | ||
from: 'main.css', | ||
to: 'main.out.css' | ||
}); | ||
result.map //=> '{"version":3,"file":"main.out.css","sources":["main.css"],"names":[],"mappings":"AAAA,KAAI"}' | ||
fs.writeFileSync('main.out.map', result.map); | ||
``` | ||
PostCSS can also modify previous source map (for example, from Sass | ||
compilation). So, if you compile: Sass to CSS and then minify CSS | ||
by postprocessor, final source map will contain mapping from Sass code | ||
to minified CSS. | ||
Just set original source map content (as string or JS object) | ||
to `map` option: | ||
```js | ||
var result = minifier.process(css, { | ||
map: fs.readFileSync('main.sass.map'), | ||
from: 'main.sass.css', | ||
to: 'main.min.css' | ||
}); | ||
result.map //=> Source map from main.sass to main.min.css | ||
``` | ||
### Nodes | ||
Processor function will receive `Root` node with CSS node tree inside. | ||
```js | ||
var processor = postcss(function (cssRoot) { | ||
}); | ||
``` | ||
There are 3 types of child nodes: `AtRule`, `Rule` and `Declaration`. | ||
All nodes contain `toString()` and `clone()` methods. | ||
You can parse CSS and get `Root` node by `postcss.parse(css, opts)` method: | ||
```js | ||
var postcss = require('postcss'); | ||
var cssRoot = postcss.parse('a { }'); | ||
``` | ||
### Node Source | ||
Every node stores it origin file (if you set `from` option to `process` | ||
or `parse` method) and position at `source` property: | ||
``` | ||
var root = postcss.parse(css, { from: 'main.css' }); | ||
var rule = root.rules[1]; | ||
rule.source.file //=> 'main.css' | ||
rule.source.start //=> { line: 5, position: 1 } | ||
rule.source.end //=> { line: 10, position: 5 } | ||
``` | ||
### Whitespaces | ||
All nodes (exclude `Root`) have `before` property with all earlier spaces and comments. | ||
Nodes with children (`Root`, `AtRule` and `Rule`) contain also `after` property | ||
with spaces after last child and before `}` or end of file. | ||
```js | ||
var root = postcss.parse("a {\n color: black;\n}\n"); | ||
root.after //=> "\n" from end of file | ||
root.rules[0].after //=> "\n" before } | ||
root.rules[0].decls[0].before //=> "\n " before color: black | ||
``` | ||
So, the simplest way to minify CSS is to clean `before` and `after` properties: | ||
```js | ||
var minifier = postcss(function (css) { | ||
css.eachDecl(function (decl) { | ||
decl.before = ''; | ||
}); | ||
css.eachRule(function (rule) { | ||
rule.before = ''; | ||
rule.after = ''; | ||
}); | ||
css.eachAtRule(function (atRule) { | ||
atRule.before = ''; | ||
atRule.after = ''; | ||
}); | ||
}); | ||
var css = "a{\n color:black\n}\n"; | ||
minifier.process(css).css //=> "a{color:black}" | ||
``` | ||
### Raw Properties | ||
Some CSS values (like selectors, at-rule params and declaration values) can | ||
contain comments. PostCSS will clean them for you: | ||
```js | ||
var root = postcss.parse("a /**/ b {}"); | ||
var ab = root.rules[0]; | ||
ab.selector //=> 'a b' trimmed and cleaned from comments | ||
``` | ||
But PostCSS saves raw content to stringify it to CSS, if you don’t | ||
set new value. As you can remember, PostCSS tries to save origin CSS | ||
byte-to-byte, when it’s possible: | ||
```js | ||
ab.toString() //=> 'a /**/ b {}' with comment | ||
ab.selector = '.link b'; | ||
ab.toString() //=> '.link b' you change value and magic was gone | ||
``` | ||
### Containers | ||
`Root`, `AtRule` and `Rule` nodes can contain children in `rules` or `decls` | ||
property. | ||
There are common method to work with children: | ||
* `append(newChild)` to add child at the end of children list. | ||
* `prepend(newChild)` to add child at the beginning of children list. | ||
* `insertBefore(existsChild, newChild)` to insert new child before some | ||
existent children. | ||
* `insertAfter(existsChild, newChild)` to insert new child after some | ||
existent children. | ||
* `remove(child)` to remove child. | ||
* `index(child)` to return child index. | ||
* `some(fn)` to return true if `fn` return true on any child. | ||
* `every(fn)` to return true if `fn` return true on all children. | ||
Methods `insertBefore`, `insertAfter` and `remove` can receive child node | ||
or child index number as existent child argument. | ||
Have in mind that `index` works much faster. | ||
### Children | ||
`AtRule`, `Rule` and `Declaration` nodes should be wrapped in other nodes. | ||
All children contain `parent` property with parent node: | ||
```js | ||
rule.decls[0].parent == rule; | ||
``` | ||
All children has `removeSelf()` method: | ||
```js | ||
rule.decls[0].removeSelf(); | ||
``` | ||
But `remove(index)` in parent with child index is much faster: | ||
```js | ||
rule.each(function (decl, i) { | ||
rule.remove(i); | ||
}); | ||
``` | ||
### Iterators | ||
All parent nodes have `each` method to iterate through children nodes: | ||
```js | ||
root = postcss.parse('a { color: black; display: none }'); | ||
root.each(function (rule, i) { | ||
console.log(rule.selector, i); // Will log "a 0" | ||
}); | ||
root.rules[0].each(function (decl, i) { | ||
console.log(decl.prop, i); // Will log "color 0" and "display 1" | ||
}); | ||
``` | ||
Instead of simple `for` or `Array#forEach()` this iterator is safe. | ||
You can change children inside iteration and it will fix current index: | ||
```js | ||
rule.rules.forEach(function (decl, i) { | ||
rule.prepend( decl.clone() ); | ||
// Will be infinity cycle, because on prepend current declaration become | ||
// second and next index will go to current declaration again | ||
}); | ||
rule.each(function (decl, i) { | ||
rule.prepend( decl.clone() ); | ||
// Will work correct (once clone each declaration), because after prepend | ||
// iterator index will be recalculated | ||
}); | ||
``` | ||
Because CSS is nested structure, PostCSS contains recursive iterator | ||
by node type: | ||
```js | ||
root.eachDecl(function (decl, i) { | ||
// Each declaration inside root | ||
}); | ||
root.eachRule(function (rule, i) { | ||
// Each rule inside root and any nested at-rules | ||
}); | ||
root.eachAtRule(function (atRule, i) { | ||
// Each at-rule inside root and any nested at-rules | ||
}); | ||
``` | ||
### Root Node | ||
`Root` node contains all CSS tree. Its children can be only `AtRule` or `Rule` | ||
nodes in `rules` property. | ||
You can create new root by shortcut: | ||
```js | ||
var root = postcss.root(); | ||
``` | ||
Method `toString()` will stringify all current CSS: | ||
```js | ||
root = postcss.parse(css); | ||
root.toString() == css; | ||
``` | ||
### AtRule Node | ||
```css | ||
@charset 'utf-8'; | ||
@font-face { | ||
font-family: 'Cool' | ||
} | ||
@media print { | ||
img { display: none } | ||
} | ||
``` | ||
`AtRule` has two own properties: `name` and `params`. | ||
As you see, some at-rules don’t contain any children (like `@charset` | ||
or `@import`), some of at-rules can contain only declarations | ||
(like `@font-face` or `@page`), but most of them can contain rules | ||
and nested at-rules (like `@media`, `@keyframes` and others). | ||
Parser select `AtRule` content type by its name. If you create `AtRule` | ||
node manually, it will detect own content type with new child type on first | ||
`append` or other add method call: | ||
```js | ||
var atRule = postcss.atRule({ name: '-x-animations' }); | ||
atRule.rules //=> undefined | ||
atRule.decls //=> undefined | ||
atRule.append( postcss.rule({ selector: 'from' }) ); | ||
atRule.rules.length //=> 1 | ||
atRule.decls //=> undefined | ||
``` | ||
You can create new at-rule by shortcut: | ||
```js | ||
var atRule = postcss.atRule({ name: 'charset', params: 'utf-8' }); | ||
``` | ||
### Rule Node | ||
```css | ||
a { | ||
color: black; | ||
} | ||
``` | ||
`Rule` node has `selector` property and contains `Declaration` children | ||
in `decls` property. | ||
You can miss `Declaration` constructor in `append` and other insert methods: | ||
```js | ||
rule.append({ prop: 'color', value: 'black' }); | ||
``` | ||
Property `semicolon` marks does last declaration in rule has semicolon or not: | ||
```js | ||
var root = postcss.parse('a { color: black }'); | ||
root.rules[0].semicolon //=> false | ||
var root = postcss.parse('a { color: black; }'); | ||
root.rules[0].semicolon //=> true | ||
``` | ||
You can create new rule by shortcut: | ||
```js | ||
var rule = postcss.rule({ selector: 'a' }); | ||
``` | ||
### Declaration Node | ||
```css | ||
color: black | ||
``` | ||
`Declaration` node has `prop` and `value` properties. | ||
You can create new declaration by shortcut: | ||
```js | ||
var decl = postcss.decl({ prop: 'color', value: 'black' }); | ||
``` | ||
Or use short form in `append()` and other add methods: | ||
```js | ||
rule.append({ prop: 'color', value: 'black' }); | ||
``` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Wildcard dependency
QualityPackage has a dependency with a floating version range. This can cause issues if the dependency publishes a new major version.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
50613
18
1122
572
1
1
+ Addedsource-map@*
+ Addedsource-map@0.7.4(transitive)