I really like the move to separate module CSS into separate files - this makes maintainance and overriding much easier.

However I have been concerned about the fact that these become separate HTTP requests, each of which adds an overhead to the initial page load. Now, before anyone screams 'but they are cached!!!' I should point out that I am talking about the *initial* page load - i.e. the first visit by a new (or infrequent) site visitor.

Anyone reading up on web performance and usability should be able to tell you that (of all page load times) this initial page load time is vitally important. If the site is slow to load that first time there is a high probability that the user will think 'this site is too slow' and hit the back button and go somewhere else. For an ecommerce site - losing 10% of your visitors this way means losing 10% of your income, then the problem becomes clear!

On the other hand, we could potentially gain by selectively including these CSS files, so only ones actually required on the page are loaded. However, in practice it would be difficult for a module to determine when it needs to include it's CSS, so most would probably just include it on all pages. This would also make debugging CSS more complex.

To test this hypothesis, we need to:
(a) Simulate a 'real' network connection (with lag and typical download speeds), but we obviously can't do these tests on a live site, as internet load times are too eratic
(b) Simulate an initial page load (i.e. ensure that the browser has nothing previously cached)
(c) Simulate a typical site, with 15 or so modules enabled

To do this I did as follows:
* Used Apache running (without any additional load) on my local machine
* Used the Charles proxy to throttle and lag downloads to the level of a 64kbps line, and also to time & measure download times
* Used a default Firefox browser profile, with caching disabled and no extensions
* Used a default Drupal install, with all core modules enabled. Of course, a normal site would probably have some core modules disabled, but they would also almost certainly be using several contrib modules. I didn't want to do this however, because contrib modules might potentially have performance problems of their own.
* Used an anonymous user (typical for initial page load)
* Appended all the site css files to an 'old style' drupal.css
* Created a patch to drupal_get_css() that returns this file instead of the separate CSS files.

I did 3 sets of test (both with, and without the patch) of 10 page loads each and averaged the results.
* With patch (i.e. drupal.css)
* Average per set (10 page loads): 72973ms
* Average per page load: 7297ms
* HTTP requests per page load: 5

* With patch (i.e. drupal.css)
* Average per set (10 page loads): 122671ms
* Average per page load: 12267ms
* HTTP requests per page load: 21

As you can see, there is a ~70% increase in total download time.

Next, it occured to me that most modern browsers both can maintain multiple persistent HTTP connections and can also pipeline HTTP requests (i.e. request several resources in parallel). Perhaps this would reduce the overhead of these requests.

To test this I used the Fasterfox Firefox extensions to time page loads, at the *firefox default* setting - i.e. just as a total, end-to-end, page load timer.
This was done with recording off in Charles, but with 64kbps throttling on, and caching off (as before).

I measured 10 page loads both with and without the patch:
* Average page load time with patch: 5.74s
* Average page load time without patch: 8.12s

This represents a 41% increase in initial page load times (and 3 more seconds) when using separate CSS files, even when using a modern pipelining browser.

I can see 4 possible answers to this issue:
1) We ignore it, and hope that not too many people are turned away by a slower initial load times
2) We go back to the old system, with a loss in maintainability and flexibility
3) Try and selectively include CSS files only when they are needed and minimize the number needed on the home page.
4) We implement a simple caching system, where individual css files are appended to a single [media-type].css and placed in the files directory. The browser is then directed to that file. This cache file only needs to be updated when the indiviual css files are changed - probably just on /admin/modules.
5) We implement hooks in drupal_get_css() that allow a contrib module to provide option (3). This oprion also has the benefit that a contrib module would potentially be able to use a CSS (or JS) syntactic compression library to provide further gains in performance.

With the advent of jQuery into core, we can also expect a large increase in the number of .js files provided by modules, and it probably would make sense to implement a similar system for these files.

The attachment contains the detailed results for the HTTP tests (in both Charles and CSV format) and browser tests (in CVSV format).

Personally I think either of options 4 or 5 are the way to go. If there is some consensus on which option we should go for I would be happy to work on a patch for this.

CommentFileSizeAuthor
css_performance.tar.gz156.97 KBOwen Barton
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

eaton’s picture

in looking over a default page, it does appear that admin.css, forum.css, help.css, and watchdog.css, are all being included unecessarily. forum.css, for exampl,e is only necessary *when navigating forum pages*. watchdog is only necessary *when examining watchdog entries*. We'd do well to include those conditionally rather than all the time. Some of the others -- block.css, user.css, system.css, node.css, and so on... well, I'm not sure. :)

Crell’s picture

The proper fix, I believe, would be to move the drupal_add_css() call that is now in every module's hook_menu() call to the page handlers for that module, and anywhere else in that module that it is needed. Let the module figure out when it will need its CSS, and add it when necessary.

I believe that was the intent originally, and it makes sense, so the task now is to tackle modules one at a time and move drupal_add_css() to where it makes sense.

logictude’s picture

I'm a fan of #3 and #4, selective construction of a single CSS file. You could do some of #3 by overriding theme_stylesheet_import, something that's been discussed before. I also tried overriding theme_get_styles, hoping to write the consolidation portion in #4, but it wouldn't take. I'm still a bit baffled by that.

Just for arguments sake I ran a test on a sample page.

Version 1: Drupal generated all of the CSS calls, a total of 4 separate CSS files.
Version 2: I modified page.tpl.php to only load 1 CSS file, a consolidated version of all the original CSS files.

I ran both pages through a simple assessment application and found only a minor difference.

HTTP Requests
V1: 19
v2: 16

Total Size (bytes)
V1: 45385
V2: 42471

Object Sizes (bytes)
HTML
V1: 6914
V2: 6693
CSS Images
V1: 4343
V2: 1647
CSS
V1: 17268
V2: 17271

Download Times (seconds)
56K
V1: 9.85
V2: 8.66
T1
V1: 1.04
V2: 0.43

Although it'd be nice to decrease the number of HTTP Requests, I'm not sure there's a great deal of benefit. Of course, I say this while handing out giant grains of salt. This is just one test on file with maybe a dozen modules activated.

Thoughts?

Owen Barton’s picture

I agree with Eaton and Crell that selectively calling drupal_add_css() would tidy things up a lot. However these calls will need to go in the modules page view, node hooks and potentially even hook_block. This feels like quite an increase in code complexity to me - it would be quite a lot of work just tracking down what code is being styled by what CSS and maintaining all these calls as the codebase changes. As Eaton points out, there would still be quite a few css files that would need to be included on practically every page anyway. That said, I think this approach could mitigate the performance problems described above, but we might want to consider if there is a simpler way of implementing this than scattering calls to drupal_add_css() all over the place :)

For me the caching solution (4) or the hook_modify_css() solution (5) seem simpler, as they involve a smaller, more localised code change and allow up to hook in to do other optimizations, such as syntax and/or gzip compression of css and js output.

Logictude: Thanks for contributing your benchmarks! I am a bit confused by how your number of http requests only dropped by 3, when the css files were consolidated, when mine were dropping by 14 or 15 (depending on whether the theme css was included in the consolidation, which we would probably not do for many sites). It is also interesting that you had a decrease in download size - my download size was almost exactly the same before and after, with only a few bytes different because of the reduced number of css include tags in the .
I also feel that, although this can already be done at the theme level, it would be much more elegant to deal with this at the core or module level.

Thanks for the feedback folks! Let's keep this one rolling...

moshe weitzman’s picture

conditionally contructing a single css file per page view doesn't work too well because you lose the benefit of browser caching. personally, i don't mind the extra requests, and think other solutions add too much complexity relative to their benefit. of the ones presented, including fewer css files (like watchdog) based on context is the simplest. and it should be watchdog itself who decides this.

benchmarks on this are appreciated but they are highly dependant on the latency of the requestor. this really only affects high latency requestors.

Dries’s picture

I can do some benchmarks. On a busy server, it can take 1-2 seconds before your request is being served. If you need to do that for each .css file, you end up with really slow page loads ... (even with client-side caching, the client has to do a HTTP request for _each_ CSS file).

Crell’s picture

I threw together a quick patch to only include forum.css when needed: http://drupal.org/node/82133

If everyone takes 2 modules or so, we can have them all nicely conditionalized quickly. No API change, some performance improvement, and doesn't contradict more advanced refactoring inside drupal_add_css().

Putting drupal_add_css() inside hook_menu() is a hack anyway, IMO. :-) A hook_headresources() or something for always-needed CSS and JS makes more sense, but for things that are not always needed, just include them where necessary.

Owen Barton’s picture

Moshe: conditionally contructing a single css file per page view doesn't work too well because you lose the benefit of browser caching. personally, i don't mind the extra requests, and think other solutions add too much complexity relative to their benefit. of the ones presented, including fewer css files (like watchdog) based on context is the simplest. and it should be watchdog itself who decides this.

I agree that mixing approaches (3) and either (4) or (5) is a bad idea, because - as you say, constructing a different css per page view will break caching. I think we either have to go the direction of (3) - by dynamically changing what css is loaded - or the direction of (4) or (5) - by presenting a single static cached file. I think we should wait for more thoughts & benchmarks before make we a decision (or start committing drupal_add_css() calls in each page view).

My gut feeling is that the reduced number of http requests with (4) or (5) may be more significant in actual page response time than the fact that there is a greater volume of code being sent initially (compared with 3).

The majority of the css files are 150-600 bytes in size. 150 bytes is about the same as http header, so 50% of the data transfer is being wasted at the http level. In addition, with files this small, this is not very efficient at the packet level either. This doesn't in itself explain the performance problem (which is mostly just latency), but I think it is a factor worth considering!

Benchmarking this issue is a challenge, as the usual server sided tools do not help - please share any useful techniques. I do think it is worth differentiating between http-level numerical, non-pipelined benchmarks (e.g. the charles proxy), and 'real life' browser based (pipelined) page generation times (e.g. the Fasterfox page timer)

The possibility of making compression possible (by a contrib at this stage, I think) is also exciting.
-- For example, the css file I made by appending all the module css files is 18784 bytes.
gzip this and it becomes 4522 bytes. This is 24% of it's original size.
-- Drupal javascript (in a single file, including jquery) is 41878 bytes, compressed it is 16288 bytes - 38% of it's original size.
Multiply the savings (39852 bytes) by (say) 5000 initial visitors a month and you just saved yourself 190GB of bandwidth.
This does come at a cost, of course (which is why I suggest contrib for now), as there would need to be some kind of handler to set mime/caching and pipe through the presaved file (as appropriate for the browser).

There is an interesting link on CSS compression over at: http://www.fiftyfoureleven.com/sandbox/weblog/2004/jun/the-definitive-cs...
I recommend reading through some of the other articles in the 'history' section - as these present a pretty balanced (IMO) view of the subject.

http://www.stuartcheshire.org/rants/Latency.html is also an interesting (if somewhat outdated) read.

logictude’s picture

Grugnog2: "I am a bit confused by how your number of http requests only dropped by 3, when the css files were consolidated". There was a decrease of three because V1 had 4 separate calls to CSS files and V2 only had 1, the consolidated file.

The decrease in HTML is due, of course, to the fewer lines of code. I'm not sure about the CSS Images difference though, that's a bit confusing.

I'm curious to see more benchmarks and of course see just whether or not subjective filtering and consolidated files actually yield a substantial increase in speed.

And, just for fun, try running running your pages through http://www.websiteoptimization.com/services/analyze/. It's fun.

m3avrck’s picture

I knew this issue was going to creep up after my split drupal.css patch went in :-)

I'm in the camp that we should better conditionally load CSS files when needed. Many of them are loaded when they aren't needed at all. However, adding in various calls to drupal_add_css() can certainly clutter up the code a bit, but in reality, it's only a handful of extra lines instead of just one catchall in hook_menu().

I was thinking a hook_add_css() would be useful for modules where they could pass in an array of when their CSS should be loaded "menu, block, node, page, admin" and then the hook can distribute those CSS calls. This would be the cleanest approach but certainly require some thinking.

logictude’s picture

So I started to wonder whether or not selective inclusion and/or a combined CSS file would ultimately help the most. I have two topics I'd like to raise on the matter.

Point 1: Backfire?
If we selectively choose which CSS files to include AND create one master CSS file ("combined.css") to call in the page header, would there be issues with caching on the browser side? I know this may not be a huge deal but we've seen some browsers choke on CSS file updates. In essence, "combined.css" may change based on the page, thus requiring a comparative check with the server. There's more to discuss on this topic, I'm sure.

Point 2: No external CSS files
I decided to continue my test from earlier before and add V3. Here are the results.

Version 1: Drupal generated all of the CSS calls, a total of 4 separate CSS files.
Version 2: I modified page.tpl.php to only load 1 CSS file, a consolidated version of all the original CSS files.
Version 3: No external CSS files. CSS was included inline within <style></style> tags.

HTTP Requests
V1: 19
v2: 16
v3: 4

Total Size (bytes)
V1: 45385
V2: 42471
V3: 39543

Object Sizes (bytes)
HTML
V1: 6914
V2: 6693
V3: 22683
CSS Images
V1: 4343
V2: 1647
V3: 0
CSS
V1: 17268
V2: 17271
V3: 0

Download Times (seconds)
56K
V1: 9.85
V2: 8.66
V3: 7.88
T1
V1: 1.04
V2: 0.43
V3: 0.21

Conclusion: We're pretty much just moving bytes around but one thing that's quite significant is the drop in HTTP requests.

Here's the breakdown.
V1: (1) HTML, (2) JS, (4) CSS, (1) IMG & (11) CSS-IMG
V2: (1) HTML, (2) JS, (1) CSS, (1) IMG & (11) CSS-IMG
V3: (1) HTML, (2) JS, & (1) IMG

Interesting discovery, something I didn't personally know - inline CSS with background:url(); styles will not increase the number of HTTP requests. Respective webservers, in this case Apache, must fulfill these requests upon the original HTML request.

It's an evening of personal discovery.

Crell’s picture

Inlining the CSS in the header may reduce the HTTP request count, but as soon as you request a second page it becomes a net loss. That's the entire point of client-side caching. :-)

-1 to any CSS/JS inclusion that breaks client-side caching. It's there for a reason as we should leverage it. I would be OK with a hook-based solution, but only if we had a good way to implement it. Just on the path won't work, as some (eg, location's css file) will be needed on some nodes but not others.

For 5.0, at least, I think the few extra bytes of PHP for multiple calls to drupal_add_css() where necessary are a good price for reducing the amount of CSS traffic needed.

logictude’s picture

Crell: I wasn't suggesting that inline CSS was the right solution, just providing more perspective and data.

Wesley Tanaka’s picture

bug 82922 was marked as a duplicate of this. commenting here to subscribe.

moshe weitzman’s picture

Ted articluated a nice fix for this issue. He proposes to append all the css files into one file and store it in the files dir with a name being a hash all the paths of all participating css files. the mega file would include comment delimiter so one could see which file contributed what css.

this way, we keep some browser caching, but cut all the requests down to one. killes suggested storing two versions (on demand): one gzipped and one not.

I expect Ted to make a patch when time permits.

sime’s picture

And...

  • If the files directory wasn't writeable, the css would be delivered as normal
  • You'd give admin a way to nuke the mega-CSS file when changes were made.
  • You'd allow modules to over-ride the mega-file as needed.

Is there any downside to this method?

Crell’s picture

Is there any downside to this method?

Still sends unneeded CSS code. On a site of 1000 users, maybe 3 need the watchdog CSS, ever. You can browse through a site and never have need of the event module CSS because you didn't happen to view an event page.

Development becomes harder, unless we're always re-stating the source CSS files for changes. You'd need to nuke the compiled file every time, or else be able to disable the cached version.

Would every theme then get its own compiled CSS? Remember, one of the main reasons for splitting up the CSS files was to make it easy (read: possible) for themers to arbitrarily cherry-pick which module CSS files they want to override or exclude. How would this affect that? What is the impact on sites using sections.module or making use of the new arbitrary template-per-path functionality? (I don't think there's any with the latter, but I'm just throwing it out there for completeness.)

Would we then do the same with a hook_js()? How would that get cached? Some of those files could get really large, and be needed on only one or two pages.

sime’s picture

That's a good summary to get the juices flowing.

Still sends unneeded CSS code. On a site of 1000 users, maybe 3 need the watchdog CSS, ever. You can browse through a site and never have need of the event module CSS because you didn't happen to view an event page.

That would be a good example of where the css is defined as always being a separate file and only sent when required. I see what you mean though, there is a grey area in the middle.

Development becomes harder, unless we're always re-stating the source CSS files for changes. You'd need to nuke the compiled file every time, or else be able to disable the cached version.

Heaps of people get stuck not clearing the cache table during development, but that doesn't make cache bad. There would soon be a solution if needed. Eg. user 1 always gets separate files, or there is a development mode.

Would every theme then get its own compiled CSS? Remember, one of the main reasons for splitting up the CSS files was to make it easy (read: possible) for themers to arbitrarily cherry-pick which module CSS files they want to override or exclude. How would this affect that? What is the impact on sites using sections.module or making use of the new arbitrary template-per-path functionality? (I don't think there's any with the latter, but I'm just throwing it out there for completeness.)

Would we then do the same with a hook_js()? How would that get cached? Some of those files could get really large, and be needed on only one or two pages.

Yes, heaps of good points that I don't have good answers for :-)

killes@www.drop.org’s picture

See also this issue abotu gzipping the CSS:

http://drupal.org/node/11128

Should be possible to create a normal and a gzipped version of the combined CSS files and server the gziped if the browser supports that.

m3avrck’s picture

Yes I'm going to start work on a patch in the next couple days to address these issues.

Owen Barton’s picture

In response to Moshe:

Ted articluated a nice fix for this issue. He proposes to append all the css files into one file and store it in the files dir with a name being a hash all the paths of all participating css files. the mega file would include comment delimiter so one could see which file contributed what css.
this way, we keep some browser caching, but cut all the requests down to one. killes suggested storing two versions (on demand): one gzipped and one not.

I feel like we are going round and round here! This is basically the same fix as I favoured in my original post and comment #8:
4) We implement a simple caching system, where individual css files are appended to a single [media-type].css and placed in the files directory. The browser is then directed to that file. This cache file only needs to be updated when the indiviual css files are changed - probably just on /admin/modules.

In response to Crell:

Still sends unneeded CSS code. On a site of 1000 users, maybe 3 need the watchdog CSS, ever. You can browse through a site and never have need of the event module CSS because you didn't happen to view an event page.

Indeed, but according to my tests above it is normally going to much quicker to send the unneeded CSS (especially gzipped), because of the http overheads.

Would every theme then get its own compiled CSS? Remember, one of the main reasons for splitting up the CSS files was to make it easy (read: possible) for themers to arbitrarily cherry-pick which module CSS files they want to override or exclude. How would this affect that? What is the impact on sites using sections.module or making use of the new arbitrary template-per-path functionality? (I don't think there's any with the latter, but I'm just throwing it out there for completeness.)

Personally, I don't see a problem with a cached css file for every theme. It's not like it is really going to take much disk space or maintanance.

Would we then do the same with a hook_js()? How would that get cached? Some of those files could get really large, and be needed on only one or two pages.

I guess there is no reason why modules could not still use drupal_set_html_head() for these cases where there is a really large css of js that only applies to certain pages. However my gut feeling is that these are pretty rare,

merlinofchaos’s picture

I am against dynamic CSS files because of IE.

IE is *really* aggressive about caching a CSS file. Try developing exclusively with IE. Go and edit your CSS file. Try and get that file to update without forcing it.

Wait a few days without clearing your cache. You'll continue to get old results.

Dynamic CSS will simply *not* work reliably with IE. As far as I can tell, ever.

The solution I believe is best is to add CSS and JS files only when necessary. I've never been a proponent of adding CSS files always, especially when a good chunk of the CSS files will only be included in particular cases.

It's very clear that things like forum.css and admin.css only need to show up on particular pages. In fact, *in general* it's very easy to tie CSS to a particular section, include it conditionally, and be done with it.

drupal_add_css should check to ensure no CSS is checked twice (I believe it already does though I'm not sure on that) and then drupal_add_css can safely be called whenever it needs to be. This is not any more complex than any other declaration -- and that's exactly what it is. It's a declaration that output this code is generating needs this CSS or this JS file.

sime’s picture

Is it possible to steamroll the IE caching issue by using uniquely named css files? OK, not a pretty solution, depending on your love of timestamps, but:

bluemarine-060928-1049.css

Once you finish development, you somehow trigger the creation of a new combined css file(s).

killes@www.drop.org’s picture

sime: That's part of the original proposal by Ted. He'll use md5 hashes of the file contents so the css will only change if its contents changes.

Dublin Drupaller’s picture

good discussion guys.

Here's my 0.02 cents on the topic...I'm not sure if it is applicable, but, here it is anyway.

I recently started using a style.php file instead of a style.css recently to handle style sheets. The main reason was it's much easier to build a theme when the css is in seperate files and you can start using php within your CSS, which is a handy thing.

Here's an example of a style.php I'm using (style.php is referenced in the page.tpl.php file in the head)

header("Content-type: text/css");
$primary = '#A9A9A9';
$primary_highlight = 'red';
$secondary = '#FFCC99';
$secondary_highlight = '#eaeaea';
$subtle = '#eaeaea';
$bg_colour = '#000';
$main_text_colour = '#A9A9A9';
$page_border_colour ='#202020';
$pre_background_colour ='purple';
$marker_text_colour ='red';
$agg_background_colour ='green';
$forum_background ='silver';
$forum_container_background ='#eaeaea';
$forum_container_link ='#990000';
$form_field_bgcolor = '#202020';
$form_field_error = 'red';
$form_field_text = '#fff';
//** link colours **//
$link_normal = '#eaeaea';
$link_title = '#eaeaea';
$link_hover_highlight = '#FFFFFF';
$title_link_normal = '#999999';
$title_link_hover_highlight = 'red';
$breadcrumb_links = '#666666';

//** block colours **//
$blocks_background = '#202020';
$block_titles = '#fff';
$default_border = '#606060';

include("page_framework.css");
include("blocks.css");
include("forum.css");
include("discography.css");
include("audio.css");
include("admin.css");
include("drupal.css");

How I reference the colours within the css is illustrated below using the body tag.

body {padding: 0;margin: 0; background-color: <?=$bg_colour?>;font: 76% Arial, Helvetica, sans-serif;}

That is extremely handy when the project is at the development stage and the client asks for a different colour scheme.

In the context of very large sites with a very large audience, I can see the logic in using the same principle in a more advanced way:

For example.

  • parse the css - remove double spaces and all comments - so you can comment to your hearts content
  • using arg(n)s, or other, to include or not include certain files, such as forum.css when viewing the forum for example

that might increase the efficiency of how styles are handled at both ends..i.e. individually they are easier to edit and the php delivers it all as one request without comments and without extra spaces, so it's very small.

on the cache front, I don't experience the same problem others have mentioned about IE cacheing the CSS when I'm using the style.php structure. I'm not an expert on cacheing, but all I have to do when previewing a change in IE is click refresh once.

I'm not sure if this adds to the discussion or not, or complicates how modules "include" their styles, but, I thought I would mention it.

Dub

moshe weitzman’s picture

yes, folks should only add their css when they need to. but thats not going to get us very far. we are trying to significantly reduce http requests andf thus speed up browser rendering.

the IE issue that earl describes is not relevant IMO. each file that gets generated has a unique filename. ie will not get confused about filenames. if you update a module and receive a new css file, then the css file cache needs clearing, just like any other cache. further, if a module is using php to produce dynamic css (i don't know any current module that does this), then it should opt out of the css cache with a simple param to drupal_add_css(). sime mentioned this a couple posts ago.

we'll all have a chance to test these theories one Ted's patch arrives. I think aggresive browser cache is a non issue. perhaps it will bother developers like the current menu cache does. our usual workarounds for that menu cache apply to the css cache.

and yes, we do seem to be going in circles a bit, but sometimes thats whats needed.

sime’s picture

Yeah, I've caused a few circles. I think I'm on track now! :-)

Crell’s picture

IE having a broken cache mechanism will affect both a regularly-recompiled CSS file and regularly-edited module-specific CSS file the same way, so that's not a deciding factor. It's a PITA either way. :-)

Performance-wise, I guess we'd need to see benchmarks to know which was better for overall performance; sending unnecessary CSS or making multiple CSS requests.

I suppose the mechanisms needn't be mutually exclusive. We could have a hook_css() that is used on visiting admin/modules (or whatever the path is now) to build the "custom-built drupal.css" :-), and a drupal_add_css() to allow for selectively adding large or genuinely rarely-used CSS. It would be up to each module's author to decide which applied in which situation.

Of course, that then still complicates the logic for allowing themes to cherry-pick what they override, since there's two different times they'd need to do so. I guess we'll have to see what magic Ted comes up with. :-)

Open question: Aren't we in a feature-freeze? Just how much CAN we do right now in this regard? Is hook_css() even legal to add at the moment?

Owen Barton’s picture

IE having a broken cache mechanism will affect both a regularly-recompiled CSS file and regularly-edited module-specific CSS file the same way, so that's not a deciding factor. It's a PITA either way. :-)

Actually I think Ted's 'contents md5' filename idea is actually an improvement over what we have now, since right now if you edit a CSS you (often) have to 'force refresh' in IE to see the results. With the new system, we generate a new cache file, with a new name, and hence IE is forced to redownload the file.

Performance-wise, I guess we'd need to see benchmarks to know which was better for overall performance; sending unnecessary CSS or making multiple CSS requests.

The benchmarks I did for the original bug report were testing exactly this, and found a 70% increase in page time (40% when browser pipelining is taken into consideration).

I suppose the mechanisms needn't be mutually exclusive. We could have a hook_css() that is used on visiting admin/modules (or whatever the path is now) to build the "custom-built drupal.css" :-), and a drupal_add_css() to allow for selectively adding large or genuinely rarely-used CSS. It would be up to each module's author to decide which applied in which situation.

Indeed - I think we want to keep the option of selectively including CSS file open, since there are obviously cases where this the most sensible option (e.g. section/subsite CSS).

Of course, that then still complicates the logic for allowing themes to cherry-pick what they override, since there's two different times they'd need to do so. I guess we'll have to see what magic Ted comes up with. :-)

Perhaps we could have a system like the menu cache system, where we maintain separate arrays of for cache/non-cache.

Something that might simplify things would be to automatcally add .css to the (cached) list, like we do for theme css now. Any thoughts on this?

Open question: Aren't we in a feature-freeze? Just how much CAN we do right now in this regard? Is hook_css() even legal to add at the moment?

I think the extra 3+ seconds initial page load time (on 64k ISDN) the separate CSS files add definately counts as a bug! As I mentioned earlier, every second increases the chance that the user will hit 'back' and go elsewhere.

Crell’s picture

Actually, I don't like the idea of md5ing the contents for the file name because it becomes incredibly obscure. There's no way from looking at it to know what in the world it is. It looks like a junk file. I'd be much happier with print.css, screen.css, etc. as it's much more semantically meaningful. We shouldn't make the system more obscure and opaque just to get around IE being dumb. Besides, you'd still need to reset the Drupal CSS cache anyway on every change anyway. (I'm assuming that restating every file on every page load is needlessly inefficient.)

dvessel’s picture

Nothing to add. Anything to minimize linked styles would be great. Just want to keep tabs on this.

Crell’s picture

Any progress here on Ted's solution? If we're not going to compile the CSS file, then we should start moving the drupal_add_css() declarations. One or the other, but we need to get it done soon.

m3avrck’s picture

Patch this week. Was waiting for Garland to land because of some technical overlap. It just went in so patch soon.

Crell’s picture

Version: x.y.z » 5.0-beta1
Priority: Normal » Critical

Assigning this to 5.0-beta1 and setting as critical. A 40% performance hit would be a bad thing to leave in there.

m3verick, how's the patch coming? If you're busy putting out other fires, is it something that you can post for someone else to work on?

m3avrck’s picture

Yes this is indeed critical, I have way more evidence to back this up from a large site I deployed last week.

Starting patch tomorrow morning, I've thought through it so much shouldn't take too long -- a day or so :-)

kkaefer’s picture

Version: 5.0-beta1 » 5.x-dev
jacauc’s picture

I'm not an expert on the subject at hand, so excuse my ignorance (Just thought i'd mention this)
What about removing whitespace as well? ...If the CSS are be generated into the files folder.
http://flumpcakes.co.uk/css/optimiser/

..and there... now i'm subscribed too :P

jakeg’s picture

I agree with the last comment, plus a bit extra on top.

What would be great is if there's an admin page where the admin can choose various CSS options such as colours and other options. On pressing submit, PHP generates an optimized CSS file which is stored as a static file and sent to the client. As the CSS file is generated by the system and not human hands, there's no reason why it can't be compressed/optimized in the same way that JQuery is. In other words, no white space at all.

Without the admin page to choose CSS options (which I would presume would be more a Drupal 6 thing at this late stage), it would still be a good idea for Drupal to create an optimized/compressed CSS file which it stores on disk and serves statically rather than dynamically, all as one file, not all the separate files.

jakeg’s picture

Just to clarify, my optimum is as per Drupal Dubliner's #25 above, but rather than serving a dynamic style.php (and the performance/caching penalty due to this) the $colours etc variables are textfield on an admin page, and on submitting that admin page, the optimized CSS file (including white space compression) is created. Again, more a Drupal 6 suggestion, but something that may be worth thinking about at this point at least.

merlinofchaos’s picture

jake: color.module already does this to some extent.

sime’s picture

We should not diffuse the focus of this ticket too much. The aim of this ticket should simply be to correct a dramatic drop in performance due to multiple css files.

Sure the optimization thing could be in the patch, but if it delays the fix then that would be bad...

Crell’s picture

Agreed. The intended fix is to generate a single CSS file to eliminate needless HTTP traffic. The HTTP overhead is the same whether it's a 4kb CSS file or a 40 kb CSS file; one "is this newer than my cache?" request.

The initial request for the file can be sped up by enabling compression in apache if needs be, but that's irrelevant to this issue.

m3averck? :-)

Owen Barton’s picture

I agree that we should focus here should be on getting a single (cached) CSS output. Let's get that sorted before we worry about the rest.

Having said that it occurs to me that we could get a lot of mileage (appearing in contrib of course) out of a simple hook or two, such as:

- Compression (gzip is much more efficient than whitespace removal here, but both are potentially very risky if you don't get them right first time). This should be available for JS as well as CSS of course.

- Dynamic CSS - I haven't looked at color module much, but it seems like there are potentially a bunch of cool possibilities here, with CSS token replacement and dynamic generation (which implies caching). Think pages.google.com here...

The advantage of adding a hook for these things now is that these features would have a chance to develop (and be tested) in contrib over the Drupal 5.x cycle, which means we would get better patches for this kind of functionality for 6.x, rather than having to start from scratch (or push it off until 7.x).

jacauc’s picture

Yep, I agree that focus shouldn't shift.
good idea to have this in mind for future discussions/features though.

FiNeX’s picture

Maybe the easyest solution is to include only the forum/admin/watchdog/... css when needed.

On the xxx_menu() function can we call the drupal_add_css() function only if we are on a specific area/page of the site?

Gzipping should be an interesting idea, if the web server allow it, but not the final solution. Idem for md5ing or collapsing all the needed css in one single css.

zis’s picture

Here are my 2 cents.. if it's not too late..

I have a connection with a remote caching proxy. It caches css files for days with no way of clearing that cache..

I think the idea of dynamically generating a single file is the best solution. BUT we must find a way to make each different version of that file have a different name..

Someone suggested using an md5 hash to generate unique names. i think it is a good solution. But we must make sure that the hash changes if the css changes..

The downside of that is that there will be redundant caching.. if two css files have different names, they will be cached 2 times.. if they only have a 10% diff, 90% of the file will be cached 2 times.. if you have 10 modules each one having a custom css, one site will have 10 cached files with the same code..
perhaps we could dynamicaly generate a signle css file with a unique name but instead of selectivly including code based on the code needed, it should include all the code needed by all modules.. like now..

This way a user will have one file to download and cache.

m3avrck’s picture

Status: Active » Closed (duplicate)

My patch has landed, it solves these issues and more.

Time to discuss code: http://drupal.org/node/98819