Python script for minifying CSS?

17,353

Solution 1

This seemed like a good task for me to get into python, which has been pending for a while. I hereby present my first ever python script:

import sys, re

with open( sys.argv[1] , 'r' ) as f:
    css = f.read()

# remove comments - this will break a lot of hacks :-P
css = re.sub( r'\s*/\*\s*\*/', "$$HACK1$$", css ) # preserve IE<6 comment hack
css = re.sub( r'/\*[\s\S]*?\*/', "", css )
css = css.replace( "$$HACK1$$", '/**/' ) # preserve IE<6 comment hack

# url() doesn't need quotes
css = re.sub( r'url\((["\'])([^)]*)\1\)', r'url(\2)', css )

# spaces may be safely collapsed as generated content will collapse them anyway
css = re.sub( r'\s+', ' ', css )

# shorten collapsable colors: #aabbcc to #abc
css = re.sub( r'#([0-9a-f])\1([0-9a-f])\2([0-9a-f])\3(\s|;)', r'#\1\2\3\4', css )

# fragment values can loose zeros
css = re.sub( r':\s*0(\.\d+([cm]m|e[mx]|in|p[ctx]))\s*;', r':\1;', css )

for rule in re.findall( r'([^{]+){([^}]*)}', css ):

    # we don't need spaces around operators
    selectors = [re.sub( r'(?<=[\[\(>+=])\s+|\s+(?=[=~^$*|>+\]\)])', r'', selector.strip() ) for selector in rule[0].split( ',' )]

    # order is important, but we still want to discard repetitions
    properties = {}
    porder = []
    for prop in re.findall( '(.*?):(.*?)(;|$)', rule[1] ):
        key = prop[0].strip().lower()
        if key not in porder: porder.append( key )
        properties[ key ] = prop[1].strip()

    # output rule if it contains any declarations
    if properties:
        print "%s{%s}" % ( ','.join( selectors ), ''.join(['%s:%s;' % (key, properties[key]) for key in porder])[:-1] ) 

I believe this to work, and output it tests fine on recent Safari, Opera, and Firefox. It will break CSS hacks other than the underscore & /**/ hacks! Do not use a minifier if you have a lot of hacks going on (or put them in a separate file).

Any tips on my python appreciated. Please be gentle though, it's my first time. :-)

Solution 2

There is a port of YUI's CSS compressor available for python.

Here is its project page on PyPi: http://pypi.python.org/pypi/cssmin/0.1.1

Solution 3

There is a nice online tool cssminifier which has also an API which is pretty simple and easy to use. I made a small python script that posts the CSS file content to that tool's API, returns the minifed CSS and saves it into a file "style.min.css". I like it because it is a small code that may be nicely integrated in an automated deployment script:

import requests
f = open("style.css", "r")
css_text = f.read()
f.close()
r = requests.post("http://cssminifier.com/raw", data={"input":css_text})
css_minified = r.text
f2 = open("style.min.css", "w")
f2.write(css_minified)
f2.close()

Solution 4

In case someone landed on this question and is using Django, there is a commonly used package for this matter called Django Compressor:

Compresses linked and inline JavaScript or CSS into a single cached file.

  • JS/CSS belong in the templates

  • Flexibility

  • It doesn’t get in the way

  • Full test suite

Solution 5

I don't know of any ready made python css minifiers, but like you said css utils has the option. After checking and verifying that the license allows for it, you could go through the source code and snip out the portions that do the minifying yourself. Then stick this in a single script and voila! There you go.

As a head start, the csscombine function in .../trunk/src/cssutils/script.py seems to do the work of minifying somewhere around line 361 (I checked out revision 1499). Note the boolean function argument called "minify".

Share:
17,353
Will Moffat
Author by

Will Moffat

Scottish geek, currently hacking on Freebase.com

Updated on June 22, 2022

Comments

  • Will Moffat
    Will Moffat about 2 years

    I'm looking for a simple Python script that can minify CSS as part of a web-site deployment process. (Python is the only scripting language supported on the server and full-blown parsers like CSS Utils are overkill for this project).

    Basically I'd like jsmin.py for CSS. A single script with no dependencies.

    Any ideas?

  • B Bulfin
    B Bulfin over 15 years
    You can use the index -1 to refer to the last element in a sequence. So you could use .append() instead of .insert(), and avoid the .reverse(). Also, if len(lst) > 0: is commonly done as if lst:
  • Borgar
    Borgar over 15 years
    Thanks for the tips. I've fixed these and a few other things. Python is a really nice language. :-)
  • Alan Plum
    Alan Plum over 14 years
    This will add redundant final semicolons and not abbreviate colors, though. If you want to do the former, you can simply store the result in a string and then replace(';}','}'). To do the later, you need to do a re.sub on '#([0-9a-f]{6})' with a callback that checks whether the color code is in the form #aabbcc and returns #abc (or the full string if it can't be abbreviated).
  • Borgar
    Borgar over 14 years
    Good points. I've added in a few more things, colors, zero-based fragment values, space removal around selector operators, and the trailing semicolon. It's at a point where it is starting to break stuff though, for example: div:content(" |= ") will be stripped to div:content("|="). I guess if you need any more you should be using a real tool anyway.
  • Ates Goral
    Ates Goral about 14 years
    Nice work! Minor issue: "Minifies" /* */ to /**/
  • Tim Post
    Tim Post about 13 years
    Years later .. still useful :) Now part of my build process
  • Sophie Alpert
    Sophie Alpert almost 13 years
    @AtesGoral Why is that a problem?
  • thom_nic
    thom_nic almost 13 years
    Not to be pedantic -- because this is great -- but it doesn't look like this will handle CSS3 @ directives.
  • Oleh Prypin
    Oleh Prypin over 9 years
    "we don't need spaces around operators" — apparently sometimes we do, because div*{} is a syntax error. A quick fix is to remove the asterisk from that regex.
  • Wtower
    Wtower about 9 years
    Unfortunately no longer being maintained.
  • Brutus
    Brutus about 9 years
    rCSSMin is another port and seems maintained: github.com/ndparker/rcssmin
  • nu everest
    nu everest over 8 years
    From cssminifier site: cssminifier.com/python
  • Mikhail Gerasimov
    Mikhail Gerasimov over 8 years
    I got error, trying to send requests to «http» urls. «https» urls of cssminifier.com and javascript-minifier.com works fine.
  • Admin
    Admin over 7 years
    Thanks a tonne for sharing, you really made my day.
  • tjespe
    tjespe almost 7 years
    Is it okay to reuse and modify this script for other open source projects?