JavaScriptを圧縮
Octopressでは通常source/javascripts というディレクトリに
JavaScriptを置いて、それがroot_url/javascripts/
に
コピーされるのでそれをhead
内とかで呼んでいます。
基本、細かく分けて呼ぶよりもまとめてしまって一度に読んだ方が速いので
そうしてみます。
さらに圧縮もかけることにします。
こんな感じのタスクをRakefileの中に用意。
Rakefile
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
require "yui/compressor"
require "parallel"
js_for_combine = [ 'footnote.js' , 'jquery.githubRepoWidget.min.js' , 'monthly_archive.js' , 'utils.js' , 'randomposts.js' ]
js_output = "all.js"
js_minify_others = false
n_cores = 4
desc "Minify JS"
task :minify_js do
puts "## Minifying JS"
Rake :: Task [ :combine_js ]. execute
if js_minify_others
Rake :: Task [ :minify_other_js ]. execute
end
end
desc "Minify JS and combine"
task :combine_js do
puts "## Combining JS"
compressor = YUI :: JavaScriptCompressor . new
if File . exist? ( " #{ source_dir } /javascripts/ #{ js_output } " )
t_alljs = File . mtime ( " #{ source_dir } /javascripts/ #{ js_output } " )
time_check = false
js_for_combine . each do | j |
if File . mtime ( " #{ source_dir } /javascripts/ #{ j } " ) > t_alljs
puts "newer file #{ j } is found"
time_check = true
break
end
end
if not time_check
next
end
end
output = File . open ( " #{ source_dir } /javascripts/ #{ js_output } " , "w" )
js_for_combine . each do | j |
input = File . read ( " #{ source_dir } /javascripts/ #{ j } " )
output << compressor . compress ( input )
end
output . close
cp_r " #{ source_dir } /javascripts/ #{ js_output } " , " #{ public_dir } /javascripts/"
end
desc "Minify other JS"
task :minify_other_js do
puts "## Minifying other JS"
n = 0
compressor = YUI :: JavaScriptCompressor . new
Parallel . map ( Dir . glob ( " #{ source_dir } /javascripts/**/*.js" ), :in_threads => n_cores ) do | j |
if ( js_for_combine +[ js_output ] ) . include? ( j . sub ( " #{ source_dir } /javascripts/" , "" )) or \
j . include? ( "compressed" )
next
end
d = j . split ( '/' ) [ 0 ..- 2 ]. join ( '/' )
n = j . split ( '/' ) [- 1 ]
compressed = d + "/compressed/" + n
if File . directory? ( d + "/compressed/" )
if File . file? ( compressed ) and File . mtime ( compressed ) > File . mtime ( j )
next
end
else
mkdir_p compressed . split ( '/' ) [ 0 ..- 2 ]. join ( '/' )
end
puts "Minifying #{ j } "
input = File . read ( j )
output = File . open ( compressed , "w" )
output << compressor . compress ( input )
output . close
n += 1
end
if n > 0
cp_r " #{ source_dir } /javascripts/." , " #{ public_dir } /javascripts/"
end
end
js_for_combine
がまとめるJavaScript達、
js_output
がまとめた物を出力するファイル名です。
上のタスクを実行すればsource/_includes/head.html 等で
all.js だけを読みこめば良いことになります。
タスクは3つあって、
minify_js: これをgenerate
タスクの中で呼ぶ。
combine_js: 指定されたファイルたちを圧縮してまとめる。
minify_other_js: 指定されて無いファイルたちの圧縮したファイルも作成する。
と言った所。
minify_js
の中ではjs_minify_others
を見て
minify_other_js
をするかどうか決めています。
圧縮にはYUI Compressorを使っています
1 。
comibine_js
の方ではインプットの中に1つでも
js_output
より新しいファイルがあれば作りなおす、という風になっています。
また、ファイルの作成はsource/javascripts の中で行い、
出来たファイルをpublic_dir
に送る様にしています。
これは単独でこのタスクを動かした時に
そのままjekyll
コマンドを打たなくても確認したりデプロイ出来る様にするためです。
ちょっとjekyll
コマンド内のコピーとやタスク同士でも重複する所が
ありますが、問題にならないレベルなので良しと。
後、minify_other_js
の方では各ファイルを別個に圧縮していくので、
Parallel
を使って並列処理をしています。
n_cores
は並列で走らせる数の設定。
これで後は
<script src="{{root_url}}/javascripts/all.js"></script>
と呼んで上げれば必要な物は全部入ります(全部をリストに入れていれば)。
新しいJavaScriptを追加した場合でも
Rakefileのリストに追加すれば自動的に加わるので
head.html を直接いじるよりも管理はし易いかな、とも思います。
HTMLを圧縮
HTMLに関してはpublic_dir
に出力されたものに対して行います。
Rakefile
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
require "yui/compressor"
require "htmlcompressor"
require "parallel"
require 'ruby-progressbar'
html_for_minify = [ "*.html" , "blog/*/*/*/*/*.html" , "windows" , "mac" ]
html_not_minify = [ "rawhtml" , "others" , "test" ]
n_cores = 4
desc "Minify HTML"
task :minify_html do
puts "## Minifying HTML"
option = {
:remove_comments => false ,
:compress_css => true ,
:css_compressor => :yui ,
:compress_javascript => true ,
:javascript_compressor => :yui
}
compressor = HtmlCompressor :: Compressor . new ( option )
posts = []
if html_for_minify == "all" or html_for_minify [ 0 ] == "all"
posts = Dir . glob ( " #{ public_dir } /**/*.html" )
else
posts_tmp = html_for_minify
if html_for_minify . is_a? ( String )
posts_tmp = [ posts_tmp ]
end
posts_tmp . each do | p |
f = " #{ public_dir } / #{ p } "
if f . scan ( "\*" ) . length > 0
posts += Dir . glob ( f )
elsif File . file? ( f )
posts . push ( f )
elsif File . directory? ( f )
posts += Dir . glob ( " #{ f } /**/*.html" )
end
end
end
if html_not_minify != nil
if html_not_minify . is_a? ( String )
html_not_minify = [ html_not_minify ]
end
html_not_minify . each do | p |
posts . delete_if { | post | post . start_with? ( " #{ public_dir } / #{ p } " )}
end
end
progressbar = ProgressBar . create ( :title => "Minify HTML" , :starting_at => 0 ,
:total => posts . size ,
:format => '%t, %a |%b%i| %p%' )
Parallel . map ( posts , :in_threads => n_cores ) do | p |
input = File . read ( p )
output = File . open ( p , "w" )
output << compressor . compress ( input )
output . close
progressbar . increment
end
end
html_for_minify
で圧縮する物を決めます。
文字列でも配列でも良くて、
与えられた物がディレクトリならその中を再帰的に、
ファイルならそれを圧縮します。
指定はルートディレクトリより下からで(public_dir
からのパス)。
all
と言う文字列を与えると全てのHTMLファイルを探して圧縮します。
また、逆に特定のものだけを除きたい場合には
html_for_minify
に入れます。
こちらもディレクトリの場合は再帰的に除きます。
HTMLにはHtmlCompressor
を使います。
これにはGoogleの
htmlcompressor
をベースにしたものが入っています。
従ってオプションもGoogleの物を参考に出来ます。
与えているオプションは
コメントを残す事と、HTML内に直接書かれたCSS/JavaScriptに関してYUIの物を使って
圧縮する事を指定しています。
コメントに関しては
Octopressで’ダブルハイフン’をそのまま残す
でも触れたように、一部コメント内を見て判断する外部サービスとかもあるので
残すことが必要です。
preserve_patterns
というオプションを使うと、
決められたパターンだけ残せるので
特定のコメントだけ残すことも可能ですが、
全て残しても大した量ではないので間違えて消すリスクを考えたら
全部残しておいた方が良いな、ということで全部残すようにしています。
後、これ結構時間がかかるので、
ruby-progressbar
を使って実行経過を表示するようにもしました。
(ruby-progressbarに関してはGitHubのWiki
に詳しく使い方が書いてあります。)
これで
Minify HTML, Time: 00:01:26 |======================================= | 85%
な感じの表示が出ます。
CSSは?
CSSもYUIのCssCompressorを使って圧縮することが出来ますが、
OctopressではCSSをcompass
を使ってコンパイルして
まとめてるので別途行う必要はないと思います。
もしやりたいなら
Rakefile lang:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
require "yui/compressor"
require "parallel"
n_cores = 4
desc "Minify CSS"
task :minify_css do
puts "## Minifying CSS"
compressor = YUI::CssCompressor.new
Parallel.map(Dir.glob("#{source_dir}/stylesheets/**/*css"), :in_threads=> n_cores) do |name|
puts "Minifying #{name}"
input = File.read(name)
output = File.open("#{name}", "w")
output << compressor.compress(input)
output.close
end
cp_r "#{source_dir}/stylesheets/.", "#{public_dir}/stylesheets/"
end
な感じ。試しにやってみたら
compass
の出力だと59027文字だったscreen.css が58707文字まで減りました。
一応まだ圧縮できる事は出来るみたいです。
まとめ
上のようにして圧縮出来る様になったので、後は
generate
タスクの中とかで
Rakefile
1
2
3
4
5
6
7
8
9
10
11
system "compass compile #{ style } --css-dir #{ source_dir } /stylesheets"
system "jekyll build"
# Minify JavaScript
Rake :: Task [ :minify_js ]. execute
# Fix double dash problem
Rake :: Task [ :fix_double_dash ]. execute
# Compress HTML
Rake :: Task [ :minify_html ]. execute
みたいな感じでjekyll build
した後に呼んであげればOK。
minify_html
の方は
Octopressで’ダブルハイフン’をそのまま残す
のとこで作ったfix_double_dash
よりは後にかけないといけません。
後は上のコードのrequire
の所をRakefileの先頭に、
js_for_combine
等のオプションを先頭の方にあるオプション群に追加、
タスクを適当な所に書いておけばOK。
参考:
Octopress: Minify HTML, CSS and JS · Andrei Mihu