windowsで使おうとすると、ちょっと工夫が必要だったというか出来なかった。つらみ( ´_ゝ`)
長いのに中身が無いので結論を先に読んで察してから中を読むことをお勧めします。*1
asseticに関しては前回書いたので良ければそっちを見てください。
asseticではlessphpのフィルタなども用意されてるので、そっちを素直に使えばいいんですが、CoffeeScriptphpFilterは無いし*2、たぶん同じ原因でCoffeeScriptFilter落ちてるので頑張ってみました。
一応コードはhttps://github.com/matsu-chara/assetic_testにあります。
lessの準備
- node.jsからnode.jsをインストール。同時にnpmもインストールされます。
>setx NODE_PATH %APPDATA%\npm\node_modules
win7以外だとnode_modulesの場所が違うかもしれません。
- 続いてlessをインストール
>npm install -g less
ここまででコマンドプロンプト上で適当なlessファイル
/* a.less */ @color: #00ff00; #zannen-red { background-color:red; p { background-color:@color; } }
に対してlesscでコンパイルするとcssになって下記のように出力されるはずです。
>lessc a.less /* a.less */ #zannen-red { background-color: red; } #zannen-red p { background-color: #00ff00; }
lessFilterの準備
公式サンプルを見ながら下記のようなPHPファイルassetic_less.phpを用意します。
<?php require '../vendor/autoload.php'; use Assetic\Asset\AssetCollection; use Assetic\Asset\FileAsset; use Assetic\Filter\LessFilter; $css = new Asset Collection (array ( new FileAsset('css/a.less', array(new LessFilter())), new FileAsset('css/b.css'), )); // header('Content-Type: text/css'); echo $css->dump();
あとはhtmlファイルのlinkでassetic_less.phpを読み込ませてやればOK・・・となるはずでした。
Assetic\Exception\FilterException: in C:\xampp\htdocs\assetic_test\vendor\kriswallsmith\assetic\src\Assetic\Exception\FilterException.php on line 40
ということで、原因を探した。
# 原因その1
vendor\kriswallsmith\assetic\src\Assetic\FilterのLessFilter.phpにあるLessFilterクラスのコンストラクタで引数に$nodeBinが必要らしい。デフォルトだと/usr/bin/nodeなのでwindowsでは渡さないと動くはずもなかった・・・。
ということでLessFilterのインスタンス作成時にnode.exeへのパスを渡してやる。
<?php require '../vendor/autoload.php'; use Assetic\Asset\AssetCollection; use Assetic\Asset\FileAsset; use Assetic\Filter\LessFilter; $nodepath = 'C:\Program Files\nodejs\node.exe'; $css = new AssetCollection(array( new FileAsset('css/a.less', array(new LessFilter($nodepath))), new FileAsset('css/b.css'), )); // header('Content-Type: text/css'); echo $css->dump();
Assetic\Exception\FilterException: An error occurred while running: "C:\Program Files\nodejs\node.exe" "C:\Windows\Temp\assC737.tmp" Error Output: module.js:340 throw err; ^ Error: Cannot find module 'less' at Function.Module._resolveFilename (module.js:338:15) at Function.Module._load (module.js:280:25) at Module.require (module.js:364:17) at require (module.js:380:17) at Object.<anonymous> (C:\Windows\Temp\assC737.tmp:1:74) at Module._compile (module.js:456:26) at Object.Module._extensions..js (module.js:474:10) at Module.load (module.js:356:32) at Function.Module._load (module.js:312:12) at Function.Module.runMain (module.js:497:10) Input: /* a.less */ @color: #00ff00; #zannen-red { background-color:red; p { background-color:@color; } } in C:\xampp\htdocs\assetic_test\vendor\kriswallsmith\assetic\src\Assetic\Exception\FilterException.php on line 40 Call Stack
原因その2
とりあえずnodepathが読み込めて居ない様子。対処法は二つで
- 下記のようにnodepathの配列をLessFilterを作成する際に渡す。
のどちらかを行えばいいらしい。
<?php require '../vendor/autoload.php'; use Assetic\Asset\AssetCollection; use Assetic\Asset\FileAsset; use Assetic\Filter\LessFilter; $nodebin = 'C:\Program Files\nodejs\node.exe'; $nodepaths = array('{{AppDataへのパス}}\Roaming\npm\node_modules'); $css = new AssetCollection(array( new FileAsset('css/a.less', array(new LessFilter($nodebin, $nodepaths))), new FileAsset('css/b.css'), )); // header('Content-Type: text/css'); echo $css->dump();
これでエラーはなくなるけどb.cssしか出力されない・・。。
原因その3
symfony process builderとcmdの間で出力が共有されてないために
nodeが正常に実行されていても$proc->getOutput()がnullになってしまうのが原因のよう。
issueでsystemroot環境変数が渡されていないせいと指摘されているけど既にcommitがmergeされている模様
(BaseProcessFilterのmergeEnv関連)
\kriswallsmith\assetic\src\Assetic\Filter\LessFilter.php
で作成されている$procをvar_dumpすると確認できる
>cmd /V:ON /E:ON /C ""C:\Program Files\nodejs\node.exe" "C:\Users\<ユーザ名>\AppData\Local\Temp\ass815C.tmp""
ということでAsseticじゃなくてSymfonyProcessBuilderを見てみました。
C:\xampp\htdocs\assetic_test\vendor\symfony\process\Symfony\Component\Process\Process.php
あたりの$descriptorsを見てみると
var_dump($descriptors); ↓ array (size=3) 0 => array (size=2) 0 => string 'pipe' (length=4) 1 => string 'r' (length=1) 1 => resource(29, stream) 2 => array (size=2) 0 => string 'pipe' (length=4) 1 => string 'w' (length=1)
となんだか見慣れない形。
ためしに
$descriptors = array( 0 => array("pipe","r"), 1 => array("pipe","w") );
にしたら動いた!第3部完!
と思ったけどgetDescriptors()のコメント読むと
//Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
//Workaround for this problem is to use temporary files instead of pipes on Windows platform.
//@see https://bugs.php.net/bug.php?id=51800
と書いてあった。proc_openで2000bytes以上かませるとハングするらしい。
bootstrap.minが104KBだったので余裕でアウトな気がします。
If you change the script below to not include the STDERR descriptor or if you change the STDERR descriptor to a file output everything will work fine. Also if you close the STDERR pipe before reading from STDOUT it will work. There seems to be some deadlock.
らしいのでサンプルバグらせコードのSTDERRを消したバージョンを用意した。
*6
大丈夫みたい。一応手元で$dataを100*1024*1024にしたけど動作した。(時間かかるけど)なので
C:\xampp\htdocs\assetic_test\vendor\symfony\process\Symfony\Component\Process\Process.phpの968行目付近を
- return array(array('pipe', 'r'), $this->fileHandles[self::STDOUT], array('pipe', 'w')); + //return array(array('pipe', 'r'), $this->fileHandles[self::STDOUT], array('pipe', 'w')); + return array(array('pipe', 'r'), array('pipe', 'w'));
とした。動いた!*7
でもstderrに書き込もうとしたら落ちそう(未確認)。というかエラー起きた時に何だかわからなくなるのは厳しい・・・。symfony process builderのアップデート待ちというところでしょうか。
結論
AsseticのLessFilter使いたいときはLessphpFilter使うかOSを変えよう!