Jun
20
2013

Javascript and CSS Minification Class- MagicMin

A long long time ago, I wrote about using PHP to merge and minify javascript and CSS.  And it sucked!  It forcefully recreated the files each and every time the page was loaded, failed to compress or minify javascript files properly (resulting in some super duper errors), and overall was lame.

It was however, short, so at least it had that going for it, which my actually awesome new class “MagicMin” does not have (it weighs in at a meager 800ish lines with comments).

MagicMin DOES however, NOT suck, performs flawless (so far) minification of CSS and javascript files, allows you to automatically write the compressed file with gzip headers and  a php extension, defaults to using the Google Closure API for javascript minification, will base64_encode images (reasonably sized please) directly INTO your stylesheets, AND will serve the compressed, gzipped, minified, and awesome files in style until you update any of the source files included in the minified stylesheet or javascript file! (phew- long sentence with random bold!)

Check out the source code of this site! You’ll see 2 meager files included: 1 stylesheet, and 1 javascript file. If you use your web inspector, you’ll also see that they have defined caching periods, and are gzipped. All of this is automatic!

“But why go to all this trouble?” you ask?  Couple big reasons!

  1. I’ve been thrown 50,000 character long single-line strings of minified filth waaaaaay too many times.  Which I then have to run through a beautifier, only to re-minify, then repeat with the inevitable change (NOOOOOO)
  2. Performance and the ability to follow normal development workflows.  You know- the kind that aren’t mashed into a unusable non-editable nightmare of singlelinedness.
  3. The ability to do #2 above, and have something smart enough to go, “Oh hey- he just added a line to one of the 18 CSS files included in the ->merge command, let’s make a new beautiful updated file!” without me having to tell it, and without having to do anything (ANYTHING) manually

To preemptively answer the forthcoming questions:

  • Yes, I am aware that other javascript and css minification/packing libraries, classes, and functions exist
  • Yes, I am aware that older versions of IE do not like base64_encoded images
  • and, Yes- I am still aware that other javascript and css minification libraries exist

And to preemptively answer the next question (“So, why then?”):

  • For fun
  • Because I can
  • Because mine is more awesome (because it’s mine)

Before you waste time reading ANY further, I feel I should demonstrate.

require( 'class.magic-min.php' );

//Default usage will echo from function calls and leave images untouched
$minified = new Minifier();

Now all you need to do is replace the normal references in your script the stylesheet includes…

//This will output a newly minified file called "style.min.css"
<link href="<?php $minified->minify( 'css/style.css' ); ?>" rel="stylesheet" />

//This will output a newly minified js file called "jquery-awesome.min.js"
<script type="text/javascript" src="<?php $minified->minify( 'js/jquery.js', 'js/jquery-awesome.min.js' ); ?>"></script>

But now you’re all like, “But, but, but- there are 19 css files in my /css directory and I only want to include 3 of them!”. GOOD.

<?php $include_only = array( 'css/base/jquery.ui.all.css', 'css/base/jquery.ui.base.css', 'css/base/jquery.ui.spinner.css' ); ?>
<link href="<?php $minified->merge( 'css/base/specified-files.css', 'css/base', $include_only ); ?>" rel="stylesheet" />

Satisfied? Good. Continue!

Enter PHP Minification  for Javascript and CSS

That’s right.  I said MAGIC.  And I meant it.

MagicMin (lame name, yes, descriptive, yes) has four primary functions (lots of bullet points today because I said so!):

  1. Minification of single javascript, or CSS files via the ->minify( ‘source-file’, ‘output-filename [optional]’, ‘version [also optional]’ ); command
  2. Merging AND minification of groups of files via the ->merge( ‘params below on this page’ ) command with some awesome stuff:
    1. Files must be the same type (css or js)
    2. Happily glob’s it’s way through any specified directory, grabbing and minifying any and all files of matched types
    3. Allows you to glob your way through a directory, excluding any files you deem unworthy
    4. Allows you to specify an order of the files, and this is only necessary for the files that need to be ordered- all others will be glob’d in after the order parameters have been met
    5. And last but CERTAINLY not least, allow you to specify ONLY an array of files to include (they really should be the same css or js type if you have any desire for your scripts or stylesheets to work)
  3. Base64_encoding images into stylesheets to enable all assets to be loaded in a single request (small files [think icons and slivers of backgrounds])
  4. Providing generated assets using gzip with specified cache control
    1. zlib Must exist and be enabled, otherwise no gzip will be used
    2. Default expires set to 30 days (60 x 60 x 24 x 31)

This class uses filemtime to determine if and when the minified version should be recreated, and will only create a new minified file IF a file selected for inclusion in the minify or merge functions is newer than the previously created minified file, however, files that contain “.min.” in the filename will not have their contents minified, but will still have their contents returned and added to compiled files as normal (as it SHOULD be assumed that those files have already been minified).

Minification of javascript assets is done using either:

  • The Google Closure API (default, however, a bit slow to initially create the minified files at approximately 7 seconds)
    • It’s google.  They’re reliable, the minification is topnotch stuff
  • JShrink
    • JShrink (JShrink.php) is included in the download, however, in the event that it does not exist in the same directory as the class.magic-min.php file, the class will retrieve it from github, write it to a file named JShrink.php in the same directory as the class, and THEN use it.
    • This only applies if the class is initiated with ‘closure’ => false

Want some more?! Good! There’s MORE!

This bad boy can also output to your javascript console via console.log by calling (assuming you’ve initialized the class using $minified = new Minifier(); anyway)

<?php $minified->logs(); ?>

For the rest, it’s best if you just read the included documentation, check out the example file (example.php naturally), and add your comments and feature requests below!

[dm]13[/dm]

20 Comments + Add Comment

  • Hi Bennett,
    I want to report an issue on the minify_contents function at line 308 where the preg_match is used to indicates that the contents are already minified. The expression now looks at all ‘min’ in the pathstring, it should be like this: ‘/\.min\./’ to look only for the ‘.min.’ match.

    You did a great job here. Unfortunately JSMin is no longer supported by Ryan Grove. Perhaps JShrink from Robert Hafner is a good replacement. https://github.com/tedivm/JShrink

    • Thanks for the feedback, and good catch! I’ve implemented that fix here: https://github.com/bennettstone/magic-min/commit/effe2179479a717f218717d3f2cb10954f99b40d, and will certainly look into jshrink.

      (Although jsmin isn’t supported, I included it as a fallback in the event that for some reason users would prefer not to use google as noted in the readme)

      • Class updated to use jShrink! Download updated.

  • Hi Bennett,

    I’ve just started trying MagicMin, so I have not tested it thoroughly “from the front to the middle to the back to the end” so far.
    It seems to work fine, but imho merging ignores e.g. @charset and @import in CSS. Is it possible, or am I doing something wrong?

    If it does so, will you consider fixing/improving the code?

    Regardless of this, I think it’s a nice job! (Y)
    Thank you.

    Cheers
    G

    • Correct, merging does ignore the @charset and @import- charset because $this->make_min declares contents as UTF-8 see around like 468, and @import rules are a bit counterproductive from a performance perspective (see: http://stackoverflow.com/a/10037093), so the @import alternative would involve adjusting the codebase to include the contents of the imported file (which would likely cause potential problems due to order conflicts and overwritten rules).

      And thank you!

  • Hi Bennett,

    I was not exact at my previous comment. I can see those @charset and @import rules in the generated .css.php file, but in the browser (Mozilla Firefox 22.0, Ubuntu 13.04) – no effect. Neither visually, nor in Firebug.

    Have you ever noticed this thingy?

    Greetz
    G

    • I hadn’t noticed that, but I should at minimum remove @charset rules most likely as the minified output already has a declared UTF-8

  • Hi Bennett,

    This is great work, but I am getting errors in PHP 5.2.13 while using it. Is there anyway to use it for PHP version 5.2.13.

    Thanks,
    Suresh

    • Hi Suresh!

      Can you provide the errors so I can help?

  • Hi – I’m using your MagicMin class (it’s new to me) and am having trouble merging css files. Although the merge processes completely, there are errors in the merged file. JS works fine. Can you help? Where can I send the file(s) for your review? Thanks.

    • Hi Keith, you can message me through the contact page (top right of the site) and I’ll respond with my email so you can send your code

  • Hi Bennett,
    Thank you so much for your beautiful shrink method. But unfortunately not all images in my CSS-file are set to base64.
    A few large images (over 32Kb) I can understand why it won’t convert the image files to base64, but some smaller images (less than 32Kb) aren’t converted although others are.
    Is there a limit to converting image to base64?

    • I’m not 100% sure which version you’ve got, but I did just update the download link to use the most recent which ‘may’ solve the issue!

      If not, check out line 65 for the constant IMAGE_MAX_SIZE which you can adjust to take care of the larger images.

      For other images, if the paths are off a bit, or the image is determined to be remote (run via whether or not there is ‘http’ or https’ in the URI) and the response is slow, it may skip it. Check https://github.com/bennettstone/magic-min/blob/master/class.magic-min.php#L65 and IF the images are http/s, you may want to add a curl_setopt timeout around line 196 (https://github.com/bennettstone/magic-min/blob/master/class.magic-min.php#L196) (http://www.php.net/manual/en/function.curl-setopt.php)

      • Hi Bennet,
        I was using an older version. Just updated to 2.7.1 but still some images didn’t convert to base64. After increasing IMAGE_MAX_SIZE to 50 all images where converting except one, probably because this image is 187Kb.
        But even then this fix helps a lot ! Thank you.

        • Great! Glad that worked for you Marcus

  • hi,
    i have just tested yout example.php and none of the src’es are filled up. They are all empty like this: src=””

    any idea?

    • Check the paths by which the files are getting sent to the class, I have had that problem when trying to write to paths that aren’t accessible, or don’t exist. You can also add the $minified->logs(); function after you add that to display the output (including any errors)

  • Does anyone know any class allow minify css from input form and echo to another form?

  • i find a free online service to minify js http://www.online-code.net/minify-js.html and compress css http://www.online-code.net/minify-css.html, so it will reduce the size of web page.

  • A comparison would be nice, your script vs some other compressors, i will try yours and let you know how it works.
    Thanks for your great work and for sharing

Leave a comment