Giter VIP home page Giter VIP logo

css-analyzer's Introduction

CSS Analyzer

Analyzer logo
A CSS analyzer that goes through your CSS to find all kinds of relevant statistics.

Features

  • Extremely detailed (150+ metrics)
  • Super fast
  • Supports both NodeJS and browsers

Install

npm install @projectwallace/css-analyzer

Usage

Analyzing CSS

import { analyze } from '@projectwallace/css-analyzer'

const result = analyze(`
	p {
		color: blue;
		font-size: 100%;
	}

	.component[data-state="loading"] {
		background-color: whitesmoke;
	}
`)
More examples output can be found in the fixtures folder and looks roughly like this:
{
  "stylesheet": {
    "sourceLinesOfCode": 5,
    "linesOfCode": 8,
    "size": 113,
    "comments": {
      "total": 0,
      "size": 0
    }
  },
  "atrules": {
    "fontface": {
      "total": 0,
      "totalUnique": 0,
      "unique": [],
      "uniquenessRatio": 1
    },
    "import": {
      "total": 0,
      "totalUnique": 0,
      "unique": {},
      "uniquenessRatio": 0
    },
    "media": {
      "total": 0,
      "totalUnique": 0,
      "unique": {},
      "uniquenessRatio": 0
    },
    "charset": {
      "total": 0,
      "totalUnique": 0,
      "unique": {},
      "uniquenessRatio": 0
    },
    "supports": {
      "total": 0,
      "totalUnique": 0,
      "unique": {},
      "uniquenessRatio": 0
    },
    "keyframes": {
      "total": 0,
      "totalUnique": 0,
      "unique": {},
      "uniquenessRatio": 0,
      "prefixed": {
        "total": 0,
        "totalUnique": 0,
        "unique": {},
        "uniquenessRatio": 0,
        "ratio": null
      }
    },
    "container": {
      "total": 0,
      "totalUnique": 0,
      "unique": {},
      "uniquenessRatio": 0
    }
  },
  "rules": {
    "total": 2,
    "empty": {
      "total": 0,
      "ratio": 0
    },
    "selectors": {
      "min": 1,
      "max": 1,
      "mean": 1,
      "mode": 1,
      "median": 1,
      "range": 0,
      "sum": 2,
      "items": [
        1,
        1
      ]
    },
    "declarations": {
      "min": 1,
      "max": 2,
      "mean": 1.5,
      "mode": 1.5,
      "median": 1.5,
      "range": 1,
      "sum": 3,
      "items": [
        2,
        1
      ]
    }
  },
  "selectors": {
    "total": 2,
    "totalUnique": 2,
    "uniquenessRatio": 1,
    "specificity": {
      "sum": [
        0,
        2,
        1
      ],
      "min": [
        0,
        0,
        1
      ],
      "max": [
        0,
        2,
        0
      ],
      "mean": [
        0,
        1,
        0.5
      ],
      "mode": [
        0,
        1,
        0.5
      ],
      "median": [
        0,
        1,
        0.5
      ],
      "items": [
        [
          0,
          0,
          1
        ],
        [
          0,
          2,
          0
        ]
      ]
    },
    "complexity": {
      "min": 1,
      "max": 3,
      "mean": 2,
      "mode": 2,
      "median": 2,
      "range": 2,
      "sum": 4,
      "total": 2,
      "totalUnique": 2,
      "unique": {
        "1": 1,
        "3": 1
      },
      "uniquenessRatio": 1,
      "items": [
        1,
        3
      ]
    },
    "id": {
      "total": 0,
      "totalUnique": 0,
      "unique": {},
      "uniquenessRatio": 0,
      "ratio": 0
    },
    "accessibility": {
      "total": 0,
      "totalUnique": 0,
      "unique": {},
      "uniquenessRatio": 0,
      "ratio": 0
    },
    "keyframes": {
      "total": 0,
      "totalUnique": 0,
      "unique": {},
      "uniquenessRatio": 0,
      "ratio": 0
    }
  },
  "declarations": {
    "total": 3,
    "unique": {
      "total": 3,
      "ratio": 1
    },
    "importants": {
      "total": 0,
      "ratio": 0,
      "inKeyframes": {
        "total": 0,
        "ratio": 0
      }
    }
  },
  "properties": {
    "total": 3,
    "totalUnique": 3,
    "unique": {
      "color": 1,
      "font-size": 1,
      "background-color": 1
    },
    "uniquenessRatio": 1,
    "prefixed": {
      "total": 0,
      "totalUnique": 0,
      "unique": {},
      "uniquenessRatio": 0,
      "ratio": 0
    },
    "custom": {
      "total": 0,
      "totalUnique": 0,
      "unique": {},
      "uniquenessRatio": 0,
      "ratio": 0
    },
    "browserhacks": {
      "total": 0,
      "totalUnique": 0,
      "unique": {},
      "uniquenessRatio": 0,
      "ratio": 0
    }
  },
  "values": {
    "colors": {
      "total": 2,
      "totalUnique": 2,
      "unique": {
        "blue": 1,
        "whitesmoke": 1
      },
      "uniquenessRatio": 1,
      "itemsPerContext": {
        "color": {
          "total": 1,
          "totalUnique": 1,
          "unique": {
            "blue": 1
          },
          "uniquenessRatio": 1
        },
        "background-color": {
          "total": 1,
          "totalUnique": 1,
          "unique": {
            "whitesmoke": 1
          },
          "uniquenessRatio": 1
        }
      }
    },
    "fontFamilies": {
      "total": 0,
      "totalUnique": 0,
      "unique": {},
      "uniquenessRatio": 0
    },
    "fontSizes": {
      "total": 1,
      "totalUnique": 1,
      "unique": {
        "100%": 1
      },
      "uniquenessRatio": 1
    },
    "zindexes": {
      "total": 0,
      "totalUnique": 0,
      "unique": {},
      "uniquenessRatio": 0
    },
    "textShadows": {
      "total": 0,
      "totalUnique": 0,
      "unique": {},
      "uniquenessRatio": 0
    },
    "boxShadows": {
      "total": 0,
      "totalUnique": 0,
      "unique": {},
      "uniquenessRatio": 0
    },
    "animations": {
      "durations": {
        "total": 0,
        "totalUnique": 0,
        "unique": {},
        "uniquenessRatio": 0
      },
      "timingFunctions": {
        "total": 0,
        "totalUnique": 0,
        "unique": {},
        "uniquenessRatio": 0
      }
    },
    "prefixes": {
      "total": 0,
      "totalUnique": 0,
      "unique": {},
      "uniquenessRatio": 0
    },
    "units": {
      "total": 0,
      "totalUnique": 0,
      "unique": {},
      "uniquenessRatio": 0,
      "itemsPerContext": {}
    }
  },
  "__meta__": {
    "parseTime": 4,
    "analyzeTime": 5,
    "total": 10
  }
}

Comparing specificity

import { compareSpecificity } from '@projectwallace/css-analyzer'

const result = [
  [0,1,1],
  [2,0,0],
  [0,0,1],
].sort((a, b) => compareSpecificity(a, b))

// => result:
// [
//   [2,0,0],
//   [0,1,1],
//   [0,0,1],
// ]

const isSpecificityEqual = compareSpecificity(
  [0,1,0],
  [0,1,0]
) === 0
// => isSpecificityEqual: true

Related projects

css-analyzer's People

Contributors

bartveneman avatar dependabot[bot] avatar martijncuppens avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

css-analyzer's Issues

Report @font-faces as collection of property-values instead of custom object

The current reporting style for @font-face is to return an object with key-values. This is undesireable, since it is a form of stringification that css-analyzer should not perform. It should just return the raw object, like so:

const fontFace = {
  declarations: [
    {
      src: 'url("...")'
    }
  ]
}

TBD: whether to just call the 'declarations' declarations or 'descriptors' to be more accurate, but less intuitive.

Report LOC

Need to determine what LOC for CSS means exactly, but it would be a useful global metric to use for quick comparisons.

Resources

Proposed LOC counting

/* 1 LOC (1 selector identifier) */
.selector {}

/* 1 LOC (2 selector identifiers) */
.selector #withId {}

/* 2 LOC (2 selectors with 1 identifier) */
.selector1,
.selector2 {}

/* 2 LOC (1 selector identifier, 1 declaration) */
.selector {
  property: value;
}

/* 2 LOC (1 selector, 1 declaration) */
.selector {
  background: -moz-linear-gradient(#aaaaaa, #bbbbbb);
}

/* 3 LOC (1 @media rule, 1 selector, 1 declaration)  */
@media (min-width: 400px) {
  .selector {
    color: #ff0000;
  }
}

/**
 * 7 LOC
 * ------
 * 1 @keyframes rule
 * 3 'selectors'
 * 3 declarations
 */
@keyframes -webkit-i-like-to-move-it-move-it {
  from {
    color: red;
  }

  50% {
    color: blue;
  }

  100% {
    color: green;
  }
}

Proposed report

{
  "stylesheets.lines": 9000, // css.split('\n')
  "stylesheets.linesOfCode": 5000,
  "rules.linesOfCode.average": 20.3, // total selectors/identifiers + total declarations
  "rules.linesOfCode.max.count": 45, // total selectors + total declarations
  "rules.linesOfCode.max.value": {
    "selectors": [{
      "value": "#my .super [selector]",
      "identifiers": ["#my", ".super", "[selector]"],
      "specificity": {"a": 0, "b": 1, "c": 2, "d": 0}
    }],
    "declarations": [{
      "property": "background",
      "value": "-moz-linear-gradient(#aaaaaa, #bbbbbb)",
      "important": false
    }]
}

Report specificity mode

As @csswizardry writes in his article https://csswizardry.com/2016/06/improving-your-css-with-parker/, it would be nice to be able to the specificity that is most common in your stylesheet.

It would be quite nice to know that ‘most of your selectors have class-level specificity’, rather than ‘the average specificity across all of your selectors is roughly that of a class’. A subtle but significant distinction.

stylesheets: {
  specificity: {
    mode: 
      total: 1234,
      share: 0.32,
      specificity: {
        a: 0,
        b: 0,
        c: 2,
        d: 0
      }
    }
  },
  top: [{ ... }}
}

Report mode, average, mean for lots of metrics

Relates to #58 and #53, but for more metrics, like importants-count, identifiers-count.

Perhaps interesting stats:

  • mode The number that appears the most often within a set of numbers
  • median The middle number in a group of ordered numbers
  • mean The average of a group of numbers
  • range The difference between the highest (maximum) and lowest (minimum) within a set of numbers
  • maximum The largest number in a set of data
  • minimum The smallest number in a set of data

(source: https://quizlet.com/3153157/mean-median-mode-range-minimummaximum-flash-cards/)

report selectors per rule

.selector1,
.selector2 {
  // declarations
}

.selector1 {
  // declarations
}
{
  "rules.selectors.avg": 1.5,
  "rules.selectors.max": 2
}

Color duplicates reporting should follow format of other reports

The color duplicates report is now a single array of objects, but for other reports of similar types we have an object with total, totalUnique and unique. For duplicates total and totalUnique will always be the same, since it is extracted from colors.unique.
Currently the total or totalUnique property is missing, so it is not easy to display it in Wallace-CLI, on projectwallace.com or to test for it easily in Gromit-CLI.

Current format:

"duplicates": [
  {
    "value": "#000",
    "count": 2,
    "aliases": [
    {
      "count": 1,
      "value": "#000"
    }, 
    {
      "count": 1,
      "value": "black"
    }]
  }
]

Proposed format:

"duplicates": {
  "total": 1,
  "totalUnique": 1,
  "unique": [
    {
      "value": "#000",
      "count": 2,
      "aliases": [
      {
        "count": 1,
        "value": "#000"
      }, 
      {
        "count": 1,
        "value": "black"
      }]
    }
  ]
}

add selectors.specificity.max

current structure:

{
  "selectors.specificity.top": [
    {
        "value": ".Foo > .Bar ~ .Baz [type=\"text\"] + span:before #bazz #fizz #buzz #drank #drugs",
        "specificity": {
          "a": 0,
          "b": 5,
          "c": 4,
          "d": 2
        }
      }
  ]
}

proposed replacement:

{
  'selectors.specificity.max': {
    value: '0,3,1,2',
    specificity: {
      a: 0,
      b: 3,
      c: 1,
      d: 2
    },
    selectors: [
      '#id1 #id2 #id3 .class1 el1 el2', 
      'el1 el2 .class1 #id1 #id2 #id3'
    ],
    count: 2
  }
}

Reasoning

  • The current .top lists a maximum of 5 selectors that have the highest specificity. The number 5 is just an arbitrary number I came up with.
  • the proposed structure is more explicit about explaining the most complex selectors and is not based on an arbitrary number I chose but actually lists the most complex selectors
  • It will be more in line with selectors.identifiers.max

Added the label breaking change, but it could be added first without removing the initial implementation.

Support analysis for resets

Some competitors analyze the amount of resets in your CSS. It would be nice to support it here too. The important part is to decide what will be considered a reset.

Candidates

  • margin: 0;
  • margin: 0 0
  • margin: 0 0 0
  • margin: 0 0 0 0
  • padding: 0;
  • padding: 0 0
  • padding: 0 0 0
  • padding: 0 0 0 0
  • all: initial

Implement count per value

I want to know how many times a certain color, font-size, font-family is used to do proper analysis.

v5 breaking changes

This comment will be updated whenever more breaking changes are discovered/suggested

  • Drop support for Node 8 and 10 (Node.js releases)
  • Rename all metrics ending on .share to .ratio
  • Rename stylesheets to stylesheet
  • Remove stylesheets.browserhacks
  • Remove stylesheets.cohesion (now rules.selectors.mean)
  • Remove atrules.documents
  • Remove atrules.mediaqueries.browserhacks
  • Add atrules.keyframes.prefixed
  • Drop atrules.namespace.*
  • Drop atrules.page.*
  • Drop atrules.supports.browserhacks.*
  • Remove stylesheets.simplicity(now `rules.selectors)
  • Add rules.selectors.mean/median/mode/etc
  • Add rules.declarations.mea/median/mode
  • Remove selectors.js.*
  • Add selectors.specificity.*
  • Add selectors.complexity.*
  • Remove selectors.browserhacks.*
  • Remove values.total
  • Remove values.browserhacks.*
  • Remove values.colors.duplicate.*

Rewrite tests

The tests currently use an input file with many test cases mixed and an example JSON output. The tests should be rewritten so every feature of this analyzer can be tested separately. After that it should be easier to spot what's broken in case anything goes wrong.

  • atrules/charsets
  • atrules/documents
  • atrules/fontfaces
  • atrules/imports
  • atrules/keyframes
  • atrules/mediaqueries
  • atrules/namespaces
  • atrules/pages
  • atrules/supports
  • declarations
  • properties
  • rules
  • selectors/general
  • selectors/types (id, js, universal, etc.)
  • selectors/specificity
  • selectors/identifiers
  • selectors/browserhacks
  • stylesheets
  • values/general
  • values/colors
  • values/fontsizes
  • values/fontfamilies
  • values/custom-properties
  • values/z-indexes
  • values/box-shadows
  • values/vendor-prefixes

Cleanup phase

  • remove .prettierignore
  • remove test/utils/scope-tester.js
  • remove "xo.rules.ava/no-import-test-files" from package.json

report something about values and notations

font-family: 'A'; is the same as font-family: A;. The value as the browser interprets it is the same, but their notations are different. Is there something we could do with this?

Some more examples where values are the same, but different notations:

  • #000 vs hsl(0, 0, 0)
  • font-size: 1em vs font-size: 100%

Report duplicate colors

I've seen many codebases that have #000, #000000 and black in their final CSS output. These are all the same values and they could probably be #000 instead, since that is the shortest form. A possible benefit is improved filesize compression. It is likely that most compression algorithms will compress a file with more repeating content better than a file with different notations for the same value.

Why would you want this report?

  1. If you have #000 and #000000 in the same CSS file, it is likely that the minifier of your CSS isn't functioning properly. Usually the minifier shortens the #000000 to #000, but if this doesn't happen, it's probably some problem in your minifier workflow.
  2. Having separate notations for the same values indicates that you could probably improve the way that you work with your colors. Maybe you aren't using a proprocessor with variables support, or maybe you are in a transitioning phase. Who knows. Maybe it's that one rogue CSS file that no-one dares touching.
  3. The detail view on Project Wallace for colors will become less noisy. Removing duplicates from the list will make it easier to look at the actual unique colors in your CSS.

Report average/mean/mode for specificity

Specificity can be reported as an object, like {a:0, b:1, c:0, d:1}. Based on this notation it should be possible to show an average/mean/mode for specificity, like {a:0.01, b:1.11, c:0.3, d:1.58}

Support lowest cohesion analysis

Cohesion is about rules and the declarations inside them. The lowest cohesion rule is the rule that has the most declarations. The main reason to analyze this is to discover the most complex rule in your CSS, assuming that more declarations make a rule more complex.

Analyse font properties for @font-face

To do proper analysis of @font-face, I need to know what properties it has:

  • font-family
    Specifies a name that will be used as the font face value for font properties.
  • font-variant
    A font-variant value.
  • font-stretch
    A font-stretch value.
  • font-weight
    A font-weight value.
  • font-style
    A font-style value.
  • unicode-range
    The range of Unicode code points to be used from the font.

Ignoring src on purpose.

From https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face

Report Duplications

Duplications in CSS may appear in many forms, so first we'll need to decide how to count them. My preference would be like this:

/* 1 duplicate (property and value are an exact match) */
.selector1 {
  color: red;
}
.selector2 {
  color: red;
}

/* No duplicate (because of !important, the value is not an exact match */
.selector3 {
  color: red;
}
.selector4 {
  color: red !important;
}

/* Duplicate (because of !important for both values */
.selector5 {
  color: red !important;
}
.selector6 {
  color: red !important;
}

/* 1 duplicate, because the selectorList for both rules is the same */
.selector7,
.selector8 {
  color: blue;
}
.selector7,
.selector8 {
  font-size: normal;
}

/* 1 duplicate, because `.selector9` appears in the selectorList of both rules */
.selector9,
.selector10 {
  color: blue;
}
.selector9 {
  color: red;
}

/* 1 duplicate, because `.selector11` appears as an ideintifier in `.selector12` */
.selector11 {
  color: blue;
}
.selector12 > .selector11 {
  color: green;
}

report gzip and brotli size

the default size reporting is in raw bytes, but it would be interesting to know what size is after compression, because that is usually what gets sent to the browser.

  • include raw filesize
  • include gzip filesize
  • include gzip compression ratio
  • include brotli filesize
  • include brotli compression ratio

Implement Promises

At the very least this module should return a Promise.

  • Return promise at root level
  • Update docs

Possibly inaccurate reporting on latest imports

Hello. I've been playing with Project Wallace, and I think it looks like a really excellent tool, but I've run into a puzzling issue.

For a while imports weren't working at all on this project. That issue seems to be fixed now - I was able to import today - but the stats now seem to be off: it thinks that the CSS has increased in size by ~500% since the last time I was able to import successfully (on 11th Feb). It reports a file size of 783kB, but I can't see where it's getting this figure from: if I visit the page directly it looks like the actual CSS size is nowhere near this size. If you compare other stats (selectors, rules, etc), they've also increased by similarly large amounts since the previous import (11th Feb).

At first I thought this increase in size might indicate a real problem, but after investigating I'm now wondering if the same file is being analysed multiple times or something?

Flatten results by default

Pretty much all the tools that use this module, use a flattened result, so it easier to lookup results with a single key.

Example:

{
  'values.colors.unique': [],
  'values.fontsizes.total': 10
}

This will be a breaking change and requires css-analyzer to go v2.x.x

Add top identifier count analysis

It is already possible to see the top identifier selectors, but it would be nice to have direct access to the top identifier count directly instead of having to get that from another metric.

Add support for exotic color notations

TinyColor doesn't support converting the following formats:

color: hsl(270deg, 60%, 70%);
color: hsl(4.71239rad, 60%, 70%);
color: hsl(.75turn, 60%, 70%);
color: rgba(1e2, .5e1, .5e0, +.25e2%);

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.