+12
| ## 0.2 “Duke Dantalion” | ||
| * Add source map support. | ||
| * Add shortcuts to create nodes. | ||
| * Method `process()` now returns object with `css` and `map` keys. | ||
| * Origin CSS file option was renamed from `file` to `from`. | ||
| * Rename `Node#remove()` method to `removeSelf()` to fix name conflict. | ||
| * Node source was moved to `source` property with origin file | ||
| and node end position. | ||
| * You can set own stringify function. | ||
| ## 0.1 “Count Andromalius” | ||
| * Initial release. |
| (function() { | ||
| var Result, SourceMap, generateMap; | ||
| SourceMap = require('source-map'); | ||
| Result = require('./result'); | ||
| generateMap = function(css, opts) { | ||
| var builder, column, line, map, prev, result; | ||
| map = new SourceMap.SourceMapGenerator({ | ||
| file: opts.to || 'to.css' | ||
| }); | ||
| result = new Result(css, ''); | ||
| line = 1; | ||
| column = 1; | ||
| builder = function(str, node, type) { | ||
| var last, lines, _ref, _ref1; | ||
| result.css += str; | ||
| if ((node != null ? (_ref = node.source) != null ? _ref.start : void 0 : void 0) && type !== 'end') { | ||
| map.addMapping({ | ||
| source: node.source.file || 'from.css', | ||
| original: { | ||
| line: node.source.start.line, | ||
| column: node.source.start.column - 1 | ||
| }, | ||
| generated: { | ||
| line: line, | ||
| column: column - 1 | ||
| } | ||
| }); | ||
| } | ||
| lines = str.match(/\n/g); | ||
| if (lines) { | ||
| line += lines.length; | ||
| last = str.lastIndexOf("\n"); | ||
| column = str.length - last; | ||
| } else { | ||
| column = column + str.length; | ||
| } | ||
| if ((node != null ? (_ref1 = node.source) != null ? _ref1.end : void 0 : void 0) && type !== 'start') { | ||
| return map.addMapping({ | ||
| source: node.source.file || 'from.css', | ||
| original: { | ||
| line: node.source.end.line, | ||
| column: node.source.end.column | ||
| }, | ||
| generated: { | ||
| line: line, | ||
| column: column | ||
| } | ||
| }); | ||
| } | ||
| }; | ||
| css.stringify(builder); | ||
| if (typeof opts.map === 'string') { | ||
| prev = new SourceMap.SourceMapConsumer(opts.map); | ||
| map.applySourceMap(prev); | ||
| } | ||
| result.map = map.toString(); | ||
| return result; | ||
| }; | ||
| module.exports = generateMap; | ||
| }).call(this); |
| (function() { | ||
| var Result; | ||
| Result = (function() { | ||
| function Result(parsed, css) { | ||
| this.parsed = parsed; | ||
| this.css = css; | ||
| } | ||
| Result.prototype.toString = function() { | ||
| return this.css; | ||
| }; | ||
| return Result; | ||
| })(); | ||
| module.exports = Result; | ||
| }).call(this); |
+10
-7
@@ -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 @@ }; |
+23
-16
@@ -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 @@ |
+18
-2
@@ -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; |
+11
-1
@@ -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; |
+75
-38
@@ -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; |
+36
-6
| (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); |
+5
-2
@@ -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 @@ |
+3
-3
@@ -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; |
+4
-3
| { | ||
| "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", |
+521
-29
| # 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' }); | ||
| ``` |
Sorry, the diff of this file is not supported yet
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
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
50613
51.12%18
12.5%1122
17.61%572
615%1
Infinity%1
Infinity%4
33.33%+ Added
+ Added