Rails5でAngular2を動かすまでの設定

2016.01.25

丹内です。 掲題のとおり、BetaになっているRails5とAngular2を連携させるまでの手順をまとめました。

バージョン

  • ruby 2.3.0
  • node 4.2.4
  • npm 3.5.3

Rails5アプリの準備

bundle initしてできたGemファイルにRails5を指定します。

source 'https://rubygems.org'

gem 'rails', '5.0.0.beta1'

bundle installしたらアプリを生成します。

$ rbenv exec bundle install
$ bundle exec rails new . -d mysql --skip-bundle -T
       exist
      create  README.md
      create  Rakefile
      create  config.ru
      create  .gitignore
    conflict  Gemfile
Overwrite /Users/tannai.yuki/dev/sample/Gemfile? (enter "h" for help) [Ynaqdh]
       force  Gemfile
      create  app
      (省略)
$ bundle exec rails db:create
$ bundle exec rails db:migrate
$ bundle exec rails s
=> Booting Puma
=> Rails 5.0.0.beta1 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
I, [2016-01-11T11:38:22.205313 #7174]  INFO -- : Celluloid 0.17.2 is running in BACKPORTED mode. [ http://git.io/vJf3J ]
Puma 2.15.3 starting...
* Min threads: 0, max threads: 16
* Environment: development
* Listening on tcp://localhost:3000

webrickではなくpumaが使われています。 welcome aboardでRails5になっていることを確認したら、次のステップです。

フロントエンド環境の準備

npm initでもtouchでも良いので、アプリケーションルートにpackage.jsonを生成し、以下のように編集します。

{
  "name": "sample-client",
  "version": "1.0.0",
  "engines": {
    "node": ">= 4.2.3 < 5",
    "npm": ">= 3"
  },
  "scripts": {
    "start": "webpack -w"
  },
  "license": "ISC",
  "dependencies": {
    "angular2": "2.0.0-beta.1",
    "es6-promise": "^3.0.2",
    "es6-shim": "^0.33.3",
    "es7-reflect-metadata": "^1.4.0",
    "rxjs": "5.0.0-beta.0",
    "zone.js": "0.5.10"
  },
  "devDependencies": {
    "concurrently": "^1.0.0",
    "exports-loader": "^0.6.2",
    "expose-loader": "^0.7.1",
    "imports-loader": "^0.6.5",
    "ts-loader": "^0.7.2",
    "typescript": "^1.7.3",
    "webpack": "^1.12.11"
  }
}

同じくアプリケーションルートに、webpack.config.jsを用意します。

const path = require('path');
const webpack = require('webpack');
const CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin;

module.exports = {
  devtool: 'source-map',
  debug: true,
  entry: {
    'vendor': './client/vendor.ts',
    'app': './client/bootstrap.ts'
  },
  output: {
    filename: 'bundle.js',
    path: './app/assets/javascripts/generated'
  },
  resolve: {
    root: [path.join(__dirname, 'client/assets/scripts')],
    extensions: ['', '.webpack.js', '.web.js', '.ts', '.tsx', '.js']
  },
  module: {
    loaders: [
      { test: /\.ts?$/, exclude: /node_modules/, loader: 'ts-loader' }
    ]
  },
  plugins: [
    new CommonsChunkPlugin({ name: 'vendor', filename: 'vendor.js', minChunks: Infinity }),
    new CommonsChunkPlugin({ name: 'common', filename: 'common.js', minChunks: 2, chunks: ['app', 'vendor'] })
  ]
}

Typescriptのコンパイラ、TSCの設定ファイルであるtsconfig.jsonも用意します。

{
  "compilerOptions": {
    "target": "ES5",
    "module": "commonjs",
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "removeComments": false,
    "noImplicitAny": false
  },
  "files": [
    "./client/vendor.ts",
    "./client/bootstrap.ts",
    "./client/components/AppComponent.ts"
  ],
  "exclude": [
    "node_modules"
  ],
  "compileOnSave": false,
  "buildOnSave": false,
  "atom": {
    "rewriteTsconfig": true
  }
}

これらアプリケーションルートに設置する設定ファイルに書かれているように、以後/client以下にAngular2のファイルを配置していきます。

Angular2の実装

まずは/client/bootstrap.tsにAngular2の設定を書きます。

import {bootstrap}    from 'angular2/platform/browser';
import {App} from './app';
import {provide} from 'angular2/core';
import {
  ROUTER_PRIMARY_COMPONENT,
  APP_BASE_HREF,
  ROUTER_PROVIDERS as NG2_ROUTER_PROVIDERS
} from 'angular2/router';

const ROUTER_PROVIDERS: Array<any> = [
  NG2_ROUTER_PROVIDERS,
  provide(ROUTER_PRIMARY_COMPONENT, {
    useValue: App
  }),
  provide(APP_BASE_HREF, {
    useValue: '/'
  })
];

const APP_PROVIDERS: Array<any> = [
  ROUTER_PROVIDERS
];

window.addEventListener("load", (e) => {
  bootstrap(App, APP_PROVIDERS);
});

この中でRouteConfigをアノテートしている、Rootになるコンポーネントを作成します。

import {ViewEncapsulation, Component} from 'angular2/core';
import {CORE_DIRECTIVES} from 'angular2/common';
import {ROUTER_DIRECTIVES, Router, RouteConfig, Location} from 'angular2/router';

import {AppComponent} from './components/AppComponent';

@Component({
  moduleId: module.id,
  selector: 'app',
  encapsulation: ViewEncapsulation.Emulated,
  template: `
    <h1>This is Root Component</h1>
    <router-outlet></router-outlet>
  `,
  directives: [
    CORE_DIRECTIVES,
    ROUTER_DIRECTIVES
  ]
})
@RouteConfig([
  { path: '/', name: 'AppComponent', component: AppComponent }
])
export class App {

  router: Router;
  location: Location;

  constructor(router: Router, location: Location) {
    console.log('app.ts');
    this.router = router;
    this.location = location;
  }
}

/client/components以下に、AppComponent.tsを作成します。

import {ViewEncapsulation, Component} from 'angular2/core';

@Component({
    template: '<h1>This is AppComponent</h1>'
})
export class AppComponent {
  constructor() {
    console.log('AppComponent.ts')
  }
}

npm startでjavascriptが生成されることを確認します。

$ npm start

> sample-client@1.0.0 start /Users/tannai.yuki/dev/sample
> webpack -w

ts-loader: Using typescript@1.7.5 and /Users/tannai.yuki/dev/sample/tsconfig.json
Hash: f7e52fb9698e7e970aaa
Version: webpack 1.12.11
Time: 5065ms
        Asset     Size  Chunks             Chunk Names
    bundle.js  3.92 kB       0  [emitted]  app
    vendor.js   766 kB       1  [emitted]  vendor
    common.js  4.52 MB       2  [emitted]  common
bundle.js.map  3.61 kB       0  [emitted]  app
vendor.js.map   889 kB       1  [emitted]  vendor
common.js.map  4.81 MB       2  [emitted]  common
    + 510 hidden modules

Railsの設定

エントリポイントになるHomeControllerを作成します。

$ bundle exec rails g controller HomeController index

/app/views/layouts/application.html.erbに、ルートコンポーネントで指定したセレクタを追加します。

<!DOCTYPE html>
<html>
  <head>
    <title>Sample</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <%= csrf_meta_tags %>
    <%= action_cable_meta_tag %>
    <%= stylesheet_link_tag    'application', media: 'all' %>
    <%= javascript_include_tag 'application' %>
  </head>
  <body>
    <app>Loading...</app>     <!-- この行です -->
    <%= yield %>
  </body>
</html>

app/assets/javascripts/application.jsで生成されるjavascriptの読み込み順を設定します。 今回の設定ではcommon、vendor、bundleの3つが生成されますが、この読み込み順によってはエラーが発生してしまうことへの対処です。

//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require generated/common
//= require generated/vendor
//= require generated/bundle
//= require_tree .

表示の確認

rails sでアプリを起動してlocalhost:3000が以下のようになっていれば成功です!

スクリーンショット 2016-01-22 9.16.03

このとき、javascript consoleには以下のように表示されます。

スクリーンショット 2016-01-22 9.34.59

まとめ

Rails5でAngular2を動かす設定をご紹介しました。 ActionCableとAngular2の組み合わせはとても強力だと思うので、今後も継続して追っていきたいです。

参考URL