日頃の行い

個人的な日頃の行いをつらつら書いてます\\\\ ٩( 'ω' )و ////

ScalaからBigQueryのAPI叩いてみた時の備忘録

ScalaでBigQuery叩いてた時の備忘録です。
使ったレポジトリはここ

github.com


この後の話の流れ

  1. 準備 (認証)
  2. クエリを叩く
  3. ハマったこと

1. 準備

BigQueryを外から叩くためにとりあえず認証が必要です。
今回はサービスアカウントを利用して叩いてみました。
認証周りはGoogleのコンソールから準備します。
プロジェクトは事前に作ってる前提で、ここではMySampleBQProjectというプロジェクトを作って遊んでます
下記ページの右上にConsoleとあると思うので、そこから入れます。
BigQuery | Google Cloud Platform — Google Cloud Platform

入ってメニューを開くとAPI Managerという欄があるので、そこで認証用の設定が出来ます。
方法は下のほうの画像を参考に。


f:id:arata3da4:20151109222519p:plain

f:id:arata3da4:20151109222524p:plain

「作成」ボタンを押すと、 {プロジェクト名}-{証明書フィンガープリント}.p12 がダウンロードされます。
その後「認証情報」のページに戻るとサービスアカウントとして {なんかの番号}-{長い文字列}@developer.gserviceaccount.comができていると思います。
このダウンロードしたp12ファイルとサービスアカウントを利用します。

2. クエリを叩く

SELECT *  FROM [publicdata:samples.wikipedia] limit 10

というふっつうのクエリをScalaから叩いてみました。
利用させていただいたライブラリはこちら

github.com

  • build.sbt
libraryDependencies += "com.github.seratch" %% "bigquery4s" % "0.3"
package vu

import bigquery4s.BigQuery

object BigQueryDemo {

  val bq = BigQuery.fromServiceAccount(
    "{なんかの番号}-{長い文字列}@developer.gserviceaccount.com", // サービスアカウント
    "/path/to/{プロジェクト名}-{証明書フィンガープリント}.p12" // 鍵ファイルへのPATH 
  )

  val projectId = "mysamplebqproject" // プロジェクト名

  def main(args: Array[String]) {
    val sql = "SELECT *  FROM [publicdata:samples.wikipedia] limit 10"
    val jobId = bq.startQuery(projectId, sql)
    val result = bq.await(jobId)
    bq.getRows(result).foreach(println)
  }
}

これを叩くとこんな感じになります。

> run
[warn] Multiple main classes detected.  Run 'show discoveredMainClasses' to see the list

Multiple main classes detected, select one to run:

 [1] vu.BigQueryDemo
 [2] vu.BigQueryDemo2

Enter number: 1

[info] Running vu.BigQueryDemo
11 09, 2015 10:42:12 午後 com.google.api.client.googleapis.services.AbstractGoogleClient <init>
警告: Application name is not set. Call Builder#setApplicationName.
WrappedTableRow({"f":[{"v":"User talk:Sonicrazy"},{"v":"1303993"},{"v":""},{"v":"3"},{"v":null},{"v":"59222308"},{"v":null},{"v":"1063944"},{"v":"DJ BatWave"},{"v":"1150604342"},{"v":null},{"v":null},{"v":null},{"v":"hey"},{"v":"4442"}]})
WrappedTableRow({"f":[{"v":"Sit Down, Shut Up (U.S. TV series)"},{"v":"17446266"},{"v":""},{"v":"0"},{"v":null},{"v":"278080927"},{"v":null},{"v":"1862829"},{"v":"Magioladitis"},{"v":"1237376427"},{"v":"true"},{"v":null},{"v":null},{"v":"new television template"},{"v":"7042"}]})
WrappedTableRow({"f":[{"v":"Talk:Homestead, Florida"},{"v":"2387164"},{"v":""},{"v":"1"},{"v":null},{"v":"20382857"},{"v":null},{"v":"28438"},{"v":"Altenmann"},{"v":"1123295494"},{"v":null},{"v":null},{"v":null},{"v":"/* Page protection */"},{"v":"10354"}]})
WrappedTableRow({"f":[{"v":"Big Brother 9 (U.S.)"},{"v":"14549699"},{"v":""},{"v":"0"},{"v":null},{"v":"193263045"},{"v":"71.225.199.168"},{"v":null},{"v":null},{"v":"1203685970"},{"v":null},{"v":null},{"v":null},{"v":"[[WP:AES|←]]Replaced page with 'Yauman CAhan'"},{"v":"12"}]})
WrappedTableRow({"f":[{"v":"Talk:Pashtun people"},{"v":"273006"},{"v":""},{"v":"1"},{"v":null},{"v":"9212968"},{"v":null},{"v":"28107"},{"v":"Jmabel"},{"v":"1105225046"},{"v":null},{"v":null},{"v":null},{"v":"/* jambel please give reference to your text */ Lost Tribes of Israel"},{"v":"57553"}]})
WrappedTableRow({"f":[{"v":"Sprint Nextel"},{"v":"2380393"},{"v":""},{"v":"0"},{"v":null},{"v":"20820941"},{"v":"69.134.50.153"},{"v":null},{"v":null},{"v":"1123811750"},{"v":null},{"v":null},{"v":null},{"v":null},{"v":"5696"}]})
WrappedTableRow({"f":[{"v":"User:TheBigJagielka/Samuel Galindo"},{"v":"25455636"},{"v":""},{"v":"2"},{"v":null},{"v":"337561555"},{"v":"203.112.84.138"},{"v":null},{"v":null},{"v":"1263372806"},{"v":null},{"v":null},{"v":null},{"v":null},{"v":"2291"}]})
WrappedTableRow({"f":[{"v":"User talk:129.12.200.49"},{"v":"1211489"},{"v":""},{"v":"3"},{"v":null},{"v":"111561147"},{"v":null},{"v":"3315117"},{"v":"Qxz"},{"v":"1172665180"},{"v":"true"},{"v":null},{"v":null},{"v":"Level 1 warning re. [[Apollo]]"},{"v":"19031"}]})
WrappedTableRow({"f":[{"v":"Bhutan"},{"v":"2421391"},{"v":""},{"v":"0"},{"v":null},{"v":"212949166"},{"v":null},{"v":"96159"},{"v":"Riyehn"},{"v":"1210984268"},{"v":null},{"v":null},{"v":null},{"v":"fixed per capita income in economy"},{"v":"49647"}]})
WrappedTableRow({"f":[{"v":"Messiah"},{"v":"19616"},{"v":""},{"v":"0"},{"v":null},{"v":"188055849"},{"v":null},{"v":"1796696"},{"v":"Noobeditor"},{"v":"1201743987"},{"v":"true"},{"v":null},{"v":null},{"v":"Reverted edits by [[Special:Contributions/162.83.159.167|162.83.159.167]] to last version by Steven J. Anderson"},{"v":"26802"}]})
[success] Total time: 4 s, completed 2015/11/09 22:42:14

やったねたえちゃんプログラムから動かせたよ。
たかがSELECT *なのになんか楽しいですね!そんな気持ちを忘れちゃいけないんだと思います。

3.ハマったこと

強くハマったわけではないんですけど、TIMESTAMP型を扱うときにうーんってなったのでメモ。
joda.time.DateTime型に変換しようとしたらちょっとめんどかったという話です。
これ書こうと思ってpublicdata見たらTIMESTAMP型なかったので generatedata.com で用意しました。
テーブルfooとか雑過ぎですね。

スキーマはこんな感じ。

f:id:arata3da4:20151109224443p:plain

  • build.sbt
libraryDependencies += "com.github.seratch" %% "bigquery4s" % "0.3"

libraryDependencies += "joda-time" % "joda-time" % "2.9"
package vu

import bigquery4s.BigQuery
import org.joda.time.DateTime

object BigQueryDemo2 {
  val bq = BigQuery.fromServiceAccount(
//略
  )

  val projectId = "mysamplebqproject"

  def main(args: Array[String]) {
    val sql = "SELECT created FROM [dummy.foo] LIMIT 10"
    val jobId = bq.startQuery(projectId, sql)
    val result = bq.await(jobId)
    bq.getRows(result).foreach(r => println(r.cells.head.value.getOrElse("")))
  }

}

これを実行すると

> run

 [1] vu.BigQueryDemo
 [2] vu.BigQueryDemo2

Enter number: 2

[info] Running vu.BigQueryDemo2 
11 09, 2015 11:02:12 午後 com.google.api.client.googleapis.services.AbstractGoogleClient <init>
警告: Application name is not set. Call Builder#setApplicationName.
1.436453925E9
1.433881786E9
1.448222214E9
1.463913534E9
1.419150889E9
1.430968225E9
1.436388839E9
1.465884312E9
1.450501949E9
1.440506521E9

となり、指数表記になり、数字として扱うならDouble型で受け取って、toFloatとかしてDateTimeに変換とかまどろっこしいことをしなければいけません。
それ以外無いのかなぁって思って探したらBQに関数があるのでそれを利用出来るみたいです。

Query Reference - BigQuery — Google Cloud Platform

FORMAT_UTC_USEC とか TIMESTAMP_TO_MSECとか使えば良さそうな感じが出てます。

package vu

import bigquery4s.BigQuery
import org.joda.time.DateTime

object BigQueryDemo3 {
  val bq = BigQuery.fromServiceAccount(
// 略
  )

  val projectId = "mysamplebqproject"

  def main(args: Array[String]) {
    val sql = "SELECT FORMAT_UTC_USEC(created) as a, TIMESTAMP_TO_MSEC(created) as b FROM [dummy.foo] LIMIT 10"
    val jobId = bq.startQuery(projectId, sql)
    val result = bq.await(jobId)
    bq.getRows(result).foreach(r => println(r.cells.head.value.getOrElse("")))
    bq.getRows(result).foreach(r => println(r.cells(1).value.getOrElse("").toString.toLong))
  }

}

これを実行すると

> run

 [1] vu.BigQueryDemo
 [2] vu.BigQueryDemo2
 [3] vu.BigQueryDemo3

Enter number: 3

[info] Running vu.BigQueryDemo3 
11 09, 2015 11:13:53 午後 com.google.api.client.googleapis.services.AbstractGoogleClient <init>
警告: Application name is not set. Call Builder#setApplicationName.
2015-07-09 14:58:45.000000
2015-06-09 20:29:46.000000
2015-11-22 19:56:54.000000
2016-05-22 10:38:54.000000
2014-12-21 08:34:49.000000
2015-05-07 03:10:25.000000
2015-07-08 20:53:59.000000
2016-06-14 06:05:12.000000
2015-12-19 05:12:29.000000
2015-08-25 12:42:01.000000
1436453925000
1433881786000
1448222214000
1463913534000
1419150889000
1430968225000
1436388839000
1465884312000
1450501949000
1440506521000

見慣れた感じになりました。
もっといい方法ありそうだけど、今日は眠いのでとりあえずここまで。
また色々遊ぼうと思います。あと3日で無料枠切れるけど。

embulkのpluginをScalaで作ってみようと思った時の備忘録

Embulkのpluginを書いてみようかなと思ったんですが、せっかくならScalaで書いてみようかなと思ってその時の備忘録です。

1. 概要

Embulkはこんな感じ(雑

github.com


pluginはJavaRubyで書けるようです。
Javaで書けるので、Scalaでも書けますね。

2. 準備

2.1. pluginの作成

色々ためしたレポジトリはこんな感じです。

github.com

とりあえずJavaでpluginを書く準備をします。
利用したEmbulkのversionは 0.7.4です

#embulk コマンドはインストール済み前提です。
$embulk --version
embulk 0.7.4
$embulk new java-input myappwithscala
...
$cd embulk-input-myappwithscala
$tree -L 3
.
├── LICENSE.txt
├── README.md
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── lib
│   └── embulk
│       └── input
└── src
    ├── main
    │   └── java
    └── test
        └── java

2.2. Scalaで書く準備

2つ変更があります。

  1. build.gradleにScala用の設定を追記
  2. PluginのクラスをScalaに変更
2.2.1. build.gradleにScala用の設定を追記
  • pluginsに id"scala" を追記
  • dependenciesに compile "org.scala-lang:scala-library:2.11.6" , testCompile 'org.scalatest:scalatest_2.11:2.2.4', testRuntime 'org.scala-lang.modules:scala-xml_2.11:1.0.3' を追記
  • 新しくtasks.withType(ScalaCompile) { scalaCompileOptions.useAnt = false } を追加

上記以外は初期のままです。

plugins {
    id "com.jfrog.bintray" version "1.1"
    id "com.github.jruby-gradle.base" version "0.1.5"
    id "scala" // scalaを追加 + javaは使わないつもりだったので削除した
}
import com.github.jrubygradle.JRubyExec
repositories {
    mavenCentral()
    jcenter()
}
configurations {
    provided
}

version = "0.1.0"

dependencies {
    compile "org.scala-lang:scala-library:2.11.6" // scala用に追加
    compile  "org.embulk:embulk-core:0.7.4"
    provided "org.embulk:embulk-core:0.7.4"
    // compile "YOUR_JAR_DEPENDENCY_GROUP:YOUR_JAR_DEPENDENCY_MODULE:YOUR_JAR_DEPENDENCY_VERSION"
    testCompile "junit:junit:4.+"
    testCompile 'org.scalatest:scalatest_2.11:2.2.4' // 同じくscalaように追加
    testRuntime 'org.scala-lang.modules:scala-xml_2.11:1.0.3' // 同じように(ry
}

// compile時に差分コンパイルにしてもらうための設定とかなんとか
// Activating the Zinc based compiler
tasks.withType(ScalaCompile) {
    scalaCompileOptions.useAnt = false
}

task classpath(type: Copy, dependsOn: ["jar"]) {
    doFirst { file("classpath").deleteDir() }
    from (configurations.runtime - configurations.provided + files(jar.archivePath))
    into "classpath"
}
clean { delete "classpath" }

task gem(type: JRubyExec, dependsOn: ["gemspec", "classpath"]) {
    jrubyArgs "-rrubygems/gem_runner", "-eGem::GemRunner.new.run(ARGV)", "build"
    script "${project.name}.gemspec"
    doLast { ant.move(file: "${project.name}-${project.version}.gem", todir: "pkg") }
}

task gemPush(type: JRubyExec, dependsOn: ["gem"]) {
    jrubyArgs "-rrubygems/gem_runner", "-eGem::GemRunner.new.run(ARGV)", "push"
    script "pkg/${project.name}-${project.version}.gem"
}

task "package"(dependsOn: ["gemspec", "classpath"]) << {
    println "> Build succeeded."
    println "> You can run embulk with '-L ${file(".").absolutePath}' argument."
}

task gemspec {
    ext.gemspecFile = file("${project.name}.gemspec")
    inputs.file "build.gradle"
    outputs.file gemspecFile
    doLast { gemspecFile.write($/
Gem::Specification.new do |spec|
  spec.name          = "${project.name}"
  spec.version       = "${project.version}"
  spec.authors       = ["Arata Tanaka"]
  spec.summary       = %[Myappwithscala input plugin for Embulk]
  spec.description   = %[Loads records from Myappwithscala.]
  spec.email         = ["tarata43@yahoo.co.jp"]
  spec.licenses      = ["MIT"]
  # TODO set this: spec.homepage      = "https://github.com/tarata43/embulk-input-myappwithscala"

  spec.files         = `git ls-files`.split("\n") + Dir["classpath/*.jar"]
  spec.test_files    = spec.files.grep(%r"^(test|spec)/")
  spec.require_paths = ["lib"]

  #spec.add_dependency 'YOUR_GEM_DEPENDENCY', ['~> YOUR_GEM_DEPENDENCY_VERSION']
  spec.add_development_dependency 'bundler', ['~> 1.0']
  spec.add_development_dependency 'rake', ['>= 10.0']
end
/$)
    }
}
clean { delete "${project.name}.gemspec" }
2.2.2. PluginのクラスをScalaに変更

そのままScala用に変えました。

Tips

  1. JavaのintはScalaのInt型に
  2. Listはそのままjava.util.Listに
  3. アホな事に拡張子が.scalaになっていないとgradlwでコンパイルしてくれないことにすごいハマったので注意です(´・ω・`)
package org.embulk.input.myappwithscala

import com.google.common.base.Optional
import org.embulk.config._
import org.embulk.spi._

class MyappwithscalaInputPlugin extends InputPlugin {
  trait PluginTask extends Task {
    // configuration option 1 (required integer)
    @Config("option1")
    def getOption1:Int

    // configuration option 2 (optional string, null is not allowed)
    @Config("optoin2")
    @ConfigDefault("\"myvalue\"")
    def getOption2:String

    // configuration option 3 (optional string, null is allowed)
    @Config("optoin3")
    @ConfigDefault("null")
    def getOption3:Optional[String]

    // if you get schema from config
    @Config("columns")
    def getColumns:SchemaConfig
  }

  override def transaction(config:ConfigSource, control:InputPlugin.Control):ConfigDiff = {
    val task:PluginTask = config.loadConfig(classOf[PluginTask])

    val schema = task.getColumns.toSchema
    val taskCount = 1 // number of run() method calls

    resume(task.dump(), schema, taskCount, control)
  }

  override def resume(
                       taskSource:TaskSource,
                       schema:Schema,
                       taskCount:Int,
                       control: InputPlugin.Control
                       ): ConfigDiff = {
    control.run(taskSource, schema, taskCount)
    Exec.newConfigDiff()
  }

  override def cleanup(
                        taskSource:TaskSource,
                        schema:Schema,
                        taskCount:Int,
                        successTaskReports:java.util.List[TaskReport]): Unit = ()

  @Override
  override def run(
                    taskSource:TaskSource,
                    schema:Schema,
                    taskIndex:Int,
                    output:PageOutput
                    ):TaskReport = {
    val task:PluginTask = taskSource.loadTask(classOf[PluginTask])
    // Write your code here :)
    println(schema.toString)
//    Exec.newCommitReport()
    throw new UnsupportedOperationException("MyappwithscalaInputPlugin.run method is not implemented yet")
  }

  override def guess(config:ConfigSource):ConfigDiff = {
    Exec.newConfigDiff()
  }
}

3. なんとなく動かしてみる

このまま動かしても throw new UnsupportedOperationException("MyappwithscalaInputPlugin.run method is not implemented yet") が記述されてるので例外が出ますが、今回具体的な実装はしていないので、その例外が発生することがゴールということにしようと思っています。

configファイルは下記のような感じです

in:
    type: myappwithscala
    option1: 1
    option2: example2
    columns: []
out:
    type: stdout

この状態でテスト的に動かしてみようと思います。

$./gradlew classpath
:compileJava UP-TO-DATE
:compileScala UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:jar UP-TO-DATE
:classpath UP-TO-DATE

BUILD SUCCESSFUL

Total time: 17.26 secs

This build could be faster, please consider using the Gradle Daemon: https://docs.gradle.org/2.6/userguide/gradle_daemon.html
$embulk run -I lib ./example.yml
2015-09-21 21:58:08.735 +0900: Embulk v0.7.4
2015-09-21 21:58:13.352 +0900 [INFO] (transaction): Loaded plugin embulk/input/myappwithscala from a load path
2015-09-21 21:58:13.511 +0900 [INFO] (transaction): {done:  0 / 1, running: 0}
Schema{
}
2015-09-21 21:58:13.774 +0900 [INFO] (transaction): {done:  1 / 1, running: 0}
org.embulk.exec.PartialExecutionException: java.lang.UnsupportedOperationException: MyappwithscalaInputPlugin.run method is not implemented yet
# 中略
Error: java.lang.UnsupportedOperationException: MyappwithscalaInputPlugin.run method is not implemented yet

エラーが出たようです。
消し忘れてたprintlnも表示されてますね
こんな感じでScalaで一応書けそうです。
そのうちplugin公開してみようかなと思います。

(だいたい)新卒エンジニア向け技術交流会vol.4 を実施して話したという話

9/19(土)にこんなイベントがありました。

ngineerxiv.doorkeeper.jp

今回シルバーウィーク前だし、そんなに人来ないかもしれないから発表するって言っておこうって思ったら結構発表者多くなってびっくりでしたw
今回はScala.jsがネタにあふれて面白そうだったので、使ってみた感じの話をしました。

■自分の発表

資料はこんな感じです。

www.slideshare.net

やったのはVue.jsのExampleにもあるTODOアプリ(?)をScala.jsで書いてみた感じのことです。
レポジトリはこちら

github.com

そして、感想にも書いたのですが、Dom周りと密接に関わるライブラリを使ってScala.jsでJavaScriptを書くのはDynamicを利用する部分が多く、Scalaの良さを活かしきれない感じがしました。
あとコンパイルの速度の話はしませんでした。

■他の方の発表

@petitviolet 氏による 「初めての関数型プログラミング

speakerdeck.com

名前初め読めませんでしたが、覚えました←w
もなどについてよくわかってないのですが、理解が少し深まった気がします。

@__papix__ 氏による「キラキラ技術に群がる俺達お前達に物申す」

資料は見当たらなかったのですが、タイトルから変更して、YAPC::EUに発表者として参加してきた時の話をしてくれました。
スペインのエレベータは閉じるボタンが無く、閉じるだと思って押すとブザーがなるというつらいインターフェースの話が心に残ってますw
インターフェース大事ですね

YAPC::Europe 2015


@YAMITZKY 氏による「Alfred Workflowで業務効率化してみた」

Alfredの無料のものを使っている、コミットメントが足りない我々に対する熱いメッセージが伝わりましたww
ライセンスを買った有料版だと色々カスタマイズしたスクリプトをターミナルから起動したり出来るみたいですね。
興味はわいたのでお金に余裕があるときに買ってみてもいいかなぁ・・・って思って・・・ます。

www.alfredapp.com

■次回予告とか

一度参加していただけた方をSlackチームに招待して色々他のイベントとかもやってたりします
今度は中で結成したチームでIsuconに出るつもりです。
(チーム名は「Dark」です)
このイベント自体はまた3ヶ月後の12月にやろうと思います。
気になる方々は是非参加してください〜

Dark - Developers at Real Kommunity | Doorkeeper

Vue.jsに入門しようと思ってScala.jsでVue.jsのサンプル書いてみた。

会社のインターンでVue.jsを利用するんですが、去年も触ったけど、やっぱりちゃんとサンプルを動かしてみないといけないなと思って写経してみました。
サンプルはこちらのページを利用しました。
日本語ページ去年はなかった気がしますが、今は日本語で読めてとても楽です。

jp.vuejs.org

ところで、最近altJSというのが流行ってますね。
TypeScriptやCoffeeScript色々ありますね。
せっかくなので今回はScala.jsを使いました。

Scala.jsとは -> http://www.scala-js.org/

やっぱScalaは型があっていいですよね。
コンパイル時にコーヒー飲めるしすばらしい。

概要

この後の流れ
1. Vue.jsをScala.jsで使うための準備
2. マークダウンエディタサンプル写経
3. マークダウンエディタサンプルを動かしてみる

利用したライブラリたち

  • Scala 2.11.5
  • Vue.js 0.12.9
  • Scalajs 0.6.4

1. Vue.jsをScala.jsで使うための準備

Scala.jsにjQueryのクラスなどは用意されていますが、それ以外は自前でインターフェイスを定義してあげる必要があります。
どうやればいいんだろうと思ってググっていたらこちらにたどり着きました。takezoe.hatenablog.com

Aceライブラリのインターフェイスの定義

scalajs-ace/Ace.scala at master · scalawarrior/scalajs-ace · GitHub

自前とかむりぽ
そこでscala-js-ts-importerです。
これはTypeScriptの型定義を元にScalaのコードに変換してくれるライブラリです。

github.com

Vue.jsの型定義ファイルはこちらにまとまっていたものを利用させていただきました。

github.com

$cd /path/to/scala-js-ts-importer
$cp /path/to/DefinitelyTyped/path/to/vue.d.ts ./
$sbt 'run vue.d.ts Vue.scala'

これでVue.scalaができ・・・ない!

$sbt 'run vue.d.ts Vue.scala'
[info] Loading project definition from /Users/a-tanaka/.ghq/github.com/sjrd/scala-js-ts-importer/project
[info] Set current project to TypeScript importer for Scala.js (in build file:/Users/a-tanaka/.ghq/github.com/sjrd/scala-js-ts-importer/)
[info] Running org.scalajs.tools.tsimporter.Main vue.d.ts Vue.scala
Parse error at 166.1
``interface'' expected but 'import' found
import Vue = vuejs.Vue;

^
java.lang.RuntimeException: Nonzero exit code: 2
	at scala.sys.package$.error(package.scala:27)
[trace] Stack trace suppressed: run last compile:run for the full output.
[error] (compile:run) Nonzero exit code: 2
[error] Total time: 3 s, completed 2015/08/07 20:50:31

# importがダメなようです
# とりあえず消して動かすことにしました
# これによってビルドされるVue.scalaのJSNameアノテーションも少し修正しないといけなくなります。

$vim vue.d.ts
# importが2つあったので削除
$sbt 'run vue.d.ts Vue.scala'
[info] Loading project definition from /Users/a-tanaka/.ghq/github.com/sjrd/scala-js-ts-importer/project
[info] Set current project to TypeScript importer for Scala.js (in build file:/Users/a-tanaka/.ghq/github.com/sjrd/scala-js-ts-importer/)
[info] Running org.scalajs.tools.tsimporter.Main vue.d.ts Vue.scala
[success] Total time: 1 s, completed 2015/08/07 20:51:06

これでできたVue.scalaを利用すればVue.jsをScalaでかけます!!!!111

2. マークダウンエディタのサンプル写経

写経してみた結果が下記の通りです。

tips
  • jsのobjectを定義するには scala.scalajs.js.Dynamic.literalを利用すると良いみたいです。
  • また、ここの markedは他ライブラリのmarked.jsのグローバル変数になるのですが、marked.jsのインターフェイス定義をまたscala-js-ts-importerで行うのはめんどうなのでここではそのままグローバル変数を入れるようにしています。
  • jsのグローバル変数を利用するためにはscala.scalajs.Dynamic.globalを利用すると良いみたいです。
package vu

import scala.scalajs.js.Dynamic.{global => g}
import scala.scalajs.js
import scala.scalajs.js.JSApp
import scala.scalajs.js.annotation.JSExport
import scala.scalajs.js.Any._
import scala.scalajs.vuejs.Vue

object VueSample extends JSApp {

  @JSExport
  def main():Unit = {
    val option = js.Dynamic.literal(
      "el" -> "#editor",
      "data" -> js.Dynamic.literal(
        "input" -> "# hello"
      ),
      "filters" -> js.Dynamic.literal(
        "marked" -> g.marked
      )
    )

    val app = new Vue(option)
  }
}

htmlはサンプルと全く同じです

<html>
    <head>
        <script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/marked/0.3.2/marked.min.js"></script>
        <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vue/0.12.9/vue.js"></script>
        <script type="text/javascript" src="../target/scala-2.11/scala-js-tutorial-fastopt.js"></script>
        <script type="text/javascript" src="./app.js" async></script>
        <style>
            html, body, #editor {
                margin: 0;
                height: 100%;
                font-family: 'Helvetica Neue', Arial, sans-serif;
                color: #333;
            }

            textarea, #editor div {
                display: inline-block;
                width: 49%;
                height: 100%;
                vertical-align: top;
                -webkit-box-sizing: border-box;
                -moz-box-sizing: border-box;
                box-sizing: border-box;
                padding: 0 20px;
            }

            textarea {
                border: none;
                border-right: 1px solid #ccc;
                resize: none;
                outline: none;
                background-color: #f6f6f6;
                font-size: 14px;
                font-family: 'Monaco', courier, monospace;
                padding: 20px;
            }

            code {
                color: #f66;
            }
        </style>
    </head>
    <body>
        <div id="editor">
            <textarea v-model="input" debounce="300"></textarea>
            <div v-html="input | marked"></div>
        </div>
    </body>
</html>

app.js

(function() {
    vu.VueSample().main()
})();

ここまで書いたコードを置いたレポジトリがこちらです。

github.com

動かしたい場合は

$git clone git@github.com:ara-ta3/scalajs-vuejs-getting-started.git
$cd scalajs-vuejs-getting-started
$sbt fastOptJS
# ↓はMacのみです。単にresources/index.htmlをブラウザで開けば問題無いです。
$open resources/index.html

また、Vue.scalaはJSNameアノテーションの部分を少し変えています。

// @JSName("vuejs.Vue")
@JSName("Vue")
class Vue protected () extends js.Object {
...

こうしないとブラウザ(chrome)で怒られてしまったので、変更しました。
さて、動かして見た結果がこちらです。(jsで書いた時と何も変わらないですw)

f:id:arata3da4:20150807214153p:plain

これでVue.jsもScalaでかけます!
JavascriptもサーバサイドもScalaで・・・!
あとはCSSだけですね!

github.com