gruntで任意の場所でnpm i --productionする

node-webkitで作成したアプリのパッケージングはappディレクトリを対象として実行してて、npmをcd app してからnpm i --productionと手動で処理していたので、Gruntに登録した。

Gruntfile.coffeeの該当箇所

  ......
    exec:
      npm_i_production:
        cwd: 'app'
        cmd: 'npm i --production'
  .....
  grunt.loadNpmTasks('grunt-exec')
  grunt.registerTask 'default', ['slim', 'concat', 'copy', 'exec','nodewebkit']

OK.

参考

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

以上。

参考