node-webkitで作ったアプリをWindows向けにパッケージングする

grunt-node-webkit-builder使う。

使う人の環境がWindowsだけど、一応Macも作っとく。
Gruntfile.coffeeはこんな感じ。appディレクトリに集めたファイルをパッケージング。

module.exports = (grunt) ->
  pkg = grunt.file.readJSON 'package.json'
  .........

  grunt.initConfig

  .........
    nodewebkit:
       options:
         mac: true
         win: true
         app_name: pkg.name
         app_version: pkg.version
         build_dir: 'build'
       src: [ 'app/**/*' ]
   .........

  grunt.loadNpmTasks('grunt-node-webkit-builder')
  grunt.registerTask 'default', ['slim', 'concat', 'copy', 'nodewebkit']

便利。

参考:How to package and distribute your apps

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

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:他にも読み込んでいるが関係ないので省略

WebStormでnode-webkitをデバッグする

WebStorm 8.0.1以上、package.json等作成済みとして話を進める。
メニューのRunからEdit Configurations…を選択

f:id:yuichi_katahira:20140528100559p:plain

node-webkitを追加

f:id:yuichi_katahira:20140528100940p:plain

アプリの場所を指定。グローバルに入れたnode-webkitではなく、npmでプロジェクト下に入れたnode-webkitを指定する場合はinterpriterをproject/to/path/node_modules/nodewebkit/bin/nodewebkitとかに指定しなおす。

f:id:yuichi_katahira:20140528101334p:plain

見えづらい・・

f:id:yuichi_katahira:20140528104858p:plain

後は普通にデバッグ出来る。

f:id:yuichi_katahira:20140528110333p:plain

以上。

参考

Node.jsの管理をhomebrewからnodebrewに変更

npmでglobalにインストールしたtypescriptとbowerを削除。

% npm uninstall -g bower
% npm uninstall -g typescript

npmを削除

% npm uninstall -g npm

Node.jsを削除

brew uninstall node

nodebrewをインストール

% curl -L git.io/nodebrew | perl - setup

.zshrcにPATHを追加

export PATH=$HOME/.nodebrew/current/bin:$PATH

読み込み直し

% source ~/.zshrc

Node.jsをインストール

% nodebrew install stable

標準で使用するNode.jsのバージョン指定

% nodebrew use stable

bowerとtypescriptをグローバルにインストール

% npm install -g bower
% npm install -g typescript

参考

TypeScriptリファレンス Ver.1.0対応のサンプルコードで--noImplicitAnyをつけて(略

備忘。TypeScriptリファレンス Ver.1.0対応のコールシグネチャのリスト6.56で--noImplicitAnyをつけてる場合。

function db(operation: string, key: string, value?: any): any {
  if (!db.data) {
    db.data = {};
  }
  var data = db.data;
  var oldValue: any; //追加
  if (operation === "update") {
    oldValue = data[key];
    data[key] = value;
    return oldValue;
  } else if (operation === "read") {
    return data[key];
  }
}
module db {
  export var data: any;

  export function update(key: string, value: any) { //追加
    return db("update", key, value);
  }
  
  export function read(key: string) { //追加
    return db("read",key);
  }
}

db.update("str", "string");
console.log(db.read("str"));

db("update", "str", "文字列");
console.log(db("read", "str"));

TypeScriptリファレンス Ver1.0対応 4-4 Enumのサンプルコードで--noiImplicitAnyをつけてるとコンパイルエラー

TypeScriptリファレンス Ver.1.0対応の4-4 Enumのサンプルコードでtscに--noImplicitAnyをつけてるとコンパイルエラー。

enum Suit {
  Spade,
  Heart,
  Club,
  Diamond
}

var s1: Suit = Suit.Spade;
var s2: number = s1;
var s3: string = Suit[s2];
var s4: number = Suit[s3]; 
//↑ error TS7017: Index signature of object type implicitly has an 'any' type. 
console.log(s1, s2, s3, s4);

通るように。

var s4: number = Suit.Spade;
//又は 
var s4: number = Suit["Spade"];

後で読む

備忘録。enumが吐き出すjsのメモ

var Suit;
(function (Suit) {
    Suit[Suit["Spade"] = 0] = "Spade";
    Suit[Suit["Heart"] = 1] = "Heart";
    Suit[Suit["Club"] = 2] = "Club";
    Suit[Suit["Diamond"] = 3] = "Diamond";
})(Suit || (Suit = {}));
//なのでSuitはこうなる。
 {0: "Spade", 1: "Heart", 2: "Club", 3: "Diamond", Spade: 0, Heart: 1, Club: 2, Diamond: 3}