UglifyJSでコードの書き換えはいろいろやれるけどちょっと分かりにくい(define, compress, source map編)

今まで UglifyJS は単に圧縮としてしか使ってなくて、あまり細かく設定を見たことがなかったが、いざ触ってみたらいくつか発見があったのでそのメモを残しておく。

UglifyJS — JavaScript parser, compressor, minifier written in JS

まとめ

  • 外部から define で値を埋め込むことができる
  • UglifyJS の基本はコードの圧縮だが、その際に dead code の除去も行える
  • source map の扱いにはややクセがある

define による外部からの設定の埋め込み

UglifyJS 自体は Node.js で動くので Node.js から環境変数を取得することはできるが、UglifyJS は Browserify ではないので、Browserify のように環境変数にアクセスしている部分をいい具合に置換してくれるわけではない。

そこで利用できるのが define.

  • オプション –define でコード内の定数をセットできる
    • literal に展開されるので文字列を埋め込むなどは難しい
    • boolean だけに留めておくのが正解っぽい
  • shell script の機能を呼び出して分岐させることはできる
uglifyjs  --define PRODUCTION=`[ "$NODE_ENV" = "production" ] && echo "true" || echo "false"`

とやると JavaScript のコードの中では

if ( PRODUCTION ) {
  ...
} else {
  ...
}

のように参照して分岐させることができる。

compress

結構細かく制御できるし、default では割と積極的にコードが削除される。

例えばデフォルトの動作で dead_code を remove できるので、上のように if を書いておくと PRODUCTION が true の場合は then の中身だけが出力コードに残り、false の場合は else の中身だけが出力コードに残る。

他の assets や API の JSON を取得する処理はよくあると思うが、その URL は development, staging 環境と production で差し替えたくなるだろう。上のようなコードにしておくと UglifyJS の compress だけでそれが実現できる。(UglifyJS 後には if はなくなって差し替え後のコードだけが残る。)

source map

  • source map オプションは output オプションの後に置かないと機能しない
    • output オプションのパスを絶対に参照するみたい(中身は見てない)
  • option の解釈が自身の生成向けオプションと読み込むソース向けのオプションと両方入っていて分かりにくい。これは UglifyJS がトランスパイルの最終工程に置かれることが多いゆえなのか、設計が古いのか、もうちょっと工夫してほしかった。

例えば uglifyjs 3.2.2 で確認したところ出力先のコードに source map を含めるには以下のようにする必要があり、

uglifyjs -o ./output/application.js \
         --source-map url=inline,includeSources

さらにこの際、./output/application.js.map の生成は抑止できない。(ファイルの生成と inline への埋め込みがトグルするものと思っていたので指定に失敗しているのかと思ってしばらく悩んだ。)

ちなみに uglifyjs の前工程で source map を inline に生成している場合は以下のように content=inline を追加する必要がある。まぁその場合は最終工程の uglifyjs を省略すればいいだけじゃね?と思わなくもない。

uglifyjs -o ./output/application.js \
         --source-map content=inline,url=inline,includeSources

cf. Emit inline source maps with UglifyJS v3 · Issue #2711 · mishoo/UglifyJS2

Cucumber を PHP アプリに対して本当に使えることが分かった

何がきっかけかよく覚えていないんだけど、この数日、急に Cucumber のことを思い出したので試してみた。

Cucumber - Making BDD fun

Cucumber は一部ではとても有名な受け入れテストの記述&実行フレームワークとでも呼んだらいいのかな。有名な記事はこの辺。

まだあまり詳しく本家ドキュメントを読んでないんだけど、あちこちの記事から分かることは、これは

外部仕様あるいは要件定義を共有しやすくするフレームワーク

ということでしょうか。あたかも自然言語で書いたプレーンテキストがテストを動かすスクリプトとして機能しているように見えるので、今の動作で仕様を満たしています、あるいはここまでできています、ということを開発者間、開発者とマネージャ、プロダクトオーナーと共有しやすくなることが嬉しいみたい。

なんのこっちゃって感じですか?

機能的な部分をごく大雑把に言い直すとテスト用の DSL を提供して、そこからあれこれテストツールを呼ぶよ、って感じです。こっちの方が具体的な動作はイメージしやすいかな。

あとは

examples at master from aslakhellesoy's cucumber - GitHub

ここら辺のコードを眺めて動かしてみるとつかめると思う。

もちろん有名なのは Rails のテストなんだけど、例えば Ruby の Test::Unit と連携することもできるし、Webrat というツールを使うことで PHP アプリをテストすることも可能。

PHP アプリのサンプルがなかったので探したみたら

Cucumber+WebratでPHPアプリのテストをする | CAPH TECH

が見つかった。ただしどうもちょっと古いバージョンをもとにしているらしく、0.6.3 では動かなかった。Ruby 1.8.7 + Cucumber 0.6.3 で動かすにはこんな感じで修正が必要。

diff --git a/cucumber.yml b/cucumber.yml
index c39f416..c1869b5 100644
--- a/cucumber.yml
+++ b/cucumber.yml
@@ -1 +1 @@
-default: --language ja features
+default: features
diff --git a/features/show.feature b/features/show.feature
index e3afa77..3887cb7 100644
--- a/features/show.feature
+++ b/features/show.feature
@@ -1,3 +1,4 @@
+# language: ja
 機能: トップページの表示とサブミット

   シナリオ: トップページの表示
diff --git a/features/step_definitions/result_steps.rb b/features/step_definitions/result_steps.rb
index b22e762..85c2824 100644
--- a/features/step_definitions/result_steps.rb
+++ b/features/step_definitions/result_steps.rb
@@ -1,11 +1,11 @@
 # -*- encoding: UTF-8 -*-

 ならば /^"(.*)"と表示される$/ do |text|
-  response_body.to_s.force_encoding("UTF-8").should =~ /#{text}/m
+  response_body.to_s.should =~ /#{text}/m
 end

 ならば /^"(.*)"と表示されない$/ do |text|
-  response_body.to_s.force_encoding("UTF-8").should_not =~ /#{text}/m
+  response_body.to_s.should_not =~ /#{text}/m
 end

 ならば /^(\w+)メッセージが表示さる$/ do |message_type|
diff --git a/features/support/env.rb b/features/support/env.rb
index 54a9528..87f3521 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -1,5 +1,6 @@
 # RSpec
 require 'spec/expectations'
+require 'cucumber/formatter/unicode'

 # Webrat
 require 'webrat'
diff --git a/index.php b/index.php
index 5623672..bac22ea 100644
--- a/index.php
+++ b/index.php
@@ -9,7 +9,7 @@

     <?php if(!isset($_GET['text']) or empty($_GET['text'])) print '<p>テキストを入力してください。</p>'; ?>

-    <form action="/php_with_cucumber/index.php" method="get">
+    <form method="get">
         <p><textarea name="text" rows="10" cols="100"></textarea></p>
         <p><input type="submit" value="Submit" /></p>
     </form>

簡単に言うと

cucumber-0.5.0へ上げるときの注意点 - ヽ( ・∀・)ノくまくまー(2009-12-21)

を読めって話になります。読め。ただし、

require 'cucumber/formatter/unicode'

はちょっとした罠かも。example を読んでれば気づくけど。

あと以下の部分でテストするアプリの URL を決め打ちしてるので、対応が必要。

features/step_definitions/webrat_steps.rb
# -*- encoding: UTF-8 -*-

前提 /^(.*)ページを表示している$/ do |path|
 @response = visit "http://localhost/php_with_cucumber#{path}"
end

もし /^(.*)ページを表示する$/ do |path|
 @response = visit "http://localhost/php_with_cucumber#{path}"
end

もし /^"(.*)"ボタンをクリックする$/ do |button|

こういうのちょっとダサイよねぇ。開発環境、検証環境、本番環境の違いをもうちょっとスマートに書き分けたり、開発環境の URI が増えていっても対応が楽になるような工夫がないものかしら。1

感想

Webrat って Mechanize と何が違うの? と思ったら内部に Mechanize 用の adapter があって、実行時のレポートで Mechanize の警告が出たので、結局実際に外部のアプリにアクセスする部分は Mechanize を利用しているらしい。ここら辺、依存が深くなってきてちょっとややこしい。

また、Rails には plugin があるので Cucumber は使い始めやすいと思うんだけど、素の状態から使うのはちょっと面倒かなって感じがした。example レベルに留まっている step の記述がもっと充実してくれば違うんだろうけど、今のままだと

  1. プロダクトコード
  2. テストストーリー
  3. ストーリーを記述するための step

を書かなきゃいけないし、step はどうしても Ruby で書かざるを得ないのでプロダクトコードを別な言語で書いている場合はかなり面倒なことになってしまう。

ただし、その面倒をあえてやるべき場面もあって、それは内部の作り替えのとき。Cucumber で外側のテストがしっかり動かせていれば中の設計、もっと言えば中の環境はどうでもいいと言える。レガシーコードに悩んでいるアプリも、外を固めて差し替えてしまうことができるようになる。個人的には Mechanize はだいぶ慣れてきてるので、うまく使えれば動作の変化を最小限に抑えつつ古いコードを捨てるために使えるんじゃないかと考えている。

※ 学習曲線の初期段階においてにはちと荷が重い取り組みなので、試しに採用できそうな適切なサイズのアプリの見極めが大事になってきそう。自分の手もそれに集中させるのは難しいだろうし、古いアプリの仕様を後から書かなきゃいけないのも大変。現場は常に理想の正反対を行っているのだなぁ。でもそれがあるから新しいアイディアを思いつき、新しいツールが生まれてくるんだろうけど。

  1. そもそも本番環境を Cucumber でテストするっていう考え方がおかしいかもしれなので、必要ないかも。 

ベクトルグラフィックスを含む文書作りとその再利用

以下、確認は

  • OOo 1.1.3 on Win
  • NeoOffice/J 1.1 Beta Patch 9
  • Word 2000 on Win

で行っている。

背景

  • Office はコストパフォーマンスが悪いのでできるだけ数は用意したくない
  • しかし Office で読める形式にすることを求められることがある
  • Office のドローツールは概して使いにくく1、最終的に Office で読める形にするときでもドローは別なものを使いたい
    • したがってベクトルグラフィックスを文書の中に「配置」する必要があるが、これをまともに実現できる組み合わせをはっきりさせる必要がある

作成

  • Word では eps を [ 挿入 ] で貼付けてベクトルグラフィックスを活かすことができる
    • 2000 より 2003 の方が扱いはいいみたい
    • すべてのバージョンの eps に対応できるわけではないので、確認が必要
  • OOo では WMF を使ってベクトルグラフィックスを含む文書を作ることができる
  • NeoOffice/J では WMF を使う方法は不可
  • OOo も NeoOffice/J も eps のベクトルグラフィックスを活かすことはできない
    • したがって NeoOffice/J ではベクトルグラフィックスを活かした文書作成は不可能
  • Pages は Preview からのコピペでベクトルグラフィックス入りの文書を作成できる
    • Preview で開ける eps は以下の通り2
バージョン可否
5.5×
6
7
8

まとめ

  • Windows 環境ではフリーの OOo で WMF の貼付けを行ってベクトルグラフィックス入り文書の作成が可能
  • OS X ではフリーの NeoOffice/J だけではダメなので、eps を始め様々なデータ形式へ対応できる Pages(iWork)を購入して文書作成するのが安上がり
    • どうしても NeoOffice/J でやりたい場合は、大きめにラスタライズして NeoOffice/J 上で縮小する。ただし文書のデータサイズはベクトルグラフィックスを利用する場合より大きくなる。
    • AppleWorks やクラリスワークス、OOo で完結させることができるならそれ使えばいいんだけど。

OS X で MS Office を使わずに頑張る

  • Pages の作成する Word DOC では eps はラスタライズされている
  • 開いてみると OOo でも NeoOffice/J でも画像の位置がずれている

ということで文書の互換性という意味では Word 形式のサポートには期待しすぎてはいけないと考えるのが妥当。読めることは読めるが手直しが要る。楽したいなら素直に Office3を買えと。

結論

  • Windows 環境では WMF と OOo で結構使いものになる
  • OS X 環境では Pages があるとあれこれデータを貼付けてきれいな PDF の生成は可能だけど、その貼付け終わったデータを Windows で再利用したいというのは難しい
  • Windows 環境でも OS X 環境でも eps などの素材は文書とは別な形できちんと保存しておくのが望ましい

うわー

Pages の作るファイルは OS X のアプリ群と同じく、実際にはパッケージなのね。他のプラットフォームで開くとびっくりするので、共有スペースに保存する場合はアーカイブにしてからの方がいいかな。

  1. MS Office しかり、OOo しかり 

  2. すべて PS2、フォントは埋め込み可能なら埋め込んである。CMYK ではない。しかし eps は細かいオプションを組み合わせるとテストが大変なので、とても片手間で全部確認できません。メーカーに検証条件を公開してほしいものです。 

  3. 今なら 2004 

PukiWiki のデータを

保護しようと思った。理由は編集できちゃうとバックアップとして機能しなくなるから。で、-w にしちゃえばいいやーと思ったら permission denied で PukiWiki がまともに動かんくなった。

あほかおれは。

PukiWiki の認証部分に手を入れて xrea.com 上で動いている場合に限り編集を許可するようにしないといけないんだった。帰ってからゆっくりやろう。(仕事中に何してるんだ。)

はい。できました。

Namazu2 on FreeBSD

いつまでも放置もどうかと思い、とりあえず入れようと思った

sudo portinstall -m WITH_CHASEN=yes namazu2

が、

===>  Installing for expat-1.95.7
===>   Generating temporary packing list
===>  Checking if textproc/expat2 already installed
===>   An older version of textproc/expat2 is already installed (expat-1.95.6_1)
      You may wish to ``make deinstall'' and install this port again
      by ``make reinstall'' to upgrade it properly.
      If you really wish to overwrite the old port of textproc/expat2
      without deleting it first, set the variable "FORCE_PKG_REGISTER"
      in your environment or the "make install" command line.

で止まった。なんかこの辺も Emacs を package で入れた名残りのような気がする。えーい面倒くせえ。

About

例によって個人のなんちゃらです