Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

csso

Package Overview
Dependencies
Maintainers
3
Versions
82
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

csso - npm Package Compare versions

Comparing version 3.0.1 to 3.1.0

lib/clean/Operator.js

10

HISTORY.md

@@ -0,1 +1,11 @@

## 3.1.0 (April 24, 2017)
- Implemented optimisation for `none` keyword in `border` and `outline` properties (@zoobestik, #41)
- Implemented replacing `rgba(x, x, x, 0)` to `transparent`
- Fixed plus sign omitting for numbers following identifier, hex color, number or unicode range, since it can change the meaning of CSS (e.g. `calc(1px+2px)` has been optimized to `calc(1px2px)` before, now it stays the same)
- Improved usage filtering for nested selectors (i.e. for `:nth-*()`, `:has()`, `:matches` and other pseudos)
- Implemented `blacklist` filtering in usage (#334, see [Black list filtering](https://github.com/css/csso#black-list-filtering))
- Improved white space removing, now white spaces are removing in the beginning and at the ending of sequences, and between stylesheet and block nodes
- Bumped `css-tree` to `1.0.0-alpha19`
## 3.0.1 (March 14, 2017)

@@ -2,0 +12,0 @@

3

lib/clean/index.js

@@ -8,3 +8,4 @@ var walk = require('css-tree').walkUp;

Comment: require('./Comment'),
Operator: require('./WhiteSpace')
Operator: require('./Operator'),
WhiteSpace: require('./WhiteSpace')
};

@@ -11,0 +12,0 @@

var hasOwnProperty = Object.prototype.hasOwnProperty;
var walk = require('css-tree').walk;
function cleanUnused(node, usageData) {
return node.selector.children.each(function(selector, item, list) {
var hasUnused = selector.children.some(function(node) {
switch (node.type) {
case 'ClassSelector':
return usageData.classes && !hasOwnProperty.call(usageData.classes, node.name);
function cleanUnused(selectorList, usageData) {
selectorList.children.each(function(selector, item, list) {
var shouldRemove = false;
case 'IdSelector':
return usageData.ids && !hasOwnProperty.call(usageData.ids, node.name);
walk(selector, function(node) {
// ignore nodes in nested selectors
if (this.selector === null || this.selector === selectorList) {
switch (node.type) {
case 'SelectorList':
// TODO: remove toLowerCase when pseudo selectors will be normalized
// ignore selectors inside :not()
if (this['function'] === null || this['function'].name.toLowerCase() !== 'not') {
if (cleanUnused(node, usageData)) {
shouldRemove = true;
}
}
break;
case 'TypeSelector':
// TODO: remove toLowerCase when type selectors will be normalized
// ignore universal selectors
if (node.name.charAt(node.name.length - 1) !== '*') {
return usageData.tags && !hasOwnProperty.call(usageData.tags, node.name.toLowerCase());
}
case 'ClassSelector':
if (usageData.whitelist !== null &&
usageData.whitelist.classes !== null &&
!hasOwnProperty.call(usageData.whitelist.classes, node.name)) {
shouldRemove = true;
}
if (usageData.blacklist !== null &&
usageData.blacklist.classes !== null &&
hasOwnProperty.call(usageData.blacklist.classes, node.name)) {
shouldRemove = true;
}
break;
case 'IdSelector':
if (usageData.whitelist !== null &&
usageData.whitelist.ids !== null &&
!hasOwnProperty.call(usageData.whitelist.ids, node.name)) {
shouldRemove = true;
}
if (usageData.blacklist !== null &&
usageData.blacklist.ids !== null &&
hasOwnProperty.call(usageData.blacklist.ids, node.name)) {
shouldRemove = true;
}
break;
case 'TypeSelector':
// TODO: remove toLowerCase when type selectors will be normalized
// ignore universal selectors
if (node.name.charAt(node.name.length - 1) !== '*') {
if (usageData.whitelist !== null &&
usageData.whitelist.tags !== null &&
!hasOwnProperty.call(usageData.whitelist.tags, node.name.toLowerCase())) {
shouldRemove = true;
}
if (usageData.blacklist !== null &&
usageData.blacklist.tags !== null &&
hasOwnProperty.call(usageData.blacklist.tags, node.name.toLowerCase())) {
shouldRemove = true;
}
}
break;
}
}
});
if (hasUnused) {
if (shouldRemove) {
list.remove(item);
}
});
return selectorList.children.isEmpty();
}
module.exports = function cleanRuleset(node, item, list, usageData) {
if (usageData) {
cleanUnused(node, usageData);
if (usageData && (usageData.whitelist !== null || usageData.blacklist !== null)) {
cleanUnused(node.selector, usageData);
}

@@ -32,0 +80,0 @@

module.exports = function cleanWhitespace(node, item, list) {
if (node.value === '+' || node.value === '-') {
// remove when first or last item in sequence
if (item.next === null || item.prev === null) {
list.remove(item);
return;
}
if (item.prev !== null && item.prev.data.type === 'WhiteSpace') {
list.remove(item.prev);
// remove when previous node is whitespace
if (item.prev.data.type === 'WhiteSpace') {
list.remove(item);
return;
}
if (item.next !== null && item.next.data.type === 'WhiteSpace') {
list.remove(item.next);
if ((this.stylesheet !== null && this.stylesheet.children === list) ||
(this.block !== null && this.block.children === list)) {
list.remove(item);
return;
}
};

@@ -370,2 +370,21 @@ var packNumber = require('./Number.js').pack;

if (args[3] === 0) {
// try to replace `rgba(x, x, x, 0)` to `transparent`
// always replace `rgba(0, 0, 0, 0)` to `transparent`
// otherwise avoid replacement in gradients since it may break color transition
// http://stackoverflow.com/questions/11829410/css3-gradient-rendering-issues-from-transparent-to-white
var scopeFunctionName = this['function'] && this['function'].name;
if ((args[0] === 0 && args[1] === 0 && args[2] === 0) ||
!/^(?:to|from|color-stop)$|gradient$/i.test(scopeFunctionName)) {
item.data = {
type: 'Identifier',
loc: node.loc,
name: 'transparent'
};
return;
}
}
if (args[3] !== 1) {

@@ -384,3 +403,3 @@ // replace argument values for normalized/interpolated

loc: node.loc,
value: packNumber(args.shift())
value: packNumber(args.shift(), null)
};

@@ -387,0 +406,0 @@ });

@@ -26,3 +26,3 @@ var packNumber = require('./Number.js').pack;

module.exports = function compressDimension(node, item) {
var value = packNumber(node.value);
var value = packNumber(node.value, item ? item.prev : null);

@@ -29,0 +29,0 @@ node.value = value;

@@ -1,5 +0,21 @@

function packNumber(value) {
var OMIT_PLUSSIGN = /^(?:\+|(-))?0*(\d*)(?:\.0*|(\.\d*?)0*)?$/;
var KEEP_PLUSSIGN = /^([\+\-])?0*(\d*)(?:\.0*|(\.\d*?)0*)?$/;
var unsafeToRemovePlusSignAfter = {
Dimension: true,
HexColor: true,
Identifier: true,
Number: true,
Raw: true,
UnicodeRange: true
};
function packNumber(value, prev) {
// omit plus sign only if no prev or prev is safe type
var regexp = prev === null || !unsafeToRemovePlusSignAfter.hasOwnProperty(prev.data.type)
? OMIT_PLUSSIGN
: KEEP_PLUSSIGN;
// 100 -> '100'
// 00100 -> '100'
// +100 -> '100'
// +100 -> '100' (only when safe, e.g. omitting plus sign for 1px+1px leads to single dimension instead of two)
// -100 -> '-100'

@@ -10,5 +26,6 @@ // 0.123 -> '.123'

// 0 -> ''
value = String(value).replace(/^(?:\+|(-))?0*(\d*)(?:\.0*|(\.\d*?)0*)?$/, '$1$2$3');
// -0 -> '-'
value = String(value).replace(regexp, '$1$2$3');
if (value.length === 0 || value === '-') {
if (value === '' || value === '-') {
value = '0';

@@ -20,5 +37,5 @@ }

module.exports = function(node) {
node.value = packNumber(node.value);
module.exports = function(node, item) {
node.value = packNumber(node.value, item.prev);
};
module.exports.pack = packNumber;

@@ -5,3 +5,5 @@ var resolveName = require('css-tree').property;

'font-weight': require('./property/font-weight.js'),
'background': require('./property/background.js')
'background': require('./property/background.js'),
'border': require('./property/border.js'),
'outline': require('./property/border.js')
};

@@ -8,0 +10,0 @@

@@ -7,3 +7,3 @@ var hasOwnProperty = Object.prototype.hasOwnProperty;

if (!Array.isArray(list)) {
return false;
return null;
}

@@ -24,2 +24,24 @@

function buildList(data) {
if (!data) {
return null;
}
var tags = buildMap(data.tags, true);
var ids = buildMap(data.ids);
var classes = buildMap(data.classes);
if (tags === null &&
ids === null &&
classes === null) {
return null;
}
return {
tags: tags,
ids: ids,
classes: classes
};
}
function buildIndex(data) {

@@ -51,5 +73,4 @@ var scopes = false;

return {
tags: buildMap(data.tags, true),
ids: buildMap(data.ids),
classes: buildMap(data.classes),
whitelist: buildList(data),
blacklist: buildList(data.blacklist),
scopes: scopes

@@ -56,0 +77,0 @@ };

{
"name": "csso",
"version": "3.0.1",
"version": "3.1.0",
"description": "CSSO (CSS Optimizer) is a CSS minifier with structural optimisations",

@@ -53,3 +53,3 @@ "keywords": [

"travis": "npm run codestyle-and-test && npm run coveralls",
"browserify": "browserify --standalone csso lib/index.js | uglifyjs --compress --mangle -o dist/csso-browser.js",
"browserify": "browserify -t package-json-versionify --standalone csso lib/index.js | uglifyjs --compress --mangle -o dist/csso-browser.js",
"gh-pages": "git clone -b gh-pages https://github.com/css/csso.git .gh-pages && npm run browserify && cp dist/csso-browser.js .gh-pages/ && cd .gh-pages && git commit -am \"update\" && git push && cd .. && rm -rf .gh-pages",

@@ -59,3 +59,3 @@ "prepublish": "npm run browserify"

"dependencies": {
"css-tree": "1.0.0-alpha17"
"css-tree": "1.0.0-alpha19"
},

@@ -69,2 +69,3 @@ "devDependencies": {

"mocha": "~2.4.2",
"package-json-versionify": "^1.0.4",
"source-map": "^0.5.6",

@@ -71,0 +72,0 @@ "uglify-js": "^2.6.1"

@@ -15,8 +15,8 @@ [![NPM version](https://img.shields.io/npm/v/csso.svg)](https://www.npmjs.com/package/csso)

- [Web interface](http://css.github.io/csso/csso.html)
- CLI: [csso-cli](https://github.com/css/csso-cli)
- Gulp: [gulp-csso](https://github.com/ben-eb/gulp-csso)
- Grunt: [grunt-csso](https://github.com/t32k/grunt-csso)
- Broccoli: [broccoli-csso](https://github.com/sindresorhus/broccoli-csso)
- PostCSS: [postcss-csso](https://github.com/lahmatiy/postcss-csso)
- Webpack: [csso-loader](https://github.com/sandark7/csso-loader)
- [csso-cli](https://github.com/css/csso-cli) – command line interface
- [gulp-csso](https://github.com/ben-eb/gulp-csso) – `Gulp` plugin
- [grunt-csso](https://github.com/t32k/grunt-csso) – `Grunt` plugin
- [broccoli-csso](https://github.com/sindresorhus/broccoli-csso) – `Broccoli` plugin
- [postcss-csso](https://github.com/lahmatiy/postcss-csso) – `PostCSS` plugin
- [csso-loader](https://github.com/sandark7/csso-loader) – `webpack` loader

@@ -38,3 +38,4 @@ ## Install

- [Usage data](#usage-data)
- [Selector filtering](#selector-filtering)
- [White list filtering](#white-list-filtering)
- [Black list filtering](#black-list-filtering)
- [Scopes](#scopes)

@@ -242,16 +243,17 @@ - [Debugging](#debugging)

`CSSO` can use data about how `CSS` is using for better compression. File with this data (`JSON` format) can be set using `usage` option. Usage data may contain follow sections:
`CSSO` can use data about how `CSS` is used in a markup for better compression. File with this data (`JSON`) can be set using `usage` option. Usage data may contain following sections:
- `blacklist` – a set of black lists (see [Black list filtering](#black-list-filtering))
- `tags` – white list of tags
- `ids` – white list of ids
- `classes` – white list of classes
- `scopes` – groups of classes which never used with classes from other groups on single element
- `scopes` – groups of classes which never used with classes from other groups on the same element
All sections are optional. Value of `tags`, `ids` and `classes` should be array of strings, value of `scopes` should be an array of arrays of strings. Other values are ignoring.
All sections are optional. Value of `tags`, `ids` and `classes` should be an array of a string, value of `scopes` should be an array of arrays of strings. Other values are ignoring.
#### Selector filtering
#### White list filtering
`tags`, `ids` and `classes` are using on clean stage to filter selectors that contains something that not in list. Selectors are filtering only by those kind of simple selector which white list is specified. For example, if only `tags` list is specified then type selectors are checking, and if selector hasn't any type selector (or even any type selector) it isn't filter.
`tags`, `ids` and `classes` are using on clean stage to filter selectors that contain something not in the lists. Selectors are filtering only by those kind of simple selector which white list is specified. For example, if only `tags` list is specified then type selectors are checking, and if all type selectors in selector present in list or selector has no any type selector it isn't filter.
> `ids` and `classes` names are case sensitive, `tags` – is not.
> `ids` and `classes` are case sensitive, `tags` – is not.

@@ -274,3 +276,3 @@ Input CSS:

Result CSS:
Resulting CSS:

@@ -281,5 +283,49 @@ ```css

Filtering performs for nested selectors too. `:not()` pseudos content is ignoring since the result of matching is unpredictable. Example for the same usage data as above:
```css
:nth-child(2n of ul, ol) { color: red }
:nth-child(3n + 1 of img) { color: yellow }
:not(div, ol, ul) { color: green }
:has(:matches(ul, ol), ul, ol) { color: blue }
```
Turns into:
```css
:nth-child(2n of ul){color:red}:not(div,ol,ul){color:green}:has(:matches(ul),ul){color:blue}
```
#### Black list filtering
Black list filtering performs the same as white list filtering, but filters things that mentioned in the lists. `blacklist` can contain the lists `tags`, `ids` and `classes`.
Black list has a higher priority, so when something mentioned in the white list and in the black list then white list occurrence is ignoring. The `:not()` pseudos content ignoring as well.
```css
* { color: green; }
ul, ol, li { color: blue; }
UL.foo, li.bar { color: red; }
```
Usage data:
```json
{
"blacklist": {
"tags": ["ul"]
},
"tags": ["ul", "LI"]
}
```
Resulting CSS:
```css
*{color:green}li{color:blue}li.bar{color:red}
```
#### Scopes
Scopes is designed for CSS scope isolation solutions such as [css-modules](https://github.com/css-modules/css-modules). Scopes are similar to namespaces and defines lists of class names that exclusively used on some markup. This information allows the optimizer to move rulesets more agressive. Since it assumes selectors from different scopes can't to be matched on the same element. That leads to better ruleset merging.
Scopes is designed for CSS scope isolation solutions such as [css-modules](https://github.com/css-modules/css-modules). Scopes are similar to namespaces and define lists of class names that exclusively used on some markup. This information allows the optimizer to move rules more agressive. Since it assumes selectors from different scopes don't match for the same element. This can improve rule merging.

@@ -296,3 +342,3 @@ Suppose we have a file:

It can be assumed that first two rules are never used with the second two on the same markup. But we can't know that for sure without markup. The optimizer doesn't know it either and will perform safe transformations only. The result will be the same as input but with no spaces and some semicolons:
It can be assumed that first two rules are never used with the second two on the same markup. But we can't say that for sure without a markup review. The optimizer doesn't know it either and will perform safe transformations only. The result will be the same as input but with no spaces and some semicolons:

@@ -303,3 +349,3 @@ ```css

But with usage data `CSSO` can get better output. If follow usage data is provided:
With usage data `CSSO` can produce better output. If follow usage data is provided:

@@ -315,3 +361,3 @@ ```json

New result (29 bytes extra saving):
The result will be (29 bytes extra saving):

@@ -322,7 +368,7 @@ ```css

If class name doesn't specified in `scopes` it belongs to default "scope". `scopes` doesn't affect `classes`. If class name presents in `scopes` but missed in `classes` (both sections specified) it will be filtered.
If class name isn't mentioned in the `scopes` it belongs to default scope. `scopes` data doesn't affect `classes` whitelist. If class name mentioned in `scopes` but missed in `classes` (both sections are specified) it will be filtered.
Note that class name can't be specified in several scopes. Also selector can't has classes from different scopes. In both cases an exception throws.
Note that class name can't be set for several scopes. Also selector can't has a class names from different scopes. In both cases an exception will thrown.
Currently the optimizer doesn't care about out-of-bounds selectors order changing safety (i.e. selectors that may be matched to elements with no class name of scope, e.g. `.scope div` or `.scope ~ :last-child`) since assumes scoped CSS modules doesn't relay on it's order. It may be fix in future if to be an issue.
Currently the optimizer doesn't care about changing order safety for out-of-bounds selectors (i.e. selectors that match to elements without class name, e.g. `.scope div` or `.scope ~ :last-child`). It assumes that scoped CSS modules doesn't relay on it's order. It may be fix in future if to be an issue.

@@ -329,0 +375,0 @@ ### Debugging

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc