Juicer - a CSS and JavaScript packaging tool
For best performance, CSS and JavaScript should be served up using as few requests and bytes as possible. Juicer is a new command line tool that helps by resolving dependencies, merging and minifying files. It can even check your syntax, add cache busters to and cycle asset hosts on URLs in CSS files and more.
Background
For web applications there's a screaming gap between development best practices, and deployment best practices. In order to achieve the best possible performance (to benefit both your users and your server) you should strive to require as few CSS and JavaScript files as possible on each page view - ideally only one of each. (Pulling in more files asynchronously is OK, since it doesn't drag down your initial rendering).
Developers generally prefer to work with some kind of source code organisation other than "one CSS file, one JavaScript file". Besides, inline documentation is hugely advantageous both for CSS and JavaScript code. With Juicer, you can organise your code any which way you want, and add in all the documentation you can come up with. When you're ready to go live, you can just do:
$ juicer merge stylesheets/main.css
Produced stylesheets/main.min.css from
stylesheets/framework/reset.css
stylesheets/framework/grid.css
stylesheets/skins/midnight.css
stylesheets/main.css
$ juicer merge javascripts/app.js
Produced javascripts/app.min.js from
javascripts/prototype.js
javascripts/lightbox.js
javascripts/app.js
The resulting files will be minified by YUI Compressor, perfect for production environments.
Installation
In order to use Juicer, you need Ruby and RubyGems. If you want to use YUI Compressor for minification (or JsLint for JavaScript syntax verification) you'll also need Java on your system.
Once you've got your dependencies straight, you can install Juicer like so:
$ gem install juicer
$ juicer install yui_compressor
$ juicer install jslint
The two last lines will cause Juicer to download and unpack YUI Compressor, Rhino (Mozilla JavaScript engine written in Java) and JsLint.
Now that you're up and running, let's look at juicer merge, the most important Juicer tool. In short it can merge and minify CSS and JavaScript files, but there's more to it than just that.
CSS files
Merging one or more CSS files is as easy as
juicer merge myfile.css myotherfile.css css/third.css
You can specify arbitrary many files, but one is often sufficient; Juicer can resolve dependencies and join all detected files.
Merging with dependencies
Dependencies are easy for CSS files; they already provide the @import directive. Juicer reads @import directives and replaces them with the contents of the file they reference in the generated output.
Cache busters
As explained in
my recent post on the Expires header, cache busters are tokens added to a URL to make it unique, causing browsers to download the referenced content even if the original URL was already read and cached. Cache busters is what make a far future Expires header work in practice.
Juicer supports both kinds of cache busters I previously described:
$ juicer merge file.css
body {
background: url(../images/bodybg.png?jcb=1234567890);
}
$ juicer merge -c hard file.css
body {
background: url(../images/bodybg-1234567890.png);
}
For cache busters, Juicer adds the timestamp of the last time of edit on a file (aka mtime).
URLs
All relative URLs are handled relatively from the CSS file itself. If you use absolute URLs (ie /images/logo.png) in your CSS files, you need to let Juicer know what directory to handle those relatively from in order to add cache busters. This can be done by way of the -d ( --document-root) option.
You can also use --document-root in cooperation with --relative-urls or --absolute-urls in order to convert all URLs to either relative (shortest possible path from CSS file) or absolute URLs. Relative URLs in the resulting output file will always be relative to the output file. This means that:
$ juicer merge -o dist/ main.css
Will cause images/logo.png in the original file to be converted to ../images/logo.png in the generated file ( dist/main.min.css).
Asset hosts
Most browsers will only download 2 files in parallel from a single domain. For this reason, using 2 or 3 domains to deliver your static assets may speed up your site. Juicer supports this technique by allowing you to specify asset hosts and then use them evenly throughout your CSS:
$ juicer merge -h http://assets1.example.com,http://assets2.example.com main.css
This will cause about half the URLs in main.css to reference http://assets1.example.com and the other half http://assets2.example.com.
JavaScript files
For JavaScript files, Juicer does much the same. However, JavaScript doesn't have a dependency construct such as CSS' @import. In addition, Juicer offers to verify the quality of JavaScript code using
JsLint. For instance, by default, Juicer won't minify files unless they pass JsLint. This can be overriden, but is generally a good idea since it helps you avoid problems caused by minification.
Merging with dependencies
Since JavaScript doesn't have an @import statement, Juicer brings it's own directive to the table, the @depends directive. It's real simple:
/**
* @depends prototype.js
* @depends widgets/lightbox.js
*/
(function(global) {
// Your code here
})(this);
If you don't like the s, you can also use @depend. @depend/ @depends takes a path relative to the JavaScript file itself, and upon merging, gets replaced by the contents of that file.
Syntax verification
juicer merge uses
JsLint for QA on your JavaScript files. If a warning is detected, Juicer aborts and tells you what's wrong. If you want to continue merging and minifying anyway (not recommended), you can pass Juicer the -i option:
$ juicer merge -i app.js
Verifying app.js with JsLint
Problems detected
Lint at line 15 character 2: Missing semicolon.
}
Problems were detected during verification
Ignoring detected problems
Produced app.min.js from
app.js
If you just want to check a file (and not merge/minify it and so on), you can invoke juicer verify app.js
Some thoughts on Sprockets
Those of you who pay attention to sites like ajaxian.com might realise that Juicer looks somewhat like Sprockets, very recently released by Sam Stephenson. Fact is, for JavaScript, Juicer is somewhat like Sprockets. I had no idea that was gonna surface, and honestly I think Sprockets is looking pretty cool.
Anyway, Sprockets has taken this all in a bit of another direction (or a bit further, depending). Sprockets does some things Juicer doesn't do, and Juicer does a few things Sprockets doesn't do. This is especially true for CSS files, which I don't think Sprockets touch at all.
Future plans
Much as the Sprockets project does, I had planned a Rails plugin which replaces the javascript_include_tag and stylesheet_link_tag helpers with Juicer enabled ones. I think it's important to treat the CSS files too, so I'll probably do this real soon.
Documentation generation is another feature I've been wanting to add to Juicer, like juicer doc, which can generate API documentation using
JsDoc,
YUI Doc and others.
I plan to add more minifiers too; Packer and JsMin are two obvious tools to add support for.
In addition there's some loose thoughts, but I'll get back to you when they're shaping up. In the meantime I hope you find Juicer useful, and I'd love feedback! Add a comment, fork me on Github, report a bug in Lighthouse, or send me an email (christian @ cjohansen.no).
Comments
Thomas Kjeldahl Nilsson
(http://www.messynotebook.com)
2. March, 07:19
Christian
2. March, 07:43
Andy Goh
3. March, 12:32
Looks like its a good tool, looking forward to experiment with it in my mini-projects :)
By the way, the link to github is mis-spelled as guthub.
Christian
3. March, 12:40
Alex
3. March, 13:13
Adam Buono
(http://www.backwardshypothesis.com)
3. March, 15:38
chief
(http://chief.fishbucket.com/)
3. March, 20:02
Under Xubuntu 8.10 I had to:
sudo apt-get install ruby ruby-dev default-jre rubygemsThen:
sudo gem install bones juicerAdded to my path
I'm getting ruby errors when I try to merge a CSS file with an incorrect URL for a background image - it'd be nice if it could verify the file exists and throw a program error. Also if it could validate the CSS - that'd rock too. Thanks for your work!
Timmy
3. March, 22:32
I'll be checking this out for sure!
Alex
3. March, 22:46
Christian
3. March, 23:23
@Timmy @Alex Looks like bundle_fu and Juicer share some functionality. I plan to distribute Juicer as a standalone application not dependent on Ruby at some point, so Juicer is kinda more than a Ruby library (and soon a Rails-plugin). The advantage in running the process manually is the possibilities for quality assurance - JsLint and also CSS validation in the long run. I think deploy time is a good time to do this.
Anton
4. March, 10:28
I built a similar tool recently for work, except I'd written it in C# (theres a handy YUI Compressor .NET port).
The only problem I see with this one is the amount of dependencies required. Would be nice to have a web deployment tool written in Java or C/C++.
Christian
4. March, 11:04
Juicer 0.2.3 is the first public release, reducing dependencies will be a focus if people think it's important (which they seem to think :)
gg33
4. March, 16:05
Christian
4. March, 22:55
Straps
6. March, 08:47
Can't install gem...
Christian
6. March, 11:38
Steve Odom
(http://www.twitter.com/steveodom)
13. March, 16:47
Arphen Lin
15. March, 14:22
1. Line#35 in *C:\ruby\lib\ruby\gems\1.8\gems\juicer-0.2.3\lib\juicer\jslint.rb* should be:
lines = execute(%Q{-jar "#{rhino}" "#{locate_lib}" "#{file}"}).split("\n")The jar file path in my PC is "C:\Documents and Settings\xxx\Application Data\juicer\lib\rhino\bin\rhino1_7R2-RC1.jar". If you don't wrap #{rhino} with "", the path would be truncated. Maybe there are same issues in other programs of juicer.
2. jsLint cannot handle UTF-8 files with Chinese characters. It's too bad.
Christian
15. March, 21:59
Arphen: Thanks for your input! I've run a few tests on Windows, but don't use Windows myself, so it's very nice to have some feedback from you guys. For the first part, I've corrected the mistake for the next version: http://github.com/cjohansen/juice...3971b21f962c6c82991426a8c4787642786e
For 2. I'm not sure what I can do, but I'll check it out.
Arphen Lin
16. March, 09:07
Christian
16. March, 09:16
wynst
(http://ruby-indah-elegan.blogspot.com)
17. March, 09:32
Javad
(http://stayupdate.com)
17. March, 11:57
Christian
17. March, 21:55
Minifying does break a few lint rules, but in a controlled matter. However, minifying is highly unlikely to work twice on the same code. That's why you should always use full versions when working with juicer.
Heikki
24. March, 15:42
(ruby 1.8.7 (2008-08-11 patchlevel 72) [i686-darwin9.5.0] juicer-0.2.3)
Installing JsLint 1.0 in /Users/heikki/.juicer/lib/jslint Installing dependencies Installing Rhino 1_7R2-RC1 in /Users/heikki/.juicer/lib/rhino Downloading ftp://ftp.mozilla.org/pub/mozilla.org/js/rhino1_7R2-RC1.zip /usr/local/lib/ruby/1.8/net/ftp.rb:241:in `getresp': 425 Failed to establish connection. (Net::FTPTempError)Changed the protocol from ftp to http and it worked. Has anyone run into this problem before?
Arphen Lin
25. March, 02:15
Jon
3. May, 16:30
I'm having some problems when merging CSS files.
Apprently, you can't use quotes in the background-image property
Is there a special option to make it works ?
Christian
4. May, 13:04
Jon
6. May, 01:23
Tim Coulter
(http://www.timothyjcoulter.com)
6. May, 16:04
Would *love* to have the plugin when it's ready -- send it my way when it goes live (email in the comment).
Thanks a lot!
Tim Coulter
(http://www.timothyjcoulter.com)
6. May, 16:16
That would absolutely rock.
Vladimir
(http://www.nikolicvladimir.com)
13. May, 11:23
I would like to share my expirience using Juicer on windows Vista.
INSTALLATION
1. I went to Ruby page and downloaded last from download page. I had Ruby installed on C:/Ryby
2. After installation you will find Ruby under Start/All programs, click on it and choose RubyGems/RubyGems Package Manager. This will lunch command tool window
4. type gem install juicer(not $gem)
5. you will be asked a few times to install some dependicies- i type Y for all of them
6. type juicer install yui_compressor
7. type install jslint
Thats all. You will get Happy juicing message :)
However, i got juicier online, and now i need to merge several files of .js into one file. Project i am working on have many javascript files, within folder structure, all over the project :)
How i can merge something like:
C:/mainfolder/folder1/script1.js
C:/mainfolder/folder2/script2.js
C:/mainfolder/folder3/script3.js
C:/mainfolder/folder4/script4.js
into one main.js file?
I am afraid not to mess up, so i copied working files into special folder so i can play with this.
Thanks in advance,
Vladimir
vladimir
13. May, 11:47
You have to type as many files as you want, delimited by space. Last file you typed should be merged file if i am not wrong?
But! After merging finished, i've got this message:
--------------------------------------------------
[ERROR] 1:0:Compilation produced 7 syntax errors.
org.mozilla.javascript.EvaluatorException: Compilation produced 7 syntax errors.
at com.yahoo.platform.yui.compressor.YUICompressor$1.runtimeError(YUICom
pressor.java:135)
at org.mozilla.javascript.Parser.parse(Parser.java:410)
at org.mozilla.javascript.Parser.parse(Parser.java:355)
at com.yahoo.platform.yui.compressor.JavaScriptCompressor.parse(JavaScri
ptCompressor.java:312)
at com.yahoo.platform.yui.compressor.JavaScriptCompressor.<init>(JavaScr
iptCompressor.java:533)
at com.yahoo.platform.yui.compressor.YUICompressor.main(YUICompressor.ja
va:112)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at com.yahoo.platform.yui.compressor.Bootstrap.main(Bootstrap.java:20)
c:/ruby/lib/ruby/1.8/pathname.rb:709:in `relative_path_from': different prefix:
"D:/" and "c:/ruby" (ArgumentError)
from c:/ruby/lib/ruby/gems/1.8/gems/juicer-0.2.4/lib/juicer/command/util
.rb:24:in `relative'
from c:/ruby/lib/ruby/gems/1.8/gems/juicer-0.2.4/lib/juicer/command/util
.rb:21:in `collect'
from c:/ruby/lib/ruby/gems/1.8/gems/juicer-0.2.4/lib/juicer/command/util
.rb:21:in `relative'
from c:/ruby/lib/ruby/gems/1.8/gems/juicer-0.2.4/lib/juicer/command/merg
e.rb:119:in `execute'
from c:/ruby/lib/ruby/gems/1.8/gems/cmdparse-2.0.2/lib/cmdparse.rb:438:i
n `parse'
from c:/ruby/lib/ruby/gems/1.8/gems/juicer-0.2.4/lib/juicer/cli.rb:27:in
`parse'
from c:/ruby/lib/ruby/gems/1.8/gems/juicer-0.2.4/lib/juicer/cli.rb:37:in
`run'
from c:/ruby/lib/ruby/gems/1.8/gems/juicer-0.2.4/bin/juicer:8
from c:/ruby/bin/juicer:16:in `load'
from c:/ruby/bin/juicer:16
c:\ruby>blink blink blink ;)
-----------------------------------------------
What to do now? :)
Christian
13. May, 23:45
Alternatively you can provide the -o (or --output) option to set a custom file name for the output file.
In order to help you with your error I probably need to see the actual files. Can you run juicer verify [file] on all the files to verify that they're lint free?
Vladimir
14. May, 11:48
So far, i found out that not last, but first file would be used to generate merged file. At least on windows vista. I did run verify on files, but i've got to many warnings. The thing is that i am not author of those, i got a task to merge all files into one file in order to make application running faster on iPhone and other mobile clients. Also i've tried to make custom file from like-
jucier -o D:/html/merged.js merge -i D:/html/file1.js D:/html/file2.js... but it rasied an error. I presume that i am just not familiar with command line :0)
Warnings i am getting from juicer are pretty strange, so i dont know how to fix them. What i notice is that i am getting error like- Lint at line 42 character 78: ['TemperatureHighF'] is better written in dot notation.
First, that is not in line 42 within the script. And second, more important is that i have no clue what that error means and how to fix it lol :)
Thanks in advance,
Vladd
vladimir
14. May, 15:33
would be corect if you want to output in new file :)
Vladimir
14. May, 15:37
is there any possibility to create automated juicer process? Like to make a batch file, which is going to run juicer on clicking that batch file?
What is needed to achieve that?
I presume that ruby has to be involved in that process, but dont know how.
Thanks,
Vladimir
Christian
15. May, 09:03
1) You're right, the first file is used as basis for the output filename, my bad.
2) The error line numbers are probably wrong because it's referring to the merged file, but I'm not 100% sure (don't remember what happens first). If you want you can mail me the scripts on christian@cjohansen.no and I'll help you with the warnings.
3) To automate juicer in a bat file, simply create a file "juicer.bat" or similar in any text editor and paste in juicer commands. Prepend the commands with "call", so the bat file doesn't abort after the commands, ie:
call juicer -f -s -o file1.js input1.js input2.js
call juicer -f -s -o file2.js input3.js input4.js
The -s argument skips verification so you don't have to wait for it. Useful when you have known warnings that you're currently unable to fix.
Vladimir
15. May, 10:19
Does juicer can compress files, or just merge them?
What i see in merged file, is that all scripts are moved into that one , merged file.
If juicer is not able to compress them, to reduce number of lines or whatever, do you know what tool can be used for that?
I saw somewhere, dont remember excatly is that was a prototype or jQuery or something else, that they have a file with a one huge line. How to achieve that?
Christian
15. May, 11:38
Vladimir
22. May, 22:51
Sorry for a long delay of my response, but i found that your web site was down a few times i was visiting.
However, i managed to make all this things i had to make in the meantime :)
Would like gladly to share my experience with you guys.
I found what those errors represented, also i made a batch file, and did gzip on js file using php, so finally my file went down from 750+kb to 127kb. Next time i will try to explain how i achieved all that.
Now i am a bit in a hurry :) Thanks for all, will write soon, i promise :)
Christian
24. May, 00:07
Be sure to update juicer now as well, just released a small bug fix the other day (0.2.5).
Eero
(http://www.onodrim.net/)
13. July, 09:56
One small bug I noticed, in CSS merging: if there's a tab-character after @import rather than a single space before the url(), that is, the dependencies don't work.
Eero
13. July, 10:37
Moreover, @charset is left untouched by juicer while it should be prepended to the file, and removed from merged files, if it exists.
Christian
18. July, 23:30
Jason H.
19. July, 22:01
.../juicer/install/yui_compressor_installer.rb:62 in 'latest': undefined method 'get_attribute' for nil:NilClass (NoMethodError)
from ... install.rb:in 'version'...
Am I running too old a version of Ruby for this to work?
Thanks,
Jason H.
Kaya
20. July, 14:46
Christian
20. July, 16:52
Christian
21. July, 07:57
gem install juicerjuicer install yui_compressorbhavna
(http://www.punenet.com)
28. July, 13:39
Pieter Michels
(http://www.wellconsidered.be)
30. July, 09:50
Easy installation and merging results you would expect.
Rob
(http://www.robertbeal.com)
5. August, 00:56
The @import stuff and image checks make my life some much easier. Was previously merging the files then just passing the merged file to YUICompressor.
Now that I'm using Rake instead of NAnt for builds, it's much easier to implement Juicer.
Great work.
Brad Sherrill
(http://www.bradfordsherrill.com)
11. August, 16:18
Smashing Themes
(http://www.smahingthemes.com/)
12. August, 14:12
Smashing Themes
(http://www.smashingthemes.com)
12. August, 14:14
Rob
(http://www.robertbeal.com)
14. August, 22:18
Strange thing is, if I pass the merged js into YUICompressor directly, it works fine. Just doesn't work via Juicer.
Any ideas?
[ERROR] 517:2:illegal character
[ERROR] 517:2:syntax error
[ERROR] 517:3:illegal character
[ERROR] 1:0:Compilation produced 3 syntax errors.
org.mozilla.javascript.EvaluatorException: Compilation produced 3 syntax errors
at com.yahoo.platform.yui.compressor.YUICompressor$1.runtimeError(YUICo
at org.mozilla.javascript.Parser.parse(Parser.java:410)
at org.mozilla.javascript.Parser.parse(Parser.java:355)
at com.yahoo.platform.yui.compressor.JavaScriptCompressor.parse(JavaScr
2)
at com.yahoo.platform.yui.compressor.JavaScriptCompressor.<init>(JavaSc
33)
at com.yahoo.platform.yui.compressor.YUICompressor.main(YUICompressor.j
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at com.yahoo.platform.yui.compressor.Bootstrap.main(Bootstrap.java:20)
Christian
15. August, 13:49
juicer merge -m none -o merged.css file1.css file2.css, and then inspect line 517 in the generated output? I'd be very interested in what's there. Maybe seeing it will help you solve your problem too, but this is most likely a Juicer bug.Alexander Støver
(http://binaerpilot.no)
17. August, 15:04
Christof Haemmerle
9. September, 17:27
i can install juicer but now way to run it and install other parts.
reco@r4:~ $ juicer
/Library/Ruby/Gems/1.8/gems/hpricot-0.8.1/lib/hpricot_scan.bundle: dlopen(/Library/Ruby/Gems/1.8/gems/hpricot-0.8.1/lib/hpricot_scan.bundle, 9): no suitable image found. Did find: (LoadError)
/Library/Ruby/Gems/1.8/gems/hpricot-0.8.1/lib/hpricot_scan.bundle: no matching architecture in universal wrapper - /Library/Ruby/Gems/1.8/gems/hpricot-0.8.1/lib/hpricot_scan.bundle
from /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/rubygems/custom_require.rb:31:in `require'
from /Library/Ruby/Gems/1.8/gems/hpricot-0.8.1/lib/hpricot.rb:20
from /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require'
from /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/rubygems/custom_require.rb:31:in `require'
from /Users/reco/.gem/ruby/1.8/gems/juicer-0.2.6/lib/juicer/install/base.rb:1
from /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require'
from /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/rubygems/custom_require.rb:31:in `require'
from /Users/reco/.gem/ruby/1.8/gems/juicer-0.2.6/lib/juicer.rb:62:in `require_all_libs_relative_to'
from /Users/reco/.gem/ruby/1.8/gems/juicer-0.2.6/lib/juicer.rb:62:in `each'
from /Users/reco/.gem/ruby/1.8/gems/juicer-0.2.6/lib/juicer.rb:62:in `require_all_libs_relative_to'
from /Users/reco/.gem/ruby/1.8/gems/juicer-0.2.6/lib/juicer.rb:67
from /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require'
from /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/rubygems/custom_require.rb:31:in `require'
from /Users/reco/.gem/ruby/1.8/gems/juicer-0.2.6/bin/juicer:5
from /usr/bin/juicer:19:in `load'
from /usr/bin/juicer:19
any idea?
Christian
14. September, 23:04
mls
15. September, 11:28
Dominic
(http://fusion.dominicwatson.co.uk)
27. September, 20:34
css.domain.com
js.domain.com
images.domain.com
Therefore I can't use relative image urls in my css in production.
Currently, I'm not using juicer's css domain feature but just soft cache busters, then running some regex to replace "url(../images/" with "url(http://images.domain.com/".
If there was anyway to do this in Juicer while adding cache-busters that'd be great.
Christian
27. September, 21:06
project/
project/stylesheets
project/javascripts
project/images
Try this:
That should work. Checkout
juicer merge --helpfor more information on the command line switches.If this doesn't work for some reason, let me know.
Christian
1. October, 00:03
Comments are closed