Introducing Combiner, a JavaScript/CSS concatenation tool
One of the things I used to love when programming in more “traditional” languages such as C++ and Java was the build process. My source files just indicated what they needed in order to run successfully, and the build tool did the rest. This had the wonderful advantage of allowing you, as a programmer, to separate your code into as many files as logically sensible without worry about the order in which they would end up.
I love JavaScript and try to bring good coding practices to it as part of my job. I was very excited when people started talking about using a build process to combine JavaScript files into single, deployable files. My colleague Julien Lecomte wrote an excellent blog post entitled, Building Web Applications with Apache Ant, which shows how easy and powerful a build process can be for your files. It seems that most people now understand the value of having a good build process, as most JavaScript libraries use one.
The problem
Most build systems I’ve seen require you to indicate your dependencies in a separate file. This has bothered me for quite some time. Why should dependency information exist outside of the files that need it? Why introduce another file into the system whose sole job is to manage dependencies? What I wanted was the equivalent of #include in C or import in Java, something that would allow me to specify dependencies in my source file and then combine all source files together in the correct order based on their dependencies. So early last year, I started working on Combiner.
What Combiner does
Combiner does just one thing: figures out the dependencies between files and creates a single file with all parts in the correct order. The process is the same for both JavaScript and CSS files. You specify that a file has a dependency on another by including a special comment in the following form:
/*requires filename.ext*/
For example:
/*requires yahoo.js*/
I chose to name the comment “requires” because it is not a static include. Combiner looks at all specified files, reads in their requirements, then arranges all files so that dependencies always occur prior to the code requiring them. You need one “requires” comment for each file that is required.
Note: You can use relative or absolute paths in the comment. For example, the following works as expected:
/*requires ../yahoo.js*/
Usage
I purposely made Combiner accept the same format and order of arguments as the YUI Compressor. Here is the help text (accessible via the -h or --help flags):
Usage: java -jar combiner-x.y.z.jar [options] [input files]
Global Options
-h, --help Displays this information
--charset <charset> Read the input file using <charset>
-v, --verbose Display informational messages and warnings
-s, --separator Output a separator between combined files
-e, --eliminate Eliminates any files that aren't explicitly required.
-o <file> Place the output into <file>. Defaults to stdout.
There are two basic ways to use Combiner for combining JavaScript and CSS files. The first way is to indicate just the core files you want to build. For example:
java -jar combiner-0.0.1.jar -o output.js file1.js file2.js
In this case, Combiner reads in file1.js and file2.js and checks for dependencies. If there are dependencies, then Combiner also reads those in. The final file, output.js, is comprised of file1.js, file2.js, plus any dependent files that might have been specified in source code. This method of usage ensures that only the necessary files end up in the resulting file.
The second way to use Combiner is to give it a pattern. You can, for instance, include all JavaScript files in a single directory:
java -jar combiner-0.0.1.jar -o output.js *.js
When all JavaScript (or CSS) files are included, Combiner reads through all files specified to find dependency information. Even if one or more files have no dependency information, meaning they don’t require any of the other files and none of the other files require them, these files still end up in the resulting output.js. If this isn’t what you want, you can tell Combiner to eliminate files without dependency information by including the -e flag:
java -jar combiner-0.0.1.jar -e -o output.js *.js
If you’re interested in seeing what Combiner has found and what it’s doing, add the -v or --verbose flag:
java -jar combiner-0.0.1.jar -v -o output.js *.js
The resulting output looks something like this:
[INFO] Using charset Cp1252
[INFO] Output file is 'yuitest.js'
[INFO] Adding file 'yuitest\ArrayAssert.js'
[INFO] Adding file 'yuitest\Assert.js'
[INFO] Adding file 'yuitest\DateAssert.js'
[INFO] Adding file 'yuitest\Mock.js'
[INFO] Adding file 'yuitest\ObjectAssert.js'
[INFO] Adding file 'yuitest\TestCase.js'
[INFO] Adding file 'yuitest\TestFormat.js'
[INFO] Adding file 'yuitest\TestManager.js'
[INFO] Adding file 'yuitest\TestReporter.js'
[INFO] Adding file 'yuitest\TestRunner.js'
[INFO] Adding file 'yuitest\TestSuite.js'
[INFO] Processing file 'yuitest\ArrayAssert.js'
[INFO] ... has dependency on Assert.js
[INFO] Processing file 'yuitest\Assert.js'
[INFO] ... no dependencies found.
[INFO] Processing file 'yuitest\DateAssert.js'
[INFO] ... has dependency on Assert.js
[INFO] Processing file 'yuitest\Mock.js'
[INFO] ... has dependency on Assert.js
[INFO] Processing file 'yuitest\ObjectAssert.js'
[INFO] ... has dependency on Assert.js
[INFO] Processing file 'yuitest\TestCase.js'
[INFO] ... has dependency on Assert.js
[INFO] Processing file 'yuitest\TestFormat.js'
[INFO] ... no dependencies found.
[INFO] Processing file 'yuitest\TestManager.js'
[INFO] ... no dependencies found.
[INFO] Processing file 'yuitest\TestReporter.js'
[INFO] ... no dependencies found.
[INFO] Processing file 'yuitest\TestRunner.js'
[INFO] ... has dependency on TestCase.js
[INFO] ... has dependency on TestSuite.js
[INFO] ... has dependency on Assert.js
[INFO] Processing file 'yuitest\TestSuite.js'
[INFO] ... has dependency on TestCase.js
[INFO] Verifying dependencies of 'yuitest\TestReporter.js'
[INFO] Verifying dependencies of 'yuitest\ObjectAssert.js'
[INFO] Verifying dependencies of 'yuitest\TestFormat.js'
[INFO] Verifying dependencies of 'yuitest\TestRunner.js'
[INFO] Verifying dependencies of 'yuitest\Assert.js'
[INFO] Verifying dependencies of 'yuitest\DateAssert.js'
[INFO] Verifying dependencies of 'yuitest\TestCase.js'
[INFO] Verifying dependencies of 'yuitest\ArrayAssert.js'
[INFO] Verifying dependencies of 'yuitest\TestSuite.js'
[INFO] Verifying dependencies of 'yuitest\TestManager.js'
[INFO] Verifying dependencies of 'yuitest\Mock.js'
[INFO] Adding 'yuitest\Assert.js' to output.
[INFO] Adding 'yuitest\ObjectAssert.js' to output.
[INFO] Adding 'yuitest\TestCase.js' to output.
[INFO] Adding 'yuitest\TestSuite.js' to output.
[INFO] Adding 'yuitest\DateAssert.js' to output.
[INFO] Adding 'yuitest\ArrayAssert.js' to output.
[INFO] Adding 'yuitest\Mock.js' to output.
[INFO] Adding 'yuitest\TestRunner.js' to output.
If you believe that your file is coming out in the wrong order, running in verbose mode can help identify the problem. The most frequent cause of incorrect file order is that dependency information is missing or incorrect.
Error checking
I tried to identify all of the areas where an error could occur in the process and give an appropriate error message. The following error conditions are checked each time Combiner is run:
- Verify that all specified files exist.
- Verify that all dependency files exist.
- Verify that circular references do not exist between files.
It’s my hope that any errors occurring in the process are indicating in an obvious and non-confusing manner. I know I’ve spent endless hours trying to decipher the output of some tools when errors occurred, and I hope Combiner saves everyone from this pain.
What Combiner isn’t
Combiner isn’t a front-end build system. There are plenty of good build systems already out there, and I don’t want to add to that list. Combiner’s job is simply to combine JavaScript and CSS files in a way that frees you from worrying about source file ordering. You can use this as part of your build process just like you’d use YUI Compressor as part of the build process.
Combiner is neither a copycat of Sprockets nor an alternative to it. Sprockets is a far more complete build system for front-end development that includes JavaScript dependency management as well as packaging of other assets such as CSS and images. Combiner is strictly for JavaScript and CSS dependency management and can be plugged into any build system.
What took so long?
I originally wrote Combiner for a talk I was scheduled to give at the Rich Web Experience in San Jose. The talk was an overview of creating a front-end build system using Combiner, YUI Compressor, and more. Unfortunately, the conference was canceled and I got involved with some other projects (including a new book) that took up most of my time. This weekend, while trying to develop another tool, I came across the old source files and decided to finish up the work I had started.
Download
Combiner is written in Java and is distributed as a jar file, which can be downloaded here: combiner-0.0.1.jar. Combiner is freeware currently. If there’s enough interest, I’ll clean up the code and open source it, so definitely feel free to contact me with feedback.
Update (18 Oct 2009): Combiner released under BSD license. Source available at GitHub.
Disclaimer: Any viewpoints and opinions expressed in this article are those of Nicholas C. Zakas and do not, in any way, reflect those of my employer, my colleagues, Wrox Publishing, O'Reilly Publishing, or anyone else. I speak only for myself, not for them.
Both comments and pings are currently closed.




20 Comments
hi Nicholas,
I’ll give Combiner a swing sometime later, but 3 questions came up while reading your blog post.
1. How do you specify more than one dependency in a file? Comma separated, and in the right order? E.g. in file.js add the comment: /* requires file1.js, file2.js */
- Can Combiner add the contents of a print CSS file to the all.css, via @media print { … }
- How does Combiner verify dependencies, for JS and for CSS? Please elaborate on this, I’m very interested/curious, especially for JavaScript.
Thx,
Aaron
Aaron Peters on September 22nd, 2009 at 3:14 pm
I like it. It simplifies the build scripts I tend to write for smaller front-end projects that don’t, IMO, warrant a full build system. Also count this as a vote for open-sourcing it as I can already think of a couple small fixes/enhancements I’d like to see:
bug?
- when passed a single file that doesn’t have any /*requires …*/ it returns nothing. I expected it to return the contents of the one file. Granted there is no need to run combiner on a file with no dependencies, but when used as part of a build system it’s nice to not have to think about that and run all root source files through.
feature request:
- allow remote requires, i.e. /*requires http://example.com/script.js*/
Throw this bad boy on github and let us play.
Curtis on September 22nd, 2009 at 4:33 pm
@Curtis – That does seem like a bug, and ironically, one that I thought I had fixed. I will definitely take a look at that. The remote requirement seems like an interesting enhancement as well. I’m going to work on getting the source up on GitHub. My main issue is that I built it with NetBeans, so there’s a bunch of NetBeans-specific stuff embedded in the source. Once I get the project running strictly on Ant, I’ll upload the source here: http://github.com/nzakas/combiner/
Nicholas C. Zakas on September 22nd, 2009 at 7:07 pm
@Aaron – You need to add a separate line for each requirement:
/*requires file1.js*//*requires file2.js*/
I’m not sure I understand your second question regarding print style sheets. Combiner just combines files, it doesn’t know about print or screen or any other CSS directives.
The dependency validation basically ensures that the specified dependency file exists and that there are no circular references.
Nicholas C. Zakas on September 22nd, 2009 at 7:30 pm
@Nicholas,
Most web developers create separate CSS file for print (print.css).
To save a http request, it’s best to add the contents of print.css to the the all.css.
For this to work, you need to put the contents of print.css in all.css like this:
@media print { print css goes here }
I was just wondering if Combiner can cope with this, but it makes sense that Combiner does not: it just combines files and cannot know that a CSS file is actually a print CSS.
Aaron Peters on September 23rd, 2009 at 6:20 am
Nice, I’ve been looking for something like this. I will give it a try on my current work project.
I have thought about implementing something similar a couple of weeks ago. My idea was to use the Securable Modules syntax, and wrap it in some boiler plate code while concatenating. This should make it possible to use the same code on the server (using the standard securable modules functionality) and the client (preprocessed and concatenated for production, using a XHR loader for debugging.)
I was planning to merge this with the JavaScript preprocessor I recently released, but I unfortunately haven’t had time for it yet.
Bram Stein on September 23rd, 2009 at 7:30 am
This tool is a must have. And the annotation is both simple and useful by itself ; I actually make this kind of notes for myself at the beginning of each Javascript file:
// Uses db (json.js)
// Uses bezen.log (bezen.log.js)
Please opensource it
Eric Bréchemier on September 25th, 2009 at 1:25 pm
When we combine loads of js files in to one or two files, doesn’t the browser has to
parse the huge javascript file and execute the called function each and every time it is called, in which case doesn’t the overall user experience slows down due to parsing the file each time and then executing ?
Madhu Jahagirdar on September 27th, 2009 at 1:56 pm
@Madhu – I’m not entirely sure what you’re asking. It sounds like you’re concerned about browser caching, which isn’t related to Combiner at all. From a performance point of view, parsing one file with a million lines takes just as long as parsing ten files each with ten thousand lines, the only difference is the number of HTTP requests.
Nicholas C. Zakas on September 27th, 2009 at 7:48 pm
Hi Nicholas
I’ve used ANT as described by Julien Lecomte and also Sprockets to join files. ANT is probably your best bet if you want the whole program, but if you just want to join *.js files, Sprockets is great. Since order and dependancies are important, I’ll definitely give Combiner a try.
Cheers
Thomas
TJ on September 29th, 2009 at 4:41 am
[...] modularisation there are several tools to handle the dependency combination of Javascript files. Depending on the amount of Javascript and [...]
Initialise External Javascript in Page Fragments | Über Software on October 1st, 2009 at 3:17 pm
Hi Nicholas,
I am now using Combiner in Ant with a macro
combiner: started processing @{in} => @{out}
combiner: completed @{in} => @{out}
which then allows me to use it with a custom element in targets
I found 2 bugs / missing features:
- when processing a single JS file without any requirements, the output is empty. I would expect it to be the unmodified file
- the glob pattern works within the same directory (*.js) but not in a relative directory (../src/*.js) which results in a file not found error
Please opensource it so that we can start posting patches
Cheers,
Eric
Eric Bréchemier on October 6th, 2009 at 12:02 pm
The Ant macro (with tags converted to square brackets)
[macrodef name="combiner"]
[attribute name="in" /]
[attribute name="out" /]
[sequential]
[echo]combiner: started processing @{in} => @{out}[/echo]
[java jar="lib/combiner/combiner-0.0.1.jar"
fork="true"
failonerror="true"
]
[arg value="-v" /]
[arg line="--charset UTF-8" /]
[arg line="-o @{out}" /]
[arg value="@{in}" /]
[/java]
[echo]combiner: completed @{in} => @{out}[/echo]
[echo/]
[/sequential]
[/macrodef]
and its use in a target
[combiner in="../src/bezen.assert.js" out="out/fullbezen-assert.js" /]
Eric Bréchemier on October 6th, 2009 at 12:07 pm
I found a turnaround for the glob issue in Ant. It looks as though the issue lies in the fact that the *.js parameter comes out differently when coming from the command line or when given to the java task in Ant.
I now convert the glob manually to a list of files using pathconvert (tags replaced with []):
[fileset id="allinfiles" dir="../src" includes="*.js" /]
[pathconvert property="allin" pathsep=" " refid="allinfiles" /]
[combiner in="${allin}" out="out/full/bezen.js" /]
and it works.
Eric Bréchemier on October 7th, 2009 at 11:48 am
Interesting work. I was curious if you’ve seen my Juicer project? It does basically exactly the same work, and adds a few optional tools in the mix as well. You can check it out here: Juicer – A CSS and JavaScript packaging tool. Cheers.
Christian on October 7th, 2009 at 5:17 pm
@Eric – Thanks for letting me know. I’ve been swamped, but want to get the source up on GitHub as soon as I can.
@Christian – I hadn’t seen your project before, looks like you’ve got some nifty stuff in there. Combiner is much simpler, it just does one thing, so I don’t put it in the same category as Sprockets or Juicer.
Nicholas C. Zakas on October 7th, 2009 at 10:41 pm
Hi everyone,
Combiner is now open source and available on GitHub at http://github.com/nzakas/combiner . Please post bugs/feature requests there. Thanks!
Nicholas C. Zakas on October 18th, 2009 at 2:53 pm
[...] 下载:http://github.com/nzakas/combiner/downloads 文档:http://www.nczonline.net/blog/2009/09/22/introducing-combiner-a-javascriptcss-concatenation-tool/ 快速上手: java -jar combiner-0.0.1.jar -v -o myfile.js *.js java -jar combiner-0.0.1.jar -v [...]
好用的命令行工具 « Kejun’s Blog on November 17th, 2009 at 6:03 am
Hi Nicholas,
I tried to use combiner in my project. The js files are distributed in different directories so I use ../foo.js to refer the fundamental files. The result seems strange. The same file are included several times and the dependency is broken.
I went through the source and found the SourceFile.getName() return a absolute path instead of canonical path which means the same file may be include multiple times if it is referred in different files located in different sub-directory.
I guess the dependency breakage is related to the file name handling.
Thank you for the tool! It is very useful.
nomad on November 19th, 2009 at 5:01 am
@Nomad – I’ve not run into trouble with this before, but if you have a good example of where this is a problem, please file it at http://github.com/nzakas/combiner/issues.
Nicholas C. Zakas on November 21st, 2009 at 1:10 pm
Comments are automatically closed after 14 days.