のらねこの気まま暮らし

技術系だけど、Qiita向きではないポエムとかを書くめったに更新されないヤツ

CasperJSを拡張したライブラリをローカルに置こうとして苦戦した記録

CasperJSを使っていて大変苦労したのでその記録。

やりたかったコト

CasperJSを継承した独自拡張のライブラリを複数のファイルでrequireして個別にプログラムを実行したかった。

grunt-casperのようなライブラリをから使う場合と、単体のファイルをcasperjsコマンドで実行する二通りの用途を想定していた。

継承

継承自体は簡単に可能。 coffee-scriptを使っているならなおのこと。

Casper = require("casper").Casepr

class MyCasper extends Casper
  start: ->
    console.log "start my casper!"
    super

外部ファイルとしてrequireする

辛かったのはここから。 上記のクラスをmodule.exportsで吐き出し、他のファイルからrequireで参照しようとした。

"use strict"

Casper = require("casper").Casper

class MyCasper extends Casper
  start: ->
    console.log "start my casper!!"
    super

module.exports = MyCasper
"use strict"

MyCasper = require("my-casper.coffee")

casper = new Casper()
casper.start "https://www.google.com/", ->
  @echo "open #{ @getTitle() }"
casper.run()

上記のようなサンプルコードを用意して、コマンドを叩く。

vimshell% casperjs runner.coffee
Error: Cannot find module 'casper'

  phantomjs://bootstrap.js:289
  phantomjs://bootstrap.js:254 in require
  /home/mizuki/project/casper-sample/my-casper.coffee:8
  /home/mizuki/project/casper-sample/my-casper.coffee:29
  /home/mizuki/project/casper-sample/my-casper.coffee:30
ReferenceError: Can't find variable: Casper

  runner.coffee:8
  runner.coffee:16

Cannot find module 'casper'

requireしようととしているcasperが見つからない。 bootstrap.js:289このファイルをまず見に行く。

情報がほとんど無いので、エスパーで回答すると、

どう見てもずれている・・・

ここからPrintDebugタイムに突入し以下の情報を仕入れた。

  • PhantomJSのrequireにpatchを当てている
  • casperなる(CasperJSに関連した)node_modulesはなく、patchedRequireの中でCasperJSのmodulesのディレクトリを見に行くようにになっている

patchedRequireのコード casperjs/bootstrap.js at 4f105a91a0e1d48826d124b2be3627276986fcb9 · n1k0/casperjs · GitHub

その中で呼ばれているcasperBuiltinPathメソッドがcasperの内部のmodulesさんからcasperを探してきている。

        function casperBuiltinPath(path) {
            return resolveFile(path, fs.pathJoin(phantom.casperPath, 'modules'));
        }

さて、ここらへんにprintデバッグのコードを埋め込むわけだが端的に結論を行ってしまうと、 呼 ば れ て い な いのだ。

require先のライブラリの中ではcasperjsのpatchが当たらないらしい。 どういう仕組でそうなっているのかは、PhantomJSも含めて周辺コードを追跡しないといけないので、ひとまずはこの仕組を諦めることにした。

いずれリベンジしたいところである。

余談

ローカルパスの検索

phantom.casperScriptBaseDirもしくはfs.workingDirectoryを基準にrequireに指定したパスを走査する。

        function localModulePath(path) {
            return resolveFile(path, phantom.casperScriptBaseDir || fs.workingDirectory);
        }

casperScriptBaseDirはどこで設定されるのかというと、

コマンドラインtestサブコマンドを除く第一パラメーターである。 すなわち、下記のrunner.coffeeのディレクトリである。

vimshell% casperjs runner.coffee

今回示した例ならさほど問題はないが、src/runner.coffeeの様なケースだと、若干面倒になる。 これはnodeJSも同様だが、相対パスと実行ディレクトリを意識しなくてはならない。

casperScriptBaseDirを書き換えることでこの問題を回避することもできるがオススメしない。 Nodeであれば、NODE_PATHを追加することで、そのディレクトリをnode_modulesの走査対象とするが、CasperJSについてはそこまで確認していない。

この辺りのソースを追っていけばヒントは得られそう。 casperjs/bootstrap.js at 4f105a91a0e1d48826d124b2be3627276986fcb9 · n1k0/casperjs · GitHub

ともあれ、非常に苦戦したので、とりあえずその調査の記録だった。