Faster Page Loads – Bundle Your CSS and Javascript with lighttpd

March 18, 2008 – 4:23 pm

Hello again,

Preface

This time I have been busy playing with Lighttpd and mod_magnet. I found a blog post where darix mentions a post from sitepoint where they demostrate a technique to speed up the HTTP GET of javascript and CSS files.

Its quite simple really, instead of doing

<link href="/css/styles1.css" rel="stylesheet" type="text/css" />
<link href="/css/styles2.css" rel="stylesheet" type="text/css" />
<link href="/css/styles3.css" rel="stylesheet" type="text/css" />

you simply do

<link href="/css/styles1.css,styles2.css,styles3.css" rel="stylesheet" type="text/css" />

and then this script will automagic concat them together into one big file, and make lighty serve that one instead. (The same goes for javascript files, or anything really)

This could save ALOT of HTTP GET’s to your server, and increase the overall loadtime and performance of your site alot, especially if you use alot of Web2.0 stuff :)

However, the Lua source for their script was not made public, so nobody could benifit from their otherwise impressive speed gains. :(

Until now.. or, well, almost – its not their script, but my own attempt to mimic what they did in pure Lua :)

I tried to keep the dependencies down to a minimum, however I decided to use one, md5.
You can find the source here (5.1, 5.0)

Config

charset (Line 42)
Set it so it matches the headers in your lighttpd.conf file. Must match your mod_compress settings aswell to utilize that feature
prefix (Line 43)
Could be anything really, its just a mean to “namespace” your cached files so you can run the script with multiple configs
rootPath (Line 44)
The path to look for the files to concat, default should be fine for most DOC_ROOT/js and DOC_ROOT/css
concatRoot (Line 45)
The folder to store the concated files, can be anywhere :)

Setup

  • Put the source below somewhere on your disk, I asume you name it bundle.lua
  • Enable mod_magnet in lighttpd.conf
  • Add magnet.attract-physical-path-to = ( “YOUR_PATH/bundle.lua” ) to your config where needed
  • Restart lighttpd
  • Modify your css/js links to utilize the new feature:)

Optimal you can enable mod_compress aswell, bundle.lua works out of the box with any other lighttpd module you may have :)

Source

download source

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
--- get stat information on a path
-- @param path to stat
-- @param the ftype to return
-- @return false if ftype or path does not exist
function file_info(path, ftype)
    local attr = lighty.stat(path)
    if attr and attr[ftype] then
        return attr[ftype]
    end
    return false
end
 
--- Wrapper for reading a full file into a string
-- @param filename Full path to the file
-- @return a string with the content of the file
function read_file(filename) 
    local content = ""
    if file_info(filename, "is_file") then
        local file = io.open(filename, "r")
        content = file:read("*a")
        io.close(file)
    end
    return content
end
 
--- Wrapper for writeing content to a file
-- @param filename Full path to the destionation file
-- @param content The string to write
function write_cache(filename, content) 
    local file = io.open(filename, "w")
    file:write(content)
    io.close(file)
end
 
--- Concat multiple files into one file 
-- @param lighty lighty global variable passed to the method
-- @param match The files that will be concat into a file
-- @param fileExtension Do !NOT! include the dot ( . )
function combine_files(lighty, files, fileExtension)
    require "md5" 
 
    local charset       = "; charset=utf-8"
    local prefix        = "cache-"
    local rootPath      = lighty.env["physical.doc-root"] .. fileExtension .. "/"
    local concatRoot     = "/tmp/cache/"
    local lastModified  = 0
 
    for file in string.gmatch(files, "(.-\." .. fileExtension .. "),?") do
        local fullPath = rootPath .. file
        modTime = file_info(fullPath, "st_mtime")
        if type(modTime) == "number" then
            lastModified = math.max(lastModified, modTime)
        end            
     end
 
     local hash = lastModified .. "-" .. md5.sumhexa(files)
     lighty.header["Etag"] = '"' .. hash ..'"'
 
    local cacheFile = prefix .. hash .. '.' .. fileExtension
    if not file_info(concatRoot .. cacheFile, "is_file") then
        local content = ""
        for file in string.gmatch(match, "(.-\." .. fileExtension .. "),?") do
            content = content .. "\n"
            content = content .. "/**\n"
            content = content .. " *\n"
            content = content .. " *  " .. file .. "\n"
            content = content .. " *\n"
            content = content .. " */\n"
            content = content .. "\n"
            content = content .. read_file(rootPath .. file)
        end
        write_cache(concatRoot .. cacheFile, content )
    end
    lighty.env["physical.path"] = concatRoot .. cacheFile
end
 
if (not file_info(lighty.env["physical.path"], "is_file")) then
    css = string.match(lighty.env["physical.path"], "css/(.*\.css)")
	if css then
        return combine_files(lighty, css, "css")
    end
 
    js = string.match(lighty.env["physical.path"], "js/(.*\.js)")	    
	if js then
        return combine_files(lighty, js, "js")
	end 
end

Tags: , , ,

  1. 10 Responses to “Faster Page Loads – Bundle Your CSS and Javascript with lighttpd”

  2. This is really good news, congratulations. Also nice guide, thanks.

    By 2ge on Mar 18, 2008

  3. Hi,

    It seems that you posted a script with an error in line 62. There’s a reference to unitialised variable “match” – should be “files” instead

    At least it works for me with “files”

    By redvasily on Jul 30, 2008

  4. WOW! nice tutorial! that’s what I exactly need. You are explaining in such a simple and straightforward! why others can’t do it as you’ve done? may be it’s a talent… thanx a lot!

    By Eve - Photostock Expert on Jan 15, 2009

  5. Hi,

    I want some of .js, .css files to be loaded before HTML contents. How can I able to do with your code.

    By Sander on Jun 8, 2009

  6. I just added “mod_compress” to the modules in the array “server.modules” and

    1. {{{ mod_compress

    compress.cache-dir = “/var/www/cache/”
    compress.filetype = (“text/css”, “text/javascript” )

    in the /etc/lighttpd/lighttpd.conf file. Obviously I created the directory /var/www/cache giving to it the permission needed by lighttpd to write there.

    And that’s just a small part of the unlimited capacities of Lighttpd!

    By CMS Website on Jul 3, 2009

  7. Basically, what the plugin does is:

    1 it combines multiple CSS or Javascript files into one big file (bundle)
    2 it compacts this bundle by removing whitespace and comments
    3 it caches the bundle by using Rails default page caching mechanism
    4 it creates an “accesspoint” to the bundle by adding a route.
    I was using this already, but nice tutorial anyway…

    By Lischet cms on Jul 3, 2009

  8. Can anyone tell me how I disable bundling in development mode? Its very very annoying during development…

    By Laatste Nieuws on Jul 30, 2009

  1. 3 Trackback(s)

  2. Jun 29, 2008: combining-js-and-css-files-using-small-script-and-lighty - NerdBase-Blog
  3. Aug 1, 2008: Combining CSS & JS with mod_concat: Code Concatenation
  4. Sep 16, 2008: mercurycomplex.com - Lighttpd plugin mod_concat

Post a Comment