2012年12月24日月曜日

Sublime Text 2 で TypeScript の開発環境を整える

このエントリは Sublime Text 2 Advent Calendar 2012 用です。

---

TypeScript については公式サイトなどを見てください。

Sublime Text 2 で TypeScript を開発するためのこまごましたチップスをブログに書いてきたので、一旦まとめたいと思います。


1. TypeScript ファイル(.ts)をシンタックスハイライトするようにする

「Y.A.M の雑記帳 - Sublime Text 2 に TypeScript の syntax highlighting を入れる」 を見よう!


2. TypeScript の補完がでるようにする

「Y.A.M の雑記料 - Sublime Text 2 で TypeScript の補完を出すプラグイン作った」 を見よう!

補完は以下のコマンドで出ます(標準機能)。

Linux : alt + /
Mac : command + space


3. TypeScript ファイルをビルドできるようにする

「Y.A.M の雑記帳 - Sublime Text 2 で TypeScript をビルドする」 を見よう!

ビルドは以下のコマンドで実行できます。

Linux : ctrl + b
Mac : command + b


4. ファイル保存時に自動でビルドさせる

「Y.A.M の雑記帳 - Sublime Text 2 でファイル保存時に自動でビルドさせる」 を見よう!


5. ctrl + / (Mac は command + /)でコメントをトグルさせる

「Y.A.M の雑記帳 - Sublime Text 2 で .ts ファイルでも Ctrl + / でコメントをトグルできるようにする」 を見よう!


6. TypeScript 用のスニペットを作る

JavaScript 用にあらかじめ用意されているスニペットがいくつかあります。

([Preferences] - [Browse packages...])/JavaScript/ の中をみると .sublime-snippet ファイルがこれだけあります。
  • for-()-{}.sublime-snippet
  • for-()-{}-(faster).sublime-snippet
  • function.sublime-snippet
  • function-(fun).sublime-snippet
  • Get-Elements.sublime-snippet
  • if.sublime-snippet
  • if-___-else.sublime-snippet
  • Object-key-key-value.sublime-snippet
  • Object-Method.sublime-snippet
  • Object-Value-JS.sublime-snippet
  • Prototype-(proto).sublime-snippet
  • setTimeout-function.sublime-snippet


例えば、for-()-{}.sublime-snippet の中身は次のようになっています。

for source.js for (…) {…}

scope で指定されている拡張子のファイルで、tabTrigger で指定されている単語の後に Tab を押すと、content で指定されているスニペットに置き換わります。Tab を押したときに候補が複数ある場合は、補完候補ウィンドウに description が表示されます。

tabTrigger に for を指定しているスニペットが
・for-()-{}.sublime-snippet
・for-()-{}-(faster).sublime-snippet
の2つあるため、次のように候補が2つ表示されます。



上部の候補を選ぶと次のように for 文のスニペットに置き換わります。



これらの補完を TypeScript でも使えるようにしてみます。

・for

せっかくなので、TypeScript の特徴である型情報を付加して、var i を var i : number にします。 scope タグを source.ts にするのを忘れずに。 このファイルを ([Preferences] - [Browse packages...])/TypeScript/ に置きます。

TypeScript/for-()-{}.sublime-snippet
<snippet> <content><![CDATA[for (var ${20:i} : number = 0; ${20:i} < ${1:Things}.length; ${20:i}++) { ${100:${1:Things}[${20:i}]}$0; }]]></content> <tabTrigger>for</tabTrigger> <scope>source.ts</scope> <description>for (…) {…}</description> </snippet>
他のも TypeScript 用にします。

TypeScript/for-()-{}-(faster).sublime-snippet
<snippet> <content><![CDATA[for (var ${20:i} : number = ${1:Things}.length - 1; ${20:i} >= 0; ${20:i}--) { ${100:${1:Things}[${20:i}]}$0; }]]></content> <tabTrigger>for</tabTrigger> <scope>source.ts</scope> <description>for (…) {…} (Improved Native For-Loop)</description> </snippet>

・function

TypeScript では無名関数は
(x) => x*x;
のように記述するので、function.sublime-snippet は次のようにしました。

TypeScript/function.sublime-snippet
<snippet> <content><![CDATA[($1) => {${0:$TM_SELECTED_TEXT}};]]></content> <tabTrigger>f</tabTrigger> <scope>source.ts</scope> <description>Anonymous Function</description> </snippet>

TypeScript/function-(fun).sublime-snippet
<snippet> <content><![CDATA[function ${1:function_name} (${2:argument} : ${3:type}) : ${4:return_type} { ${0:// body...} }]]></content> <tabTrigger>fun</tabTrigger> <scope>source.ts</scope> <description>Function</description> </snippet>

・get elements

TypeScript/Get-Elements.sublime-snippet <snippet> <content><![CDATA[getElement${1/(T)|.*/(?1:s)/}By${1:T}${1/(T)|(I)|.*/(?1:agName)(?2:d)/}('$2')]]></content> <tabTrigger>get</tabTrigger> <scope>source.ts</scope> <description>Get Elements</description> </snippet>

・if

TypeScript/if.sublime-snippet
<snippet> <content><![CDATA[if (${1:true}) { ${0:$TM_SELECTED_TEXT} }]]></content> <tabTrigger>if</tabTrigger> <scope>source.ts</scope> <description>if</description> </snippet>

TypeScript/if-___-else.sublime-snippet
<snippet> <content><![CDATA[if (${1:true}) { ${2:$TM_SELECTED_TEXT} } else { ${3:$TM_SELECTED_TEXT} }]]></content> <tabTrigger>ife</tabTrigger> <scope>source.ts</scope> <description>if … else</description> </snippet>

Object-key-key-value.sublime-snippet, Object-Method.sublime-snippet, Object-Value-JS.sublime-snippet は JavaScript でもうまく動いていないのでスキップ。


・function

TypeScript/setTimeout-function.sublime-snippet
<snippet> <content><![CDATA[setTimeout(function() {$0}${2:}, ${1:10});]]></content> <tabTrigger>timeout</tabTrigger> <scope>source.ts</scope> <description>setTimeout function</description> </snippet>


TypeScript では、class や module が使えます。
これら用のスニペットも用意しましょう(これらに置き換えるので Prototype-(proto).sublime-snippet はスキップ)

・class

"class" + tab

TypeScript/class.sublime-snippet
<snippet> <content><![CDATA[class ${1:className} extends ${2:superClass} implements ${3:interface} { ${4:varName} : ${5:varType}; ${6:funcName}(${7:arg} : ${8:argType}) : ${9:returnType} { ${10} } constructor(${11:arg} : ${12:argType}) { ${13:super();} } static ${21:varName} : ${22:varType}; static ${23:funcName}(${24:arg} : ${25:argType}) : ${26:returnType} { ${27} } }]]></content> <tabTrigger>class</tabTrigger> <scope>source.ts</scope> <description>class</description> </snippet>

・module

"module" + tab

TypeScript/module.sublime-snippet
<snippet> <content><![CDATA[module ${1:moduleName} { ${0:$TM_SELECTED_TEXT} }]]></content> <tabTrigger>module</tabTrigger> <scope>source.ts</scope> <description>module</description> </snippet>

・interface

"interface" + tab

TypeScript/interface.sublime-snippet
<snippet> <content><![CDATA[interface ${1:interface-name} { ${0:$TM_SELECTED_TEXT} }]]></content> <tabTrigger>interface</tabTrigger> <scope>source.ts</scope> <description>interface</description> </snippet>

・Index Signature

"index" + tab

var dic : { [index : string] : type; } = {};

TypeScript/index-signature.sublime-snippet
<snippet> <content><![CDATA[var dic : { [index : ${1:string}] : ${2:type}; } = {};]]></content> <tabTrigger>index</tabTrigger> <scope>source.ts</scope> <description>Index Signature</description> </snippet>

・reference

"reference" + tab

/// <reference path="…"/>

TypeScript/reference.sublime-snippet
]]> reference source.ts reference

その他に以下も用意してみました。

・Array

"array" + tab

var array : type[] = [];

TypeScript/array.sublime-snippet <snippet> <content><![CDATA[var array : ${0:type}[] = [];]]></content> <tabTrigger>array</tabTrigger> <scope>source.ts</scope> <description>Array</description> </snippet>

これで
  • array
  • class
  • for
  • function
  • get
  • if
  • index
  • interface
  • module
  • reference
  • timeout
でスニペット補完がでます。

ここから全部のファイルをコピーするのは面倒だと思うので、TypeScript フォルダを zip にしました。
ここからどうぞ TypeScript.zip



2012年12月19日水曜日

Sublime Text 2 で .ts ファイルでも Ctrl + / でコメントをトグルできるようにする

JavaScript の設定をそのまま流用します。

([Preferences] - [Browse Packages...]) /JavaScript/Comments.tmPreferences



([Preferences] - [Browse Packages...]) /TypeScript/

にコピー。

開いて、

<key>scope</key>
<string>source.js, source.json</string>



<key>scope</key>
<string>source.ts</string>

に変更し、Sublime Text 2 を再起動。これだけ!


2012年12月18日火曜日

Sublime Text 2 で Emmet プラグインを使う

Emmet は Zen-Codingの次期バージョンで、仕様も大幅に変わっているようです。さらに使いやすくなっている!

emmet-sublime

Ctrl + Shift + P (Command + Shift + P) で Install Package を選択



Emmet を選択



* 参考にさせていただいたサイトでは github のリポジトリを Package Control に add repository してからインストールする方法が説明されていますが、現在は Package Control の Install Package から Emmet Plugin をインストールすることができます。

参考にさせていただいたサイト

1つ前のエントリで ZenCoding を紹介しておきながら、Emmet に移行することにしました。 主な理由が以下のポイントです。


気に入ったポイント

1. $ がちゃんと動く

ZenCoding は

ul#main>(li>img#main-$)*4

の展開が <ul id="main"> <li><img src="" alt="" id="main-1"></li> <li><img src="" alt="" id="main-1"></li> <li><img src="" alt="" id="main-1"></li> <li><img src="" alt="" id="main-1"></li> </ul> のように $ 部分がすべて1になってしまう場合がありました。

Emmet だとちゃんと <ul id="main"> <li><img src="" alt="" id="main-1"></li> <li><img src="" alt="" id="main-2"></li> <li><img src="" alt="" id="main-3"></li> <li><img src="" alt="" id="main-4"></li> </ul> になってくれます。


2. lorem ipsum が出力できる

p>lorem

を展開すると <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Laboriosam sunt sapiente ratione reprehenderit eveniet sit possimus a porro amet veniam ipsam repellat expedita nam excepturi autem cupiditate cumque obcaecati recusandae.</p> になります。

p>lorem16

のように単語数を指定することもできます。 <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quis temporibus minima quae voluptates architecto voluptas obcaecati.</p>

3. CSS の補完がすごい

m40

margin: 40px;

m0-8

margin: 0 8px;


p8

padding: 8px;


h55

height: 55px;


lstn

list-style-type: none;


fll

float: left;


cb

clear: both;


4. CSS で - 補完がすごい

-bdrs

-webkit-border-radius: ;
-moz-border-radius: ;
border-radius: ;


4. カスタマイズの方法がわかりやすい

[Preferences] - [Package Settings] - [Emmet] に
[Settings - Default] と [Settings - User] が用意されました。

カスタムスニペットとかは [Settings - User] に書きます。

デフォルトのスニペットは
([Prefernces] - [Browse Pacakges...] で開くディレクトリ)/Emmet/emmet/snippets.json
に定義されていますが、こっちは変更せずに [Settings - User] に書くようにします。

[Settings - User] を以下のようにしてます。 { // Custom snippets definitions, as per https://github.com/emmetio/emmet/blob/master/snippets.json "snippets": { "html": { "snippets": { "html:5": "<!DOCTYPE html>\n<html lang='${lang}'>\n<head>\n\t<meta charset='${charset}' />\n\t<title>${1:Document}</title>\n</head>\n<body>\n\t${child}${2}\n</body>\n</html>", "html:t": "<!DOCTYPE html>\n<html lang='${lang}'>\n<head>\n\t<meta charset='${charset}' />\n\t<link rel='stylesheet' href='${1:style}.css' type='text/css' />\n\t<script type='text/javascript' src='${2:../jquery-1.8.3.min.js}' />\n\t<script type='text/javascript' src='${3:main.js}' />\n\t<title></title>\n</head>\n<body>\n\t${child}|\n</body>\n</html>" }, "abbreviations": { "less:css": "<link rel='stylesheet/less' type='text/css' href='${1:style}.less' />", "link:css": "<link rel='stylesheet' type='text/css' href='${1:style}.css' />", "script:src": "<script type='text/javascript' src='' />" } } } }


---

前回のエントリにも書いた、 html のときに br の展開を <br /> にする方法の修正点は

([Prefernces] - [Browse Pacakges...] で開くディレクトリ)/Emmet/emmet/emmet-app.js

に変わりました。同じように self_closing_tag を変更します。

5132行の createProfile('html', {self_closing_tag: false}); が該当部分です。

createProfile('html', {self_closing_tag: false});
だと(デフォルト)
<link rel="stylesheet" href="">

createProfile('html', {self_closing_tag: true});
だと
<link rel="stylesheet" href=""/>

createProfile('html');
だと(デフォルトの self_closing_tag: 'xhtml' になる)
<link rel="stylesheet" href="" />

になります。



Sublime Text 2 の ZenCoding プラグインのカスタマイズ方法

Sublime Text 2 用の Zen Coding プラグインがあります。

Sublime Package Control に登録されているので、Sublime Package Control を入れている場合は、そこから Zen Coding パッケージを入れれば使えます。

ちなみに Sublime Package Control のインストール方法は、[View] - [Show Console] でコンソールを開いて、そこに Sublime Package Control Installation に載ってるコマンドを入力し、Sublime Text 2 を再起動するだけです。

ZenCoding は例えば

div#page>div.logo+ul#navigation>li*5>a

と入力してタブを押すと <div id="page"> <div class="logo"></div> <ul id="navigation"> <li><a href=""></a></li> <li><a href=""></a></li> <li><a href=""></a></li> <li><a href=""></a></li> <li><a href=""></a></li> </ul> </div> に変換してくれる機能をエディタに加えるプラグインです。
Zen Coding syntax は Zen Coding の Google Code の Wiki にあります。

さて、Zen Coding には abbreviations という機能があります。

例えば

<a href=""></a>

のように展開したい場合、

a[href]

と打ってタブを押す必要がありますが、

a

の状態でタブを押しても a[href] と同じように展開されるように略語を設定できる機能です。

他にも

a:link

という略語が用意されていて、この状態でタブを押すと

<a href="http://"></a>

に展開されます。

また、

html:5

でタブを押すと <!DOCTYPE HTML> <html lang="en-US"> <head> <meta charset="UTF-8"> <title></title> </head> <body> </body> </html> に変換する snippet 機能もあります。


各略語がどのように変換されるかは Zen Coding プラグインフォルダの zen_settings.py に書かれています。

([Preferences] - [Browse Packages...] で開くディレクトリ)/ZenCoding/zencoding/zen_settings.py


私は次のように変更して使っています。

■ 'html' の 'snippets' の 'html:5': '<!DOCTYPE HTML>\n' + '<html lang="${locale}">\n' + '<head>\n' + ' <meta charset="${charset}">\n' + ' <title></title>\n' + '</head>\n' + '<body>\n\t${child}|\n</body>\n' + '</html>' 'html:5': '<!DOCTYPE html>\n' + '<html lang="${lang}">\n' + '<head>\n' + ' <meta charset="${charset}" />\n' + ' <title></title>\n' + '</head>\n' + '<body>\n\t${child}|\n</body>\n' + '</html>' に変更

■ 'html ' の 'snippets' に以下を追加
(本当は 'common' に入れるべきなんだろうけど、なんか動かなかった。) 'html:t': '<!DOCTYPE html>\n' + '<html lang="${lang}">\n' + '<head>\n' + ' <meta charset="${charset}" />\n' + ' <link rel="stylesheet" href="${1:style}.css" type="text/css" />\n' + ' <script type="text/javascript" src="${2:../jquery-1.8.3.min.js}" />\n' + ' <script type="text/javascript" src="${3:main.js}" />\n' + ' <title></title>\n' + '</head>\n' + '<body>\n\t${child}|\n</body>\n' + '</html>'

■ 'html' の 'abbreviations' の 'script:src': '<script type="text/javascript" src=""></script>', 'script:src': '<script type="text/javascript" src="" />', に変更

■ 'html' の 'abbreviations' に以下を追加 'less:css': '<link rel="stylesheet/less" type="text/css" href="${1:style}.less" />',


---

html で(xhtml ではなく)br が <br> に展開されるのを <br /> にするには、

([Preferences] - [Browse Packages...] で開くディレクトリ)/ZenCoding/zencoding/utils.py を変更します。

setup_profile('html', {'check_valid' : True, 'self_closing_tag': False});

の 'self_closing_tag' によって振る舞いが異なります。

■ setup_profile('html', {'check_valid' : True, 'self_closing_tag': False});
だと(デフォルト)
<link rel="stylesheet" href="">

■ setup_profile('html', {'check_valid' : True, 'self_closing_tag': True});
だと
<link rel="stylesheet" href=""/>

■ setup_profile('html', {'check_valid' : True});
だと
<link rel="stylesheet" href="" />

になります。

吉川さんありがとうございます。



2012年12月17日月曜日

Sublime Text 2 の設定

Sublime Text 2 自体の設定(例えばテキストサイズとかフォントとか)は、[Preferences] - [Settings -User] に書きます。



デフォルトだと { "ignored_packages": [ "Vintage" ] } なので、何を書けばいいかよくわからん!と思いますが。
[Preferences] - [Settings - Default] にデフォルトの設定があるので、これを参考にして上書きしたい設定を [Settings - User] の方に書きます。[Settings - Default] の方は変更しません。

いくつか紹介します。
  • "font_face" 文字のフォント
    システムフォントとして設定されている必要があります。
    私のオススメは monaco (ver 2.0) と Source Code Pro です。

    Mac はフォントファイルをダブルクリックすれば、システムフォントに登録されるので楽ですね。
    Ubuntu では、フォントファイルを ~/.fonts/ にコピーすれば OK。

    "font_face": "monaco",
    "font_face": "SourceCodePro",

  • "font_size" 文字の大きさ。デフォルトは 10。
    整数で指定します。10.5 と指定しても 10 と同じ大きさになります。

    "font_size": 10,

  • "line_numbers" 左に行数を表示するかどうか。true の場合表示される。デフォルトは true。

    "line_numbers": true,

  • "gutter" 左に余白を設けるかどうか。デフォルトは true。この余白に行数が表示される。false にすると line_numbers で true を指定していても行数は表示されない。

    "gutter": true,

  • "margin" gutter とコードの間の余白。デフォルトは 4。整数を指定する。

    "margin": 8,

  • "rulers" ガイド線の位置を配列を指定する。デフォルトは空配列。

    "rulers": [40, 60],

  • "tab_size" タブのサイズをスペース何個分にするかを指定する。

    "tab_size": 4,

  • "translate_tabs_to_spaces" タブが入力された場合にスペースに置き換える。デフォルトは false。

    "translate_tabs_to_spaces": true,

  • "trim_automatic_white_space" キャレットを行から外したときに自動インデントで追加された空白を削除するかどうか。デフォルトは true = 削除する。

    "trim_automatic_white_space": true,

  • "highlight_line" 選択している行をハイライトするかどうか。true の場合ハイライトする。デフォルトは false。

    "highlight_line": true,

  • "caret_style" キャレットのスタイル。smooth, phase, blink, wide, solid のいずれかを指定する。デフォルトは smooth。

    "caret_style": "phase",

  • "line_padding_top" 行の上部の余白。デフォルトは 0。整数を指定する。

    "line_padding_top": 0,

  • "line_padding_bottom" 行の下部の余白。デフォルトは 0。整数を指定する。

    "line_padding_bottom": 0,

  • "draw_white_space" 空白にマークを表示するかどうか。none, selection, all のいずれかを指定する。none の場合、常に表示されない。selection の場合、選択状態の部分だけ表示される。all の場合、常に表示される。スペースは・、タブは―で表示される。

    "draw_white_space": "all",

  • "indent_guide_options" インデントガイドの描画方法。draw_normal か draw_active のいずれかを指定する。デフォルトは draw_normal。draw_active を指定した場合、キャレットを含むインデントガイドを別の色で表示する。

    "indent_guide_options": ["draw_active"],

  • "trim_trailing_white_space_on_save" 保存時に末尾の空白を削除するかどうか。デフォルトは false。

    "trim_trailing_white_space_on_save": true,

  • "ensure_newline_at_eof_on_save" 保存時に eof が改行ではない場合、改行になるようにするかどうか。デフォルトは false。

    "ensure_newline_at_eof_on_save": true,

  • "default_encoding" デフォルトの文字コード

    "default_encoding": "UTF-8",

  • "auto_complete_delay" タイピングの後に自動補完ウィンドウが表示されるまでのディレイ[ms]

    "auto_complete_delay": 50,

  • "open_files_in_new_window" OSX のみ。Finder からファイルを開いたとき、もしくはドックアイコンにファイルをドラッグしたときに、新しいウィンドウで開くかどうか。デフォルトは true。

    "open_files_in_new_window": false,

  • "folder_exclude_patterns" フォルダ内でサイドバーに表示しないフォルダ名のパターンの配列。デフォルトは .svn, .git, .hg, CVS の配列。

    "folder_exclude_patterns": [".svn", ".git", ".hg", "CVS"],

  • "file_exclude_patterns" フォルダ内でサイドバーに表示しないファイル名のパターンの配列。デフォルトは以下。

    "file_exclude_patterns": ["*.pyc", "*.pyo", "*.exe", "*.dll", "*.obj","*.o", "*.a", "*.lib", "*.so", "*.dylib", "*.ncb", "*.sdf", "*.suo", "*.pdb", "*.idb", ".DS_Store", "*.class", "*.psd", "*.db"],

  • "binary_file_patterns" サイドバーには表示するが、Goto Anything や Find in Files の対象にしないファイル名のパターン。デフォルトは以下。

    "binary_file_patterns": ["*.jpg", "*.jpeg", "*.png", "*.gif", "*.ttf", "*.tga", "*.dds", "*.ico", "*.eot", "*.pdf", "*.swf", "*.jar", "*.zip"],

  • "ignored_packages" インストールしているが、無視するパッケージのリスト

    "ignored_packages": ["Vintage"]
いまのところこんな感じにしてます。 { "font_face": "SourceCodePro", "font_size": 10, "margin": 8, "rulers": [80], "translate_tabs_to_spaces": true, "highlight_line": true, "caret_style": "phase", "draw_white_space": "all", "indent_guide_options": ["draw_active"], "trim_trailing_white_space_on_save": true, "ensure_newline_at_eof_on_save": true, "open_files_in_new_window": false, "ignored_packages": ["Vintage"] }

2012年12月14日金曜日

Sublime Text 2 の html の入力補完をカスタマイズする

Sublime Text 2 には、デフォルトで html の入力補完機能が用意されています。

例えば、html まで打って Tab キーを押すと、 <html> <head> <title></title> </head> <body> </body> </html> に変換されます。

link まで打って Tab キーを押すと、 <link rel="stylesheet" type="text/css" href=""> に変換されます。

script まで打って Tab キーを押すと、 <script type="text/javascript"></script> に変換されます。


ここで、例えば script と打ってタブを押したときに <script type="text/javascript" src=""></script> と src 属性も入れてほしいなと思った場合、

Subime Text 2 のパッケージディレクトリ([Preferences] - [Browse Packages...] で開くディレクトリ)の HTML フォルダの HTML.sublime-completions を変更します。 { "trigger": "script", "contents": "" }, { "trigger": "script", "contents": "" }, に変更すれば OK です。$n は補完した後にタブでカーソルが移動する位置を指定しています。


html だけはこのファイルではなく、html.sublime-snippet に記述します。

例えば、html と打ってタブを押したときに <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> </head> <body> </body> </html> のように <!DOCTYPE html> と <meta> 部分も入れたいなという場合、

html.sublime-snippet を次のように変更すれば OK です。 <snippet> <content><![CDATA[ <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>$1</title> </head> <body> $0 </body> </html>]]></content> <tabTrigger>html</tabTrigger> <scope>text.html</scope> </snippet>


2012年12月12日水曜日

モダンな TypeScript テスト環境

「これが現代のテスト環境や、どやぁ。」 と @vvakame が用意してくれました。 https://github.com/vvakame/typescript-project-sample

初心者の私にはたくさんのライブラリ(フレームワーク?)が入っててよくわからなかったので、それぞれの立ち位置を教えてもらったのでまとめておきます。


PhantomJS

PhantomJS は JavaScript の API も利用できる headless な WebKit です。実際の描画処理を行わないので速いという特徴があります。また、DOM 操作、CSS セレクタ、JSON、Canvas、SVG などいくつかの web 標準をネイティブでサポートしています。
JavaScript のエンジンは JavaScriptCore らしいです。

実際に Web ページにアクセスして、レンダリング結果の画面のキャプチャをとったり、ページのタイトルを取ってきたり、DOM の値を取ってきたり、いろいろできます。

Quick Start を見ると何ができるかが分かると思います。

例えば、

evaluate.js var url = "http://www.google.com"; var page = require("webpage").create(); page.open(url, function(status) { var title = page.evaluate(function() { return document.title; }); console.log("Page title is " + title); phantom.exit(); }); というコードを書いて実行すると、ページのタイトルを取れます。

$ phantomjs evaluate.js
Page title is Google
サンプルもたくさん用意されています。



Jasmine

Jasmine は JavaScript コードをテストするためのフレームワークです。 使い方は Introduction のコードを見るのがわかりやすいです。その他の情報は Wiki にあります。

最新版は Jasmine 1.3.1 で https://github.com/pivotal/jasmine/downloads からダウンロードできます。

jasmine-standalone-1.3.1.zip を展開すると、次のような構成になっています。

lib/
spec/
src/
SpecRunner.html
lib/ には jasmine.js や jasmine-html.js が入っています。

SpecRunner.html は jasmine を使ったテストのサンプルです。
基本的には、この SpecRunner.html を自分のプロジェクトに合わせて変えることでテストをセットアップできます。

SpecRunner.html で Player.js, Song.js が指定されている部分 にテストしたい元コードを、
SpecHelper.js, PlayerSpec.js が指定されている部分に テストコードを置きます。

下の方の Javascript のコードは window.onload でテストを実行し、結果を html に表示するための部分なので変更はいらいないです。 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>Jasmine Spec Runner</title> <link rel="shortcut icon" type="image/png" href="lib/jasmine-1.3.1/jasmine_favicon.png"> <link rel="stylesheet" type="text/css" href="lib/jasmine-1.3.1/jasmine.css"> <script type="text/javascript" src="lib/jasmine-1.3.1/jasmine.js"></script> <script type="text/javascript" src="lib/jasmine-1.3.1/jasmine-html.js"></script> <!-- include source files here... --> <script type="text/javascript" src="src/Player.js"></script> <script type="text/javascript" src="src/Song.js"></script> <!-- include spec files here... --> <script type="text/javascript" src="spec/SpecHelper.js"></script> <script type="text/javascript" src="spec/PlayerSpec.js"></script> <script type="text/javascript"> (function() { var jasmineEnv = jasmine.getEnv(); jasmineEnv.updateInterval = 1000; var htmlReporter = new jasmine.HtmlReporter(); jasmineEnv.addReporter(htmlReporter); jasmineEnv.specFilter = function(spec) { return htmlReporter.specFilter(spec); }; var currentWindowOnload = window.onload; window.onload = function() { if (currentWindowOnload) { currentWindowOnload(); } execJasmine(); }; function execJasmine() { jasmineEnv.execute(); } })(); </script> </head> <body> </body> </html> この SpecRunner.html をブラウザで開くと、このようにテスト結果が表示されます。

Jasmine は JavaScript で構築されているため、Jasmine を実行するには、web page など JavaScript が実行できる環境にいなければいけません。

しかし、この JavaScript を実行するのに毎回ブラウザを立ち上げていたらテストに時間がかかってしまいます。そこで上記の PhantomJS の登場です。

追記 : X Windowとか動いてないLinux機上(大抵のJenkins氏が動いてるサーバはそういう環境)の上で、ブラウザを使ってテストしたいなーというのもある by @vvakame

例えば、
test-jasmine1.js var url = "file:///home/yanzm/Downloads/jasmine-standalone-1.3.1/SpecRunner.html" var page = require("webpage").create(); page.onConsoleMessage = function (msg) { if(msg == "finish-jasmine") { phantom.exit(); } else if(msg != "\n") { console.log(msg); } }; page.open(url, function(status) { page.evaluate(function() { var jasmineEnv = jasmine.getEnv(); jasmineEnv.updateInterval = 1000; var consoleReporter = new jasmine.ConsoleReporter( function(log) { console.log(log); }, function(runner) { console.log("finish-jasmine"); }, false); jasmineEnv.addReporter(consoleReporter); jasmineEnv.execute(); }); }); SpecRunner.html <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>Jasmine Spec Runner</title> <script type="text/javascript" src="lib/jasmine-1.3.1/jasmine.js"></script> <script type="text/javascript" src="lib/jasmine-1.3.1/jasmine-console.js"></script> <!-- include source files here... --> <script type="text/javascript" src="src/Player.js"></script> <script type="text/javascript" src="src/Song.js"></script> <!-- include spec files here... --> <script type="text/javascript" src="spec/SpecHelper.js"></script> <script type="text/javascript" src="spec/PlayerSpec.js"></script> </head> <body> </body> </html> (jasmine-console.js の中身は ConsoleReporter.js です。)

のようにすると、コンソールからテストできるようになります。
$ phantomjs test-jasmin1.js 
Started
.
.
.
.
.
Finished in 0.005 seconds
5 specs, 0 failures



grunt

grunt は要は make みたいなものです。

makegrunt
Makefilegrunt.js
make hogegrunt hoge


gruntfile と呼ばれる grunt.js に設定を書いていきます。
Getting Started を見るとなんとなく書き方がわかると思います。

grunt は実行時にカレントディレクトリの grunt.js を見に行くのですが、ない場合は見つけるまで親のディレクトリを遡って探します。普通はプロジェクトリポジトリのルートに置きます。
grunt.js は次の3つの要素からなる JavaScript です。
  • プロジェクトの設定(依存しているライブラリのダウンロードなど)
  • grunt のプラグインやタスクフォルダの読み込み
  • タスクとヘルパー
プロジェクトの設定は grunt.initConfig() で行います。
プラグインやタスクフォルダの指定は grunt.loadTasks() や grunt.loadNpmTasks() で行います。
タスクやヘルパーの指定は grunt.registerTask() で行います。

module.exports = function(grunt) { // Project configuration. grunt.initConfig({ lint: { all: ['grunt.js', 'lib/**/*.js', 'test/**/*.js'] }, jshint: { options: { browser: true } } }); // Load tasks from "grunt-sample" grunt plugin installed via Npm. grunt.loadNpmTasks('grunt-sample'); // Default task. grunt.registerTask('default', 'lint sample'); }; 各 grunt タスクは、grunt.initConfig() に渡されるオブジェクトで定義されている情報に依存します。
このオブジェクトは JavaScript であって JSON ではないので、プログラムで設定オブジェクトを生成させるようなこともできます。
ビルトインのタスクの設定オブジェクトの詳細は Document の Built-in tasks にあります。


フォルダからタスクやヘルパーを読み込むには
grunt.loadTask(folderName);
を使います。

npm を介して grunt のプラグインを読み込むには
grunt.loadNpmTasks(pluginName)
で行います。


grunt.registerTask() で default タスクを設定するまで、単に grunt を実行してもになにも起こりません。

grunt.registerTask('default', 'hoge_task fuga_task');

のように default タスクを登録すると

$ grunt
$ grunt default

と実行したときに第2引数で指定したタスクが実行されます。
上記の場合

$ grunt hoge_task fuga_task

と実行した場合と同じになります。

grunt.registerTask('test', 'hoge_test fuga_test');

のように、好きな alias を登録できます。
この場合、

$ grunt test

とすれば

$ grunt hoge_test fuga_test

と実行したのと同じになります。

init task(ビルトインのタスク)を見てみると、grunt による project の構成がだいだいわかると思います。



まとめ

@vvakame のプロジェクトの grunt.js では以下のライブラリを使っています。

まとめると、
grunt でディレクトリの clean、typescript や less のコンパイル、jasmine の設定など

jasmine で phantomJS を使ってテスト

結果が JUnit 形式の XML で出力される(出力先は grunt.js で設定)

という流れです。



2012年12月8日土曜日

Ubuntu に PhantomJS を入れる

Ubuntu Lucid (10.04) でビルドしたバイナリが http://phantomjs.org/download.html からダウンロードできます。

Lucid 以降のディストリビューションであれば動くそうです。
このバイナリのインストールにあたっては、Qt, WebKit など他のライブラリのインストールは必要ありません。

Lucid より古いシステムでは、ソースからビルドする必要があります。
$ cd /usr/local/share
$ sudo cp ~/Download/phantomjs-1.7.0-linux-x86_64.tar.bz2 .
$ sudo tar -xjvf phantomjs-1.7.0-linux-x86_64.tar.bz2 
$ sudo rm phantomjs-1.7.0-linux-x86_64.tar.bz2
$ sudo ln -s /usr/local/share/phantomjs-1.7.0-linux-x86_64/ /usr/local/share/phantomjs
$ sudo ln -s /usr/local/share/phantomjs/bin/phantomjs /usr/local/bin/phantomjs

$ which phantomjs
/usr/local/bin/phantomjs

$ phantomjs -v
1.7.0

$ phantomjs 
phantomjs> 


PhantomJS Quick Start



2012年12月7日金曜日

TypeScript での Type Assertion

document.getElementById("#input"); の戻り値は HTMLElement なので var input : HTMLInputElement = document.getElementById("#input"); とすると、キャストできないと怒られます。
Cannot convert 'HTMLElement' to 'HTMLInputElement': Type 'HTMLElement' is missing property 'setSelectionRange' from type 'HTMLInputElement'

そこで、次のように < > を使ってキャストすれば怒られなくなります。 var input : HTMLInputElement = <HTMLInputElement>document.getElementById("#input");

2012年12月6日木曜日

TypeScript で jQuery を使う (Sublime Text 2 で jquery の補完)

TypeScript で jQuery を使うには、jQuery の型を定義したファイルが必要です。

TypeScript のコードと一緒に公開されているので、ダウンロードして作業フォルダに入れます。
jquery.d.ts

現状は // Typing for the jQuery library, version 1.7.x とあるので、1.7.x の jQuery に対応しているようです。


次に、自分の .ts ファイルの先頭に
/// <reference path="jquery.d.ts" /> を追加します。これは外部の ts ファイルを読み込むための書き方です。

で、ここで気づきました。Sublime Text 2 で jquery の補完がでません。。。

以前作ったプラグイン(Sublime Text 2 で TypeScript の補完を出すプラグイン作った")を修正しないといけない(修正すれば jquery、というか reference で指定してるファイルの補完がでる)ということにも気づきました。

ということで修正しました。
現在の tsc_completion のバージョンは v1.0.3 になってます。
Sublime Text 2 のプラグインのコードも新しくなっているので、こちらも置き換えてください。

こんな感じで補完がでます。



例えば、こういうのが
/// <reference path="jquery.d.ts" /> var questionArea : JQuery; var inputArea : JQuery; var star1 : JQuery; var star2 : JQuery; var star3 : JQuery; ... $(document).ready(function () { inputArea = $("#text_area"); inputArea.keyup(onInput); questionArea = $("#question_area"); star1 = $("#star1"); star2 = $("#star2"); star3 = $("#star3"); inputArea.focus(); // show first text showNextText(); })
こういうのに変換されます。
var questionArea; var inputArea; var star1; var star2; var star3; ... $(document).ready(function () { inputArea = $("#text_area"); inputArea.keyup(onInput); questionArea = $("#question_area"); star1 = $("#star1"); star2 = $("#star2"); star3 = $("#star3"); inputArea.focus(); showNextText(); }); クラスとか使ってないからそのままだねw


jquery で HTMLInputElement にあたる要素で focus() 呼んでもフォーカスあたらないのはなんでなんだ。。。

jquery 1.8.3 にしたら直った


TypeScript で SourceMap を使う

tsc コマンドに --sourcemap をつけてコンパイルすることで SourceMap ファイル(hoge.js.map)と、SourceMap の情報が書かれた js ファイルが作成されます。
> tsc --sourcemap hoge.ts

> ls
hoge.ts  hoge.js  hoge.js.map

> tail -1 hoge.js
//@ sourceMappingURL=hoge.js.map


この SourceMap を使って Chrome でデバッグするには、Chrome Dev Tool を開いて(右クリックして Inspect Element をクリック or Ctrl + Shif + i (Mac は Command + option + i))、右下の設定(歯車アイコン)をクリックします。



General タブの Enable source maps にチェックを入れます。



リロードすると、Sources で .ts が選択できるようになります。
# なんで .js が2つでてるのかは謎



.ts のほうの行数のところをクリックして Breakpoint を設定できます。



.ts の方の Breakpoint で止まります。




# .ts のほうでうまく Breakpoint が動くときと、なぜか Breakpoint の対応する js 側の行で止まることがあり、まだ SourceMap はうまく動かないのかもしれない。。。


2012年12月5日水曜日

Android CheckedTextView を使うときの注意

ListView で CHOICE_MODE_SINGLE とか CHOICE_MODE_MULTIPLE とか使うときに、1行のレイアウトとして CheckedTextView を使うことがあると思いますが、チェックマークの切り替えには注意が必要です。

チェックマーク画像を設定するメソッドとして

setCheckMarkDrawable(int resid)



setCheckMarkDrawable(Drawable d)

があります。

例えば、チェックできる行にはチェクマーク画像をセットして、チェックできない行(例えばフラグが立ってるとか、ロックされているなど)にはチェックマーク画像をセットしないようにする場合、 if(isChecked) { ctv.setCheckMarkDrawable(R.drawable.check); } else { ctv.setCheckMarkDrawable(null); } とすると、2回目に R.drawable.check をセットしたとき、

setCheckMarkDrawable(R.drawable.check)

setCheckMarkDrawable(null)

setCheckMarkDrawable(R.drawable.check) ←ここ

で画像がセットされません。ListView の1行のレイアウトに使うと行の View が再利用されるのでこういう状況によくなります。

正しく動くようにするには、 if(isChecked) { ctv.setCheckMarkDrawable(R.drawable.check); } else { ctv.setCheckMarkDrawable(0); } のように null ではなく 0 を渡すようにします。

CheckedTextView のコードをみると、setCheckMarkDrawable(int resid) では、mCheckMarkResource にセットされたリソースID を保持しておいて、新しくセットされたリソースIDがこれと同じ場合はなにも処理をしないようになっています。

http://tools.oesf.biz/android-4.1.2_r1.0/xref/frameworks/base/core/java/android/widget/CheckedTextView.java 112 public void setCheckMarkDrawable(int resid) { 113 if (resid != 0 && resid == mCheckMarkResource) { 114 return; 115 } 116 117 mCheckMarkResource = resid; 118 119 Drawable d = null; 120 if (mCheckMarkResource != 0) { 121 d = getResources().getDrawable(mCheckMarkResource); 122 } 123 setCheckMarkDrawable(d); 124 } 125 126 /** 127 * Set the checkmark to a given Drawable. This will be drawn when {@link #isChecked()} is true. 128 * 129 * @param d The Drawable to use for the checkmark. 130 * 131 * @see #setCheckMarkDrawable(int) 132 * @see #getCheckMarkDrawable() 133 * 134 * @attr ref android.R.styleable#CheckedTextView_checkMark 135 */ 136 public void setCheckMarkDrawable(Drawable d) { 137 if (mCheckMarkDrawable != null) { 138 mCheckMarkDrawable.setCallback(null); 139 unscheduleDrawable(mCheckMarkDrawable); 140 } 141 mNeedRequestlayout = (d != mCheckMarkDrawable); 142 if (d != null) { 143 d.setCallback(this); 144 d.setVisible(getVisibility() == VISIBLE, false); 145 d.setState(CHECKED_STATE_SET); 146 setMinHeight(d.getIntrinsicHeight()); 147 148 mCheckMarkWidth = d.getIntrinsicWidth(); 149 d.setState(getDrawableState()); 150 } else { 151 mCheckMarkWidth = 0; 152 } 153 mCheckMarkDrawable = d; 154 // Do padding resolution. This will call setPadding() and do a requestLayout() if needed. 155 resolvePadding(); 156 }

つまり、
setCheckMarkDrawable(R.drawable.check)
: mCheckMarkResource = R.drawable.check

setCheckMarkDrawable(null)
: setCheckMarkDrawable(int resid) を通らないので mCheckMarkResource = R.drawable.check のまま

setCheckMarkDrawable(R.drawable.check) ←ここ
:mCheckMarkResource が引数の resid と同じなので画像をセットしない = 画像は null のまま

という動作になってしまうのです。。。


2012年12月3日月曜日

いきなり jquery 使い始める前に DOM のことちゃんと理解しようね。

ちょっと古い本ですがすごくよかったので紹介します。

「標準 DOM スクリプティング」 羽田野太巳



もう、さすが羽田野さんというしかないです。
こんなわかりやすい日本語が書けるようになりたい。。。

1.1 が「DOMとは」ですよ。
DOM の歴史、概要、仕様など基礎がしっかり抑えられます。

2007年の本なので、出てくるブラウザが IE6、Firefox 1.5、Opera 9.0、Safari 1.3 と 2.0、と古いですが(Chrome がない!)、新しいブラウザはより DOM に準拠しているはずなので、参考になると思います。
なんでみんな IE6 が嫌いなのかこの本を読むとよくわかりますw

個人的にはごっちゃになってた DHTML と DOM がきちんと整理できたで助かりました。


もちろんクロスブラウザ対策についての話も随所で取り上げられていて、

対策コード部分をモジュール化して使い回したいよね

ライブラリとしてみんなで使えれば便利だよね

jquery

という流れがちゃんと理解できて jquery を使うのと、よくわからないけど便利らしいから使うかー、では大違いです。
よくわからないままにライブラリを使うと、困った事が起こったときに原因が突き止められないなどいろいろ大変なことになります。

基礎知識大事!


最新のブラウザの対応状況を追記した改訂版でないかなぁ



2012年12月2日日曜日

TypeScript でタグ属性値の操作

helloworld2.html <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>hello world 2</title> <style type="text/css"> #box { width: 150px; height: 150px; border: 1px solid #4c4c4c; } .red { background-color: #ffcccc; } .blue { background-color: #ccccff;} </style> </head> <body> <h1>Hello World 2</h1> <div id="box"> Hello World 2 </div> <img id="shape" src="circle.png"></img> <br /> <br /> <input type="button" value="get attributes" id="button1"> <script src="helloworld2.js"></script> </body> </html>

helloworld2.ts function boxOver(e : MouseEvent) { box.className = "red"; } function boxNormal(e : MouseEvent) { box.className = "blue"; } function shapeOver(e : MouseEvent) { image.src = "star.png"; } function shapeNormal(e : MouseEvent) { image.src = "circle.png"; } function getAttributes(e : MouseEvent) { var attrs : Attr[] = box.attributes; var msg = ""; for(var i = 0; i < attrs.length; i++) { var attr = attrs[i]; msg = msg + attr.nodeName + ":" + attr.nodeValue + "\n"; } alert(msg); } var box : HTMLElement = document.getElementById("box"); box.addEventListener("mouseover", boxOver, false); box.addEventListener("mouseout", boxNormal, false); var image : HTMLImageElement = <HTMLImageElement>document.getElementById("shape") image.addEventListener("mouseover", shapeOver, false); image.addEventListener("mouseout", shapeNormal, false); var btn1 : HTMLElement = document.getElementById("button1"); btn1.addEventListener("click", getAttributes, false);

HTMLElement の attributes は Attr[] という配列になる。

TypeScript で DOM 操作

helloworld1.html <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>hello world 1</title> </head> <body> <h1>Hello World 1</h1> <input type="button" id="button1" value="pick h1 tag text" /> <br /> <br /> <div id="authors"> <div>芥川 竜之介</div> <div>夏目 漱石</div> <div></div> <div>江戸川 乱歩</div> </div> <input type="button" id="button2" value="pick all authors" /> <script src="helloworld1.js"></script> </body> </html>

helloworld1.ts function helloworld1(e : MouseEvent) { var h1Elm : Node = document.getElementsByTagName('h1')[0]; var textNode : Node = h1Elm.childNodes[0]; var text : string = textNode.nodeValue; alert(text); } function helloworld2(e : MouseEvent) { var authorsElem : Node = document.getElementById('authors'); var authors = new Array(); for (var i = 0; i < authorsElem.childNodes.length; i++) { var node = authorsElem.childNodes.item(i); if(node.nodeType != 1) { continue; } if(!node.hasChildNodes()) { continue; } if(node.firstChild.nodeType != 3) { continue; } authors.push(node.firstChild.nodeValue); } alert(authors.join("\n")); } var btn1 : HTMLElement = document.getElementById("button1"); btn1.addEventListener("click", helloworld1, false); var btn2 : HTMLElement = document.getElementById("button2"); btn2.addEventListener("click", helloworld2, false);
ノードの種類nodeTypenodeNamenodeValue
要素ノード1タグ名null
属性ノード2属性名属性値
テキストノード3#textテキストの内容
コメントノード8#commentコメントの内容
ドキュメントノード9#documentnull