読者です 読者をやめる 読者になる 読者になる

node-webkit特有の問題にはまった

JavaScript memo

node-webkitでアプリを開発していて、node-webkitの環境ならでは?の問題にはまったのでメモ。

前提

% bower install eonasdan-bootstrap-datetimepicker --save

nodeのcontextではなくJavaScriptのcontextでの話。

アプリでbootstrap 3対応のdatetimepickerを使おうとしたら、読み込み時点でnot defineでエラー。

f:id:yuichi_katahira:20140528112828p:plain

読み込まれているJSは公式に書いてあるとおりの下記JSを結合したもの*1

  <script type="text/javascript" src="/bower_components/jquery/dist/jquery.min.js"></script>
  <script type="text/javascript" src="/bower_components/moment/min/moment-with-langs.min.js"></script>
  <script type="text/javascript" src="/bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
  <script type="text/javascript" src="/bower_components/eonasdan-bootstrap-datetimepicker/build/js/bootstrap-datetimepicker.min.js"></script>

エラーの箇所はdatetimepickerが依存するmomentがあるか確認している部分。

//bootstrap-datetimepicker.js

; (function (factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD is used - Register as an anonymous module.
        define(['jquery', 'moment'], factory);
    } else {
        // AMD is not used - Attempt to fetch dependencies from scope.
        if (!jQuery) {
            throw 'bootstrap-datetimepicker requires jQuery to be loaded first';
        } else if (!moment) { // ←ここでエラー
            throw 'bootstrap-datetimepicker requires moment.js to be loaded first';
        } else {
            factory(jQuery, moment);
        }
    }
}

breakpointをしかけて確認。デバッグはWebStormで。グローバル空間に確かにmomentがいない模様。

f:id:yuichi_katahira:20140528120138p:plain

moment.jsのコードを確認。requirejsなどをつかっていないので、一番下、else節のmakeGlobalが呼ばれる。

//moment.js
    // CommonJS module is defined
    if (hasModule) {
        module.exports = moment;
    } else if (typeof define === "function" && define.amd) {
        define("moment", function (require, exports, module) {
            if (module.config && module.config() && module.config().noGlobal === true) {
                // release the global variable
                globalScope.moment = oldGlobalMoment;
            }

            return moment;
        });
        makeGlobal(true);
    } else {
        makeGlobal(); //←これが呼ばれる
    }

makeGlobalを確認。
globalScope変数にmomentを追加してる。

//moment.js
    function makeGlobal(shouldDeprecate) {
        /*global ender:false */
        if (typeof ender !== 'undefined') {
            return;
        }
        oldGlobalMoment = globalScope.moment;
        if (shouldDeprecate) {
            globalScope.moment = deprecate(
                    "Accessing Moment through the global scope is " +
                    "deprecated, and will be removed in an upcoming " +
                    "release.",
                    moment);
        } else {
            globalScope.moment = moment;//←コレ
        }
    }

globalScopeを確認。 nodeならglobal、ブラウザ空間ならthisなので、windowと読める。

//moment.js

    var moment,
        VERSION = "2.6.0",
        // the global-scope this is NOT the global object in Node.js
        globalScope = typeof global !== 'undefined' ? global : this,
        oldGlobalMoment,
        round = Math.round,
        i,
........

コード的にはwindowにmomentが定義されるように思える・・。もう一回じっくりデバッグ

f:id:yuichi_katahira:20140528122827p:plain

f:id:yuichi_katahira:20140528122837p:plain

f:id:yuichi_katahira:20140528124628p:plain

f:id:yuichi_katahira:20140528123121p:plain

Window下にglobalがいるので、Window.global.momentになって、window.momentではなかった。
グローバルはwindowだと思っていたがWindowだった。
momentの読み込みとdatetimepickerの読み込みの間にworkaroundを入れて対処。

window.moment = global.moment;

https://github.com/rogerwang/node-webkit/wiki/Window

Window is a wrapper of DOM's window object, it has extended operations and can receive various window events.

Wikiをよく読みましょう、思い込みを捨てましょうという話。

*1:他にも読み込んでいるが関係ないので省略