タグ「jsdom」が付けられているもの

jsdomへのpull request: XHRサポート

  • 投稿日:
  • by

connect-prerendererをちゃんと動かすためにやったjsdomの修正その2をpull requestにしました。

https://github.com/tmpvar/jsdom/pull/654

実はXHR自体は、 node-XMLHttpRequest をそのまま使うだけでほとんど苦労はありませんでした。

大変だったのは、クッキーを引き継ぐところでした。 そもそもXHRを使わない場合も、jsdomはクッキーの引継ぎをサポートしていませんでした。XHRを使わない場合というのは、JavaScriptやCSSや画像ファイルなどを読み込む場合です。

まず、JavaScriptのロード時にもクッキーを引き継ぐようにするコーディングをしてから、XHRでもクッキーを引き継ぐようにしました。XHRはnode-XMLHttpRequestの内部のコードまで理解しなくてはならず、苦労しました。ちょっと強引に実装したため、もしかしたら将来のバージョンのnode-XMLHttpRequestでは動かないかもしれません。

pull requestは無事マージされて、jsdom v0.8.1がリリースされました。興味がある方はお試しください。

https://npmjs.org/package/jsdom

connect-prerenderer もこのバージョンを使うように修正しました。

https://npmjs.org/package/connect-prerenderer

connect-prerendererをちゃんと動かすためにやった 修正をjsdomのpull requestにして欲しいと依頼されました。

unit testを書いて欲しいということなので、書いてみたのですが動かなくて苦労しました。 __defineGetter__と__defineSetter__を初めて使いました。 ECMAScriptではこれらは入っていない(definePropertyを使う)そうですが、nodeでは__defineGetter__と__defineSetter__は使えるようです。

結局、当初やっていた修正では全くダメ(なぜconnect-prerendererが動いたのか不明)で、window.locationまわりのコードを全部書き直しました。 おかげで、全体的により仕様に合った動きをするようになり、立派なpull requestができあがりました。

ちゃんとマージされたようで、めでたしめでたし。

https://github.com/tmpvar/jsdom/pull/650

connect-prerendererのIssue #3を解決

  • 投稿日:
  • by

DailyJSに取り上げてもらって、 一気にStar数が増えたconnect-prerendererですが、 Issueが一つ残ってました。

https://github.com/dai-shi/connect-prerenderer/issues/3

jsdomがらみなので、難しいなぁと思っていたのですが、 こっちの方 でも使ってくれそうな様子なので、ちょっとがんばってみました。

ヒントは、Issue #3で報告してくれた、

http://stackoverflow.com/questions/10054071/jsdom-hashchange-event

と、jsdomの古いIssueの

https://github.com/tmpvar/jsdom/issues/433

でした。

そもそも、hashchangeイベントの発火が実装されていないとのことでした。 みようみまねでjsdomに手を加えて、コードを追加しました。

結果、動くようになりました。まだ簡単なe2eテストが動いただけですが、 不具合があれば誰か報告してくれることでしょう。それまで、待つ。

最近、connect-prerendererのIssueを報告してもらって、少しずつ不具合が見つかっては修正しています。

うまく解決したのは、

の二つ。前者は、

<div>
  count={{count}}
  <span>hoge</span>
</div>

のように、TextNodeとHTMLElementが並んでいるときに、 ng-bind-templateで上書きしてしまう問題でした。

<div>
  <span>count={{count}}</span>
  <span>hoge</span>
</div>

とコンパイル時になるように、Angular.jsをいじりました。 ただ、CSSがそれを想定しない書き方だと、表示に不具合がでるかもしれません。

後者は、doctypeがjsdomで消えている問題でした。こっちはすぐに直りました。

https://github.com/dai-shi/connect-prerenderer/issues/3 がなかなか手ごわく、まだ解決の糸口が見つかりません。 以前書いた、jsdomからAngularJSの不具合二つ目の問題です。

なんにしても、興味を持ってくれる人が増えるのはうれしいことです。 connect-prerendererはほとんど宣伝していないのに、Star数が増えてきました。それはそれで不思議。

connect-prerendererが一応完成

  • 投稿日:
  • by

AngularJSのマイルストーンにserver-side prerenderingというのがあるのですが、当分できそうにないので自分で作ってしまっています。

https://github.com/dai-shi/connect-prerenderer

express.jsのmiddlewareで使うことを想定していますが、express.jsには依存しないようにしたので、connect-prerendererです。expressとconnectの違いは、他のブログなどでも説明されていることでしょう。

何をやっているかを一言で言うと、サーバ側でjsdomを使ってJavaScriptを走らせて、レンダリング後のHTMLページをクライアントに返すというものです。

基本的なところはしばらく前にできていて、簡単なテストケースは動いていたのですが、AngularJSを動かすのがなかなか大変でした。結局、angular.jsのソースに手を入れることになりました。

AngularJSを使った簡単なテストケースも動いて、満足です。 もってきてすぐ使えるようなライブラリではないですが、たぶん世界初の取り組みでしょう。

あとははAngulaJS側でもっとうまく取り込んでもらわないとつらいところです。今は決めうち(ng-repeat="..."はOKだが、class="ng-repeat:..."は動かないとか)でやっている部分があるため。

あまり説明できずに意味不明かと思いますが、これにて。

jsdomからAngularJSの不具合二つ目

  • 投稿日:
  • by

jsdomからAngularJSで作ったページにアクセスしたら、こんなエラーがでました。

Error: 10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: [["fn: $locationWatch; newVal:18; oldVal: 17"],["fn: $locationWatch; newVal: 19; oldVal:18"],["fn: $locationWatch; newVal: 20; oldVal: 19"],["fn:$locationWatch; newVal: 21; oldVal: 20"],["fn: $locationWatch;newVal: 22; oldVal: 21"]]

このエラーは"familiar"らしいです。一番該当しそうなのが、

angular.js Issue #1417

これです。現時点ではまだ解決されていない様子です。

果たして、$routeProviderでredirectToを使うのを止めてみたらエラーが出なくなりました。んー、不便です。

https://github.com/dai-shi/connect-prerendererを開発中に出会った事象について書き留めておきます。

jsdomでAngularJSを使ったテストページにアクセスすることになったのですが、 angularが初期化されなくて困ってました。ぐぐったところ、

AngularJSがIE8で動かないときは

を見つけたのですが、この方法では解決せず。結局、次のようにしたら動きました。

<html ng-app class="ng-app">

id="ng-app"はあってもなくても変わらず。モジュールがある場合は、class="ng-app:<modulename>"でもうまくいくのかもしれません。

angularのソースコードを少し眺めると、

function angularInit(element, bootstrap) {
  var elements = [element],
      appElement,
      module,
      names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'],
      NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;
//-----snipped-----
  forEach(names, function(name) {
    names[name] = true;
    append(document.getElementById(name));
    name = name.replace(':', '\\:');
    if (element.querySelectorAll) {
      forEach(element.querySelectorAll('.' + name), append);
      forEach(element.querySelectorAll('.' + name + '\\:'), append);
      forEach(element.querySelectorAll('[' + name + ']'), append);
    }
  });

ってな感じで、どんな風に書いても動きそうです。なぜ、id="ng-app"でうまくいかなかったのか疑問が残りますが、追求しないことにします。


追記。

ng-appだけではダメだった理由は、ng-app="ng-app"に展開されてしまうからのようなので、ng-app="<modulename>"にすればうまくいくのかもしれません。

さらに、追記。

結局、ng-app=""で動いたので、ng-appだけだとjsdomでうまくいかないというのは正しそうです。class="ng-app:<modulename>"もうまくいきそう。id="ng-app"で動かなかった理由はおそらく、ng-appが残っていたからでしょう。