-
Queued, Concatenated, and Gzipped Assets with the Zend Framework
Posted on November 18th, 2009 1 commentLinking assets to your templates and layouts is always a mine field. Every person has a different way of doing it and if it is done poorly, it can adversely affect the load time of your website, particularly if you have lots of websites. It’s helpful to understand how the http protocol fetches things like stylesheets and javascript files while it’s loading your page. In order to alleviate traffic for any individual server, a browser is limited to only so many concurrent requests to the same host. I do not recall what that limit is on each browser but I’m sure google does but in any case, if your site is of significant scope, chances are, you are going to need more assets than your browser can pull in at once. If you don’t believe me, install the yslow extension for firefox and run it on a page with many assets and you will see what i’m talking about.
Essentially you start with something like this
-
-
<head>
-
<script type="test/javascript" src="/assets/scripts/jquery.js"></script>
-
<script type="test/javascript" src="/assets/scripts/jquery.ui.js"></script>
-
<script type="test/javascript" src="/assets/scripts/jquery.form.js"></script>
-
<script type="test/javascript" src="/assets/scripts/jquery.validate"></script>
-
<link rel="stylesheet" href="/assets/styles/jquery.ui.css"/>
-
<link rel="stylesheet" href="/assets/styles/global.css"/>
-
<link rel="stylesheet" href="/assets/styles/page.css"/>
-
</head>
-
Pretty nasty but not uncommon and fairly minimal for a site that uses alot of jquery plugins. When the page loads, the browser must make an entire http request for each asset with all the overhead that entails. Don’t panic, there’s a solution to this mess that allows you to bundle all these assets withouth compromising in performance.
The solution is a two step approach, the first is a common php trick that concatenates javascript files and gzips them. It is well known that it is much cheaper to download one larger file than it is to download multiple smaller files. In your javascript folder create a php file called js.php:
-
-
<?php
-
$offset = 60 * 60 ;
-
$ExpStr = "Expires: " .
-
foreach($includes as $include){
-
}
-
}
-
?>
-
This script takes a single parameter ‘include’ which is set to a comma separated list of javascript file names sans the .js extension. The assumption is that the script files are in the same folder as js.php. The script will load the contents of all of the javascript files specified and stream them as one long gzipped string. We use it as follows:
-
-
<script type="text/javascript" src="/assets/scripts/js.php?include=jquery,jquery.ui,jquery.form,jquery.validate"></script>
-
Now we have optimized the assets so the browser makes only one request to the server for all javascript assets. It’s only a minor edit to the script to do the same trick for css files. We create another file called css.php:
-
-
<?php
-
$offset = 60 * 60 ;
-
$ExpStr = "Expires: " .
-
foreach($includes as $include){
-
}
-
}
-
?>
-
The only major difference is the Content-Type header. This allows us to include css files in the same way:
-
-
<link rel="stylesheet" type="text/css" href="/assets/styles/css.php?include=global,page,jquery.ui"/>
-
Great. So now we need a way to leverage this method using the view helpers included with the Zend Framework when using Zend_View and Zend_Layout. Initially, I used Zend_View_HeadScript and Zend_View_HeadLink to do this which works fine however these helpers do not intelligently manage redundant includes. What do I mean by this? Well let’s say we have a global layout that includes a number of stylesheets and javascript files that are shared across a web application. Something like this:
-
-
<?php
-
$ctx->this->headScript()->appendFile(‘/assets/scripts/js.php?include=jquery,jquery.ui’);
-
$ctx->this->headLink()->appendStylesheet(‘/assets/styles/css.php?include=global,page,jquery.iu’);
-
?>
-
<tal:block tal:content="structure php:this.doctype(’XHTML1_STRICT’)"/>
-
<html xmlns="http://www.w3.org/1999/xhtml">
-
<head>
-
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
-
<tal:block tal:content="structure this/headTitle"/>
-
<tal:block tal:content="structure this/headMeta"/>
-
<tal:block tal:content="structure this/headStyle"/>
-
<tal:block tal:content="structure this/headLink"/>
-
<tal:block tal:content="structure this/headScript"/>
-
</head>
-
<body>
-
<tal:block tal:content="structure this/layout/content"/>
-
</body>
-
</html>
-
This will cover the common cases. We don’t want to throw in everything but the kitchen sink in the layout as it imposes performance penalties on many pages that may not need access to certains scripts or stylesheets, so we instead invoke the view helpers in the action templates themselves to inject additional asset dependencies. For example:
-
-
$ctx->this->headScript()->appendFile(‘/assets/scripts/js.php?include=jquery.form,jquery.validate’);
-
?>
-
<div class="page-content">
-
<!–snip –>
-
</div>
-
This works great for the most part but occasionally there is some overlap either because of a developer mistake or maybe the layout is dynamically chosen for whatever purpose resulting in redundant script or stylesheet includes. Also we are making two distinct requests instead of one.
-
-
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><htmlxmlns="http://www.w3.org/1999/xhtml">
-
<head>
-
<script type="text/javascript" src="/assets/scripts/js.php?include=jquery,jquery.ui"></script>
-
<script type="text/javascript" src="/assets/scripts/js.php?include=jquery.form,jquery.validate"></script>
-
</head>
-
<body>
-
<!– snip –>
-
It’s not the end of the world but it bloats up the size of the assets download. So what we need is some additional view helpers which extend functionality of Zend_View_HeadScript and Zend_View_HeadLink to more intelligently queue requests for assets and eliminate redundant calls. I have created a view helper called AppendJs which does just this:
-
-
<?php
-
class FP_View_Helper_AppendJs {
-
-
public function setView(Zend_View_Interface $pView){
-
$this->view = $pView;
-
}
-
-
public function appendJs($pJs=null, $pModule=null){
-
if (Zend_Registry::isRegistered(‘assets_js___’))
-
$js = Zend_Registry::get(‘assets_js___’);
-
else
-
-
if ($pJs){
-
if (!$pModule)
-
$pModule = ‘default’;
-
-
$module = $js[$pModule];
-
else
-
-
else {
-
}
-
$js[$pModule] = $module;
-
Zend_Registry::set(‘assets_js___’, $js);
-
} else {
-
foreach ($js as $module => $assets){
-
if ($module == ‘default’)
-
return "<script type=\"text/javascript\" src=\"{$this->view->assets}/scripts/js.php?include=" . implode(‘,’, $assets) . "\"></script>";
-
else
-
return "<script type=\"text/javascript\" src=\"{$this->view->assets}/$module/scripts/js.php?include=" . implode(‘,’, $assets) . "\"></script>";
-
}
-
}
-
}
-
}
-
?>
-
This view helper stores an array in the Zend_Registry to keep track of all the javascript includes that have been requested in the current request. This way it is able to remember what has already been requested and ignore redundant requests. Similar to the way Zend_View_HeadScript and similar view helpers work, if no parameters are passed to the view helper, it instead outputs a script tag with a automatically constructed csv string. Additionally, if you separate out your assets for each application module, you may want to indicate which module you wish to include assets for. You may need to adjust the view helper to accomodate your asset folder structure.
Let’s take our original layout (minus the stylesheet examples) and modify it to use our new view helper:
-
-
<?php
-
?>
-
<tal:block tal:content="structure php:this.doctype(’XHTML1_STRICT’)"/>
-
<html xmlns="http://www.w3.org/1999/xhtml">
-
<head>
-
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
-
<tal:block tal:content="structure this/headTitle"/>
-
<tal:block tal:content="structure this/headMeta"/>
-
<tal:block tal:content="structure this/headStyle"/>
-
<tal:block tal:content="structure this/headLink"/>
-
<tal:block tal:content="structure this/headScript"/>
-
<tal:block tal:content="structure this/appendJs"/>
-
</head>
-
<body>
-
<tal:block tal:content="structure this/layout/content"/>
-
</body>
-
</html>
-
We are now using our appendJs view helper. We will keep the framework view helpers for special cases where templates need to use them.
-
-
-
<?php
-
?>
-
<div class="page-content">
-
</div>
-
We do the same with our action template. The final output will look like:
-
-
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml">
-
<head>
-
<script type="text/javascript" src="/assets/scripts/js.php?include=jquery,jquery.ui,jquery.form,jquery.validate"></script>
-
</head>
-
<body>
-
<!– snip –>
-
Using this approach we now have a single script tag with everything concatenated and gzipped. The same thing can be done with stylesheets by defining a similar view helper called AppendStylesheet. I won’t show it here, I think you get the idea.
Have your cake and eat it too.
One response to “Queued, Concatenated, and Gzipped Assets with the Zend Framework”
-
[...] wissen möchte, was “Queued, Concatenated, and Gzipped Assets” sind und wie man diese mit dem Zend Framework umsetzen kann, schaue mal in Brandons Blog [...]
Leave a reply
-


