Thursday, August 2, 2007

Javascript Lint and AssetPackager

The AssetPackager plugin is a configurable tool which allows you to package multiple javascript (or css) files, in whatever order you want, into a single file. This speeds up your page loads by reducing the number of times the users browser needs to fetch files from your server. So instead of a dozen separate javascript and css fetches, you can get down to two if you want. In addition, it includes tools to compact the javascript and css, making the files even smaller.

http://synthesis.sbecker.net/pages/asset_packager

You install it as a plugin, add a capistrano deployment task, and really you're good to go. For my needs, it's perfect.

The only problem that I ran into was something I've seen in other projects that used javascript compactors. If you have things like a missing semi-colon, the compacted code ends up producing errors in the browser, where the uncompacted code does not. This can be a real headache because typically you never actually run the compacted code locally, only when it is finally deployed onto your server.

What I wanted was a way to check for this problem, and ideally check for it as part of my normal deployment process. Since I run all my tests before installing, it seemed natural to include a check of the javascript syntax as part of my tests, so that's what I did.

The first part of this is finding a javascript lint program. I found the "JavaScript Lint" tool:

http://www.javascriptlint.com/

This lint tool was created by Matthias Miller. It's great, and very configurable. It also is available as an executable, which was convenient for automation.

Having downloaded and installed it, I ran into another roadblock. The prototype, scriptaculous and behavior libraries that I was using did not pass lint. Actually, some of my own code didn't pass lint either, but at least there wasn't much of it, so it was easy to fix.

When it comes to lint, you really want to start early, and keep your code clean. If you have a ton of code that is dirty, it tends to just linger because most people don't want to go make 200 changes (even if they are as simple as adding a semicolon or braces), and since hunting for new issues in a sea of old lint errors isn't any fun, the end result is that you just stop using lint entirely. I am lucky enough to be at the start of a project, so fixing the dozen warnings took minutes, and it means that any new warnings are indicators of something that I can (and should) really pay attention to.

With my own code clean, what I wanted was for my tests to run lint on any javascript files which I hadn't specifically exempted, so that new files would be caught in the lint trap, rather than being omitted until I remembered to add them.

That led to the test you see below, which was created in test/unit/javascript_validation_test.rb in my rails application. It runs through every file in the public/javascript folder, removes the ones that I've specifically exempted, removes anything matching a pattern (which was useful for removing files generated by the AssetPackager) and then runs lint on the remaining files, creating output only if the test fails.

The end result is automated javascript lint checking, which runs as part of my standard install process. I'm sure that there will be cases that won't get caught, but at least I feel pretty comfortable that issues like missing semicolons and extra commas in array definitions will be caught and fixed before any code gets installed.


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
#
# A test for valid javascript. Every javascript file is passed through
# jsl, except for the ones which we know will fail, but we feel good about
# because they come from standard libraries.
#
require File.dirname(__FILE__) + '/../test_helper'

class JavascriptValidationTest < Test::Unit::TestCase
# Where to find the JSL program
JSL_EXECUTABLE = "c:/jsl-0.3.0/jsl"

# Where to find the config file with all our lint settings
JSL_CONFIG_FILE = File.dirname(__FILE__) + '/../jsl.conf'

# Where our javascript is located
JAVASCRIPT_PATH = File.dirname(__FILE__) + '/../../public/javascripts'

# Files we don't want to process because they are either not actually javascript
# files, or because we tested them with the asset packer once, they worked fine,
# and we don't expect them to change.
EXCEPTION_LIST = %w{
.
..
.svn
behaviour.js
builder.js
controls.js
dragdrop.js
effects.js
prototype.js
redbox.js
scriptaculous.js
slider.js
tiny_mce
}


# Pattern for stuff we don't want to process
EXCEPTION_PATTERN = /(^base_\d+\.js$)/

def test_for_valid_javascript
# For each javascript file in the folder
all_files_in_path = Dir::entries(JAVASCRIPT_PATH)

# Figure out which files match our pattern of things not to process
files_matching_exception_pattern = all_files_in_path.grep(EXCEPTION_PATTERN)

# The set of files to process does not include our exceptions
files_to_process = all_files_in_path - (EXCEPTION_LIST + files_matching_exception_pattern)

# Process the files -- checking for exception patterns
files_to_process.each do |file|
# Capture the output
jsl_output = `#{JSL_EXECUTABLE} -conf #{JSL_CONFIG_FILE} -nologo -process "#{JAVASCRIPT_PATH}/#{file}"`

# Printing it only if something went wrong
print jsl_output if $? != 0

# And the obligatory assert, so our tests can fail if need be
assert_equal $?, 0, "#{file} fails to pass lint"
end
end
end

0 comments: