CoffeeScriptのコードをJavascriptに変換するコマンドラインツール「decaffeinate」の紹介です。

 数年前は Javascriptのサブセットとして Coffeeスクリプトで書くプロジェクトも多く存在しましたが、
ECMAScriptで仕様が6 (ES6) になった辺りから 流れも変わってきました。

 フロントエンド側をCoffeeスクリプトで 進めているプロジェクトなら心配無さそうですが、
過去に書いたCoffeeスクリプトのコードを 他プロジェクトで利用するとなると 
Webpack用のローダーを咬ますことから始まり、色々とメンテナンスし難くなるかもしれません。​

そのような場合を想定して、便利に使えそうなコマンドラインツールが「decaffeinate」です。
 

decaffeinateで出来ること

- CoffeeScriptで書かれたコードを *モダンなJavascriptコードに変換できる
( * Classlet, const, アロー関数を含む )

* 'decaffeinate' を使えば既存のCoffeeScriptコード解析し、JSコードが作成されます 


decaffeinateのインストール

 decaffeinateは node.js で作らているCLIツールなので、コマンドライン上から
npmコマンドを使ってインストールします。

# npmコマンドを使ってインストール
npm install -g decaffeinate

 

decaffeinateの使い方

 decaffeinateの使い方は簡単で
コマンドライン上で 'decaffeinate 読み込むファイル' とういう形で使用します。

# この例では 'input.coffee' を読み込み、'input.js'が作成されます

decaffeinate input.coffee
# output
input.coffee → input.js

# オプションを指定する場合
decaffeinate input.coffee --loose-for-expressions


 また、decaffeinateにはオプションが用意されており、
可能であれば 'const'を使ったりなど、さまざまな環境に合わせることができます。
( 例: Coffee + Backboneの環境であれば、場合によっては
 '--enable-babel-constructor-workaround'のオプションが必要になります
)

* 用意されているオプション
・ --modernize-js: javascriptファイルをモダンJSファイルに変換
--literate: Treat the input file as Literate CoffeeScript.
--keep-commonjs: 'require', 'module.exports' を ' import, export' に変換しない.
--force-default-export: 'export'(モジュール) 変換中に 可能であれば1つの'import'にする
--safe-import-function-identifiers: コンマで区切られた配列内の関数を安全に読み込む ('import/require')
--prefer-const: 可能であれば'const (定数)'を使う
--loose-default-params: CooffeeスクリプトのパラメータをJSのデフォルトパラメータに変換
・ --loose-for-expressions: 配列内の'for'ループ内の式をラップしない
--loose-for-of: 配列内の'for' 'of'ループ式内をラップしない
・ --loose-includes: 配列内の'includes'をラップしない
--loose-comparison-negation: 推薦されていない式を許可する ( 例 : !(a > b) to a <= b)
--allow-invalid-constructors: 無効なコンストラクタを許可する
・ --enable-babel-constructor-workaround: 'this'をsuper()が呼び出される前に使うことを許可 (* TypeScriptでも動作)


実際にdecaffeinateを使って Coffee -> js に変換したコード

 自身の環境に 過去にCoffeeスクリプトで書いたコードが 大量あるので、
実際にdecaffeinateを使って Coffeeスクリプト -> Javascript に変換してみました。

下のスニペットは 'undersocre.js' の '_.mixin'を使って自作関数を登録しています。

# 変換前のCoffeeコード
_.mixin

  isValidURL: (url) ->
    urlPattern = /// (?:^|[\s ]+)((?:https?|ftp):\/\/[^\s ]+) ///i

    if url.match urlPattern
      return true

  # get uri segment
  uriSegment: (segment) ->
    uri = _.compact(location.pathname.split('/'))
    return uri[segment]
// 変換されたjsコード
_.mixin({

  isValidURL(url) {
    let urlPattern = new RegExp(`(?:^|[\\s]+)((?:https?|ftp):\\/\\/[^\\s]+)`, 'i');

    if (url.match(urlPattern)) {
      return true;
    }
  },

  // get uri segment
  uriSegment(segment) {
    let uri = _.compact(location.pathname.split('/'));
    return uri[segment];
  }});


 続いては Coffee + Backbone.jsのViewです。
変換時に '--enable-babel-constructor-workaround' オプションを指定しました。

# 変換前のCoffeeコード

class articleDraftView extends Backbone.View
  el: '#draftWrapper'

  events:
    'click .deleteArticleDraft': 'delete'

  initialize: () ->
    @deleteSetter = new deleteOutlineSetterModel()


  delete: (ev) =>
    ev.preventDefault()

    id = $(ev.target).data('id')

    if id
      METHOD.confirm('Do you wanna delete this article?', 'Yes, delete it!',
        () =>
          @deleteSetter.set({id: id})

          @deleteSetter.save(null,
            success: (model, resp) =>
              METHOD.reload(MESSAGE.reload)
            error: (model, resp) =>
              swal("ERROR!!", resp.statusText, "error")
          )
      )


articleDraftView = new articleDraftView()
// 変換されたJSコード

class articleDraftView extends Backbone.View {
  constructor(...args) {
    {
      // Hack: trick babel into allowing this before super.
      if (false) { super(); }
      let thisFn = (() => { this; }).toString();
      let thisName = thisFn.slice(thisFn.indexOf('{') + 1, thisFn.indexOf(';')).trim();
      eval(`${thisName} = this;`);
    }
    this.delete = this.delete.bind(this);
    super(...args);
  }

  static initClass() {
    this.prototype.el = '#draftWrapper';
  
    this.prototype.events =
      {'click .deleteArticleDraft': 'delete'};
  }

  initialize() {
    return this.deleteSetter = new deleteOutlineSetterModel();
  }


  delete(ev) {
    ev.preventDefault();

    let id = $(ev.target).data('id');

    if (id) {
      return METHOD.confirm('Do you wanna delete this article?', 'Yes, delete it!',
        () => {
          this.deleteSetter.set({id});

          return this.deleteSetter.save(null, {
            success: (model, resp) => {
              return METHOD.reload(MESSAGE.reload);
            },
            error: (model, resp) => {
              return swal("ERROR!!", resp.statusText, "error");
            }
          }
          );
      });
    }
  }
}
articleDraftView.initClass();


articleDraftView = new articleDraftView();

 
 最後に '--modernize-js' オプションを使って、
ES5で書かれたコードをモダンなJavascriptコードに変換してみました。

// decaffeinate input.js --modernize-js

// 変換前のコード
var __checkIsValid = function(variable) {
  return typeof variable == "function" || variable == undefined
    ? true
    : false;
}; // ! __checkIsValid()

// 変換後のコード
let __checkIsValid = variable =>
  typeof variable == "function" || variable == undefined
    ? true
    : false
; // ! __checkIsValid()

 

Summary

以上が decaffeinate の紹介でした。

 Babelとはコンセプトが違い、見やすいコードに変換することが目的なので
変換後もメンテナンスしやすいコードになりました。

また、一度に複数のCoffeeスクリプトを変換するためのパッケージも用意されています。
( GitHub : decaffeinate/bulk-decaffeinate )

 過去にCoffeeScriptで書いたライブラリなどを 再利用したい時や、 
ES6/2015にコードをリプレイスしたい時に使用すると便利そうですね!

GitHub : decaffeinate/decaffeinate

 

この記事のカテゴリ

プログラミング

この記事のタグ

開発ツール , nodejs , CoffeeScript

Socialシェアボタン

スポンサーリンク