仙台 Ruby Vim JavaScript 社長

片平堂のブログ

Middlemanの入れ子レイアウトでエラー undefined method `safe_concat' for "":String

slimでwrap_layout使おうとしてエラーった。- を = に。

wrap_layout doesn't work with slim

参考: wrap_layout doesn't work with slim on ~> 3.3.2 #1269

gulpfileをCoffeeScriptで書く

メモ。

//gulpfile.js
require('coffee-script/register');
require('./gulpfile.coffee');
//gulpfile.coffee
gulp = require('gulp')

gulp.task 'default', () ->
  console.log('gulp!')

参考。

普通にCoffeeScriptで書けるようになってた。

node-webkitでコールバックで書いた非同期処理をPromise使用に書き換えてみた

node-webkitで作ったアプリで、データロード処理と保存処理部分をそれぞれ書き換えてみた。
PromiseのライブラリはBluebirdを使ってみた。
あと、cheerioをDOMのパースに使用。
FTPは、jsftpを使用。

データロード処理の流れ(load)
1. FTPでデータ取得
2. FTPで取得したファイルを読み込み
3. 読み込んだHTMLをパースしてデータ取得
4. 入力フォームに取得したデータを反映

保存処理の流れ(save)
1. FTPで取得していたファイルを読み込み
2. 読み込んだHTMLをパースしてフォームに入力されたデータをHTMLに反映
3. フォームのデータが反映されたHTMLのデータを一時ファイルに書き込み
4. 書き込まれた一時ファイルをFTPでアップロード
5. アップロード完了後、別画面に遷移、完了通知などの処理

v1がコールバック版、v2がPromise版

callback版、Promise版

2014/6/25追記:loadとsaveを呼び出している部分(index.coffee)も追加してみた。

File SystemのAPIはBluebirdのPromisificationという機能を使ってみた。

HTML部分の入力フォームやFTPの設定などはgistに上げてませんが、大体やってる流れは把握できるかと。

cheerioでパースすると、日本語が数値文字列参照になっているので、変換。
https://github.com/cheeriojs/cheerio/issues/466

Promiseについては以下が大変参考になりました。

node-webkitで作ったアプリをTypeScript化,CoffeeScript化してみての雑感

素のJavaScriptで書いて出来上がったnode-webkitアプリを今後の参考とするためにCoffeeScript化,TYpeScript化。
ライブラリ以外で書いたJSコードは300行程度と、かなり小規模なアプリ。*1
bowerで入れたのがBootstrap,Bootstrap v3 datetimepicker,jQuery,Moment.js,Vue.js。
npmで入れたのがjsftp(grunt周りを除く)。

JSからCoffeeScriptへの作業は3時間ぐらい。
JSからTypeScriptへの作業は型定義ファイルを書くのに不慣れだったため2日半ほど。

こちらと大体同じような感想。
中、大規模ならTypeScript,小中規模ならCofeeScriptを使うとよさ気。

TypeScript化は型定義ファイルを書くのにほとんど時間を書けた感じ。
書いた型定義ファイルは自前の外部モジュール2つとjsftp、node-webkit等のこのアプリを動かすのに必要最低限度の部分だけ。
正直、型定義ファイルを書くのは結構面倒だった。
規模が小さいので、書くコストに見合うご利益を感じられなかったからだと思う。
noImplicitAnyオプションを付けなければもっと早く終わったと思うが、それだとTypeScriptのメリットがいきないと感じた。

*1:なお、お客さんへの納品物なのでソースの公開はなし。

node-webkitでTypeScriptを使ってはまるケース

node-webkitJavaScript Contextでのrequireでのパス解決でハマる場合がある。

例えばこんなファイル構成で

  • index.html
  • js
    • index.js(index.ts)
    • fileUploader.js

各ファイルがこんな感じ。

<!-- index.html -->
<!DOCTYPE html>
<html>
  <head >
    <meta charset="utf-8" />
    .................
    <script src="js/index.js"></script>
  </body>
</html>
//js/index.js
..................
var fileUploader = require('./js/fileUploader');
.................
//↑node-webkit的に問題なし.

//index.ts
import fileUploader = require('./js/fileUploader');
//↑index.tsからだとrequire('./fileUploader')なのでエラー。

↑node-webkitJavaScript Context(いわゆるブラウザの世界)でrequireをした場合、JavaScriptファイルからのパスではなく、そのJavaScriptを読み込んでいるhtmlからのパスになる。

参考:

回避策1

その場しのぎ的。requireをindex.htmlのインラインに持ってくる。

<!-- index.html -->
<!DOCTYPE html>
<html>
  <head >
    <meta charset="utf-8" />
    .................
    <script>
      //..................
       var fileUploader = require('./js/fileUploader');
      //................. 
    </script>
    <script src="
  </body>
</html>

回避策2

構造を変える

  • index.html
  • index.js(index.ts)
  • js
    • fileUploader.js
<!-- index.html -->
<!DOCTYPE html>
<html>
  <head >
    <meta charset="utf-8" />
    .................
    <script src="index.js"></script>
  </body>
</html>

うーむ。

requireしてnewして使用するnpmのパッケージにTypeScript用の型定義ファイルを書く

こういうの。

///<reference path='./typing/node.d.ts' />
///<reference path='./typing/jsftp.d.ts' />

import JSFtp = require('jsftp');

..........................
   var Ftp = new JSFtp({
      host: connect.host,
      port: connect.port,
      user: connect.account,
      pass: connect.password
    });
..........................

jsftp用に書いた型定義ファイル(簡略)

//  ./typing/jsftp.d.ts

/// <reference path="./node.d.ts" />

interface JSFtpOption {
  host?: string;
  port?: number;
  user?: string;
  pass?: string;
  ....................
}

declare module 'jsftp' {

  class Ftp {
    constructor(cfg?: JSFtpOption);
    put(from: string, to: string, callback?: (err: NodeJS.ErrnoException) => void): void;
    .......
  }

export = Ftp;
}

参考

declare module 'jsftp' {

  class Ftp {
    constructor(cfg?: {host?:string;port?:number;user?:string;pass?: string});

    put(from: string, to: string, callback?: (err: NodeJS.ErrnoException) => void): void;
    ...........
  }

export = Ftp;
}

オブジェクト型リテラルにすれば重複しないけど。 うーむ。

追記

おー、なるほど。

declare module JSFTP {
  export interface JSFtpOption {
    host?: string;
    port?: number;
    user?: string;
    pass?: string;
    .......................
  }
}

declare module 'jsftp' {

  class Ftp {
    constructor(cfg?: JSFTP.JSFtpOption);

    put(from: string, to: string, callback?: (err: NodeJS.ErrnoException) => void): void;
    .............................................
  }

export = Ftp;
}

このJSFTPモジュールは非インスタンス化モジュールで、型定義のネームスペースにしか存在しないので、変数空間を汚さないと。
ネームスペースを噛ませているので重複しにくくなりました。
勉強になりました。
ありがとうございます。

更に追記

あー・・・。TypeScriptリファレンス Ver.1.0対応の6-4-4インスタンス化・非インスタンス化モジュールに書いてあった。
読破したはずなのに覚えてなかった・・・。

*1:module.exports=Ftp

node-webkitでNode contextからGUIのAPIを使う

メニュー出したりとかデスクトップアプリ特有のネイティブのアレをNode側から使いたい。

1.require時に引数として渡す

//index.js

var gui = require('nw.gui');
//↓コレ
var fileUploader = require('fileUploader')(gui);

例なので全部渡しちゃってるけど必要な項目だけに絞って渡した方がいいと思う。

2.window.require('nw.gui')する

//fileUploader.js

var fs = require('fs');

var gui = window.require('nw.gui');

Window.globalとかGlobal.windowとかになっている話はこちらを参照

node-webkitの場合、windowにもrequireメソッドがあって、実装はというとこんな

function (name) {  if (name == 'nw.gui')    return nwDispatcher.requireNwGui();  return global.require(name);}

見てそのまま。
nw.guiはwindowのrequireを呼ばないといけないと。

contextが混在しているのはささっと呼べて便利といえば便利だけど、サンドボックス機構があったほうがメンテナンス的には幸せなのかもしれない。
以上。

参考