読者です 読者をやめる 読者になる 読者になる

The future starts today

Webとか英語とか育児とかに関する雑記

Jestのスナップショット機能を使ってみた

JavaScript Electron

f:id:shibe97:20170320232906j:plain

Jestとは

facebook.github.io

  • Facebook製のテストフレームワーク
  • Reactアプリケーションのテストに向いてる
  • スナップショットテストがある

スナップショットテストとは

  • スナップショット != スクリーンショット
  • 画像の差分を取るのではない
  • 出力されるHTMLの構造を比較する

Worcに導入してみた

shibe97.hatenablog.com

(別件ですが、先日Windowsにも対応しました!)

Worcはかなり細かくコンポーネントを分けているので、試しやすかった。
完全に自分にとっての実験環境になりつつある・・・

対象のコンポーネント

とりあえず今回は、ツイート部分のテキストエリアコンポーネントを対象としてテストを行なった。
以下のようなコンポーネント

  1 import React from 'react';
  2 import styles from './textarea.css';
  3
  4 export default ({ value = '', inputUpdate }) => (
  5   <textarea
  6     className={styles.textarea}
  7     placeholder="What's happening?"
  8     value={value}
  9     onChange={e => inputUpdate(e.target.value)}
10   />
11 );

スナップショット機能の使い方

スナップショット機能を使うにはreact-test-rendererというパッケージが必要。

$ npm i -D jest react-test-renderer

今回は対象のコンポーネントと同階層にテストファイルも置いた。
テストファイルの中身は以下のような感じ。

  1 import React from 'react';
  2 import renderer from 'react-test-renderer';
  3
  4 import Textarea from './Textarea';
  5
  6 describe('components/atoms/textarea', () => {
  7   it('renders correctly', () => {
  8     const tree = renderer.create(
  9       <Textarea value="test" inputUpdate={() => {}} />
10     ).toJSON();
11     expect(tree).toMatchSnapshot();
12   });
13 });

スナップショットが生成される場所

テストを実行すると、テストファイルが置いてある階層に__snapshots__ディレクトリが生成される。
このディレクトリ内にスナップショットファイルが入る。

.
├── Textarea.js
├── Textarea.test.js
├── __snapshots__
│   └── Textarea.test.js.snap
└── textarea.css

生成されたスナップショット

次のようなスナップショットファイルが生成される。

exports[`components/atoms/textarea renders correctly 1`] = `
<textarea
  className="textarea"
  onChange={[Function]}
  placeholder="What\'s happening?"
  value="test" />
`;

テスト時にこのファイルと常に比較され、差分があるとこける。

そのままでは修正するたびにこけてしまうし、意図的な変更をする場合でもテストがこけてしまう。

そこでスナップショットを更新するオプションが用意されている。

$ npm test -- -u

ちなみに--はエスケープの役割だそう。 npm testjestが呼ばれるようにpackage.jsonscriptsに設定してある。

試しに少し変更を入れてテストを走らせてみる

valueの部分にtestという文字列を付け加える。

  1 import React from 'react';
  2 import styles from './textarea.css';
  3
  4 export default ({ value = '', inputUpdate }) => (
  5   <textarea
  6     className={styles.textarea}
  7     placeholder="What's happening?"
  8     value={`${value}test`}
  9     onChange={e => inputUpdate(e.target.value)}
10   />
11 );

この状態でテストを走らせると次のようになる。

f:id:shibe97:20170321215237p:plain

差分がとても分かりやすく表示されている。
オプションを付けてスナップショットを更新するとエラーはなくなる。

Worcでの運用方法

とりあえず以下のようにしてみた。けどもっと良い運用方法あるはず。
皆どうしているんだろうか。

  • 元々push時にCIでnpm testを回していた
  • しかし、CI上でnpm test -- -uしてしまうとスナップショットテスト全て通過してしまうので意味がなくなってしまう
  • そこで、push直前にnpm testして、うまくいった場合にCI上でnpm test -- -uを行う
  • push直前の処理はGit hooksを利用

Git hooksとは

Gitコマンドにフックして何らかの処理を行わせる仕組み。

.git/hooks以下のシェルファイルをいじると動く。

  • pre-commit
  • post-commit
  • pre-push
  • post-push
  • pre-receive
  • post-receive
  • etc…

今回自分はpre-pushを利用。

.git以下はGitHubで共有できないので、postinstall時などに書き換えるようなスクリプトを用意する必要がある

まとめ

スナップショットという単語に興味は持っていたけどずっと触れていなくて、ついに試すことができて良かった。 非常に簡単にテスト実行まで試すことができたので、誰でもさくっと導入できると思う。。

難しいと思うのは運用面で、複数個所がエラーで落ちた際に、本当は正解なのに前回のスナップショットを正としているためエラーとなってしまっているパターンが混ざっていると、その切り分けをしなくてはならないのが大変だと思った。

このあたりはよく考えれば良い解決方法がありそうなので、色々と試していきたい。

関数の魅力を引き出すmap, filter, reduce

JavaScript

f:id:shibe97:20170302175949j:plain

JavaScriptの関数は第一級オブジェクトである。

変数に関数を代入できるし、関数の引数に関数を与えられるし、関数の返り値として関数を返せる。

ES2015で追加されたmap, filter, reduceを使うと今まで何だか綺麗に書けなかったものがスッキリいい感じに書けるようになる。

よくあるJSON配列をごにょごにょと変換する例を挙げていこう。

対象のJSON配列:

const list = [
    {
        id : "1",
        name : "hoge",
        status : "active",
        num : 13,
        children : ["a", "c", "e"]
    },
    {
        id : "2",
        name : "fuga",
        status : "stop",
        num : 22,
        children : ["a", "g"]
    },
    {
        id : "3",
        name : "piyo",
        status : "active",
        num : 57,
        children : ["e", "h"]
    }
];

map

mapを使うと、ある配列を新しい配列に変換できる。 要素数を変えずに他の配列書き換えたい場合に便利。

idだけを抜き出した配列を作りたい

従来の書き方

var ids = [];
for (var i = 0; i < list.length; i++) {
    ids.push(list[i].id);
}

console.log(ids);   // [ '1', '2', '3' ]

mapを使った書き方

const ids = list.map((item) => item.id);

console.log(ids);   // [ '1', '2', '3' ]

idとnameだけを抜き出した配列を作りたい

従来の書き方

var ids = [];
for (var i = 0; i < list.length; i++) {
    ids.push({
        id : list[i].id,
        name : list[i].name
    });
}

console.log(ids);
// [ { id: '1', name: 'hoge' },
//  { id: '2', name: 'fuga' },
//  { id: '3', name: 'piyo' } ]

mapを使った書き方

const ids = list.map(({id, name}) => ({id, name}));

console.log(ids);
// [ { id: '1', name: 'hoge' },
//  { id: '2', name: 'fuga' },
//  { id: '3', name: 'piyo' } ]

filter

filterを使うと、ある配列から必要なものだけを選んだ新しい配列を作ることができる。

statusがactiveのものに絞り込んでidを取得したい

従来の書き方

var activeList = [];
for (var i = 0; i < list.length; i++) {
    if (list[i].status === 'active') {
        activeList.push(list[i].id);
    }
}

console.log(activeList);   // [ '1', '3' ]

filterを使った書き方(mapも用いる)

const activeList = list.filter((item) => item.status === 'active')
                       .map((item) => item.id);

console.log(activeList);   // [ '1', '3' ]

statusがactiveのものがあるかどうかをBool値で取得したい

従来の書き方

var flag = false;
for (var i = 0; i < list.length; i++) {
    if (list[i].status === 'active') {
        flag = true;
    }
}

console.log(flag);   // true

filterを使った書き方(mapも用いる)

const flag = 
    list.filter((item) => item.status === 'active')
    .length > 0;

console.log(flag);   // true

reduce

reduceは配列内の全要素を用いて値やオブジェクトを生成する。 使い方が難しいが、使いこなせると強い。

各要素が保持しているnumの合計値を取得したい

従来の書き方

var sum = 0;
for (var i = 0; i < list.length; i++) {
    sum += list[i].num;
}

console.log(sum);   // 92

reduceを使った書き方(mapも用いる)

const sum = list.map((item) => item.num)
                     .reduce((x, y) => x + y);

console.log(sum);   // 92

各要素が保持しているchildren配列を連結したい

従来の書き方

var children = [];
for (var i = 0; i < list.length; i++) {
    children = children.concat(list[i].children);
}

console.log(children);
// [ 'a', 'c', 'e', 'a', 'g', 'e', 'h' ]

reduceを使った書き方(mapも用いる)

const children = list.map((item) => item.children)
                     .reduce((x, y) => [...x, ...y]);

console.log(children);
// [ 'a', 'c', 'e', 'a', 'g', 'e', 'h' ]

まとめ

for文やforEach文はループ処理であり、returnが存在しないため、ループ処理後は一度式を途切らせなくてはならない。
しかし、mapfilterreduceを用いると次の処理にそのままつなげることができる。
関数型の入り口として知っておいて損はない。

また、ほとんどの処理を1行で書くことができるため、コードが簡潔で済む。
慣れていないと解読する側は大変だが、mapfilterreduceさえ抑えておけば、後々活きてくるだろう。

electron-builderを使ってdmgファイルを生成する

Electron JavaScript

f:id:shibe97:20170216225044p:plain

shibe97.github.io

このサイト上からdmgファイルをダウンロードし、ユーザーの端末上でインストールできるようにした。

Electronアプリからdmgファイルを生成する方法

electron-builderを用いる。

github.com

electron-builderを使うための設定

まず、package.json内に以下のプロパティを設定している必要がある。

  • name
  • description
  • version
  • author

また、electron-builder自身の設定もpackage.json内に記述する。
dependenciesscriptsと並列にbuildというプロパティを設定する。

"build": {
    "appId": "com.electron.worc",
    "mac": {
        "target": "dmg"
    },
    "directories": {
        "output": "docs"
    }
}

appId

アプリの識別子。
MacではCFBundleIdentifier、WindowsではApplication User Model IDとして用いられる。
デフォルトはcom.electron.${name}という形式。

Apple Developer Programに登録していないと警告が出るが、そのままパッケージングは進められる。
Code Signingに関してはこのあたり参照。

github.com

mac

Macにおける設定。
パッケージングファイル形式をdmgにしたい場合はtargetプロパティにそう指定する。

directories

パス関連の設定。
dmgファイルのアウトプット先はoutputプロパティに指定する。
今回はGitHub Pagesにホスティングさせるためにdocsディレクトリを指定した。

その他の設定

github.com

electron-builderを起動する

package.jsonに以下のようにscriptsを登録した。

"scripts": {
    "pack:osx": "build --mac --x64"
},

また、次の作業も必要。

  • electronをdevDependenciesに置く必要がある
  • package.jsonmainプロパティに指定しているファイルをもとにパッケージング処理が走る
  • アプリのアイコンをbuildディレクトリ配下に用意する(ファイル名はicon.icns)

Worcのディレクトリ構成は以下のような感じ(一階層のみ表示)

.
├── README.md
├── app
├── assets
├── build   <= アプリのアイコンを置く
├── dist
├── docs
├── node_modules
├── package.json
└── webpack.config.js

全てが設定済みの状態で先ほどpackage.jsonscriptsに登録したコマンドを叩けば起動する。

$ npm run pack:osx

dmgファイルをGitHub Pagesにホストする

Worcでは、GitHub Pagesを利用してランディングページをホストしている。 ここにdmgファイルも一緒において、ダウンロードしてもらおうと考えた。

しかし、dmgファイルをGitHubにpushしようとすると100MBを超えていて、pushできない現象が陥った。 そこで、Git Large File Storage(LFS)を利用した。

LFSの使い方

下記の手順で行う。(hoge.dmgは適宜、正しいパスに置き換えてください)

$ git lfs install
$ git lfs track hoge.dmg
$ git add .gitattributes
$ git add hoge.dmg
$ git commit -m "add dmg file"
$ git push origin master

git lfs trackを行うと、.gitattributesが生成され、そこに対象のファイルが記述される仕組みとなっている。

これらの作業により、100MB以上のファイルもpushできる。(ただし月に1GBまで、それ以降は有料)
たぶん1ヶ月にそんなに多くリリースしないと思うので、無料でいけるはず・・・。

まとめ

今までelectron-packagerを用いてパッケージングを行なった後、zip形式にし、GitHubのreleaseページにデプロイしていた。

なぜかzipにした後に解凍してみるとアプリケーションが動かなかったり、よくわからない現象が起こって困っていた。
また、GitHubのreleaseページからのダウンロードはあまり良いユースケースとは思えなかったため、ランディングページから直接ダウンロードできるようになって良かった。

ElectronアプリにGoogle Analyticsを導入する

JavaScript Electron

f:id:shibe97:20170116230507p:plain

先日、Electron製のTwitterクライアントであるworcを公開した。

shibe97.hatenablog.com

せっかくなのでアクセス解析をしようと思い、Google Analyticsを導入したのだが、色々とハマりどころが多かったのでメモ。

Google Analyticsに登録

f:id:shibe97:20170116221551p:plain

新規にアカウントを作ろうとすると上記のような画面が出る。

その際、ウェブサイトのURLを入力する欄があるが、Electron製のためURLがない。 ここは、example.comなど適当に入力しておく。

ラッキングコードを設定する

アカウントの作成が終わるとトラッキングコードが生成される。
しかし、このコードをそのままElectronアプリのHTMLに挿入してもうまく計測がされない。

Google Analyticsではcookieを用いてユニークユーザーの判定をしているが、Electronアプリはドメインがないためcookieの登録ができない。

そこで、electron-cookiesを用いる。

github.com

その名の通り、Electron上でcookieを擬似的に持たせることができる。

npm installを行なった後、これを用いて以下のようにトラッキングコードを設定する。

$ npm install @exponent/electron-cookies --save
<script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
  (() => {
    'use strict';
    const ElectronCookies = require('@exponent/electron-cookies');
    ElectronCookies.enable({ origin: 'https://example.com' });
    ga('create', 'GOOGLE_ANALYTICS_ID', 'auto');
    ga('set', 'location', 'https://example.com/');
    ga('set', 'checkProtocolTask', null);
    ga('send', 'pageview');
  })();
</script>

node_modulesに注意

Electronアプリは容量が多いためWebpackなどでバンドル後、ビルド時にnode_modulesを除外しているパターンもあるだろう。
一見、Webpackでの依存解決でelectron-cookiesbundle.jsに含まれそうだが、このトラッキングコードはindex.htmlに記述されているため、Webpackのバンドルの対象外だ。

よって、node_modules/electron-cookiesを含めた状態でアプリをパッケージングする必要がある。

これに気付くのにすごい時間かかった。。

2017年の目標

ブログで宣言。

Happy New Year

ブログ

月2回以上書く。
去年は途中全然書いていなかったので、今年は飽きずに頑張る。

英語

とりあえずTOEIC800点超える。
海外出張1回以上行く。
海外ドラマや映画に手を出す。

社外活動

登壇4回以上する。
WorcのDAUを1000人以上にする。
定常的な副収入源を作る。

社内活動

熟練JSerを増やす。
給料上げる。
残業時間を月10時間以内に抑える。

総じて

今年で30歳になるので、色々とチャレンジしていきます〜!

2016年振り返り

思考 英語 育児

2016年も残すところあと数日ということで振り返ってみる。

f:id:shibe97:20161228230741j:plain

社内外活動

デブサミ2016登壇

codezine.jp

かなりの大舞台でさすがに緊張した。
ここぞとばかりに黒曜石プレゼンターをポチって発表して、いい経験になった。

初の海外出張でFluent Conferenceに参加

conferences.oreilly.com

※これは2017年のリンク

Fluentに参加するために英語を勉強し始め、1年越しでなんとか目標のTOEICスコアを獲得し、海外出張の権利を獲得した。
憧れのサンフランシスコに降り立ち、現地の雰囲気を肌で感じることができたので良かった。

レベル的には自分たちでも充分海外勢と戦えるという印象を持った。
英語さえ完璧に使いこなせればw

Scripty #5 参加

scripty.connpass.com

Bikeshed.js 参加

connpass.com 無駄にすごい人たちが集まっていてすごかった(語彙がひどい)

社内 React Meetup 開催

弊社人数が多いので色々隠れた事例が多いだろうということで社内向けにReact Meetupを企画して主催した。 社内フロントエンドの横のつながりができて良かった。

CodeGrid4周年記念パーティ 参加

atnd.org

2年連続で参加。いつも通りゆるい感じで良かった。

管理画面チラ見せ♡ナイト #3 参加

connpass.com

Modern Web App LT!(freee x WACUL) 参加

connpass.com

meyasubacoリリース

meyasuba.co

友人と進めていたWebサービスをリリース。 アプリのグロースハックのためにユーザーのフィードバックを簡単に得る仕組みを提供。

Scripty #6 登壇

scripty.connpass.com CSS Modulesについて発表したけど、発表後すぐに考えがだいぶ変わり、発表内容を封印。

「いまから始めるWebフロントエンド開発」発売

友人と共に進めていた電子書籍が発売された。自分はほぼレビューしかしていないが、意外と簡単に本を出せるということを知った。

React.js Meetup #4登壇

reactjs-meetup.connpass.com

ReactコンポーネントCSSコンポーネントの関係について発表した。
ReactコンポーネントとCSSコンポーネントは1対1なのか問題について // Speaker Deck

W3C TAG デベロッパー・サミット 参加

frontend.connpass.com

YJ meetup 登壇

yj-meetup.connpass.com Houdiniについて発表。学生向けのイベントだったため、難しすぎた説。

社内JavaScriptセミナー 登壇

社内のJavaScript人口を増やすためにセミナーを開催。
自分はnpmのエコシステム周りについて発表した。

アドベントカレンダー2本 公開

shibe97.hatenablog.com

shibe97.hatenablog.com

毎年書こうと思いつつ、チキって書けていなかったアドベントカレンダーを2本書くことができ、はてブ数も上々だったのでやって良かった。

その他

  • DMM英会話1年以上継続中
  • エブリデイ育児
    • 地味にこれがこの1年のほとんどを占める
  • 1年を通して残業ほとんどしてない
  • 社内フロントエンドもくもく会を13回開催
    • こじんまりとやっている
    • 来年はもうちょっと人増やしたい
  • ご近所とBBQ、こどもの国、ボードゲーム
  • 両家ともたくさん遊んだ(けっこう親孝行できた)
    • 毎週末だいたいどちらかの家に遊びに行っていた

総括

思い返してみると、今年も公私ともに色々やったなぁ。

去年の7月に娘が産まれてから、基本的には18時に退勤して、家族との時間を優先するようにした。 おかげで残業もほぼゼロだし、娘が寝てからは自分の趣味開発に力を注ぐことができた。(良い感じの生活スタイル)

個人的には海外のカンファレンスに参加したいというのが長年の想いとしてあったので、3月のサンフランシスコ出張は思い出深い。また、そこで英語の勉強を辞めずにここまで続けてこれたのは我ながら偉いと思う。

デブサミ2016、React.js Meetupなど、著名なイベントに登壇できたのも嬉しかった。社外のつながりも増えた。

家庭と仕事+プライベート活動の両立が出来た1年だったかな。

来年は節目の30歳になってしまうので、より一層頑張りたいところ。

今年もお疲れ様でした〜〜〜!!!

仕事中に閲覧できるTwitterクライアント「Worc」を作った

JavaScript

この記事は Electron Advent Calendar 2016 の9日目の記事です。

作ったもの

f:id:shibe97:20161123233256p:plain

github.com

GitHubで公開しています。Releaseページから最新バージョンの「worc-darwin-x64.zip」というリンクからダウンロード可能です。

以下のランディングページからダウンロードできます。(追記:2017/2/17)

shibe97.github.io

※未署名のため、ダウンロード後は「右クリック」→「開く」という手順で開く必要があります。

動機

Twitterは情報収集のツールとして非常に優秀だと思っていて、特にTech系の情報はTwitter経由が一番速くつかみやすいです。 界隈で強い方々はよくTwitter使って発信しているイメージがあります。

世の中には非常に多くのTwitterクライアントが存在しているのですが、個人的にしっくりくる物がないんですよね。。

これはあくまで僕個人の話ですが、会社内でTwitterを見て情報収集したいという願望があります。
しかし、SNSは娯楽系のイメージが強いため、会社内で堂々と閲覧しているのは憚れます。。

「よし、会社内で普通に見れるようなTwitterクライアントを自作しよう!」

と思ったわけです。

加えて、普段から気になっている技術や、試してみたい構成を突っ込んでみる良い機会ということで作り始めました。

どんなものを作ったか

Twitterが娯楽っぽく見えてしまう要因を考えてみたところ、個人アイコンがずらっと並んでいる感じがそれを助長しているのではないかと考えました。
そこで、できる限りテキストベースにし、派手さをなくした物を作りました。

f:id:shibe97:20161127222012p:plain

意外とアイコンなしでも人を判別できるものです。
しかし時たま、これ誰だっけ…というときのためにハンドル名クリックでアイコンは表示されるようにしました。

f:id:shibe97:20161127222450p:plain

基本的にはデスクトップの左端、または右端においてコーディング横目に閲覧するようなイメージです。
黒い画面の隣だと若干目を引きますが、他のアプリケーションと同時に使うことも想定し、無難な白基調のものにしました。

f:id:shibe97:20161127230808p:plain

とりあえず、ストリーム形式のホームタイムラインと、リストタイムラインは必須だと思い、導入しています。

ハッシュタグの検索結果のストリーム版もエンジニアにとっては欲しい機能かな〜と思ったのですが、勉強会とかに参加していない時はそこまでハッシュタグ追わないかなと考え、後回し中です。(昼のイベントに参加できず、会社でハッシュタグを追うというのはたまにありそうですが)

フリーで落とせるのでぜひ気になる方は使ってみてください! Star、Issue、プルリクも大歓迎です!

ダウンロード

今後の展望

  • メニューからデザインを自由に変更できるようにしたい(背景色など)
  • ストリームAPIの接続が切れた際に自動でつなぎ直したい(やり方がよくわからない)
  • ハッシュタグにリンクをつける
  • リプライ先にリンクをつける
  • 自動アップデート機能(Apple Developer Programへの登録が必要)

などなど、やりたいことはたくさんあります。
地道にアップデートを重ねていく所存です。

名前の由来

ちなみに由来は、仕事という意味を表す「Work」です。
そして、やはりTwitterクライアントということで鳥に関するサービス名が良いと思い、今回は闇に溶け込んで集中を途切れさせない的な意味を込めて、カラスあたりが適役かなと思いました。
カラスは英語で「Crow」です。

勘の良い方はお気づきになったかもしれませんが、「Worc」を逆から読むと「Crow」となります。
奇跡か!!と思いました。

構成

ここから先は技術の話となります。 今回は以下の構成で作りました。

  • Electron
  • React
  • Redux
  • Redux-saga
  • Webpack
  • CSS Modules
  • Jest
  • Enzyme
  • ESLint
  • Atomic Design
  • Travis CI

ズラッと並べると凄そうに見えますね。笑

コンポーネントの内訳

以前、「ReactコンポーネントCSSコンポーネントは1対1なのか問題について」という発表をReact Meetup #4で行ないました。

ReactコンポーネントとCSSコンポーネントは1対1なのか問題について // Speaker Deck

その時はまだ机上の空論感があったので実際に試してみました。
試してみた結果、やはり1対1の粒度で作っていくと綺麗に作れると感じました。

Atomic Designを取り入れ、できるだけ細かい単位で分割し、今回は以下のものをコンポーネントとしてひとまとまりにしました。

Loadingの例を挙げると以下のような感じです。

  • Loading.js
import React from 'react'; 
import styles from './loading.css'; 

export default () => ( 
  <div className={styles.loading} />
); 
  • Loading.test.js
import React from 'react'; 
import { shallow, mount, render } from 'enzyme'; 
import Loading from './Loading'; 

describe('components/atoms/loading', function() { 
  it('should have a "loading" class', function() { 
    expect(shallow(<Loading />).hasClass('loading')).toEqual(true); 
  }); 
}); 
.loading { 
  height: 100%; 
  background: url('./loading.svg') no-repeat center center; 
  background-size: 40px 40px; 
} 

この4つを同一階層に配置します。
Loadingの表示に必要なものが全てまとまっているので、これぞコンポーネントだ!という感想を持ちました。

ちなみに、CSSの読み込みにはCSS Modulesを、ReactコンポーネントのテストにはEnzymeを利用しています。

こうなった経緯

最初からこの方式で作り始められれば良かったのですが、実は途中からリファクタリングで徐々に直していきました。
当初、普通にsassを使っていたのを途中からCSS Modules形式に変更しました。
同時にAtomic Designに基づいた形でコンポーネントを分割し直しました。

Atomic Designの話

今年は結構話題になることも多かったのでご存知の方も多いと思いますが、 Atomic Designとは、コンポーネントをそれ以上分割できない単位(原子)まで分割し、それらを組み合わせて徐々に大きな単位のコンポーネントを形作っていくという考え方です。

通常、以下の5つに分割するのがAtomic Designの考え方になります。

  • Atom(原子)
  • Molecule(分子)
  • Organism(有機体)
  • Template(テンプレート)
  • Page(ページ)

しかし、個人的にはTemplateとPageがあまりしっくり来なかったので、今回はtemplate以上の部分をModuleと名付けた以下の構成にしました。

  • Atoms
  • Molecules
  • Organisms
  • Modules

ページごとにディレクトリが分かれるのでは無く、コンポーネントの大きさ単位でディレクトリが分かれているので、より再利用性に富んだ形であると思います。

進めていくと、「あ〜あのコンポーネントはどこだっけな?」とわざわざ読み込み先からパスを見て探しに行くケースも出てきました。
なんとなくコンポーネントの粒度で当たりはつけられるのですが、そのあたりはちょっと慣れが必要そうです。

Electronはどうだったか

Electronはブラウザプロセスとレンダラプロセスで構成されています。

OSの機能を直接つかったりするのはブラウザプロセス側です。重い処理はブラウザプロセス側でやるのが推奨されていたりもします。プロセス間は通信の手段がいろいろあります。

今回はTwitterクライアントということで、基本的にはTwitterAPIをフルに利用したものなので、ほとんどの処理はレンダラプロセスのみでいけました。

レンダラプロセスに入ってしまえば、後は普通のWebアプリと一緒です。 出力したindex.htmlからバンドルされたJSを読み込み、Reactアプリケーションを構築します。

特有なのはブラウザプロセスだけなので、その部分さえ乗り越えてしまえば楽でした。

Electronのファイルサイズ

今回、136MBという馬鹿でかいサイズとなってしまいました。

当初はJSのバンドルは行なっていなかったのですが、バンドルした方が容量が小さくなるという記事を見つけ、Webpackで1つのファイルにしました。 実は最初はもっと重くて、162MBあったものが、Webpackを通すと135MBになりました。(それでもまぁ重いのですが・・・)

V8が搭載されているため、空の状態でもかなりの容量があるのでしょう。

アプリケーションのダウンロードに時間がかかってしまうのが難点です。
何か良い解決策がありましたら是非教えてください〜。

まとめ

Electron触ってみたいなぁ〜ということでちょうど欲しかったTwitterクライアント作りに挑戦してみました。
ほとんどWeb開発と同じ感覚で作れるのでやりやすかったです。

Chromeの最新版のみに対応すれば良いので、ブラウザ対応という面倒なことを考えなくて良いのが最高でした。

パッケージングやGitHubへのデプロイなど、今まであまりやったことがなかったため良い勉強になりました。