Introduction
The CombineAndMinify package discussed here automatically speeds up the loading
of JavaScript files, CSS files and or images (loaded from image tags or from CSS
files). The result can be a dramatic improvement in ASP.NET performance.
Installation is easy - just add a dll, update your web.config and add a small
file. If you use IIS 6, you'll also need to update the configuration of IIS (no
such update needed for IIS 7). Step by step installation instructions are in the
installation section.
Download
Requirements
- ASP.NET 3.5 or higher
- IIS 6 or higher for your live site
Features
The features below can all be switched on and off individually via the
web.config file (full description). If you just install the package and not do
any further configuration, it only minifies and combines JavaScript and CSS
files, and than only when the site is in Release mode.
Although the feature set below is extensive, there are many more ways to improve
ASP.NET performance. My recently released book ASP.NET Site Performance Secrets
shows how to pinpoint the biggest performance bottlenecks in your web site,
using various tools and performance counters built into Windows, IIS and SQL
Server. It then shows how to fix those bottlenecks. It covers all environments
used by a web site - the web server, the database server, and the browser. The
book is extremely hands on - the aim is to improve web site performance today,
without wading through a lot of theory first.
CombineAndMinify features:
- Minifies JavaScript and CSS files.
Minification involves stripping superfluous white space and comments. Only
JavaScript and CSS files that are loaded from the head sections of your
pages are minified.
- Correctly processes files that contain
non-English text such as Chinese. The package uses the efficient YUI
minifier to minify JavaScript and CSS files that are all in English (that
is, contain only ASCII characters). Because the YUI minifier doesn't handle
non-ASCII characters (such as Chinese), the package automatically uses the
JSMIN algorithm for files containing such characters. JSMIN is less
efficient, but handles non-ASCII characters well.
- Combines JavaScript files and CSS files.
Loading a single large file is often much quicker than loading a series of
small files, because that saves the overhead involved in all the request and
response messages.
If a CSS file contains image urls that are relative to the folder containing
the CSS file itself, those urls are fixed up by the package. That way, they
continue to work even if CSS files from several different folders are
combined.
- Allows you to configure the package so it
only kicks in in release mode. That way, you see your individual files
complete with white space and comments while developing, and reap the
performance improvement in your live site.
- Reduces the size of the HTML generated by
your .aspx pages by removing white space and comments. Note that the .aspx
files themselves are not affected, only the HTML sent to the browser.
- Allows you to configure cookieless domains
from which to load JavaScript files, CSS files and images. This way, the
browser no longer sends cookies when it requests those files, reducing wait
times for the visitor.
- Lets you configure multiple cookieless
domains. This causes the browser to load more JavaScript, CSS and image
files in parallel.
- Optimizes use of the browser cache by
allowing the browser to store JavaScript, CSS and image files for up to a
year. Uses version ids in file names to ensure the browser picks up new
versions of your files right away, so visitor never see outdated files
(details on how this works).
- Unlike similar packages, doesn't add query
strings when combining files or when inserting versions. This optimizes
caching by proxy servers (many proxy servers won't cache files with query
strings).
- Converts image file names to lower case,
to make it easier for those proxy and browser caches that do case sensitive
file name comparisons to find your file in their caches - so they don't
request the same file again.
- Preloads images immediately when the page
starts loading, instead of when the browser gets round to loading the image
tags - so your images appear quicker. You can give specific images priority.
- Helps you detect missing files by throwing
an exception when a JavaScript file, CSS file or image is missing. By
default, the package handles missing files silently, without throwing an
exception.
- To reduce CPU overhead and disk accesses
caused by the package, it caches intermediate results, such as minified
files. A cache entry is removed when the underlying file is changed, so
you'll never serve outdated files.
Server compression
If you are interested in web site performance, you may be interested in this
short digression into server compression.
IIS 6 and 7, and Apache as well, provide the option to gzip compress all text
files (html, JavaScript, CSS, etc.) sent to the browser. All modern browsers
know how to decompress those files. Compression can save you a lot of bandwidth
and download time. It is not uncommon to reduce file sizes by way over 50%.
In IIS, compression is switched off by default for dynamic files, such as .aspx
files. This is because it increases the load on the CPU. However, with the
overabundence of CPU cycles on modern server hardware, switching on compression
for dynamic files on your server is almost always a great idea. Also, IIS 6 and
7 allow you to set the compression level, so you can choose a level that you're
comfortable with. Finally, IIS 7 can automatically switch off compression when
CPU usage goes over a predetermined level (set by you), and switch it back on
after CPU usage has dropped below a second level (also set by you). It even lets
you cache compressed dynamic files, which makes compression extremely
attractive.
Switching on basic compression on IIS 7 is easy, but getting the most out of it
is a bit tricky. Switching on compression in IIS 6 is just tricky. Good places
to find out more would be here (for IIS 7) and here (for IIS 6).
Or you could read chapter 10 of my book ASP.NET Site Performance Secrets where
it is all spelt out in one place (believe me, this will save you a lot of time).
Installation
Installing the CombineAndMinify package involves these steps:
- Common installation - applies to all
environments.
- IIS 7 related - only applies if your live
site runs under IIS 7.
- IIS 6 related - only applies if your live
site runs under IIS 6.
- Development environment related - only
applies if you want to use the package not just in your live site, but also
in your development environment.
Common Installation
- Compile the package:
- Download the zip file with the source code, and unzip in a directory.
- Open the CombineAndMinify.sln file in Visual Studio 2008 or later.
- You'll find that the sources are organized in a solution, with these
elements:
1. Project CombineAndMinify is the actual package.
2. Web site Testsite contains a lot of (functional but rather disorganised)
test cases. Ignore this unless you want to test the package.
- Compile the solution. This will produce a CombineAndMinify.dll file in
the CombineAndMinify\bin folder.
- Update your web site:
1. Add a reference to CombineAndMinify.dll to your web site (in Visual
Studio, right click your web site, choose Add Reference)
2. Add the custom section combineAndMinify to your web.config:
<configuration>
...
<configSections>
...
<section name="combineAndMinify" type="CombineAndMinify.ConfigSection"/>
...
</configSections>
...
</configuration>
- Allow the package to process the head
sections of your pages. That way, it can replace the tags loading individual
JavaScript and CSS files with tags loading combined files:
1. Make sure that the head tags of your pages have runat="server", like so:
<head runat="server">
When you create a new page in Visual Studio, it creates a head section with
runat="server", so you probably don't have to change anything here.
2. Add a folder called App_Browsers to the root folder of your web site.
3. Use your favorite text editor (such as Notepad) to create a text file in
the App_Browsers folder. Call it HeadAdapter.browser. Into that file, copy
and paste this code:
<browsers>
<browser refID="Default">
<controlAdapters>
<adapter controlType="System.Web.UI.HtmlControls.HtmlHead"
adapterType="CombineAndMinify.HeadAdapter" />
</controlAdapters>
</browser>
</browsers>
This tells ASP.NET to leave processing of all HtmlHead controls (which
represent the head tags) to the class CombineAndMinify.HeadAdapter (which is
part of the package).
- Make sure that your site has trust
level Full. This allows it to access the combineAndMinify section. If your
web.config contains
<trust level=... />
remove it. You'll than get the default trust level, which is Full.
IIS 7
Only do these steps if you use IIS 7 for your live site or your development
environment. If you only use IIS 6 or IIS 7 in classic mode, skip to the IIS 6
section.
The package uses an HTTP Handler to combine and minify JavaScript and CSS files
on the fly. To reduce CPU overhead, it caches the combined files, so they only
get combined and minified after they have been changed on the file system.
To configure the HTTP Handler, add the following to your web.config:
</configuration>
...
<system.webServer>
<validation validateIntegratedModeConfiguration="false"/>
...
<handlers>
...
<add name="JavaScriptHandler" verb="*" path="*.js" type="CombineAndMinify.HttpHandler,
CombineAndMinify" resourceType="Unspecified"/>
<add name="CssHandler" verb="*" path="*.css" type="CombineAndMinify.HttpHandler,
CombineAndMinify" resourceType="Unspecified"/>
</handlers>
...
</system.webServer>
...
</configuration>
IIS 6
Only do these steps if you use IIS 6 or IIS 7 in classic mode. If you use IIS 7,
do the installation for IIS 7. If you want to invoke the package not only in
your live environment but your development environment as well, than do the
installation related to your development environment as well.
1. Configure the HTTP Handler in your web.config:
<configuration>
...
<system.web>
...
<httpHandlers>
...
<add verb="*" path="*.js" type="CombineAndMinify.HttpHandler, CombineAndMinify"
/>
<add verb="*" path="*.css" type="CombineAndMinify.HttpHandler, CombineAndMinify"
/>
</httpHandlers>
...
</system.web>
...
</configuration>
2. Send all requests for JavaScript and CSS files to the ASP.NET handler.
That allows the package to combine and minify them.
- Open the IIS Manager - click Start | Administrative Tools | Internet
Information Services (IIS) Manager.
- Expand your server. Expand Web Sites. Right click your web site and choose
Properties.
- Click the Home Directory tab, and then click the Configuration button.
- Get the path to the ASP.NET handler:
1. Click the line with the .aspx extension.
2. Click the Edit button.
3. Copy the contents of the Executable box. If you use .Net 2 or .Net 3.5, it
will be
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll.
4. Click Cancel to dismiss the dialog.
5. Tell IIS 6 to let ASP.NET handle all requests for files with extension .js:
- Click the Add button.
- Paste the path to the ASP.NET handler you just found into the Executable
field.
- Enter .js into the Extension field.
- Uncheck the Verify that file exists box.
- Click OK.
- Repeat the last step, but now for extension .css.
- Click OK to dismiss the Configuration dialog.
- Click the Apply button, and then click OK to dismiss the Properties dialog.
Development Environment
By default, the package is only active when your site is in Release mode - that
is, if you have
<compilation debug="false">
in your web.config. That makes sense, because the package doesn't affect the
functionality of your site - it just makes it faster. If this arrangement works
for you, there is no need to do any installation related to your development
environment, so you can skip this section.
If you do want to activate the package in your development environment, you need
to run your debugging sessions under IIS 7, instead of under Cassini (the web
server that normally runs when you hit F5 in Visual Studio). This is because the
package uses an HTTP Handler to process JavaScript and CSS files, and Cassini in
.Net 3.5 doesn't support HTTP Handlers.
Note that IIS 7 is included in the Home Premium, Business, Enterprise and
Ultimate versions of Windows Vista and Win 7 (but not the Starter and Home
versions). That means you probably already have it on your system.
Here is how to run your debugging sessions under IIS 7:
- Install IIS 7 on your development machine
- Create a development site in IIS 7
- Get Visual Studio to use IIS 7 instead of Cassini
- Do the installation steps related to IIS 7
- Make sure the package is active in debug mode
Let's go through these steps one by one.
1. Install IIS 7 on your development machine
- Click Start | Control Panel | Programs | Turn windows features on or off.
- Check Internet Information Services.
- Expand the Internet Information Services node, expand Web Management Tools
and make sure IIS Management Console is checked. This will allow you to run the
IIS Manager.
- Expand IIS 6 Management Compatibility and make sure IIS Metabase and IIS 6
configuration compatibility is checked. You need this to be able to use IIS from
Visual Studio.
- Expand the World Wide Web Services node and then the Application Development
Features node. Make sure that ASP.NET is checked, so the server can run ASP.NET
applications.
- Also under World Wide Web Services, expand Security and make sure Windows
Authentication is selected. You need this to be able to use IIS from Visual
Studio.
- Click OK. Windows will take a while to implement the changes.
2. Create a development site in IIS
Now that you have installed IIS 7 on your machine, create a site in IIS 7 that
Visual Studio can use for debugging sessions:
- While still in IIS Manager, expand your machine. Right click Sites and choose
Add Web Site. The Add Web Site dialog opens.
- Make up a site name. As the physical path, enter the root folder of your
source files – the one with default.aspx and web.config. Enter a port that isn't
already in use, for example 1080. Click OK.
- Enable Windows Authentication. Double click your new site, double click
Authentication, right click Windows Authentication and choose Enable.
3. Get Visual Studio to use IIS 7 instead of Cassini
If you use a Web Site:
- Run Visual Studio as administrator. Right click your web site and choose
Start Options. The Property Pages window opens, with the Start Options tab
selected.
- In the Server section, select Use custom server. In the Base Url field, enter
the localhost address with the port you entered when you created the development
site in IIS manager - for example http://localhost:1080.
- Click OK.
If you use a Web Application:
- In Visual Studio, right click the web application and choose Properties. The
properties window opens.
- Click the Web tab.
- Scroll down to the Servers section and select Use Local IIS Web server. In
the Project Url field, enter the localhost address with the port you entered
when you created the development site in IIS manager.
- Save the properties.
Now when you hit F5 in Visual Studio to run your site in debug mode, you'll find
it runs under IIS 7 instead of under Cassini. Apart from that, your debugging
experience is the same - you can still set breakpoints, etc.
4. Do the installation steps related to IIS 7
Now that you're using IIS 7 to do development, do the IIS 7 related installation
if you have not already done that.
5. Make sure the package is active in debug mode
Follow the steps listed here.
Configuration
By default, the package minifies and combines JavaScript and CSS files. To use
the other features, or to switch off minification or combining of JavaScript or
CSS files, add a combineAndMinify element to your web.config file, like so:
<configuration>
...
<combineAndMinify ... >
</combineAndMinify>
...
</configuration>
The combineAndMinify element supports these attributes and child elements:
- active
- combineCSSFiles
- combineJavaScriptFiles
- minifyCSS
- minifyJavaScript
- cookielessDomains
- enableCookielessDomains
- preloadAllImages
- prioritizedImages
- makeImageUrlsLowercase
- insertVersionIdInImageUrls
- exceptionOnMissingFile
- removeWhitespace
- headCaching
active
Determines when the package is active. When it is not active, it doesn't affect
your site at all and none of the other attributes or child elements listed in
this section have any effect.
Value |
Description |
Never |
Package is never active, irrespective of debug mode |
Always |
Package is always active, irrespective of debug mode |
ReleaseModeOnly (default) |
Package is only active in release mode |
DebugModeOnly |
Package is only active in debug mode |
Example
<configuration>
...
<combineAndMinify active="Always" >
</combineAndMinify>
...
</configuration>
Whether your site is in debug or release mode depends on the debug attribute of
the compilation element in your web.config file. If that attribute is set to
false, your site is in release mode (as it should be when live). When it is set
true, it is in debug mode (as it should be in development). It looks like this
in web.config:
<configuration>
...
<system.web>
<compilation debug="true">
...
</compilation>
...
</system.web>
...
</configuration>
Note that by default, the package is only active in release mode - so you
won't see any effect while you're developing. To ensure that the package is
active in both release mode and in debug mode as well, set active to Always, as
shown in the example above.
Note that the active attribute acts as a master switch for the whole package.
It's like the ignition in a car - if you don't turn on the ignition (or at least
the battery), pressing any other buttons on the dashboard won't do anything.
combineCSSFiles
Determines whether the package combines CSS files.
Value |
Description |
None |
CSS files are never combined |
PerGroup
(default) |
CSS files are combined per group. See explanation below. |
All |
All CSS files are combined into a single CSS file. |
Example
<configuration>
...
<combineAndMinify combineCSSFiles="All" >
</combineAndMinify>
...
</configuration>
To see what is meant with "per group", have a look at this example:
<link rel="Stylesheet" type="text/css" href="/css/site1.css" />
<link rel="Stylesheet" type="text/css" href="/css/site2.css" />
<script type="text/javascript" src="js/script1.js"></script>
<link rel="Stylesheet" type="text/css" href="/css/site3.css" />
<link rel="Stylesheet" type="text/css" href="/css/site4.css" />
By default, the package tries to ensure that the order of the JavaScript and CSS
definitions doesn't change when JavaScript and CSS files are combined. It does
this by grouping CSS files whose tags are after each other. In this case, there
are 2 groups - site1.css and site2.css, and site3.css and site4.css. This causes
the browser to load the CSS and JavaScrip definitions in the exact same order as
when the files had not been combined:
- Combined file with all CSS definitions in
site1.css and site2.css
- script1.js
- Combined file with all CSS definitions in
site3.css and site4.css
You get this behaviour when you set
combineCSSFiles to PerGroup (which is the default). The package supports
grouping for JavaScript files as well, which is controlled by the
combineJavaScriptFiles attribute.
Combining all CSS files into one file
If you set combineCSSFiles to All, all CSS files get combined into a single
file. The link tag to load that combined CSS file gets placed where the link tag
of the first CSS file used to be. In our example, that causes this load order:
- Combined file with all CSS definitions in
site1.css, site2.css, site3.css and site4.css
- script1.js
As you see, the CSS definitions in site3.css
and site4.css now get loaded before script1.js, instead of after script1.js. As
a result, the number of CSS files that need to be loaded is reduced from two to
one (as compared with grouping), but you also change the order in which CSS and
JavaScript definitions are loaded. It depends on your site whether that is an
issue or not.
Loading CSS files from another site
If you decide to set combineCSSFiles to All, be sure that your site doesn't load
CSS files from another web site. This is uncommon, but if yours is one of the
rare sites that does this, consider this example:
<link rel="Stylesheet" type="text/css" href="/css/site1.css" />
<link rel="Stylesheet" type="text/css" href="/css/site2.css" />
<link rel="Stylesheet" type="text/css" href="http://anothersite.com/css/other.css"
/>
<link rel="Stylesheet" type="text/css" href="/css/site3.css" />
<link rel="Stylesheet" type="text/css" href="/css/site4.css" />
The package never combines CSS files from other sites - not with CSS files from
your site, not with CSS files from the other site. As a result, if you change
combineCSSFiles to All, the package will combine all CSS files loaded from your
site (but not those from the other site), causing the following load order:
- Combined file with all CSS definitions in
site1.css, site2.css, site3.css and site4.css
-
http://anothersite.com/css/other.css
The definitions in other.css now came after
those in site3.css and site4.css, meaning they may take precedence - which could
break your CSS.
combineJavaScriptFiles
Determines whether the package combines JavaScript files.
Value |
Description |
None |
JavaScript files are never combined |
PerGroup
(default) |
JavaScript files are combined per
group. See explanation below. |
All |
All JavaScript files are combined into
a single JavaScript file. |
Example
<configuration>
...
<combineAndMinify combineJavaScriptFiles="All" >
</combineAndMinify>
...
</configuration>
As you saw in the description of combineCSSFiles, the package groups JavaScript
files in the same way as CSS files. Similarly, if you set combineJavaScriptFiles
to All, all JavaScript files that are loaded from your site get combined into a
single JavaScript file.
However, there is one major difference with combining CSS files: When you
combine all JavaScript files into a single file (combineJavaScriptFiles is All),
the script tag for the combined file winds up at the location where the last
original script tag used to be. This in contract to a combined CSS file, which
winds up at the location of the first original CSS tag.
This makes life easier if you load JavaScript libraries from extenal sites. If
you use a popular package such as jQuery, you can load it from the free Google
Content Delivery Network (CDN) and also from the free Microsoft CDN - which
saves you bandwidth and download time (details about those CDNs are in chapter
13 of my book).
Take this example:
<script type="text/javascript" src="/js/script1.js" ></script>
<script type="text/javascript" src="/js/script2.js" ></script>
<!-- load jQuery from free Google CDN -->
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript" src="/js/script3.js" ></script>
<script type="text/javascript" src="/js/script4.js" ></script>
script3.js and script4.js may well be dependent on jquery.min.js. If
combineJavaScriptFiles is set to PerGroup, you wind up with this load order:
- Combined file with all JavaScript
definitions in script1.js and script2.js
- jquery.min.js
- Combined file with all JavaScript
definitions in script3.js and script4.js
So all definitions load in the same order as
before the files were combined, as you'd expect. Now when you set
combineJavaScriptFiles to All, you wind up with this load order:
- jquery.min.js
- Combined file with all CSS definitions in
script1.js, script2.js, script3.js and script4.js
This would probably still work well, because
the definitions in script3.js and script4.js still get loaded after those in
jquery.min.js. script1.js and script2.js now get loaded after jquery.min.js, but
than jquery.min.js wouldn't have been dependent on those files anyway.
minifyCSS
Determines whether the package minifies CSS files.
Value |
Description |
true(default) |
CSS files get minified |
false |
CSS files do not get minified |
Example
<configuration>
...
<combineAndMinify minifyCSS="false" >
</combineAndMinify>
...
</configuration>
Minifying a file means removing redundant white space and comments. This not
only reduces your bandwidth usage and download times, but also makes it harder
for outsiders to reverse engineer your web site. It also encourages developers
to add comments to their CSS files (and especially their JavaScript files), now
that those comments do not travel over the wire to the browser.
It is normally safe to minify files, so this feature is enabled by default.
However, just in case removing white space or comments breaks your file, the
option exists to switch it off.
Remember that the package doesn't minify your source files. It reads your source
files, and minifies their content before sending them to the browser. To save
CPU cycles, it caches the minified versions, using file dependencies to ensure
cached versions are removed the moment you change the underlying files.
minifyJavaScript
Determines whether the package minifies JavaScript files.
Value |
Description |
true(default) |
JavaScript files get minified |
false |
JavaScript files do not get minified |
Example
<configuration>
...
<combineAndMinify minifyJavaScript="false" >
</combineAndMinify>
...
</configuration>
cookielessDomains
To have your JavaScript, CSS and image files loaded from one or more cookieless
domains, specify those domains using a cookielessDomains child element.
Example
<configuration>
...
<combineAndMinify ... >
<cookielessDomains>
<add domain="http://static1.mydomain.com"/>
<add domain="http://static2.mydomain.com/"/>
</cookielessDomains>
</combineAndMinify>
...
</configuration>
Cookieless domains help you boost web site performance in two ways. They let you
cut down on the overhead created by cookies. And they let you get the browser to
load more JavaScript, CSS and image files in parallel. Let's look at the cookie
overhead first, and then the parallel loading.
Cookie overhead
If your site sets a cookie on the browser, then each time the browser sends a
request to your site, that request contains the cookie. The issue is that the
cookie is not only sent when requesting an .aspx file, but also when requesting
static files, such JavaScript files, CSS files and images. In most cases, this
is a waste of bandwidth (the exception would be if you have a handler that
processes for example JavaScript files and that uses the cookie).
However, the browser won't send the cookie to another domain or sub domain. So
if your page is at http://www.mydomain.com/page.aspx (using subdomain www), and
you put your images and other static files on http://static1.mydomain.com (using
subdomain static1), than the browser won't send cookies when requesting static
files.
As an aside, if your site uses cookies (or ASP.NET sessions, which uses
cookies), you should never allow visitors to access your pages without a
subdomain. That is, don't allow them to access http://mydomain.com/page.aspx.
Otherwise, if a visitor first accesses http://www.mydomain.com/page.aspx (using
subdomain www), sets a cookie, and then comes back via http://mydomain.com/page.aspx
(no subdomain), the browser won't send the cookie! IIS 7 makes it very easy to
redirect requests to http://mydomain.com to http://www.mydomain.com using an
entry in web.config. See Microsoft's iis.net, or the image control adapter
included in chapter 12 of my book ASP.NET Site Performance Secrets.
Parallel loading
When a browser loads a page, it loads the static files (images, JavaScript
files, CSS files) in parallel to reduce the visitor's wait time. However, the
browser limits the number of files that are loaded in parallel. Modern browsers
(IE7 and better, Firefox, Chrome, Safari) have a limit of about 6, while older
browsers (such as IE6) have a limit of 2.
However, this limit is per (sub)domain. It isn't per IP address. This means that
if you spread your static files over for example 2 cookieless domains, you allow
the browser to load up to two times more files in parallel.
Using cookieless domains on your site
If you add a cookielessDomains element with one or more domains to the
combineAndMinify element, the package adds those domains to the urls of all
static files in your site. This includes images referenced from CSS files.
This means that if you use:
<configuration>
...
<combineAndMinify ... >
<cookielessDomains>
<add domain="http://static1.mydomain.com"/>
</cookielessDomains>
</combineAndMinify>
...
</configuration>
Then for example
<img src="images/ball3.png" height="10" width="10" />
is replaced by
<img src="http://static1.mydomain.com/images/ball3.png" height="10" width="10"
/>
If you define multiple domains, such as:
<configuration>
...
<combineAndMinify ... >
<cookielessDomains>
<add domain="http://static1.mydomain.com"/>
<add domain="http://static2.mydomain.com"/>
</cookielessDomains>
</combineAndMinify>
...
</configuration>
Then the package attempts to spread the files over the available domains (note
that you can add more than 2 domains if you want). This to get the browser to
load more files in parallel. So if you have these image tags:
<img src="images/ball3.png" />
<img src="images/woodentoy4.png" />
You would wind up with:
<img src="http://static2.mydomain.com/images/ball3.png" />
<img src="http://static1.mydomain.com/images/woodentoy4.png" />
To create the subdomains, log into your account at your domain registrar and
create the static1, static2, etc. subdomains (you can use any subdomain names
you like). Make sure they point to the same IP address as your www subdomain.
This way, you don't have to physically move your static files. Note that every
subdomain that is not your www subdomain acts as a "cookieless" subdomain - it
isn't like there are special "cookieless" subdomains as such.
Contrary to what you may think, if you have say 2 cookieless domains, the
package won't use one domain for half the static files and the other domain for
the other half. This is because it needs to make sure that on every page, a
given static file is always given the same domain. If images/ball3.png were
turned into http://static1.mydomain.com/images/ball3.png on one page, but to
http://static2.mydomain.com/images/ball3.png on a second page, than the browser
won't find ball3.png in its cache when it hits the second page, even if it
stored ball3.png when it accessed the first page. Because of the different
domains, it would regard the two urls as different, even though they actually
point at the same resource.
Because of this requirement, the package uses the hash code of the file name to
work out which domain to use. So if there are two domains, than if the hash is
even it uses the first domain, and if it is odd it uses the second domain.
Because it is unlikely that 50% of file names have an even hash code, you are
unlikely to get a perfect distribution of the static files over the available
domains.
enableCookielessDomains
Determines whether cookieless domains are used.
Value |
Description |
Never |
Cookieless domains are never used. |
Always(default) |
Cookieless domains are always used, provided that 1) the package is
active, and 2) you have defined a cookielessDomains element with cookieless
domains. |
ReleaseModeOnly |
Cookieless domains are only used in release mode. |
DebugModeOnly |
Cookieless domains are only used in debug mode. |
Example
<configuration>
...
<combineAndMinify active="Always" enableCookielessDomains="ReleaseModeOnly" >
<cookielessDomains>
<add domain="http://static1.mydomain.com"/>
<add domain="http://static2.mydomain.com"/>
</cookielessDomains>
</combineAndMinify>
...
</configuration>
This option is really only useful if you decide to activate the package in your
development environment. In that case, you may decide to only use cookieless
domains in release mode, while using all the other features of the package in
both release and debug mode.
The reason for this is that if you have new images in your development
environment that are not yet on your live site, than they won't show up in your
development environment if you use the cookieless domains - which point to your
live site.
If you want to take that route, set enableCookielessDomains to ReleaseModeOnly.
The default value for enableCookielessDomains is Always. However, keep in mind
that for the package to use cookieless domains, it has to be active. And by
default, it is only active in release mode.
preloadAllImages
Determines whether the package inserts code that preloads all images when the
page starts loading.
Value |
Description |
true |
All images are preloaded |
false(default) |
No images are preloaded (except for those specified using child
element prioritizedImages, described further below) |
Example
<configuration>
...
<combineAndMinify preloadAllImages="true" >
</combineAndMinify>
...
</configuration>
Normally, the browser only starts loading an image after it has encountered its
image tag in the HTML or in a CSS file. If the image tag is towards the end of a
big page, or if it takes a while to load the CSS file, it can take a while
before image loading starts.
To get the browser to start loading all images immediately when the page itself
starts loading, set the preloadAllImages attribute to true. The package than
generates JavaScript at the start of the page head to load each image into the
browser cache, along these lines:
<script type="text/javascript">
var img0=new Image();img0.src='http://www.codeproject.com/images/ball3.png';
var img1=new Image();img1.src='http://www.codeproject.com/css/chemistry.png';
var img2=new Image();img2.src='http://www.codeproject.com/images/toy4.png';
...
</script>
Now when the browser encounters an image tag, the image is already in browser
cache, so the browser can show the image right away.
prioritizedImages
Allows you to prioritize certain images for preloading.
Example
<combineAndMinify ... >
<prioritizedImages>
<add url="images/salesbutton.png"/>
<add url="images/logo.png"/>
</prioritizedImages>
</combineAndMinify>
If you have many images on your pages, you may want to prioritize certain
images. For example, if your "order now" button is an image, you want that image
in front of your visitors as soon as possible.
You can use prioritizedImages without setting preloadAllImages to true. Here is
how these two attributes interact:
prioritizedImages |
preloadAllImages |
Images preloaded |
Empty or not present |
true |
All the images referred to from CSS files and all the
images on the page are preloaded. They are loaded in the order in which their
tags appear in the CSS or in the HTML. Images referred to from CSS are preloaded
before images on the page itself. |
Empty or not present |
false |
None |
One or more urls |
true |
All urls listed in prioritizedImages are preloaded first,
than all the other images. |
One or more urls |
false |
Only the urls listed in prioritizedImages are preloaded |
makeImageUrlsLowercase
Determines whether the package makes all image urls lowercase.
Value |
Description |
true |
All images urls are converted to lowercase |
false(default) |
Images url casing is left as it is |
Example
<configuration>
...
<combineAndMinify makeImageUrlsLowercase="true" >
</combineAndMinify>
...
</configuration>
You may be using inconsistent casing in your web pages to refer to the same
image. For example:
<img src="/images/woodentoy4.png" height="10" width="10" />
...
<img src="/images/WoodenToy4.png" height="10" width="10" />
Assume a browser or proxy loads woodentoy4.png and stores it in its cache. When
it then needs to load WoodenToy4.png, it may not recognize it is the same as the
woodentoy4.png that it already has in cache, and send a superfluous request for
WoodenToy4.png.
To prevent this, set makeImageUrlsLowercase to true. This way, all images urls
in the HTML and CSS sent to the browser will be lowercase, so there is no
inconsistent casing. Note that the package doesn't change your source files.
Instead, it changes the HTML that gets generated from your source files and sent
to the browser.
insertVersionIdInImageUrls
Determines whether the package 1) inserts version ids in image file names and 2)
allows the browser to cache images for up to a year.
Value |
Description |
true |
Version ids are inserted in image file
names. Requires changes to web.config (see below). |
false(default) |
No version ids are
inserted in image file names |
Example
<configuration>
...
<combineAndMinify insertVersionIdInImageUrls="true" >
</combineAndMinify>
...
</configuration>
When a browser receives an image, it stores it in its browser cache. That way,
if it needs to load the image again, it may still be in cache, so there is no
need to send a request to the server. The result is less waiting for the visitor
and less bandwidth used by your server.
One issue here is how long the browser should keep the image in its cache. Too
long, and you may have visitors looking at outdated images. Too short, and the
browser sends more requests for the image than necessary.
By setting insertVersionIdInImageUrls to true, you get the best of both worlds:
- It causes the package to send HTTP
Response Headers when sending images that tell the browser it can cache the
image for up to a year - the maximum you can ask for according to the HTTP
specification.
- And it inserts a version id into the image
file name as used in image tags (both in the page HTML and in the CSS). The
package calculates that version id from the last update time of the image
file - so if you update an image, the version id changes. That way, when you
update an image, the browser immediately picks up the new image. It won't
pick up the image it has in cache, because that has a file name with the old
version id.
A few more details about how this feature works:
- The package only inserts the version id in
the tags that are sent to the browser. The file names of your images on disk
stay the same.
- There is no need to change your image file
names manually. When the browser sends a request for an image, the image
file name will have the version id. But the package takes care of stripping
out the version id and serving the correct image to the browser.
- To find out the version id, the package
needs to access the file system to find out the last update time of the
image file. You don't want this to happen for each request, so the package
stores the version ids in server cache. These cache entries are invalidated
the moment the underlying file changes, so the cache is never outdated.
- Why insert the version id in the file
name? Why not just add it as a query string? That would be a bit easier to
handle. However, proxies (intermediate servers that pass on messages on the
Internet) are less likely to cache files with query strings. So by inserting
the version id in the file name instead of using a query string, you gain
maximum proxy caching.
- There is no counterpart of
insertVersionIdInImageUrls for JavaScript and CSS files, because the package
always uses version ids and long term caching for those files.
Additional Installation
Now that the package inserts a version id in the image file names used in image
tags, the browser will send requests for file names with version ids. Those file
names won't match actual image files on your server, because they don't have the
version ids. To solve this, you need to get the package to handle incoming
requests for images, so it can remove the version id and load the correct file.
Additional Installation for IIS 7
Take these steps if you use IIS 7 for your live site, and also if you use the
package in your development environment. The steps to take for IIS 6 are here.
In the installation instructions for IIS 7 for the entire package, you modified
your web.config file to get the HTTP Handler to handle .js and .css files.
Now change web.config again, so the HTTP Hander also handles .gif, .png and .jpg
files:
</configuration>
...
<system.webServer>
<validation validateIntegratedModeConfiguration="false"/>
...
<handlers>
...
<!-- required for insertVersionIdInImageUrls attribute -->
<add name="GifHandler" verb="*" path="*.gif" type="CombineAndMinify.HttpHandler,
CombineAndMinify" resourceType="Unspecified"/>
<add name="PngHandler" verb="*" path="*.png" type="CombineAndMinify.HttpHandler,
CombineAndMinify" resourceType="Unspecified"/>
<add name="JpegHandler" verb="*" path="*.jpg" type="CombineAndMinify.HttpHandler,
CombineAndMinify" resourceType="Unspecified"/>
<!-- required for all features -->
<add name="JavaScriptHandler" verb="*" path="*.js" type="CombineAndMinify.HttpHandler,
CombineAndMinify" resourceType="Unspecified"/>
<add name="CssHandler" verb="*" path="*.css" type="CombineAndMinify.HttpHandler,
CombineAndMinify" resourceType="Unspecified"/>
</handlers>
...
</system.webServer>
...
</configuration>
Additional Installation for IIS 6 or IIS 7 in classic mode
Follow these steps if your live site uses IIS 6. If your live site uses IIS 7 or
higher, you can skip this.
1. In the installation instructions for IIS 6 for the entire package, you
modified your web.config file to get the HTTP Handler to handle .js and .css
files.
Now change web.config again, so the HTTP Hander also handles .gif, .png and .jpg
files:
<configuration>
...
</system.web>
...
<httpHandlers>
...
<!-- required for insertVersionIdInImageUrls attribute -->
<add verb="*" path="*.gif" type="CombineAndMinify.HttpHandler, CombineAndMinify"
/>
<add verb="*" path="*.png" type="CombineAndMinify.HttpHandler, CombineAndMinify"
/>
<add verb="*" path="*.jpg" type="CombineAndMinify.HttpHandler, CombineAndMinify"
/>
<!-- required for all features -->
<add verb="*" path="*.js" type="CombineAndMinify.HttpHandler, CombineAndMinify"
/>
<add verb="*" path="*.css" type="CombineAndMinify.HttpHandler, CombineAndMinify"
/>
</httpHandlers>
...
</system.web>
...
<configuration>
2. In the installation instructions for IIS 6, you also saw how to route all
requests for .js and .css files to ASP.NET. Now use the same instructions to do
the same for these extensions:
For a lot more on browser caching and proxy
caching, see www.iis.net or chapter 12 of my my recently released book ASP.NET
Performance Secrets.
exceptionOnMissingFile
Determines whether the package throws an exception when an image file is
missing.
Value |
Description |
Never
(default) |
The package never throws an exception when an image file is missing. |
Always |
The package always throws an exception when an image file is missing. |
ReleaseModeOnly |
The package only throws an exception if the site is in release
mode. |
DebugModeOnly |
The package only throws an exception if the site is in debug mode. |
Example
<configuration>
...
<combineAndMinify exceptionOnMissingFile="DebugModeOnly"
insertVersionIdInImageUrls="true" >
</combineAndMinify>
...
</configuration>
Assume insertVersionIdInImageUrls is set to true, so the package inserts a
version id in all image urls. This means it has to access each image file to
find its last updated time. What happens if the image file cannot be found? That
is determined by the exceptionOnMissingFile attribute:
- If exceptionOnMissingFile is active (see
table above) and the package finds that an image file cannot be found, it
throws an exception with the path of the image. That makes it easier to find
missing images.
- If exceptionOnMissingFile is not active,
the package doesn't throw an exception but recovers by not inserting a
version id in the image url.
If all images should be present in your
development environment, than it makes sense to set exceptionOnMissingFile to
DebugModeOnly. That way, you quickly find broken images while developing your
site, while preventing exceptions in your live site where you probably prefer a
broken image over an exception.
What about JavaScript and CSS files? The package accesses these files when
combining and / or minifying them:
- If exceptionOnMissingFile is active and a
JavaScript or CSS files can't be found, you'll get an exception, just as
with images.
- If exceptionOnMissingFile is not active
and a JavaScript or CSS files can't be found, it just writes a comment in
the combined and / or minified file, specifying the full name of the file
that couldn't be found.
Keep in mind that if you want exceptions while
the site is in debug mode, you have to ensure that the package is actually
active in debug mode - set active to Always to make that happen.
removeWhitespace
Determines whether the package removes superfluous white space and comments from
the HTML of the page.
Value |
Description |
true |
Superfluous white space and comments are removed. |
false
(default) |
No superfluous white space and comments are removed. |
Example
<configuration>
...
<combineAndMinify removeWhitespace="true" >
</combineAndMinify>
...
</configuration>
When you set removeWhitespace to true, the package removes all HTML comments
from the page and collapses all runs of white space into a space. However, if a
run of white space contains one or more line breaks, it is collapsed into a line
break. That way, inline JavaScript will not be broken.
headCaching
Determines how tags for combined JavaScript files and CSS files are cached.
Value |
Description |
None
(default) |
Caching of replacement tags is switched off. |
PerSite |
There is a single cache entry for the entire site. |
PerFolder |
There is a cache entry per folder. |
PerPage |
There is a cache entry per page (ignoring any query string). |
PerUrl |
There is a cache entry per url (including any query string). |
Example
<configuration>
...
<combineAndMinify headCaching="PerSite" >
</combineAndMinify>
...
</configuration>
Even though the package caches all minified and combined files, there is still
some work involved in replacing tags of individual JavaScript files and CSS
files with tags of combined files. Without further caching, this needs to be
done for each page request.
To reduce the CPU usage involved in this, the package provides the option to
cache the replacement tags. The recommended way to do this depends on the way
you load your JavaScript and and CSS files:
Situation |
Recommended
Value |
The additional CPU usage of the package is not an issue. Or the tags to load
JavaScript and CSS files are totally ad hoc per page. |
None
(default) |
All pages load the same JavaScript and CSS files in the same order.
For example, all pages uses a single master page, and the master page
has all the script and link tags to load the JavaScript and CSS files. |
PerSite |
Your pages are split over folders, and the JavaScript and CSS files you load
depend on the folder. For example, pages in the admin folder all load the same
JavaScript and CSS files in the same order, but those files are different from
the ones loaded by pages in the products folder. |
PerFolder |
Each page loads different JavaScript and / or CSS files. However, the query
string doesn't influence which files are loaded. So toys.aspx?id=1 and
toys.aspx?id=2 load the same files in the same order, but categories.aspx loads
different files. |
PerPage |
The JavaScript and CSS files used by a page depend on the entire url, including
its query string. So toys.aspx?id=1 and toys.aspx?id=2 load different files. |
PerUrl |
The headCaching attribute really comes into its own if you load JavaScript and
CSS files from a master page or a shared user control. This is because the
package caches entire groups of tags, and the tags to replace those groups with.
This process is sensitive to for example white space in between tags. That means
that
<script type="text/javascript" src="/js/script1.js" ></script>
<script type="text/javascript" src="/js/script2.js" ></script>
and
<script type="text/javascript" src="/js/script1.js" ></script>
<script type="text/javascript" src="/js/script2.js" ></script>
are not the same, due to the extra white line in the second block.
Conclusion
This package will improve the performance of any web site that loads JavaScript,
CSS or image files. Please give it a try with your site. If you find any bugs or
have any trouble with the documentation, let me know and I'll try to fix the
issue. Feature requests would be welcome too.