bundle-phu – minify, bundle, and compress your js/css in Zend Framework

I’ve used a few different CSS/JS bundlers, but none have ever fulfilled all
that I needed. Specifically, I wanted one that could do all of the following:

  • Bundle automatically with little configuration
  • Optionally minify
  • Optionally compress
  • Allow the bundled files to be served directly by the webserver, instead of
    by PHP.
  • Easily allow files to be served compressed, but without recompressing on every request
  • Automatically detect modification the the source files, and update bundles as needed.

Thus, I created bundle-phu. Bundle-phu is a set of Zend Framework view helpers
that do all of the above.

Bundle-phu is inspired by, bundle-fu a Ruby on Rails equivalent.

Before

After

Highlights

  • Changes to your source js/css is detected, and bundles regenerated automatically
  • Minification can be done using either an external program (YUI compressor), or
    via callback in PHP.
  • Compression is achieved using gzencode()

Installation

  1. Place the BundlePhu directory somewhere in your include_path:

  1. Add the BundlePhu view helpers to your view’s helper path, and configure the helpers:

  1. Ensure your CacheDir is writable by the user your web server runs as
  2. Using either an Alias (apache) or location/alias (nginx) map the UrlPrefix to CacheDir.
    You can also do this using a symlink from your /public directory.
    e.g. /public/javascripts -> /../data/cache/js

Usage

As both these helpers extend from the existing HeadScript and HeadLink helpers in Zend Framework,
you can use them just as you do those.

17 Responses to bundle-phu – minify, bundle, and compress your js/css in Zend Framework

  1. Valentin Golev January 17, 2010 at 04:27 #

    How quick is it, or are you using something like fork or queuing?

    • David Abdemoulaie January 17, 2010 at 12:27 #

      With minification disabled, it’s _very_ quick. I profiled it with my application that was using the regular Zend_View_Helper_HeadXxxx classes, and the ZF toString() method took 1ms on average. Using BundlePhu in the best case, where the file is already cached took 500us on average. If the bundle is invalidated and is regenerated, the toString() time averages about 5ms. When using compression it averages 18ms.

      Minification is a huge time sink though depending on how you use it. When using YUI compressor (which isn’t really intended for on the fly use) the worst case time was an additional 2-3s on the page load. If you want the additional bandwidth savings of minification you have to weigh whether that initial cost of generating the bundle is ok in your app.

      Personally, right now I’m using it with minification disabled, until I can possibly find a faster solution.

  2. Vladimir January 22, 2010 at 14:56 #

    Thank you for sharing. Some notes:
    1. setDocRoot() path should be with trailing slash (PATH_PROJECT . ‘/public/’) in your examples;
    2. There are also jQuery (and Dojo I guess) specific helpers to insert JavaScript/CSS code. Are you going to support them?
    3. Now it is required to replace all headScript() calls with bundleScript(), but it’s much better do not modify any existing sources and use helper like this:
    bundleScript($this->headScript(), $this->jQuery()); ?>
    As you see it is also the possible way to support jQuery/Dojo. Are you interested in this or I should patch your helpers myself?

    • David Abdemoulaie January 22, 2010 at 16:54 #

      Thanks for the comments Vladimir:1. No. Why?2. I don’t use either of those and thus have no plan to.3. I’m aware of this limitation. I initially wrote them as drop in replacements for the HeadLink & HeadScript helpers, but changed my mind. There are several good reasons:

      * These are not 100% compatible with the existing ZF helpers. Things like setting the charset, or setting the “type” to something besides “application/javascript” or “text/css” are not supported and would behave unexpectedly.

      * Capturing dynamically generated content: var foo = ; would result in the initial value being cached forever.

      * There would be no easy way to use the standard ZF helpers if so desired.

      * It’s trivially easy to do a s/headScript/bundleScript/ on your entire project.

      • Vladimir January 23, 2010 at 02:02 #

        >>1. No. Why?
        $mtime = filemtime($this->_docRoot . $src);
        I am using relative $src in script tag () so at least it is required for me.

  3. Marcy Sutton November 18, 2010 at 13:46 #

    I could only get this to work if I appended my scripts in a layout file. What if I have multiple layout files? Can you please give an example of how to append scripts in the Bootstrap file? I have append commands like this one after I getHelper()

    $view->bundleScript()->appendFile(‘/_ui/scripts/lib/jquery-1.4.2.min.js’);

    But nothing outputs. Could it be that my paths are wrong but there is no error?

  4. Andrey Kostromin April 13, 2011 at 16:04 #

    ->setMinifyCommand(‘java -jar yuicompressor -o :filename’)

    How I can use only php without jar? For example http://code.google.com/p/minify/ ?

  5. Marcy Sutton April 29, 2011 at 15:44 #

    We are having permissions issues with the bundle script files when we push to different servers. Is there a way to configure the permissions of the bundle files when they are created?

  6. Saeed June 6, 2012 at 01:19 #

    “Using either an Alias (apache) or location/alias (nginx) map the UrlPrefix to CacheDir. You can also do this using a symlink from your /public directory. e.g. /public/javascripts -> /../data/cache/js”
    I donot understand this section of installation. and may be for this reason I couldn’t make it work. please help me

    • David Abdemoulaie June 6, 2012 at 10:09 #

      UrlPrefix is how your JS files will be prefixed in the request URI. For example, an UrlPrefix of /javascripts will result in URIs of /javascripts/bundle12345.js.

      CacheDir is the location on disk of where the bundles are written. 

      These are separate because it is a very good practice to not have your /public directory writable. For Zend Framework if you are following the recommended project structure then the proper location for written files is /application/data (relative to project root).

      This means you need to do one of two things to map the /javascripts URI to the /application/data/js disk location.

      1. You can use a symlink (often the easiest)
      2. You could use the appropriate mechanism of either Apache, or Nginx to map the request URI to the proper disk location. You would need to consult the documentation for this.

  7. Saeed June 6, 2012 at 06:30 #

    I’m getting “stat failed” at line 343 BundleScript for¬†filemtime function. any help

    • David Abdemoulaie June 6, 2012 at 09:47 #

      This simply means that the attempt to write the $cacheFile (in either _writeUncompressed or _writeCompressed) is failing and the file doesn’t exist. This would be a permissions issue that you need to diagnose and fix on your system.

Leave a Reply