今回はBlades Lab.で開発したJavaScriptアプリ、JS Packerをご紹介します。
JS PackerはローカルのJavaScriptファイルをブラウザ上にドラッグ&ドロップするとそれらを結合してくれるアプリです。
ドラッグ&ドロップによりファイルの追加、並び替えや削除、また、結合時の様々なオプションを設定する事も出来ます。
「ウェブサイトのパフォーマンス向上のため複数のJavaScriptファイルを結合してリクエスト数を減らしたい・・・」なんて時に使えるのではないでしょうか?
なお、このアプリはドラッグ&ドロップAPI、ファイルAPI、WebWorkers、Selectors API、DATA URI、JavaScript1.8、HTML5/CSS3といった最新技術を用いて作成されています。
そのためブラウザはFirefoxの最新版をご利用下さい。
JS Packerのアドレスは以下の通りです。
https://www.blades.co.jp/applications/jspacker/
それでは、このJS Packerの全貌に迫ってみましょう。
結合データの作成
まずはJS PackerをFirefoxの最新版で開きましょう。
画面上のグレーの部分が入力、つまり結合対象のJavaScriptファイルをドラッグ&ドロップする領域です。
一方、画面下の白い部分は出力、ドラッグ&ドロップされたJavaScriptファイルを結合したテキストデータが表示される領域です。
それでは、ローカルにあるJavaScriptファイルを複数選択しブラウザ上にドラッグしてみてください。
ドラッグしたJavaScriptファイルが下図のように表示されましたか?
同時にブラウザ画面下の白い領域にはローディングアイコンが現れ、その後結合されたテキストデータが表示されているはずです。
結合に必要な操作はたったこれだけです!!
以降は様々なオプション機能について解説したいと思います。
JavaScriptファイルの追加
さらにJavaScriptファイルを追加したい場合もブラウザ領域にドラッグするだけです。
1個または複数個のローカルファイルをさらにドラッグ&ドロップしてみてください。
ファイルが追加され、同時に結合データも再作成されます。
結合順序の入替・不要なJavaScriptファイルの削除
複数のJavaScriptファイルが互いに依存している場合、実行する順番が重要になってきます。
これの前にあれを実行したい・・・という場面も多くあるのではないでしょうか?
そんなときは入力領域内に並んでいるボタンを上下にドラッグしてみてください。
結合順序を入れ替えることができます。
さらに、やっぱりこのファイルはいらない・・・なんて時はそれを入力領域の外にドラッグすることで結合対象から削除できます。
このように簡単なマウス操作でファイルの追加・並び替え・削除が可能です。
無名関数でラップし、シンボル名の重複を避ける
複数のJavaScriptファイル内で同名のグローバルシンボルを作成している場合、それらファイルを結合すると不具合が起こる可能性があります。
そんな時はWrap each code in anonymous functionのチェックボックスをオンにします。
それぞれのファイルが(function(){})();でラップされ不具合の可能性を抑えてくれます。
(function(){ // // ファイル(1)のソースコード // })(); (function(){ // // ファイル(2)のソースコード // })(); (function(){ // // ファイル(3)のソースコード // })();
ただし、この機能はグローバル・ローカルシンボルの概念の理解と、(function(){})();でラップする必要性の検討が必要です。
グローバルシンボルを複数のファイルで共有している場合、逆に動作しなくなる可能性もあります。
(実際のところ、この機能が必要になる場面は少ないかもしれません。)
結合したJavaScriptファイル名などの情報をコメントとして挿入する。
Insert commentsのチェックボックスをONにしてみましょう
(初期値はONとなっています)。
出力画面にローディングアイコンが現れコメントを含んだデータが再作成されます。
データ先頭に結合したJavaScriptのファイル名とそれらの結合順、データの作成日時、
さらに、それぞれのファイル先頭にファイル名がコメントで挿入されます。
/** * THIS CODE WAS GENERATED BY JS PACKER VER.0.0.1 @ 18:42:55 July 29, 2010 * * 1 : jquery-1.3.2.js * 2 : jquery.mousewheel.js * 3 : swfobject.js * */ /*----- jquery-1.3.2.js -----*/ // // ソースコード // /*----- jquery.mousewheel.js -----*/ // // ソースコード // /*----- swfobject.js -----*/ // // ソースコード //
さて、以上がJS Packerの主な機能です。
"disabled"になっているチェックボックスがありますが、それらは今後追加するかもしれない機能(コメント削除、最小化)です。
しかし、これらの機能はすでに他のすばらしいサイトで利用可能ですのでこのJS Packerに追加する必要はないでしょう。
JS Packerの機能の説明は以上ですが、ここからはJS Packerに用いている様々な技術について簡単に触れてみたいと思います。
Selectors API
$$ = function(exp, prnt){ prnt = prnt || doc; var elms = prnt.querySelectorAll(exp), $s = Array.map(elms, function(elm) $(elm)); /** * イテレータ&生要素取得用メソッドです。 * 引数が関数でない時は要素を返します。 */ var cmd = function(arg){ //引数無しで呼び出した場合は$要素の配列を返す if(!arguments.length){ return $s; //関数が渡された場合は全$要素に対しそれを実行 }else if(typeof arg === "function"){ $s.forEach(function($, i) $(arg, i, elms) ); return arguments.callee; //その他の場合 }else{ return let ($ = $s[arg]) $ ? $ : void 0; } };
▲JS Packerのソースコードの一部を抜粋
JS Packerでは各種DOM操作用の簡易的な専用ライブラリを使用しています。
その専用ライブラリではDOM要素取得にSelectors APIを用いています。
Selectors APIとはCSSのセレクタを引数にとり対応するDOM要素を取得するためのAPIです。
JQuery等の各種ライブラリでも採用されていますね。
おなじみのgetElementById()やgetElementsByTagName()等を使うよりも高速・簡単に要素を取得する事ができます。
classList
これまで、要素のクラス名を操作するには要素のclassNameプロパティ内の文字列を直接変更するしかありませんでした。
しかしclassListを用いればもっと簡単にできます。
各要素はclassListというプロパティをもっており、classListにはクラス名を操作するための各種メソッドが用意されています。
/** * クラス名操作用 */ cmd.cls = function(exp){ var [, op, cls] = /^\s*(-|\+|\*)?\s*([-a-zA-Z0-9_$]+)/.exec(exp); switch(op){ case "-" : elm.classList.remove(cls); return this; case "+" : elm.classList.add(cls); return this; case "*" : elm.classList.toggle(cls); return this; default : return elm.classList.contains(cls); } };
▲JS Packerのソースコードの一部を抜粋
ドラッグ&ドロップAPIとファイルAPIでローカルファイルにアクセス!!
//windowへのイベント追加 $(win).on("dragover, dragenter, dragleave", function(evt) evt.preventDefault() );//「ドラッグを受け付けない」というデフォルト動作を抑止
▲JS Packerのソースコードの一部を抜粋
ウェブページ上でドラッグ&ドロップ機能を再現することはこれまでも可能でした。
CMSの管理画面などで実際に使用したことのある方も多いのではないでしょうか?
しかし、それはmouseover、mousedown、mousemoveといったマウスイベントを利用し疑似的に、しかも力ずくで(見た目のみを)再現していたにすぎません。
OSのGUIにより提供されるドラッグ&ドロップ機能とは根本的に違うものであり、
ブラウザ上、つまりhtml内の要素を移動する事は出来ても、ブラウザの外の世界とやり取りする事は出来なかったのです。
そこで登場するのがドラッグ&ドロップAPIとファイルAPIです。
これらの技術を用いるとブラウザの外の世界にアクセスできます。
エクスプローラ等からブラウザ上にドラッグ&ドロップされたローカルファイルにアクセスする事が出来るのです。
JS Packerではこれらの技術を用い、複数のローカルのJavaScriptファイルを結合する機能を提供します。
WebWorkersによるマルチスレッドプログラミング
JavaScriptはシングルスレッドの言語です。
setTimout/setIntervalを用いて処理を分割し、マルチスレッドっぽい処理は可能でしたが、本格的なマルチスレッド処理は不可能だったのです。
しかし、WebWorkersを用いることでそれが可能になります。
ユーザーエクスペリエンスを犠牲にすることなく、重い処理をバックグラウンドで実行する事が出来るのです。
JS PackerではDOM操作(つまり見た目)以外の処理(テキストデータの結合・整形等)をWebWorkersに任せています。
let packer = $( new Worker("./packer.js") ).on("message", function(evt){ $output.fdin().value = evt.data; })();
▲JS Packerのソースコードの一部を抜粋
DATA URI
DATA URIとは画像等のリソースを指定するスキームのひとつです。
具体的には画像等のデータをBase64に変換したものです。
JS PackerではDATA URIを用いて全ての画像データをCSSに埋め込みリクエスト数を抑えています。
<p> <img src="data:image/png;base64,ここにデータが入る" alt="画像"> </p>
▲HTMLファイル内でpng画像をDATA URIに置き換えた例
textarea{ background-image: url(data:image/png;base64,ここにデータが入る); }
▲CSSファイル内でpng画像をDATA URIに置き換えた例
JavaScript 1.8
JS Packerは対象ブラウザをFirefoxとしており、様々な新技術を実験的に使用しています。
もちろんJavaScriptも例外ではありません。
セッタ、ゲッタ、イテレータ・ジェネレータ、letキーワードを用いたブロックスコープといった他の(一部の)ブラウザでは利用できない最新機能を利用しています。
/** * フェードイン */ var fdin= (function(){ var c = css, min = MIN_DELTA_OPACITY, cur = +c.opacity; while(1){ let d = (1 - cur) / 2.0; cur += d < min ? min : d; if( cur < 1 ){ c.opacity = cur; yield true; }else{ c.opacity = 1; yield false; cur = +c.opacity; } } })(); cmd.fdin = function(fnc){ var itv = INTERVAL, sto = win.setTimeout, itr = fdin; (function(){ itr.next() ? sto(arguments.callee, itv) : cmd(fnc); })(); return this; };
▲JS Packerのソースコードの一部を抜粋
HTML5/CSS3
最近ではHTML5/CSS3で作成されたウェブサイトも多く見かけるようになりました。
JS Packerではnth-of-type等の各種疑似要素からtransform等のプロパティまで様々な最新機能を使用しています。
<!doctype html> <head> <meta charset="utf-8"> <title>JS Packer</title> <meta name="keywords" content="JavaScript, js, 連結, 結合, ファイルAPI, ドラッグ&ドロップAPI, html5, css3, file api, drag and drop api, WebWorkers"> <meta name="description" content="複数のJavaScriptを結合できる!!HTML5,ドラッグ&ドロップAPI,ファイルAPI,CSS3を使ったウェブアプリケーションです。">
▲HTML5の例
div#input p:before{ position: absolute; top: 50%; left: 5px; display: block; width: 20px; height: 20px; margin-top: -16px; padding: 5px; background: -moz-linear-gradient(top, #87e7ff, #1b35ae); border: 1px solid #488ed6; -moz-border-radius: 5px; border-radius: 5px; color: #ffffff; text-align: center; } div#input p:nth-of-type(1):before{ content: "1"; } div#input p:nth-of-type(2):before{ content: "2"; } div#input p:nth-of-type(3):before{ content: "3"; } div#input p:nth-of-type(4):before{ content: "4"; } div#input p:nth-of-type(5):before{ content: "5"; }
▲CSS3の例
最後に
以上でJS Packerについての記事は終了です。
このアプリ作成を通して感じたのは、ブラウザ互換性の問題を考える必要が無いと非常に楽だということです。
特にIEに対応する必要が無いと、CSS/JavaScriptともに非常にシンプルに書けますね。
通常の案件でもIEの呪縛から解放されたいものです・・・
Appendix
今回ご紹介したJS Packerで使用しているJavaScript, CSSの一覧です。
興味がある方はご覧ください。