ローカルのJavaScriptファイルをClosure Compiler ServiceのREST APIでコンパイルできるように

前に作って愛用しているClosure Compiler ServiceのREST APIを叩くPerlスクリプトにローカルのJavaScriptファイルを読み込んでコンパイルに含められる機能を付けた。Closure CompilerJARファイルを使ってローカルで動かしても良かったんだけど、APIやWeb UIのコメントでオプション設定を書く方法(Greasemonkey的な奴)が気に入っているので@code_pathというパラメーターを追加する形にした。

コンパイルするJavaScriptファイルでは

// ==ClosureCompiler==
// @code_path foo.js
// @code_path bar.js
// @code_url  http://example.com/js/buz.js
// ==/ClosureCompiler==

というように@code_pathで取り込むJavaScriptファイルのパスを指定するだけ。@code_url等の他のオプションとはバッティングしないけど、依存関係によって書く順序には注意する必要がある。これらに続けて普通にJavaScriptコードも書けるとかそこらへんはWeb UIと一緒。

コンパイル方法は前のと同じで標準入力と出力で。

$ gccs.pl <test.js >test.min.js

肝心のgccs.plは以下の通り。

#!/usr/bin/env perl

# gccs.pl - Compile your JavaScript code with Google Closure Compiler Service
# Usage:    gccs.pl < <original file> > <compiled file>
# License:  http://hail2u.mit-license.org/2009
# Modified: 2012-01-04T18:43:15+09:00

use strict;
use warnings;

use JSON;
use LWP::UserAgent;
use Path::Class qw();

my @params = (
	"output_info",   "compiled_code",
	"output_format", "json",
);

&main;
exit;

sub main {
	my @code = <STDIN>;
	push @params, "js_code", join("", @code);
	my $idx = 0;
	my $found_metadata = 0;

	while (my $line = $code[$idx++]) {
		if ($line =~ /^\/\/ ==ClosureCompiler==/) {
			$found_metadata = 1;
			last;
		}
	}

	if ($found_metadata) {
		while (my $line = $code[$idx++]) {
			if ($line =~ /^\/\/ ==\/ClosureCompiler==/) {
				last;
			} elsif ($line =~ /^\/\/ @(\S+)\s*(.*)$/) {
				if ($1 eq "code_path" && -e $2) {
					my $js_code = ";" . Path::Class::File->new($2)->slurp() . ";";
					push @params, "js_code", $js_code;
				} else {
					push @params, $1, $2;
				}
			}
		}
	}

	&compile(@params);
}

sub compile {
	my $ua = LWP::UserAgent->new;
	my $res = $ua->post("http://closure-compiler.appspot.com/compile", \@_);

	if ($res->is_success) {
		my $c = from_json($res->decoded_content);

		if (defined $c->{"serverErrors"}) {
			foreach (@{$c->{"serverErrors"}}) {
				warn "Error(" . $_->{"code"} . "): " . $_->{"error"};
			}

			die "Failed to compile";
		} else {
			binmode STDOUT;
			print $c->{"compiledCode"};
		}
	} else {
		die $res->status_line;
	}
}

一応セミコロンの無いダメなJavaScriptファイルのことも考慮して前後にセミコロンを挟んでやったりはしておいた。ちょっと美しくないけどClosure CompilerをSIMPLE_OPTIMIZATIONS以上で通せば消えるのでまぁ良いかなとか。

普通にバラバラにスクリプトを書いて、Makefile的なJavaScriptファイルで@code_pathを使って参照し、それをコンパイルとかで使ってください。catよりはマシ程度ですけどね!