東京Node学園祭 2015 で Electron について話した

f:id:rhysd:20151110121341p:plain,h200

画像は公式サイトから

発表について

11/7 にあった 東京 Node 学園祭 2015 で Electron の発表した.

周りすごい人達ばかりで,明らかに今年の6月ぐらいに初めた自分よりも,JavaScript も Node.js もフロントエンドも書ける人達に囲まれての発表だったので,正直どうしようかなと思ったけれど,自分が Electron で Web フロントエンド&Node.js に入門することになった経緯と,その過程で得た知見を共有する感じにしようとなってこんな感じの資料になった.

色々詰め込もうとして25分に対して42ページになったものの,一応発表練習しておいたのもあって,時間通り喋れてよかった.ただ,スライド内で紹介した僕が書いたアプリのコードはまだかなり汚くて設計もいけてないと思うので,awesome-electron にある他のアプリのほうが参考になるかもしれない…

本当は <webview> についても少し載せようとしたけど,時間がなかったのでここで追記します.

<webview> はレンダラプロセス上でさらに別のプロセスを生成し別権限で WebContents を描画できるタグで,レンダリング元とのやり取りは ipc 経由になる.でも,外部ページを表示する場合は node integration を有効にすると jQuery がロードできなくなったり,セキュリティ的にも完全にアウトなので node integration は無効にする.それだと require('ipc') できなくなるので困る!となるけれど,実は <webview> には preload というプロパティ があって,それが解決してくれる.node integration を無効にしていても preload で読み込まれたスクリプト内では node の API が使えるらしい.よって大雑把には下記のような順でスクリプトが読まれる.

  1. preload で指定したスクリプトが読まれる(require() など使用可)
  2. node 関連の API が削除される
  3. <webview> のコンテンツがロードされる

よって,レンダリング元と ipc で通信しつつ node integration をコンテンツ内では無効にできる.

また,懇親会でいくつか質問をいただいたので,それについてもここにメモしておきます.

  • メニューは OS によって微妙に異なるが,その差異はどうするのか

例えば OS X ではメニューアイテムの一番最初にアプリ名の項目があって,WindowsLinux には無い.Electron にはこれをサポートする仕組みは特に無いので,テンプレートを出し分けるしかない.

import {buildFromTemplate, setApplicationMenu} from 'menu';

const osx_template = {...};
const other_template = {...};

const template = process.platform === 'darwin' ? osx_template : other_template;

const menu = buildFromTemplate(template);
setApplicationMenu(menu);
  • アプリのアイコンはどうしてるのか

他の発表の感想

The State of JavaScript

僕の知らない JavaScript がいっぱい出てきた.個人的には WebAssembly が気になっていて,LLVM IR を吐くコンパイラフロントエンドがブラウザで動くコードを吐けるようになったりするのかなと思うと楽しみ.

技術文書をソフトウェア開発する話

文書を正しくソフトウェアエンジニアリングしていてすごい.コード片とかは裏ではテストできる形でフルで持っておいて,書籍には一部だけ載せるみたいなことができると良いなぁと思った.用語の統一とか口調の統一とかは複数人で書くとかなりブレが出てしまうみたいなので,ツール使って CI 回すのはかなり効果ありそう.

"npm":">=3"

npm3 は node_modules 以下がフラットになるのとインストール状況が表示されるぐらいしか知らなかったので色々参考になった.shrinkwrap も知らなかった… npm のロードマップ も面白そうなことがいっぱい書いてあって,特にフロントエンドモジュールが気になる…

Electroknit! - Pixel to sweater with Node.js

編み物…?と思ったけれど,正しく Node.js で編み物していた. 画像処理とか問題を表層的にでなくちゃんと中身まで理解して進めていて,(本人は色々謙遜されていたけれど)正しくエンジニアリングして進めていてすごい.

ESDoc - ES6時代のドキュメンテーションツール -

とりあえずまずは手元の小さいモジュールにドキュメントつけてみるぞ!と思ったら,今まで書いた node モジュールは全部 TypeScript 製だった… TypeScript の型定義ファイル読み込んで反映出来たり,assertion の条件を反映出来たりするとコードで書かれた仕様がそのままドキュメントに落ちてきて良さそうだなと思った.

フロントエンドに秩序を取り戻す方法 〜はてなブログ編集画面をリニューアルするためにやったこと〜

すごくパワフルな発表だった.継続的に改善し続けるしかなくて体力が要ると思うし,ひたすらやっていく💪しかない.あと,フロントエンド界隈はやっぱり捨てられるかどうかが大きいんだなぁと思って聞いていた.

懇親会

知り合いと話したり,知り合いの知り合いの方と話したり,発表を聞いていただいた方に声をかけてもらって話したりしていた.特にこの界隈に顔なじみの人もあまりいないので,声をかけてもらえるのはありがたかった.:sushi: :pizza: :beer: 最高だった.

全体の感想

Electron や Node.js を触り始めてぼちぼち 東京 Node 学園とか React meetup とかに出かけて行って感じていたけれど,やっぱり JavaScript 界隈は熱量がすごいなと思った.勉強会はどこも大盛況だし,スポンサーも IBM とか Microsoft はじめでかいところとか有名なところがついてる.その分流れてくる情報量もすごく多いので,ガンガン吸収していきたい.

運営の方々,素晴らしいイベントをありがとうございました.

あと,「ウェブの人になるんですか?」と最近たまに聞かれますが,今までどおり C++Vim もやっていく気持ちです.

Crystal 言語で CLI ツールを書いてみる

f:id:rhysd:20151005225303p:plain

Crystal はつくりかけの言語です.この記事は 0.8.0 を元に書きましたが,今後かなり変更されることが予想されます

CrystalRuby に強くインスパイアされたコンパイル言語です.結構前から気になっていて,Crystal のコンパイラRuby で書かれていた頃(今はセルフホストしています)から見ていて最近はぼちぼちコントリビュートしたりしてます.インスパイアされているというだけで,色々 Ruby と違うところもあります(例えばいま話題の文字列は Crystal では全て immutable です)

僕は Ruby が好きなので,ちょっとしたツールや書き捨てのスクリプトは特に理由が無い場合は Ruby で書いています.それもあって Crystal でちょっとしたツールを書くのは次のような利点があります.

  • Ruby と同じように楽に(そして雑に)書ける
  • Ruby とは違い実行速度が速い(まだあまり実感してないですが)
  • Ruby とは違いコンパイル時に型チェックが入る(各式が取り得る型の union type で型付けします)
  • Ruby とは違いバイナリファイル1つになる(共有ライブラリなどを使わなければ)

コマンドラインツールCrisp (Crystal で書かれた Lisp 処理系)や crdocコマンドラインから Crystal のドキュメントを検索&開くツール)をつくってみたので,今回は自分なりのやり方の手順をまとめてみます.

といっても大体 Ruby と同じですが…

準備

Go 言語における go コマンドのように,Crystal にも crystal コマンドがあります. 公式のインストールページ に従ってインストールします.残念ながらまだ Windows では利用できません(対応はぼちぼち進んでいるようです).

次に開発環境を入れます.お好みに合わせて導入してください.(もし需要があれば開発ツール周りもブログに書きたい.)

リポジトリ生成

試しにファイルの中身を表示するだけのコマンド mycat をつくってみます.

まずは crystal init コマンドで初期のリポジトリを生成します.

$ crystal init app mycat

次のようなディレクトリ構成が自動でつくられます.

--- mycat
     |--- .gitignore
     |--- LICENSE
     |--- README.md
     |--- .travis.yml
     |--- shard.yml
     |--- src
     |     |--- mycat.cr
     |     |--- mycat/version.cr
     |
     |--- spec
     |     |--- spec_helper.cr
     |     |--- mycat_spec.cr
     |
     |--- .gitignore

僕はコマンドラインツールを書く時はなるべくライブラリとしても使えるようにつくるので,src/mycat.crAPI だけ書いて,bin/mycat.cr にコマンド実行処理を書きます.そのほうがテストもしやすいです.

最終的にはこんな感じです.

--- mycat
     |--- .gitignore
     |--- LICENSE
     |--- README.md
     |--- .travis.yml
     |--- shard.yml
     |--- bin
     |     |--- mycat.cr
     |
     |--- src
     |     |--- mycat.cr
     |     |--- mycat/version.cr
     |
     |--- spec
     |     |--- spec_helper.cr
     |     |--- mycat_spec.cr
     |
     |--- .gitignore

ドキュメントを書く

最初の時点で脳内で決まっている範囲で簡単なドキュメント(intro,コマンドの引数など)を README.md に書きます.生成された README.mdスニペットになっているので,GitHub のユーザ名などを自分で埋めます.

また,(なぜか)LICENSE が MIT で決め打ちになっているので,必要があれば修正します.

shard.yml を書く

npm の package.json と同じように,パッケージ情報を shard.yml に書きます.これは半標準(いずれ本体に取り込まれる?)のパッケージマネージャ shards を使う前提で生成されています.

name: mycat
version: 0.0.1

authors:
  - 名前 <mail@address>

license: MIT

依存するパッケージが出てきた時はここに書きます.

スペックを書く

まずは mycat ディレクトリでおもむろに crystal spec と実行してみます.

次のように失敗すると思います.

F

Failures:

  1) Mycat works
     Failure/Error: false.should eq(true)

       expected: true
            got: false

     # ./spec/mycat_spec.cr:7

Finished in 0.55 milliseconds
1 examples, 1 failures, 0 errors, 0 pending

Failed examples:

crystal spec ./spec/mycat_spec.cr:6 # Mycat works

それも当然です.spec/mycat_spec.cr は次のようになっています.

require "./spec_helper"

describe Mycat do
  # TODO: Write tests

  it "works" do
    false.should eq(true)
  end
end

ここで実装を考えながら必要なメソッドのテストを書きます.ファイル名を受け取って IO に出力に出すメソッド meow を持ったクラス Cat をつくることにするとこんな感じでしょうか.test.txt は spec ファイルと同じディレクトリに適当につくります.__DIR__ はそのファイルの親ディレクトリを表す大域定数(みたいなもの)です.

require "./spec_helper"

describe Mycat do
  describe Mycat::Cat do
    describe "meow" do
        it "writes the content of file to specified IO" do
        io = StringIO.new
        c = Mycat::Cat.new io
        c.meow "#{__DIR__}/test.txt"
        io.to_s.should eq("foo\nbar\nbaz\n")
        end
    end
  end
end

まだ実装が無いのでもちろんこれも失敗します.

実装を書く

require "./mycat/*"

module Mycat
  # TODO Put your code here
end

最初はこうなっていると思います.

require "./mycat/*"

module Mycat
  class Cat
    def initialize(@mouse)
    end

    def meow(file_name)
      File.open file_name do |f|
        @mouse << f.read
      end
    end
  end
end

できました.受け取った IO にファイルの中身を吐くだけなので簡単ですね.コンストラクタの引数のように,直接インスタンス変数名を指定してその変数に代入することができます.(コンストラクタ以外のメソッドでも使えます) 試しにビルドしてみましょう.crystal build コマンドを使います.

$ crystal build src/mycat.cr

問題が無ければ,先ほどと同じ方法で spec を実行します.今度はテストが通ると思います.

コマンド実行処理をつくる

bin/mycat.cr を実装します.

require "../src/mycat"
require "option_parser"

begin
  OptionParser.parse! do |parser|
    parser.banner = "Usage: mycat file_name"
    parser.on("-v", "--version", "Show version") { puts Mycat::VERSION; exit 0 }
    parser.on("-h", "--help", "Show this help") { puts parser; exit 0 }
  end

  cat = Mycat::Cat.new STDOUT
  ARGV.first?.try do |arg|
    cat.meow arg
  end
rescue e
  STDERR.puts e
  exit 1
end

オプションの実装は Ruby でもおなじみの OptionParser が使えます.今回はバージョンとヘルプだけをパースしています.

Enumerable#first? はリストの最初の要素があればそれを返し,無ければ nil を返します.Object#tryRuby ではお馴染みの,nil で無い値で実行すればブロック部分が実行されるメソッドです.例えばここで間違えて cat.meow ARGV.first? など書くとコンパイラエラーになってくれます.(コンパイラCat#meow(f: String) に推論したのに String? な値を渡しているため)

試しに実行してみましょう.crystal コマンドはデフォルトでファイルを直接実行する(実際はコンパイルして一時ファイルを生成しそれを実行する)ので,下記のようにします.crystal コマンドで実行されるプログラム側に引数を与えるには -- オプションの後に置く必要があります.

$ crystal ./bin/mycat.cr -- README.md
$ crystal ./bin/mycat.cr -- -v
$ crystal ./bin/mycat.cr -- --help

正しく動いていることが確認できたらリリースビルドで最適化されたバイナリを作成します.

$ crystal build --release ./bin/mycat.cr

カレントディレクトに mycat というバイナリファイルができていると思うので,それをパスの通ったところに置くなりします.

Travis での CI

デフォルトで .travis.yml が生成されますが,中身はほぼ空っぽです. 次のようにテストスクリプトを書きます. Crystal の CI 環境は Crystal コミュニティによって提供されているので,こちらで頑張って用意する必要はありません.

language: crystal
script:
    - crystal spec
    - crystal build ./bin/mycat.cr

まとめ

プログラミング言語 Crystalコマンドラインツールを書く自分なりの手順をまとめました.まだ開発途中の言語なのでこれを使ってでかいツールを…とはなかなかいかないかもしれませんが,僕みたいに Ruby 好きな人は書いてみるとハードルも低く新鮮で楽しいかもしれません.

GitHub のトレンドリポジトリを見逃さない,Trendy をつくりました

僕が1日に1回ぐらいの頻度で見ているページの中の1つに GitHubTrending repositories のページがあります.このページには言語ごとに日毎・週毎・月毎の単位で GitHub 上で人気のリポジトリがランキング形式で表示されます.

話題になっているライブラリやソフトウェアの一次ソースとして便利なのですが,微妙にアクセスが悪い位置にあり,言語ごとにしか見られません.また,ランキングには常に人気な「常連」リポジトリが多々いるので,新しく話題になっているリポジトリはその中に埋もれがちになってしまいます.

そこで,今回はこれらの問題を解決すべく,GitHub のトレンドクライアント Trendy を Electron ベースでつくりました.

Trendy - Menubar App to Keep You in the Trend

logo

TrendyGitHub のトレンドページを監視し,トレンドに新しいリポジトリが現れた時に通知したり,過去にトレンドに挙がったリポジトリを管理して後から検索したりできます.メニューバーの中に常駐するので,いつでもメニューバーからアクセスできます.

README を書いたり,今回は勉強もかねて Landing Page 書いたりしましたが,せっかくなので簡単に紹介します.

インストール

GitHub のリリースページ からお使いの OS に合うファイルをダウンロードできます.OS XTrendy.app が入っているので,それをそのまま使ってください.Windows の場合は trendy.exe を,Linux の場合は trendy という executable があるのでそれらをそれぞれ使ってください.インストールは不要なので好きな場所に置いて OK です.

ファイルを実行すると,Trendy のメニューバーアイコンが出ると同時に初回は監視したいトレンドを選択するウィンドウが立ち上がります.

lang picker

上にあるフォームでインクリメンタル検索ができるので,気になる言語をいくつか選んだら Go! ボタンを押します.

Trendy が各トレンドページをスクレイピングして GitHub API を使ってリポジトリの情報を収集した後,メニューバーの中にあるアイコンが赤くなると思います. それ以降は1時間に1回のペースでトレンドページを見に行って新着リポジトリを探します.

使い方

main screen shot

'New' タブ

新着のリポジトリが表示されます.チェック済みのリポジトリはマウスオーバーすると左の方に表示されるでかいチェックマークボタンを押すと 'New' タブから削除されます.

'Current' タブ

現在のトレンドページのランキングです.

'All' タブ

Trendy が今まで出てきた収集したすべてのリポジトリ情報一覧です.

通知機能

Trendy は1時間に1度スクレイピングを行い,新しいリポジトリを見つけた時はメニューバーのアイコンの色を変えて通知します.緊急性のある通知ではないので,この程度の目立たなさの通知が一番気に入っています.

通知時 通常時
notified menubar normal menubar

組み込みブラウザ

リポジトリリポジトリ作者のページなどへのリンクをクリックすると,外部のブラウザではなく,モバイルアプリのようにその場でブラウザを開きます.開くページは,ウィンドウが小さい場合はモバイルページ,大きい場合は PC ページになります.

サイドメニュー

右上の3本線ボタンを押すとサイドメニューが開きます.メニュー内では次の3つの機能が使えます.

  • 検索フォームからリポジトリを検索できます.検索ワードを入れて Enter を押すと,現在のタブ内のリポジトリが絞り込み検索されます.
  • 現在のタブをトレンドでフィルタリングすることができます.普段はすべてのトレンドページの結果がタブ内に表示されています.
  • 最下段では設定ファイルを開いたり,手動で更新したり,アプリを終了したりできます.

メニューウィンドウとノーマルウィンドウ

ここまで Trendy はメニューバーに統合されたアプリだと紹介しましたが,普通のウィンドウで使うことも出来ます.

isolated window

Windows ではタスクバーが下に無いとメニューウィンドウの位置がおかしくなってしまうので,デフォルトでこの設定になっています. 後述する config.json を書き換えるとメニューウィンドウとノーマルウィンドウを切り替えられます.

ノーマルウィンドウ時は横幅が充分あることを想定して,サイドメニューは常に表示するようになっています.

GitHub API リミット

スクレイピングは対象ページの構成に依存して追従しないといけないため,極力スクレイピングに頼らないような設計になっています.Trendy では最低限の情報(リポジトリ名と作者名)だけスクレイピングで収集し,リポジトリのその他の情報は GitHub API を使っています.

ログインしていない場合は1時間に60回しか API を呼べないため,スクレイピング対象が少し多いとこの制限にひっかかってしまいます.その場合はウィンドウ内に赤いアラートダイアログが表示され,ログインを促されます.

API limit error

'login' リンクをクリックするとログインウィンドウが開くので,ログイン情報を入力してログインしてください.2段階認証にも対応しています.これで API リミットが 60 から 5000 まで増えるので,上記エラーは出なくなるはずです.

カスタマイズ

上記の2タイプのウィンドウを含め,いくつかの項目がカスタマイズ可能です.詳しくは README に書きました.

Electron + TypeScript + React

TypeScript 1.6 が beta になった頃につくりはじめたので,今回は renderer プロセス側も main プロセス側も両方 TypeScript でつくってみました.使ってみたうえでの感想は Qiita のほうにも簡単に書きましたが,

Qiita - TypeScript1.6 + React 書いてみてハマったポイントとか

メソッド名やキー名の間違いなど,うっかりミスを大体拾ってくれるので概ね良い感じです.

まとめ

GitHub のトレンドページクライアント Trendy をつくりました.

元は 最強のTwitterクライアント戦争 というのでぼちぼちアプリをつくっていて,ちょっと息抜きに前々から困っていたトレンドページを改善するか程度の気持ちだったのですが,なんだかんだで時間がかかってしまいました…

OS XUbuntu で使っている感じでは,今のところ問題なく動いているようです.よければお試しください.