CSS Server-side Constants

Update

Please be sure that you are using at least v004 of CSS Server-side Constants. Previous versions had a critical security flaw. Please remove older versions from your server and download v004

Fueled by Friday’s feedback on CSS Server-side Variables I’ve taken another whack at this idea. The term “variables” was the source of some confusion so I’ve change the name to CSS Server-side Constants which is more representative of what I’m actually trying to implement—and makes for a nice palindrome when abbreviated (as pointed out by Silus Grok).

Syntax

Constant definition and embedding are the same as before:

@server constants {
    constantName: constantValue;
    }

selector {
    property: constantName;
    }

I’ve also added the option to import constants (and CSS) from other files. The syntax for this new feature borrows from the @import rule:

@server url(constants.css);

Constants and nested imports will be parsed and applied to the CSS before it is sent to the browser. Constants are applied in the order they are imported, overriding earlier definitions.

Other Features

CSS-SSC should now work with PHP-as-CGI. Instead of using php_value auto_prepend_file it uses mod_rewrite. As a result CSS files no longer need to be added to the list of file types parsed as PHP.

CSS-SSC keeps track of when files were last modified and sends the appropriate headers to the browser. This should avoid the caching issues mentioned in the comments of the original post.

Installation

Download, unzip and upload the new CSS-SSC replacement script to your CSS directory (if you played with CSS-SSV delete all associated files and lines from .htaccess). Add the following lines to your .htaccess file:

RewriteEngine on
RewriteCond %{REQUEST_URI}  .css$
RewriteRule ^(.+)$ css-ssc.php?css=%{REQUEST_URI}

And start using the @server rules as described above.

Benchmarking

I’ve trimmed a preg_replace() or two and eliminated a for loop in CSS-SSC. But I’ve also added another preg_match_all(), this time in a while loop that uses file_get_contents(). So how much overhead is there? Open up css-ssc.php and uncomment the final echo statement to benchmark your server’s performance. On my server this test case containing 50 constants imported from 4 nested files consistently takes less than three or four thousandths of a second to render. And then the results are cached by the browser. Not too shabby.

Caveats

Importing doesn’t allow you to move up directories. This was a concious decision to elimate another preg_replace() call.

As I mentioned last time, you’ll need to take care with your constant naming. Constants can’t contain the names of other constants or other properties/strings that might appear in your CSS.

Sorry, still no calc() functionality. Since this all happens on the server-side there’s know way to know what width: calc(100%-24px);' equals. That would require a client-side solution. But as far as I know, current browser CSS implementations ignore values they don’t understand and there is no reliable way to get the string contents of a CSS file.

As with all of my offerings, CSS-SSV is provided strictly on an as is basis. You are soley responsible and liable for any damage you do to your website or server.

Previous
Thin is always in
Next
Designing Grid Systems: Part 3
Author
Shaun Inman
Posted
August 9th, 2005 at 12:10 am
Categories
CSS
PHP
Web
CSS-SSC
Comments
035 (Now closed)

035 Comments

001

Aaah, renaming was a good idea… When I read the title of your first post I thought “oh heck, Inman’s smoked his guitar”.

As someone who works with multiple designers in different locations on a daily basis, I can defintely see a place for this technique.

Jolly good show :p

Author
Andrew K
Posted
Aug 8th, 2005 11:16 pm
002

That sounds very good, keep up the good work :)

Author
Abdelrahman
Posted
Aug 9th, 2005 1:18 am
003

Piping CSS through the PHP interpreter isn’t the most efficient approach, because you have to implement caching yourself and every request goes through the PHP interpreter.

This seems like a good use for “funky caching”. Set the 404 handler to your PHP script. Have your PHP script output the stylesheet to a file and then output its contents.

This means that the first access works in the same way as your current approach, and every single access after that is served from the static file - which means Apache handles the 304 Not Modified, ETags, etc by itself.

There’s a small maintenance cost - the stylesheet directory needs to be writable by the Apache user, and you need to delete the static file whenever you update the dynamic stylesheet.

Author
Jim Dabell
Posted
Aug 9th, 2005 3:02 am
004

The $_SERVER['HTTP_IF_MODIFIED_SINCE']isn’t set on my system (Tiger 10.4.2, Apache/1.3.33 and PHP 5.0.4). This can be solved with apache_request_headers() which gets all the headers.

$header = apache_request_headers();
$_SERVER['HTTP_IF_MODIFIED_SINCE'] = $header['If-Modified-Since'];

This function is only supported when PHP is installed as an Apache module.

Author
Sen
Posted
Aug 9th, 2005 3:53 am
005

Jim: The script takes care of caching, no need to worry about permissions or deleting old caches. The goal here is maintainable simplicity.

Sen: I came across that last night but didn’t add it because as you mention it requires PHP be installed as an Apache module. Your comment made me take another look though and I realized I could just test for $_SERVER['HTTP_IF_MODIFIED_SINCE'] and if it is undefined test if the If-Modified-Since header had been set and remap it. I’ve swapped out the download links to the updated version.

Author
Shaun Inman
Posted
Aug 9th, 2005 4:41 am
006

The script takes care of caching

I’m looking at the code now. It generates ETags but doesn’t handle them (If-None-Match).

I went down this path a few years ago and wasted a decent amount of time before realising that I was basically reinventing the wheel and that it’s a better approach to simply let Apache do its job.

IIRC, there were other issues as well - not respecting .htaccess caching directives, not compressing when available, etc.

If you think the tradeoff’s worth it, that’s fair enough.

One bugfix: shouldn’t the stylesheets be publically cachable, not private?

Author
Jim Dabell
Posted
Aug 9th, 2005 5:24 am
007

And more taxonomic goodness: the new name could be pronounced “cassock”, so if it ever takes-off, nay-sayers can speak of the cassock invasion.

Heh.

Of course, I’m still waiting for a calculations engine.

  • waits patiently in the corner *
Author
Silus Grok
Posted
Aug 9th, 2005 6:57 am
008

Now I’m wondering if there can be a mod_rewrite version of Mike’s “Make your website mobile friendly” solution. Having to turn off “Execute PHP as CGI” is a real bummer.

Thanks for CSS-SSC and creating this new version :)

Author
Justin Perkins
Posted
Aug 9th, 2005 9:53 am
009

I’m having a little trouble getting this up and running. I am running WordPress and have placed the css-ssc.php file in the same directory as my CSS file. I have two questions:

1) For the .htaccess stuff, are we creating a new .htaccess file that should reside in the same directory as our CSS files or are we just adding some stuff to the webroot .htaccess file?

2) Our @server rules go in the CSS file, correct?

I have tried all the above (placing the Rewrite rule in my webroot .htaccess and a new .htaccess in my CSS directory. Neither one worked.

Author
Justin Perkins
Posted
Aug 9th, 2005 10:39 am
010

Nevermind, I got it! The .htaccess file with the CSS-SSV rules should reside in the CSS directory and yes, the @server rules go in the CSS file (or in an import).

Works like a dream, thanks Shaun.

Author
Justin Perkins
Posted
Aug 9th, 2005 11:10 am
011

Great idea Shaun - looking forward to implementing it, but I’m having a bit of a problem getting this to work on my setup. Seems to be the .htaccess file. Should it only include the 3 lines in this article, or should it also have the 2 lines from the CSS-SSV post? Thanks

Author
Steve Palmer
Posted
Aug 10th, 2005 6:46 am
012

I’ve got to admit, I don’t understand why this is better than embeding php in the css file. Personally I think it’s easier to scan the css file because I can have php sections highlighted so I can quickly see where I have my variables. In this system, there is no easy way to differentiate a constant from a normal css property value. It also seems like an extra layer of code is being added just because. The css isn’t any more portable or easy to maintain because you are still using constant names that a browser can’t understand. In fact, it’s less portable because you have to have mod_rewrite enabled. I can understand the caching benefits, but I would think it would be better to just come up with a good caching solution and leave the rest to php or asp or whatever other scripting language is your favorite. I seem to be the only one who thinks this, but I still don’t get it.

Author
Ryan
Posted
Aug 10th, 2005 8:05 pm
013

I think I go with Ryan. When I put PHP in de CSS file I can see directly what is CSS and what is PHP. Besides that, that way you can make use of the dynamics PHP has to offer. You corectly talk about Constants but I can think of numorous occasions where Variables might come in handy.

Your way sounds good but it feels like making it what it is not. You present it as being just CSS. But there is a lot going on in the background. With PHP in the CSS file I see it for what it’s worth. CSS with PHP.

Author
Bertje
Posted
Aug 11th, 2005 3:23 am
014

What’s with the apache_request_headers() function, my host (Dreamhost) doesn’t support that (unless I am missing an include).

Author
justin Perkins
Posted
Aug 11th, 2005 11:01 pm
015

Nicely done Shaun. Fantastic idea.

I will have a quick play around later but well done for not just thinking about it and making the idea physical.

Author
Zach Inglis
Posted
Aug 12th, 2005 7:09 am
016

Good work Shaun.

Would it be of any use if the script could strip away tabs, new-lines, unnecessary spaces and CSS comments (like a js-crunchinator) to minimize data the client has to download? Would a smaller CSS file compensate the additional server work?

Author
Miha Filej
Posted
Aug 16th, 2005 7:42 am
017

Just a note for those who question the usefulness of this in the first place (since I’m late to the party): I work on a site which is .NET, and requires localization using images that have text. Of course, the images need to be different if the language is different, and when I started the job, I was told that using CSS background images was really not a good idea because of this, and that I should hardcode everything (and use tables…). Of course, I looked for a better solution, and we’re now writing ASP variables in CSS code in much the same way as the PHP solution. I don’t know if there’s an equivalent of this new technique for .NET and XML files; I’ll look at it when I have more time and pass it on to a developer at my company.

This concept truly is necessary when dealing with anything on a scale larger than a small business site, and it’s allowed me to get started doing standards-compliant work at Fisher-Price.

Author
Eric Shepherd
Posted
Aug 16th, 2005 3:09 pm
018

WOW. This makes life so easy. Thanks so much for this Shaun.

I’ve just started another blog for myself (for coding), using an almost identical design as my original site. blog.jalenack.com.

I made a base css file and put it in my root directory. Each blog has a css file with server variables that make the designs different. The base file is then included and parsed for variables. The fantastic part is that I have one CSS file that makes changes on both domains, while having color differences.

I had to hack up CSS-SSC a little for this to work. Here’s what I did:

$filebase = dirname($_SERVER['DOCUMENT_ROOT']).\"/cssbase.css\"; $css .= file_get_contents(\"$filebase\");

The CSS files called by the browser simply contain @server variables, nothing else. The cssbase.css file is in my root folder. So convenient!

One last thing: I had the same problem Justin Perkins with apache_request_headers() not being defined. I’m on dreamhost as well. Deleting that part fixed it, but is there a better work around?

Thanks again.

Author
Jalenack
Posted
Aug 18th, 2005 3:30 pm
019

Shaun,

Really nice work on this! Though I sort of agree with some of the commenters that it might be overkill, I think it’s great for certain situations. I’d be hesitant to let my programming-impaired coworkers edit a PHP file full of CSS constants, but I’d give ‘em a constants.css template in a second. The syntax is familiar and unintimidating.

That said, I think I’ve found a bit of a bug:

The problem occurs when you define two or more constants that share part of the same name. For example:

@server constants
{
    textColor: #000;
    textColorLight: #666;
}

The CSS-SSC script will first run through the stylesheet and parse all instances of the string textColor, including anywhere it occurs as a substring of textColorLight. So after the first pass, you’re left with #000Light wherever textColorLight had been. This obviously causes some trouble for further parsing.

It’s easy to fix though—just sort the list of variables so they’re parsed in order of longest to shortest. That way textColorLight will already be converted by the time textColor comes along.

This, right before the $css = strreplace(...) line, will do the trick:

function sortKeyLength($a,$b)
{
    return (strlen($a) > strlen($b)) ? -1 : 1;
}

uksort($variables, 'sortKeyLength');

I’ve tested this on my own setup, and it fixes the bug—while not introducing any new ones… that I know of, anyway (even with the sorting, constants are still applied in the order that they were imported).

Again, thanks for all the work!

Author
Zac
Posted
Aug 19th, 2005 10:29 pm
020

Rock Zac, that seems like a great solution. Constants are all added to the $variable array before you do the sort so everything still works as expected. Very nice.

I was going to resort to preg_replace—which might still be necessary if we want to enable using simpler constant names that have exact matches in selectors and CSS property names. Have any insight into that one? :D

Author
Shaun Inman
Posted
Aug 20th, 2005 3:56 am
021

Haha…nah, looks like I’m a one-trick pony. Although, after a bit of experimenting, I did come up with a ridiculously overcomplex method that seems to work:

Replaces line #79:

$variables[' '.$value.' '] = ' '.$vars[2][$var].' ';

Replaces line #83:

$bounds = array(':'=>': ',';'=>' ;','('=>'( ',')'=>' )');
$wspace = array(' :'=>'`:',' `'=>'``');
$css = str_replace(array_keys($wspace),array_values($wspace),$css);
$css = str_replace(array_keys($bounds),array_values($bounds),$css);
$css = str_replace(array_keys($variables),array_values($variables),$css);
$css = str_replace(array_values($bounds),array_keys($bounds),$css);
$css = str_replace(array_values(array_reverse($wspace)),array_keys(array_reverse($wspace)),$css);

Basically, this is what’s going on:

  1. Convert all whitespace preceding a colon to backticks (could just as well be any other character that’s illegal in CSS)
  2. Add whitespace after all colons, before all semicolons, and inside parentheses (Any CSS constant should now be surrounded by whitespace…but CSS property names, which must come before colons, are now adjacent to backticks instead.)
  3. Convert CSS constants — but only those which are entirely surrounded by whitespace
  4. Clean up the backticks and extra whitespace we added in steps 1 and 2.

Got all that? ;) Just looking at it makes my head hurt.

As I said, this works. And it’s about 3 times faster than a comparable preg_replace. But I sure wouldn’t use it! Your caching seems pretty solid, so a few hundredths of a second of speed increase probably isn’t worth turning your code into an ugly Rube Goldberg machine.

Still, I thought I’d throw it out there anyway. Maybe it’ll give someone else an idea.

Author
Zac
Posted
Aug 21st, 2005 5:38 am
022

I get frustrated that the “cascading” aspect of CSS does NOT help me (and frustrated at the folks that simply answer “cascading” to every question:-), so I’m very sympathetic to CSS variables.

I want to use a particular color for my editorial comments throughout a page: in the header, the main portion, and the footer. What I want to do is PERPENDICULAR to the CSS hierarchy, which PARALLELS the document structure.

When my CSS variations fit into the heirarchy everything is hunky-dory, but when they cross the document heirarchy I don’t know of any solution cleaner than CSS variables.

Author
Chuck Kollars
Posted
Aug 28th, 2005 2:30 pm
023

I’m having an issue with Firefox 1.0.6 on a PC where half the CSS file shows up at the top of the page, where the user can see it, closely followed by the HTML code.

Any ideas? (Doesn’t seem to be happening in IE 6, also PC.)

Author
Simon
Posted
Sep 8th, 2005 6:11 am
024

(I should have mentioned that this only happens intermittently.)

Author
Simon
Posted
Sep 8th, 2005 6:39 am
025

A very nice use of the semi-css @server syntax, but I’m currious about other (possible future) uses for it except @server constants. For example @server import(header.css for server side importing of css files (or even contant definitions).

Does anyone have any nice future uses of @server???

I was planning on implementing (server side) conditional comments in css. This little script however will most definitely be included. I just love the syntax.

Author
Analgesia
Posted
Sep 12th, 2005 9:59 am
026

I have made a version of this that has a built in CSS optimizer for removing whitespace and comments, and even creating shorthand. It also has a few more options and better stats. Check it out: pixeljunkies.com/forum/viewtopic.php?id=148

Author
Cody
Posted
Sep 17th, 2005 12:01 am
027

For those of you in asp.net land, Rory Blyth did this a while back. Same concept, NFI whether the implementation is the same. I never did get around to trying it out. One of these days… http://neopoleon.com/blog/posts/4069.aspx

Glad someone has done it for PHP, I might give works site a bit of a run over with it.

Personally, I like the idea of a preprocessor rather than complete dynamic on the fly replacements. This kinda thing is excellent for development work tho!

Author
Daniel F
Posted
Sep 21st, 2005 2:21 am
028

It would be really cool if you could implement something like this as an Apache Module, similar to SSI.

That way it would be quite seemless to implement.

Author
Kevin Cannon
Posted
Sep 23rd, 2005 7:32 am
029

sure! nice idea!

Author
phoku
Posted
Sep 29th, 2005 1:30 am
030

Hi. First of all, thanks for making this real!

I get this error in Firefox 1.0.7 (windows):

Fatal error: Call to undefined function: apache_request_headers() in /var/www/html/css/css-ssc.php on line 54

It works fine the first time i load the page, but when I refresh, it doesn’t work anymore. It works fine in Explorer 6 windows.

Any idea?

Author
Maira
Posted
Oct 11th, 2005 6:41 am
031

Ryan, you are not alone in your thinking. I would much prefer to drive dynamic CSS through a PHP script directly over this .htaccess/mod_rewrite/parsing approach. And HERE HERE to heredoc syntax so your CSS code is just as easy to read in this context. So much so, I wrote an article on this technique for Digital Web. You have plenty of cache control from PHP with HTTP headers, and can easily write out a static version while you’re at it.

Author
Douglas Clifton
Posted
Oct 19th, 2005 11:18 pm
032

Hi,

Im having problems. Can you help me out?

Using Windows, I’m placing the css-ssc.php in the css-ssc folder under my css folder (just unpacked v004). So its called “root/css/css-ssc.php”

Next, (using Apache) Im enabling mod_rewrite, setting “AllowOverride All” on httpd.conf and creating that .htaccess file under the “root/css” folder.

Finally, Im creating a .css file under the “root/css” folder and linking my scripts to it.

Is there anything more I need to do? I cannot even use selectors not using server constants (thats why I believe .htaccess is working).

Thanks in advance, Alexandre Martini

Author
Alexandre Martini
Posted
Nov 5th, 2005 4:09 pm
033

… complementing my previous post, Im getting the following in the Apache access log:

127.0.0.1 - - [06/Nov/2005:00:53:33 -0300] "GET /teste/teste.php HTTP/1.1" 200 571
127.0.0.1 - - [06/Nov/2005:00:53:33 -0300] "GET /teste/css/global.css HTTP/1.1" 404 303

The paths are all correct, but whats really happening is a 404 followed by a 303. If Im correct, thats a redirection error.

Can you help me with that? Thanks! Alexandre Martini

Author
Alexandre Martini
Posted
Nov 5th, 2005 5:03 pm
034

I just dont remember what was my last modification since the last postings, but its working. Thanks anyway!

Author
Alexandre Martini
Posted
Nov 5th, 2005 6:34 pm
035

Is anyone aware of a means to add a server side font not found on the user’s system and have the css utilize that font rather than a user defined font.

Author
Jason
Posted
Jan 23rd, 2006 11:34 am