技術探し

JavaScriptを中心に記事を書いていきます :;(∩´﹏`∩);:

アイルランドから帰ってきた

3/2 - 8/26 までアイルランドで生活していました。

長かったようであっという間に過ぎていって本当に幸せな日々でした。人生の中で一番良かったんじゃないかな。。。

アイルランド

イギリスの左にある島で北部がイギリス領です。

首都はタックスヘイブンで有名なダブリンで多くの有名なIT企業がこの都市に集まっています。
日本から直接アイルランドに行くことは出来ず、一度乗り継ぎが必要となります。

時差

タイムゾーンGMT+0でイギリスと同じとなりますが、サマータイムにより現在はGMT+1となります。
サマータイムは、3月の最終日曜日から10月の最終日曜日の期間となります。
つまり、日本との時差が8, 9時間なので日本で一番活発な時間帯がアイルランドの深夜となります。

気候

天気が不安定なのは有名な話で突然雨が降ったり突然晴れたりします。
しかしながら、夏は最高20度ぐらいで、クーラーはお店にしか存在しないらしく(実際見たことない気がする)、冬は5 - 7度ぐらいで雪はほぼ降りません。

実際、メキシコ暖流の影響により1年を通して温暖ですが、樺太と同じ緯度にあるため、日本より少し涼しい感じとなります。

なので実際には日本と比較しても大変過ごしやすい環境となります。

治安

治安はヨーロッパの中ではかなり安全です。
しかしながら自転車の盗難とティーンエージャーには注意する必要があります。

アイルランドは、奇数地区が危険と言われています。(もちろん安全なところはありますが)

アイルランドは中央の川で別れており、上が奇数で下が偶数のアドレスとなります。
偶数は安全だと呼ばれており、その代わり地価が高いイメージがあります。

https://www.enginnier.com/wp-content/uploads/2015/10/493b030f4d21e15e5b88bf26676a6afc.png

ref: https://www.enginnier.com/ireland-district/

ご飯は、イギリス同様に芋と肉が中心となります。
自分は日本料理が本当に食べたかったのと同時に日本料理って手が込んでるなって本当に思いました。

アイルランドは有名なビールの消費量が多い国です。
ギネスの工場もあり、道を歩けば昼からお酒を飲んでる人は多いです。

f:id:about_hiroppy:20190309184026j:plain

ビール美味しすぎるぞ!

交通

バスと路面電車(Luas)がメインになります。

自分は路面電車をよく乗ってましたが、朝は常に満員電車で駅で4本ぐらい待たないと乗れません。
また、日本と違いホームに柵はなくそもそも対面に行くときには線路を歩きます。

f:id:about_hiroppy:20190905104628p:plain

ワンマン電車で、改札はありませんが抜き打ちでチケットの検査があるので必ずチケットを購入するべきです。

バスにはwifiが備わっており、すべて電子的に管理させているのであと何分でバスが来る等がリアルタイムにわかります。

基本的にダブリンはコンパクトな街なので、多くの人は歩いたり自転車を使うことも多いです。

また、歩行者信号は自動で変わらずに稼働させても時間がかなり短いためみんな信号無視をします。
車に関しては、日本と同じなので運転しやすいです。

お金

€1 = ¥122で、基本的に物価は高いです。

諸事情により海外転出を出してなかったので(出してない事自体には12月までに帰国しているため問題なし)、日本に税金等を払っていた等もあり月に30万近くの出費がありました。これは、アイルランドの食費や住居も含みます。

半年の変化

  • webpackのcommitterに再度なった
  • Google Summer of Codeのメンターを努めた
  • 彼女と別れた
  • 初海外登壇した
  • スマホを一台おとして、画面が壊れ使えなくなった

blog.hiroppy.me

感想

海外で住むのは案外簡単でそこまで英語力は必要ないです。(病院除く)
しかし、これも有名ですがアイルランド人の英語は速く、アイルランド訛りがあります。(イギリス英語 + アクセントが強い + アイルランドでしか使わない言い回しがある)
自分の場合は、イミグレーションの二重支払い対応(3ヶ月の手紙やり取り)や眼鏡の作成及び視力検査等も行ったが特に問題なく行えました。

少なくとも自分は、海外で一度住むことをみんなにオススメしたい。
多様性の許容範囲が広がる点と日本を客観的にみれる様になると思う。
異文化の下で暮らすと、自分が許容できる範囲が本当に広がるなって実感しました。
日本では普通なこと(e.g. 電車の中では電話しないみたいな)が海外では当たり前ではないということが多いです。

また、日本という国や環境、様々なことを一歩下がったところから見て考えれるようになります。
これは転職と同じで他の会社に行くと昔の会社や良いところや悪いところが見えるのと同じ感じです。
自分にとって、他の国に住むことにより比較対象ができ自国を客観的に見れるようになったのはでかい思っています。

また、日本の様々な情勢等の影響で多くのストレスがあり、疲労していたのは事実です。
昼間からテンプルバーで生ライブをみんなで歌うのはよく見ますし、ゆったりした時間ですごせるのは本当にいい環境だなと思っています。
歌ってるときが一番幸せでした。

アイルランドは自分がいつか戻りたいと思うほど好きな国でしたし、いつか戻るでしょう。

もし、英語に自信がなくて海外に住むのは怖いって考えている人がいたら、その考えはいつになっても上達しないのでさっさと行ったほうがいいと自分は答えます。


自分はこの歌が本当に大好きで、この歌がなければ今までの人生はなかったのでここに貼っておきます。

youtu.be

エンジニアの自分が思う「焦燥感」

potato4dくんのエンジニアキャリアにおける「焦燥感」を読んでこの文章を書いています。残念ながら日本にいなかったため、直では聞いていないです。

読んで思ったことは、自分より若いのに(年齢は仕事等には関係ないが人生的な話ね)将来のことを考えていてすごいなーって思っています。

logmi.jp

さて、自分について纏めてみようと思う。

前提として、今はマネージメント的な話は考えておらず、今はコードを書くことに集中したいなって思っている。
ただ組織論的な部分はすごい興味あるから、多くの人を参考にしていきたいなとは思う。
どうエンジニアが気持ちよく働ける組織にするのかなって部分。

記事はここらへん

blog.hiroppy.me

焦燥感

自分は仕事的な焦燥感はないが、自分の技術力の低下には恐れている。
これが、自分が毎日コードを書く理由である。自分を信用していないという部分があり、英語もそうだが毎日使わないとすぐに低下していくと思っている。

現に今、アイルランドに住んでいるのでもちろん全部英語だが、土日喋らなかっただけで月曜日が英語脳じゃなくなっているのを実感したことが多々ある。

仕事だけでいいじゃんって思うかもしれないけど、自分は記憶力とかいいわけではないので、それだけではやっぱり足りないんだよね。
たしかに高校時代からプログラミングをしていたので、ある程度は自信がついている部分もあるけど、やっぱり技術の変化が激しい部分はあるので常に学習をし続ける必要はあるし、たとえ基礎は自信があってもそれだけでは足りない。

またエンジニアとして、自分の会社・世の中へ提供する価値を維持するという面も同様である。そのためにコードを書き続ける。

まぁ、コード書くの楽しいからいいんだけどね!
で、そのためのフィールドがOSSになっている。

なぜOSSをやるのか?

最初に自分は以下のルールで動いている。

作りたいものがあったら作るし、なければコミュニティにコミットする。

自分はどこかに雇われているわけではないから、好きにやっていい。

ただ、モチベーション維持って部分がやっぱり難しくって、自分は承認欲求で成立させているんだと思う。

新機能とかバグ修正とかでフィードバックや「ありがとう」って言ってもらえるだけでいい。それだけでやってよかったなーって思える。期待しているわけじゃないけど、みんな素直に言ってくれるんだよね(特に英語って過剰に言ってくれるので)。

本当にこういう些細なことでやる気がめっちゃでる。

これだけで、モチベーションを維持できる人間で良かったと思っている。
おまけにレビュワーはみんなすごいし、自分が知らない書き方やパフォーマンス等の知識がタダで手に入る。天国じゃんって思う。
だから、24時間の中の数時間をOSSに割くことができる。

あと、コアに入ると知識が偏らない?ってよく聞かれるんだけど、ライブラリの規模や種類によって変わると思うけど、自分の管轄内はいろんなツールのハブになっていることが多く、結局全部知らないといけなくなるからあまり偏らないとも思う。

だいたいこれが1日3時間ぐらい。

仕事

焦燥感の箇所で書いたとおり、不安を軽減させるために技術の楽しさは自分にとっては必要。

また、仕事の面もOSSと同様でユーザーのフィードバックがモチベーションになっているかなーって思っている。
フィードバックって言葉だけだとは思ってなくって、数値(離脱率等)も同様だと思う。 だからこそ、自分にとっては数値もやっぱり大切だし、開発者のモチベーション維持のためにも公開はするべきだと思っている。

もちろん、「なんでここ変えたの?ありえなくね?」みたいな落ち込むようなフィードバックもあるけど、それは受け止めるべきで次へのステップへつなげればいい。

これは、OSSも仕事も同じで、使う側にどれだけ満足してもらえるかというところがやりがいかなって思う。だからこそ、OSSは特に不満があればissueを出してほしいし、議論したい。

キャリアアップ・キャリア

キャリアアップという観点で見れば、自分は複数の会社で働かせてもらってるし、今はGoogle Summer of Codeのメンターもやらせてもらっているから教育という点もたしかに経験はしている。まぁあとは海外登壇とかかな。

ただ、特に何か考えているとかはなくって自分がやりたいこと・任されてやっていることをやっているだけで、数年後こうなりたいっていう明確なイメージはいまのところ無い気がしている。

今、どのフェーズにいますか?

今の自分のフェーズは少なくともキャリアチェンジではない。
今はOSSが最高に楽しいし、まだまだ仕事でもコードを書いていたい。
OSSもユーザーの数が300万人を超え、次のメジャーバージョンリリースが楽しみだ。

3年後にまだずっとコードを書いていたかなんてわかりもしないが、やはりマネージャーという仕事を知らずに批判・評価するのは問題なので一度は経験するべきだとは思っている。
ただ、現実の問題としてマネージャーになるとコードを書く時間が減るのは事実あるので今ではないという判断である。

まとめ

長々書いてしまったが、自分はまだエンジニア(エキスパート方面?)を続けていくと思う。
また、自分は1日コードを書かないだけで技術力が低下するのではないかという不安が常にある。
だからこそ逆に今の状態が維持できていると思う。
ただ、プライベートがすでに圧迫されているのも事実でもう少し効率の良い時間の使い方を考えないといけないなっていうのは課題として存在する。

あと、SHIROBAKOを例に上げたキャリアパス問題の記事が好きなので、ぜひ読んで欲しい。

motida-japan.hatenablog.com

自分はやっぱり ものづくり をしたいんだなぁと。それもただ作っていたいわけでなく、売り上げがどうこうとかじゃなくて、 ひたすらユーザーへの価値を第一に考えて、ほぼほぼそれのみKGI・KPIにおいて磨いてたら、売り上げもついてきたみたいなものづくりをしてサービスを育てたい。それをいくつも。

自分の考えをまとめるいい機会だった。ありがとうございます。

react-apollo@3.0.0へ移行する

現在のステータスはbetaですので、急ぐ必要はないです。

github.com

$ npm i react-apollo@beta

大きな注目点としては、hooksの追加と@apollo/react-testingへの分離でしょうか。

この記事は、自分がぶつかった部分のまとめなので抜けはあります、注意してください。

Entry Point

内部的な話ですが、すべて名前空間にパッケージが移され、react-apollo自体は後方互換のためのプロキシとなります。

react-apolloのentryは以下のようになります。

// index.js

export { getApolloContext, resetApolloContext, ApolloProvider, ApolloConsumer } from '@apollo/react-common';
export { Query, Mutation, Subscription } from '@apollo/react-components';
export { graphql, withQuery, withMutation, withSubscription, withApollo } from '@apollo/react-hoc';
export { useQuery, useMutation, useSubscription, useApolloClient, getMarkupFromTree, getDataFromTree, renderToStringWithData } from '@apollo/react-hooks';
//# sourceMappingURL=index.js.map

Hooks

react-hooksの登場に伴って、apolloも今後はhooksが主流になっていきます。

QueryとMutationは以下のような書き方に変更できます。

// スキーマ
const GET_ORGS = gql`
  query {
    organizations {
      name
      uid
    }
  }
`;

const ADD_ORG = gql`
  mutation addOrganization($name: String!) {
    addOrganization(name: $name) {
      name
      uri
      uid
    }
  }
`;

以前のクラス

// ここでdataの型を今までは入れないと推論ができなかった
class GetOrgsQuery extends Query<{ organizations: Organizations }> {}

export class OrganizationsBox extends React.PureComponent<unknown, State> {
  state = { currentValue: '' };

  onChange = (e: React.FormEvent<HTMLInputElement>) => {
    this.setState({ currentValue: e.currentTarget.value });
  };

  onSubmit = (addOrganization: MutationFunc) => {
    addOrganization({ variables: { name: this.state.currentValue } });
    this.setState({ currentValue: '' });
  };

  render() {
    return (
      <GetOrgsQuery query={GET_ORGS}>
        {({ loading, error, data }) => (
          <Ul>
            {error || loading ? <p>{error ? `Error! ${error.message}` : 'loading...'}</p> : null}
            {data &&
              data.organizations &&
              data.organizations.map(({ name, uid }) => (
                <Li key={uid}>
                  <A to={`/orgs/${name}`}>{name}</A>
                </Li>
              ))}
            <Mutation mutation={ADD_ORG} refetchQueries={[{ query: GET_ORGS }]}>
              {(addOrganization, { error }) => (
                <React.Fragment>
                  {error && <p>{`Error! ${error.message}`}</p>}
                  <InputBox>
                    <input onChange={this.onChange} value={this.state.currentValue} />
                    <button onClick={() => this.onSubmit(addOrganization)}>Add</button>
                  </InputBox>
                </React.Fragment>
              )}
            </Mutation>
          </Ul>
        )}
      </GetOrgsQuery>
    );
  }
}

Hooks

import { useQuery, useMutation } from 'react-apollo';

export const OrganizationsBox: React.FC = () => {
  const [currentValue, updateCurrentValue] = useState('');
  const { loading, error, data } = useQuery<{ organizations: Organizations }>(GET_ORGS);
  const [addOrganization, { error, data }]  = useMutation(ADD_ORG, {
    refetchQueries: [{ query: GET_ORGS }]
  });

  const onChange = (e: React.FormEvent<HTMLInputElement>) => {
    updateCurrentValue(e.currentTarget.value);
  };

  const onSubmit = (addOrganization: any) => {
    addOrganization({ variables: { name: currentValue } });
    updateCurrentValue('');
  };

  if (error || loading) {
    return <p>{error ? `Error! ${error.message}` : 'loading...'}</p>;
  }

  return (
    <Ul>
      {data &&
        data.organizations.map(({ name, uid }) => (
          <Li key={uid}>
            <A to={`/orgs/${name}`}>{name}</A>
          </Li>
        ))}
      <InputBox>
        <input onChange={onChange} value={currentValue} />
        <button onClick={() => onSubmit(addOrganization)}>Add</button>
      </InputBox>
    </Ul>
  );
};

上記の通り、jsx内で関数を書く必要がなくなり、可読性が増しました。

const { loading, error, data } = useQuery(GET_ORGS);
const [addOrganization, { error, data }]  = useMutation(ADD_ORG);
const { loading, data } = useSubscription(LATEST_ORGS);

と定義し、コールすれば終わりです。
注意点として、Mutationだけユーザー定義が入るため配列です。

react-testing

よくテストやstorybookで使われる MockedProviderreact-apolloからのインポートではなく、@apollo-react-testingというライブラリからのインポートに変更されます。

- import { MockedProvider } from 'react-apollo/test-utils';
+ import { MockedProvider } from '@apollo/react-testing';

MockLink, MockSubscriptionLink も同様に移動しました。

型定義

リネーム

MutationFnMutationFunction に中身は同様でリネームされました。

削除

自分が把握している限りで以下の型がなくなりました。

GraphqlQueryControlsMutationFuncChildDataProps

https://github.com/apollographql/react-apollo/pull/2892/commits/ade881f07b1175d28b0aae79915bfc5ed8dd9e5a

独自で定義する場合は以下のようになります。

import { OperationVariables, QueryControls, MutationFunction, DataProps } from 'react-apollo';

export interface GraphqlQueryControls<TGraphQLVariables = OperationVariables>
  extends QueryControls<any, TGraphQLVariables> {}

export type MutationFunc<TData = any, TVariables = OperationVariables> = MutationFunction<
  TData,
  TVariables
>;

export type ChildDataProps<
  TProps = {},
  TData = {},
  TGraphQLVariables = OperationVariables
> = TProps & DataProps<TData, TGraphQLVariables>;

その他

  • walkTreeのサポートが無くなる
  • preactのサポートがなくなる
  • composeもなくなる
    • 中身がlodashのflowRightと同じなので各自で定義してねとのこと

昔から思っていた不満がどんどんなくなってきていて、最高になってきているので今後も楽しみ。

初めてソシャゲというものをやった

TL;DR

昨日、初めてやったソシャゲに5万円近く投資した結果、ガチャでは出ずに、天井だった。


事の発端は、昨日の深夜である。あまりにも何もやる気が出なかったのでYouTubeに行ったら、とある動画がサジェストされた。 まぁ暇やし見るかって気持ちで見たらそれはソシャゲのガチャを回している動画だった。そう、プリンセスコネクトだ。

この動画は、今期間限定のガチャを回していたのだが、そのピックアップキャラのアニメーション動画があまりにも自分に謎フィットしてしまった。そして、気付いたらアプリをダウンロードしていた。内部データー関連で1時間ぐらいダウンロードにかかった気がする。

最初にチュートリアルをやった。長かった。本当に長かった。多分目的がガチャを引くということしか頭になかったと思う。そして、ガチャの画面まで自由に移動できる状態になった。まずは最初に数回引けるぐらいのジュエルをもらう。ジュエルというのはガチャを回す金のソフト版言い回しだ。回した、出ない。

とりあえずジュエルの購入を見に行ったら、通常価格が9800円でジュエル5000個だが、3回だけ9800円でジュエル7500個じゃないか。これはお得だ。と思い買った。(この時点で麻痺)。1回回すのにジュエルが150個いる。つまり、7500個持ってても、50回しか引けない。つらい。。

10連で回すことの問題点は、1つの画面で10個一緒に出てくる。これにより1つづつ処理しないから重みが安い。出なければ次、出なければ次の繰り返しが意図も簡単に実現される。これも金銭感覚を狂わせる。

購入は大変シンプルで、指紋認証をすれば1万円が吹っ飛ぶ。これは自分にとってカード支払いより金銭感覚が狂ってしまう。頼む! 購入手前でもう一言ぐらいなにか言ってくれ。「1万円だよ?諭吉だよ?」って指紋認証前に言ってくれたら現実に戻れた可能性もあるかもしれない。

ソシャゲには天井と呼ばれる物があるらしい。このソシャゲだと1回ガチャすると1溜まり300溜まるとピックアップキャラと交換できるのだ。すごい、救済処置だ。つまりジュエルが45000(150 * 300)個あればキャラが手に入るのだ。なぞの安心感とともに、金銭感覚が麻痺していく。自分の場合、初めたばっかだったのでクエストとかでジュエルが手に入りやすかったので結果的に安くはなった。ガチャを引いている途中から感情が変わっていく。これだけ課金したし、天井もあるし出さんと勿体無いなって。

ガチャで出るわけもなく、こうして、300溜めてキャラを交換した。そして、謎の満足感があり今に至る。自分は育成が好きなのでもしかしたら長続きするかもしれないし、しないかもしれない。自分は後から知ったことだが、確率は低いが、本当はリセマラで目的のキャラを出せるらしい。

今回、課金したことに後悔はしてない。学びがあった。ソシャゲとはどういうものかということと、自分がハマるとどうなるかという自分の側面を見てれてよかった。

ソシャゲの課金は程々にね!!

以上。

Markdownだけでスライドを作るCLIの大規模アップデートをした

前回の記事

blog.hiroppy.me

この一週間は、自分のOSSのみをやっていました。
そして、以下の機能を実装したのでまとめて紹介したいと思います。

スターが3000越しました🌟 やった!

github.com

変更点

機能追加

  • zero-config
    • initして設定ファイルを生成する必要がなくなった
    • SEOやスライドの設定するときは生成してください
  • mdxの利用が可能に
    • markdownの中でreact componentsを扱う
  • Live Modeの追加
    • 登壇中にツイートをリアルタイムで流せる
  • SidebarのUIを変更
    • 全体的に色を落とした
  • スライドの動きをタイムラインとして可視化
    • いつページ送ったかわかる
  • Recording Modeの追加
    • 音声を録音して、登壇時の状態を再現できる

内部

その他

  • 画像がwebpになったため、IESafariでは画像が表示されなくなった
    • ブラウザ対応はやる気が出たら。(<picture> に変換するのが大変)
  • 本番ビルドの最適化が行われた
  • repositoryのurlを自動的に特定するように変更
  • name -> title へキー名を変更(互換のためnameも使用可能)
  • :smile: -> 😁 の変換が可能になった

mdxの追加

github.com

Markdownの中で、Reactコンポーネントを書けるようになりました。
これにより、Markdownではできなかったモジュールの再利用性が高まり共通化が行いやすくなります。

以下の例は、styled-componentsを使った例です。

<!-- index.mdx -->
import { Sample } from '../scripts/sample';

<Sample />
// sample.js

import React from 'react';
import styled, { keyframes } from 'styled-components';

const rotate = keyframes`
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
`;

const Rotate = styled.h2`
  display: inline-block;
  animation: ${rotate} 3s linear infinite;
`;

export const Sample = () => <Rotate>Hello from jsx!!</Rotate>;

f:id:about_hiroppy:20190624054258p:plain

スライド: hiroppy.github.io

Live Modeの追加

登壇中にツイート等を流したい場合は、このモードで起動するといいと思います。
Twitterからのツイートと fusuma本体のapiエンドポイントがあるので2つの方法でスライドに投稿が可能です。

fusuma本体がサーバーとして起動します。
もし、本体のapiを使いたい場合はどこかのサーバーにデプロイするかprivate ipに投げてください。

Twitterの取得はローカルホストからで大丈夫ですが、起動時にapiを叩くために開発者トークンを要求します。

# create .env
$ npx fusuma init -s live
# add keys to .env
$ npx fusuma live -w '@nodejs'
$ open http://localhost:3000

f:id:about_hiroppy:20190622191242p:plain

SidebarのUI変更

f:id:about_hiroppy:20190622190850p:plain

全体的に青を中心に見やすい感じにしました。

登壇者の行動をトラッキングする

lacolacoの案です。ありがとうございます。

いつスライドを切り替えたかの記録を取れるようになりました。
この機能はタイマーを開始してからリセットするまで記録します。

f:id:about_hiroppy:20190622192057p:plain

音声を録音してタイムトラベルする

上記に加えて、音声を録音することが可能となりました。
WebRTCに対応しているchrome, firefoxのどちらか使ってください。
これにより、上記のタイムラインと音声が紐付き登壇時と同じ状態を再現できます。

また、シークバーを動かすとその時のスライドに戻れるため登壇の練習にも役に立つと思います。

機能を試したい場合は、サンプルで確認できます。

hiroppy.github.io

左下のをクリックして、その後に上にあるディスプレイのマークをクリックするとプレゼンターモードになります。
その後に下にあるマイクのアイコンをオンにしてスタートすれば録音が開始されます。

パフォーマンス

本番ビルドでのパフォーマンスが向上されました。
FusumaではPWAの対応は行いません。(SWぐらいは入れてもいいけど、manifestは予定なし)

これ以上はgithub pagesとライブラリに依存した問題で修正ができないためこれが最高の値となります。
あとやれることは、purgecss-webpack-pluginで使ってないcss消すぐらいかな?

f:id:about_hiroppy:20190624053009p:plain

https://github.com/hiroppy/fusuma#performance

さいごに

かなり良くなってきているのでぜひ使ってみてください。
バグ・機能追加等のフィードバック、PRをお待ちしております。

github.com

ついったー

twitter.com

テストの実行時間を2倍速くした話

webpack-dev-serverのテストを高速化しました。
jestを使っていて、--runInBandを今までは使っていましたが、それを外しました。

--runInBand

jestはデフォルトでワーカーを使い並列実行を行います。
しかし、このオプションをつけるとそれが直列実行できます。

理由としては、serverのlistenするテストが多く、mochaで書かれていたため、急にjestに移行してもコード自体が並列実行できるものではなかったからです。

PR

github.com

このPRはベネチアで書かれました:)

結果

直列実行

Test Suites: 1 skipped, 49 passed, 49 of 50 total
Tests:       9 skipped, 419 passed, 428 total
Snapshots:   152 passed, 152 total
Time:        113.313s, estimated 173s
Ran all test suites.

並列実行

Test Suites: 1 skipped, 49 passed, 49 of 50 total
Tests:       9 skipped, 419 passed, 428 total
Snapshots:   152 passed, 152 total
Time:        60.5s
Ran all test suites.

約2倍、速くなりました🎉

戦略

当たり前ですが、ポートを富豪的に使うことにより、並列実行をさせます。

ポートマップ

当初は個数じゃなくて、ポート番号にしてたのですが、柔軟性がなかったため、個数に変更しました。
また、この書き方だと手書きのミスによる重複が絶対に発生しません。

const portsList = {
  cli: 2, // cliのテストでは、2個ポートを使う
  sockJSClient: 1, // ファイル名が小文字だったので、別PRで直す
  SockJSServer: 1,
  Client: 1,
  ClientOptions: 3,
  MultiCompiler: 1,
  ...
};

let startPort = 8079;
const ports = {};

Object.entries(portsList).forEach(([key, value]) => {
  // no-plusplusの影響で ++ はなし
  ports[key] =
    value === 1
      ? (startPort += 1)
      : [...new Array(value)].map(() => (startPort += 1));
});

module.exports = ports;

// const [port1, port2] = require('./ports-map')['cli'];

起動時

jestには、初回起動時に一回だけ実行できる、globalSetupというキーが存在します。
そして、起動時に使用するすべてのポートが空いているかを確認するスクリプトを実行させます。

// jest.config.js globalSetup: '<rootDir>/globalSetupTest.js'

// globalSetupTest.js

// node.jsのネイティブでポート確認するのめんどくさいのでこのモジュールを使う
const tcpPortUsed = require('tcp-port-used'); 
const ports = require('./test/ports-map');

async function validatePorts() {
  const samples = [];

  Object.entries(ports).forEach(([key, value]) => {
    const arr = Array.isArray(value) ? value : [value];

    arr.forEach((port) => {
      const check = tcpPortUsed.check(port, 'localhost').then((inUse) => {
        if (inUse) throw new Error(`${port} has already used. [${key}]`);
      });

      samples.push(check);
    });
  });

  try {
    await Promise.all(samples);
  } catch (e) {
    console.error(e);
    process.exit(1);
  }
}

module.exports = validatePorts;

これで、テスト実行前にテストで使用するすべてのポートが使用されていない場合のみテストを実行できるようになります。

テストコード

各テストが以下のようにポートを引用すれば競合しないです。

const [port1, port2, port3] = require('../ports-map').ClientOptions;

ただ、ポート込みのsnapshotを取っている場合、結構更新が走りやすいので注意してください。
一つ真ん中とかでポート増やすとずれ込みます。

さいごに

まず、serverが起動する多くのテストを並列実行する場合のポート管理のベストプラクティスがいまいちわからないです。
サーバー起動ばっかりやってる参考になるプロダクトがあったら教えてください。

一旦、他のメンテナの意見を待ちつつもっといい方法があれば考えます。
この戦略は、レビューによって変わる可能性があります。

けど、一番何が言いたかったかって言うと、並列実行はCPUをめっちゃ使うけどくっそ速くなるぞ!!!ってことでした。(当たり前)

追記

Node6をサポート対象外にしたので、async/awaitが使えるようになりました😁

PRを出したユーザーのブランチにpushする方法

久しぶりにPR出した人のブランチ使おうと思ったら忘れてしまったのでメモ。
例えば、PRがinactiveになるとかrebaseするとかこちらで修正入れてしまう時とかに行います。

cloneする

これはめんどくさいので自分はやらないです。

help.github.com

remoteを登録する

# 相手先を自分のローカルに登録
$ git remote add reviewee url # urlは相手先のurl
# ローカルの状態を更新
$ git fetch reviewee
# remote-prは相手のPRのブランチ名, 自分のローカルのブランチ名はなんでもいい
$ git checkout reviewee/remote-pr -b local-pr
# ブランチが古い場合は、masterからrebaseする
$ git rebase master local-pr # or $ git pull --rebase origin master
# 相手のブランチにpush
$ git push -f reviewee local-pr # or $ git push -f reviewee remote-pr
+ f4e919b...a3b06dc local-pr-> remote-pr (forced update)

上記を実行すると、PRに反映されます。
これは、そのリポジトリのwrite権限があるメンテナならできます。

activemq.apache.org