require.jsとQの組み合わせにおけるintellisenseの挙動

公開:2014-09-19 08:30
更新:2020-02-15 04:37
カテゴリ:javascript,intellisense,q.js

横スクロール・アクションを作る前にライブラリをRequireJSを使ったモジュール構造に改造しようとしたが、intellisenseがうまく働かないという問題に直面して10日が過ぎようとしている。intellisenseが使えないと私のような記憶力の衰えたお年寄りには非常につらい。何とか解決しようとIntellisenseのドキュメント類を読み漁ってきたがいまだに問題の核心には迫っていない状況である。

その問題をもう少し具体的に書くと、まずはrequirejsにより最初に呼び出されるjsファイルを下記のように書く。

var req = require.config({
  baseUrl: '/Scripts',
  paths: {
    "Q": "http://cdnjs.cloudflare.com/ajax/libs/q.js/1.0.1/q",
    "jquery": "http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery"

  }
  }
);

req(["Q", "jquery"],
function (q, jq) {
  console.log(q);
  console.log(jq);
  alert('loaded!!');
}
 ,
// Error //
function (err) {
  var elm = document.getElementById('#loading');
  if (!elm) {
    elm = document.createElement('div');
    document.body.appendChild(elm);
  }

  elm.classList.add('alert');
  elm.classList.add('alert-danger');
  elm.innerText = "エラー:" + err.description();
}
);

QもJQueryもRequireJSに対応していて、Chromeで実行するとrequire()メソッドのコールバックの変数q,jqにはQとjQueryのオブジェクトが返される。
この変数q,jqをVS2013のエディタ上でintellisenseを効かせたい。なので_reference.jsにRequireJSへの<reference>ディレクティブを書く。

/// <autosync enabled="false" />
/// <reference path="http://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.14/require.js" />

この状態でintellisenseを試すと、q,jq変数のコード補完ができない。

その際以下のようなログが「JavaScript言語サービス」から吐き出される。

09:43:37.6508: その他のファイル プロジェクト内のファイルのリモート参照はダウンロードできません。リモート参照のダウンロードを有効にするには、[ツール]、[オプション]、[テキスト エディター]、[JavaScript]、[IntelliSense] の順に選択し、該当のオプションをオンにします。
09:43:37.6568: ファイル H:\pj\test\Scripts\b.js に、読み込むことができないスクリプト ローダー ファイル参照 (url = http://cdnjs.cloudflare.com/ajax/libs/q.js/1.0.1/q.js、type = text/javascript) が含まれています。パスを解決できませんでした。
09:43:37.6578: ファイル H:\pj\test\Scripts\b.js に、読み込むことができないスクリプト ローダー ファイル参照 (url = http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.js、type = text/javascript) が含まれています。パスを解決できませんでした。

このエラーを元に[ツール]、[オプション]、[テキスト エディター]、[JavaScript]、[IntelliSense] [全般]の中にあるそれらしき設定、「その他のファイルプロジェクト内のリモート参照( http://など)をダウンロードする」にチェックを入れる。

そうするとRequireJS内で利用されるスクリプトファイルが読み込まれるようになる。

09:52:51.2757: 参照ファイル 'http://cdnjs.cloudflare.com/ajax/libs/q.js/1.0.1/q.js' をダウンロードしています...
09:52:51.2767: 参照ファイル 'http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.js' をダウンロードしています...
09:52:51.7440: 'http://cdnjs.cloudflare.com/ajax/libs/q.js/1.0.1/q.js' のダウンロードが完了しました。
09:52:51.7610: 'http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.js' のダウンロードが完了しました。
09:52:55.4290: ファイル H:\pj\test\Scripts\b.js 内のスクリプト ローダーの呼び出しによって参照されたスクリプト http://cdnjs.cloudflare.com/ajax/libs/q.js/1.0.1/q.js が読み込まれました。
09:52:55.4300: ファイル H:\pj\test\Scripts\b.js 内のスクリプト ローダーの呼び出しによって参照されたスクリプト http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.js が読み込まれました。

設定後試すとjq変数はintellisenseできるようになるがq変数は依然としてできない。

考えるに、jQueryはうまくintellisenseできるのでQ側の問題そうだ。そもそもQはintellisenseはうまく動くのだろうかというと、確かめてみたところRequireJSを使わず<script>タグでダイレクトに読み込むように書くとintellisenseできる。

ただしQは<script>タグ、RequireJSでrequireされたとき、Nodeでrequireされたときそれぞれで動きが異なり、<script>タグでは単純にグローバルなQオブジェクトとして登録されるからintellisenseされるようである。以下はq.jsのコードの一部である。

(function (definition) {
    // Turn off strict mode for this function so we can assign to global.Q
    /* jshint strict: false */

    // This file will function properly as a <script> tag, or a module
    // using CommonJS and NodeJS or RequireJS module formats.  In
    // Common/Node/RequireJS, the module exports the Q API and when
    // executed as a simple <script>, it creates a Q global instead.

    // Montage Require
    if (typeof bootstrap === "function") {
        bootstrap("promise", definition);

    // CommonJS
    } else if (typeof exports === "object" && typeof module === "object") {
        module.exports = definition();

    // RequireJS
    } else if (typeof define === "function" && define.amd) {
        define(definition);

    // SES (Secure EcmaScript)
    } else if (typeof ses !== "undefined") {
        if (!ses.ok()) {
            return;
        } else {
            ses.makeQ = definition;
        }

    // <script>
    } else {
        Q = definition();
    }

})(function () {
.
.
.
.

ご覧いただくとわかるとおり、QはRequireJSが存在する環境下ではdefine(definition)を呼び出している。RequireJSでは実際にオブジェクトが作成されるのはrequire()したときだが、その際のデザイン時の挙動がintellisenseに影響しているのではないかと推測しているのだが、どこで影響しているのかがわからないのが今の状況だ。

今後はq.intellisense.jsファイルを書いてデザイン時の挙動を変化させてうまくintellisenseさせることができないかなと思い試してみようと思っている。そのまえにrequire()時の挙動をもう少しきちんと押さえておく必要もあるね。

この辺の動作を試している時に気付いたのだが、意図したような挙動をintellisenseがしなくなるようなときがある。例えば何かの設定を試してintellisenseが効かなくなり、以前同じ設定に戻したのににintellisenseが効かなくなる。どうもintellisenseのキャッシュが影響してこうなってしまうらしい。VS2012だとCtrl+Shift+Rでクリアできたのだが、VS2013だと有効ではないようだ。どこかのページにソリューションファイルが入っているフォルダにある.suoファイルを削除すると良いと書いてあった。試してみると確かにintellisenseが再び有効になった。しかしこの.suoファイルが何をするファイルなのか今一つ把握していないので削除することでVS2013にどのような影響が発生するのかわかっていない。

またintellisenseはコード補完候補表示を行うのに実際にJavaScriptを実行している。なので<reference>ディレクティブの定義順序でintellisenseの表示内容が変わったり、intellisenseが効かなくなったりもするから注意が必要である。