my story blog

JavaScriptとかRubyの技術的なことを書きたい

Rails command generetor を作ってみた

angularJsとbootstrapの練習を兼ねてRailsのgenerate modelのコマンドを出力するサービス(ツール?)を作ってみた。

Rails command Generator - http://rails-command-generator.herokuapp.com/

サービスそのものにはあまり意味はなく、個人的なプログラミングの練習ということで。(個人的にはコマンドをよく忘れるので重宝しますがw)

他のコマンドは時間があれば作りたいな。

以降は使った技術の話

今回使った技術は、

  • まずnpmで開発に必要な各パッケージを管理。
  • bowerを使って、js,CSSの外部ライブラリ群を管理。
  • メタ言語としてTypeScript(JavaScript)、scss(CSS)、jade(html)を利用しました。
  • gruntでその各メタ言語のファイルのコンパイルと自動リロードをさせています。
  • javascriptのライブラリはangularJSを使っています(これがメインw)
  • 一部に自動補完をするtypeahead.jsを利用しています。
  • 全体のレイアウト・スタイルにはtwitter Bootstarpを使って細かい所は手で修正しています。
  • テストフレームワークにはjasmineを使い、テストランナーとして、karmaを使っています。

 

以下は半分備忘録のつもりで書いているので、なんか雑ですが。。。

npm

今回利用したgruntライブラリ群と、karmaのライブラリ群を中心にパッケージとして登録しています。 expressとpathはherokuでアプリ動かすため用に入れています。

//package.json

{
  "name": "rails-command-generator",
  "version": "0.0.0",
  "description": "",
  "main": "web.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "BSD",
  "devDependencies": {
    "grunt": "~0.4.1",
    "grunt-contrib-watch": "~0.5.3",
    "grunt-contrib-jade": "~0.8.0",
    "grunt-contrib-sass": "~0.5.0",
    "grunt-contrib-coffee": "~0.7.0",
    "grunt-contrib-compass": "~0.6.0",
    "grunt-contrib-connect": "~0.5.0",
    "grunt-bower-task": "~0.3.4",
    "grunt-ts": "~1.4.4",
    "karma-script-launcher": "~0.1.0",
    "karma-chrome-launcher": "~0.1.1",
    "karma-html2js-preprocessor": "~0.1.0",
    "karma-firefox-launcher": "~0.1.2",
    "karma-jasmine": "~0.1.3",
    "karma-coffee-preprocessor": "~0.1.1",
    "requirejs": "~2.1.9",
    "karma-requirejs": "~0.2.0",
    "karma-phantomjs-launcher": "~0.1.1",
    "karma": "~0.10.7",
    "karma-ng-scenario": "~0.1.0",
    "karma-coverage":"~0.1.4"
  },
  "dependencies": {
    "express": "~3.4.5",
    "path": "~0.4.9"
  }
}

bower

続いてbower.json、最終的には以下のようになりました。

ライブラリ群をdependenciesに書いているけど、devDependenciesでも良かったのかなと思います。

//bower.json
{
  "name"           : "rails-command-generator",
  "version"        : "0.0.0",
  "authors"        : [
    "kyohei8 <tsukuda.kyouhei@gmail.com>"
  ],
  "license"        : "MIT",
  "ignore"         : [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ],
  "dependencies"   : {
    "angular"         : "~1.2.2",
    "bootstrap"       : "~3.0.2",
    "jasmine"         : "~1.3.1",
    "typeahead.js"    : "latest",
    "angular-typeahead"    : "latest"
  },
  "exportsOverride": {
    "angular"         : {
      "js": "**/angular.min.js"
    },
    "bootstrap"       : {
      "js" : "**/bootstrap.min.js",
      "css": "**/bootstrap.min.css"
    },
    "jquery"          : {
      "js": "**/jquery.min.js"
    },
    "jasmine"         : {
      "js"     : ["**/jasmine.js", "**/jasmine-html.js"],
      "css"    : "**/jasmine.css",
      "favicon": "**/jasmine_favicon.png"
    },
    "typeahead.js":{
      "js" : "dist/typeahead.min.js"
    },
    "angular-typeahead":{
      "js" : "**/angular-typeahead.js"
    }
  }
}

ディレクトリ構成を

root
├─ lib
│   ├─js
│   │ ├ angular.min.js
│   │ └ …
│   └─css
│      └ bootstarp.css

のようにしたかったので、exportsOverrideは個別にファイルを指定しています。(詳細はこちらの記事参照)

grunt

gruntでは以下のことをしています。

bower:install

grunt-bower-task を使ってbowerのインストールと各ファイルを指定のフォルダに配置。 (詳細はこちらの記事にもあります)

watch

app/src以下にある、ts, scss, jade のファイルが更新された時に、compileを行いそれぞれを js, css, htmlファイルとしてapp/distディレクトリに出力しています。 typescriptのcompileがCPUに結構影響するみたいで、corei7のPCでは2秒くらいなのに対し、 c2Dのcpuの場合は10秒くらいかかるので、すぐ保存ボタンを押してしまう僕には少しストレスでした。

connect

livereloadを指定することによって、保存時に(上記のcompileの後)ブラウザでリロードが行われます。 Chromeで行う場合はExtensionのliveReloadが必要になるので、インストール。

Chrome Web Store - LiveReload

Chrome以外は以下のページを参考 https://github.com/livereload/livereload-extensions

grunt実行後、コンソール上では

$ grunt 
> Running "connect:livereload" (connect) task
> Started connect web server on 0.0.0.0:9000.

> Running "watch" task
> Waiting...

のように表示され、この状態で 0.0.0.0:9000にアクセスし、ChromeのExtensionのliveReloadのアイコンの中心が黒くなったら(結構わかりづらいw)、connectされています。 この状態でなにかファイルを編集すると、ブラウザが自動的にリロードしてくれます。

module.exports = function (grunt) {
  'use strict';
  grunt.initConfig({
    pkg  : grunt.file.readJSON("package.json"),

    /**
     * bower install
     * $ grunt bower:install
     */
    bower: {
      install: {
        options: {
          //中略
        }
      }
    },

    /**
     * watch
     * ファイル変更時、変換とブラウザのリロードをおこなう
     */
    watch: {
      options: {
        livereload: true
      },

      html: {
        files: ['app/src/**/*.jade'],
        tasks: ['jade']
      },
      css : {
        files: ['app/src/**/*.scss'],
        tasks: ['sass']
      },
      cs  : {
        files: ['app/src/coffee/**/*.coffee', 'test/spec/**/*.coffee'],
        tasks: ['coffee']
      },
      ts  : {
        files: ['app/src/ts/**/*.ts', 'test/spec/**/*.ts'],
        tasks: ['ts']
      }
    },

    /**
     * connect
     * 0.0.0.0:9000へアクセス
     */
    connect: {
      livereload: {
        options: {
          hostname: '*',
          port    : 9000
        }
      }
    },

    /**
     * jadeファイルのcompileを行う
     */
    jade   : {
      dest: {
        expand : true,
        cwd    : 'app/src/jade',
        src    : ['**/*.jade', '!**/*tmpl.jade'],
        dest   : 'app/dist',
        ext    : '.html',
        options: {
          pretty: true
        }
      }
    },

    /**
     * sass
     */
    sass   : {
      dest: {
       // 略
      }
    },

    /**
     * compile CoffeeScript
     */
    coffee : {
      dest : {
       // 略
      },
      test: {//同じフォルダに生成
        // 略
      }
    },

    /**
     * compile TypeScript
     */
    ts: {
      dest: {
        // 略
      }
    }

  });

  // load
  grunt.loadNpmTasks('grunt-bower-task');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-jade');
  grunt.loadNpmTasks('grunt-contrib-sass');
  grunt.loadNpmTasks('grunt-contrib-coffee');
  grunt.loadNpmTasks('grunt-contrib-connect');
  grunt.loadNpmTasks('grunt-contrib-compass');
  grunt.loadNpmTasks('grunt-ts');

  // default task
  grunt.registerTask('default', ['connect', 'watch']);
};

まとめ

モダンな開発手法をためしたくこんなことをしてみました。 個人的にはgruntのwatchからのコンパイル、リロードは便利で、メタ言語を使って開発する場合はなくてはならないコマンドになるのかなと思います。

実はgruntがなくても、Webstromがあればできたりするのですが、やはりチームでの開発とかだと、同じ構成(手法)でやらないといけなかったりするので、結局はgruntでやるほうがいいのかな。

あと、Gruntfile.jsとかbower.jsonとかは一度作っておけば流用もできるのでそこも利点かなと思います。