React で簡単に遅延読み込みを実装できるライブラリを作りました

はじめに

どうも、さぃと (@saitoeku3) です。

突然ですが、あなたは自作のライブラリを公開したことがありますか?
僕は公開したいと思いつつもなかなかできずに年を越してしまいそうだったので、思い切って公開しちゃいました。

ネタは最近よく使っている遅延読み込みと Gatsby.js + Contentful 関連の2つあったのですが、今回は前者の方です。(後者もそのうち公開します)

作ったもの

github.com

返り値の ref を任意のエレメントの ref に渡すだけで遅延読み込みをしてくれるカスタムフックを利用できるライブラリです。React での使用を想定しています。

今までにもいくつか遅延読み込みをしてくれるライブラリはあったのですが、コンポーネントベースで利用するものが多くて使いづらかったという問題を解決しています。(強引な押し売り)

特に差別化できると思っているのは、isLoaded という読み込みが終わると false から true に切り替わる変数を使えるところで、プレースホルダーを出して UX を改善するときにも使えます。他にも CSSbackground-image に対応していたりと便利なライブラリになっています。

対応しているエレメントは imgiframevideoaudio の4つで background-image についてはその他の HTML エレメント全てで使えるはずです。

import React from 'react'
import useLazyloadRef from 'use-lazyload-ref'

const Component = ({ url }) => {
  const [ref, isLoaded] = useLazyloadRef()

  return (
    <>
      {isLoaded || <Skeleton />}
      <img ref={ref} data-src={url}>
    </>
  )
}

内部実装

画面に入っているかの検知は IntersectionObserverref の使い方は「コールバック形式の ref」を使用しました。やっていることは割とシンプルで次のようになっています。

  1. マウント時に任意のエレメントを IntersectionObserver で監視させる
  2. 画面に入ったことを IntersectionObserver が検知する
  3. 任意のエレメントの data-srcsrc に渡す
  4. 読み込みが終わると onload で読み込みが終了したことを検知して isLoadedtrue にする

ただし CSSbackground-image を遅延読み込みする場合は少し特殊なことをします。理由は CSS だと onload で読み込みの検知ができないからです。そのため、次のように1度 img を生成して img で読み込んだ後に background-image を更新するという手を取りました。

const url = target.dataset.src || ''
const img = new Image()
img.src = url
img.onload = () => {
  target.style.backgroundImage = `url(${url})`
  setIsLoaded(true)
}

参考

おわりに

今回は自作ライブラリの紹介と解説をしました。

フロントエンドの UX 改善やパフォーマンスチューニングに使えるものなので、そのあたりに興味のある方は実際に使ってみていただけると嬉しいです。不具合や質問があればすぐに対処するので連絡ください。

最後に GitHub でスターをつけていただけると励みになるのでお待ちしてます。

github.com

初めて Elm に触れたので初心者向けに特徴と感想を紹介する

本エントリは Elm Advent Calendar 2019 16日目の記事です。

はじめに

どうも、さぃと (@saitoeku3) です。
Elm 界隈が温かいという話を聞いて軽い気持ちでアドカレに登録したのですが、ゴリゴリの技術記事ばかりなので緊張しています。

さて、今回は普段 React や Vue.js を書いている僕が Elm に触れて感想を紹介する記事です。そのため Elm に慣れている方よりは「これから Elm に挑戦してみたいからザックリと知りたい」という初心者向けになります。

きっかけ

僕が Elm を触ろうと思ったきっかけは周囲に Elm 好きが多かったからです。

今年度に入ってからなのですが、たまたまイベントやインターン等で知り合った人の中に Elm 好きがいたり、夏のインターン先が Fringe81 (プロダクションで Elm を採用している) だったりして普段から「Elm はいいぞ」という話をよく聞いていました。

今まで触れたことのない技術に触れるのは多少ハードルを感じるものなのですが、Elm に関してはすぐ聞ける人がいるので心配はありませんでした。仮に自分に合わなかったとしても、今回の経験をもとに Elm の良さを少しでも吸収できれば嬉しいです。

触れてみた

公式チュートリアルの日本訳 を読みながら手を動かしていました。

もともと Elm は 関数型であることJavaScriptコンパイルする言語であること公式キャラクター?がヤギであること しか知らなかったのでかなり勉強になりました。ここからは初心者が Elm を触って驚いたことや良いと思ったことを紹介します。

オブジェクト指向じゃない

関数型なので当たり前ですが、オブジェクト指向の常識が通用しません。
例えば、リスト (JavaScript の配列に似ているもの) の操作をしたいときは List モジュールを使います。JavaScript の場合は配列に生えているメソッドを呼び出して利用していたので新鮮な気分でした。

['apple', 'melon', 'orange'].length // => 3
List.length ["apple", "melon", "orange"] -- => 3

状態管理にパターンマッチングを用いる

チュートリアルの序盤にこんなコードがありました。

type alias Model = Int

init : Model
init =
  0

type Msg = Increment | Decrement

update : Msg -> Model -> Model
update msg model =
  case msg of
    Increment ->
      model + 1

    Decrement ->
      model - 1

view : Model -> Html Msg
view model =
  div []
    [ button [ onClick Decrement ] [ text "-" ]
    , div [] [ text (String.fromInt model) ]
    , button [ onClick Increment ] [ text "+" ]
    ]

React を書いたことがある人の多くはぱっと見でも既視感を感じるのではないでしょうか。
そうです。Redux の Reducer にそっくりですよね。

Redux の用語で説明すると次のようになります。

  • init: State の初期値 (Model 型)
  • Msg: Action のタイプ
  • update: Reducer

話をもとに戻すと、Elm では想定される状態更新されるパターンを事前にすべて MsgModel に定義するという特徴があります。そして、そのパターンを関数型でよく見られるパターンマッチング構文を用いて適切に処理します。

JavaScript で状態を更新するときは更新するための関数を作ってイベントハンドラに登録するのが一般的なので、コードが膨れていくと予期せぬ状態更新が行われてバグの原因になることがあります。しかし、Elm は事前にパターンを明示するお約束になっているので、安全に状態の更新ができるというメリットを感じました。

ランタイムエラーがない

チュートリアルの冒頭で紹介されているように Elm は実行上ランタイムエラーが出ないらしいです。本当か?と疑心暗鬼になって読み進めていると、どうやらエラーもデータをして扱うことでアプリがクラッシュするのを防いでいるようでいた。この秘密は Maybe 型と Result 型にあります。

Maybe 型は名前の通り「ちょうど1つの値を持っているか」「何も持っていないか」の2パターンがありえる型です。

Result 型は失敗する可能性がある処理で「処理が成功したか」「失敗した場合はなぜ失敗したのか」を表現するのに適している型です。

両者は先ほど紹介したパターンマッチング構文と相性が良くて、次のような例が載っていました。

type Error
  = BadUrl String
  | Timeout
  | NetworkError
  | BadStatus Int
  | BadBody String

-- Ok "All happy ..." : Result Error String
-- Err Timeout        : Result Error String
-- Err NetworkError   : Result Error String

どのエラーが起きたか、そのエラーに対してどんな処理をするべきかを明示的に表現できるのは素敵ですね。

おわりに

ここまでにざっと Elm の特徴の一部に触れた感想を紹介しました。
以前 Rust を触ることがあって関数型の魅力はほんの少し知ったつもりでいたのですが、改めて魅力を感じることができてよかったです。フロントエンドでもこの魅力を強みとして発揮できる Elm は面白いですね。(割と気に入ってる)

本来は Web アプリをサクッと作ったほうが使用感の比較までできてよかったのですが、しばらく忙しいので年末にチャレンジしてみます。
明日は @ababup1192 さんがモバイル UI と Elm について書かれるのでお楽しみに!!

Node.js に初めてのコントリビューションをしている話

本エントリは Node.js Advent Calendar 2019 14日目の記事です。

はじめに

どうも、さぃと (@saitoeku3) です。
今日はあまりにも家を出たくなかったので、ピザとコーラのデリバリーをして幸せになっていました。

さて僕は普段から Node.js でバックエンド APISSR サーバを書いたり、Electron から child_process で FFmpeg を叩いたりしているのですが、どれを思い返しても割と普通の使い方しかしていなかったので最初はネタに困っていました。

それでも何かできることがないか探していたところ、前から Node.js にコントリビューションしたかったけれど踏み出せていないことを思い出して、この際だからチャレンジすることにしました。

きっかけ

少し前になるのですが、2019年2月に Yahoo Japan が開催したインターンシップの「Node.js 黒帯・OSSデベロッパーコース」に参加していました。そこでは、大津さん (Node.js の TSC Emeriti) や 栗山さん (Node.js の黒帯)、伊藤さん (現在フロントエンドの黒帯) から Node.js の仕組みや OSS に貢献する意義を1週間かけて学びました。

当時は Node.js 初心者どころかプログラミングを本格的に学び始めてから数ヶ月しか経っていなかったのですが、非同期処理の話から実際にアプリケーションを作るところまで非常に丁寧に教えていただけて最高の体験ができました。

もう察している方もいるかもしれませんが、このインターンの名前に OSS とあるように最終的には「Node.js にコミットできるといいよね」という話が期間中にありました。しかし、まだ Node.js への理解が浅いことや期間が短いことで実現しませんでした。

それから1年弱が過ぎ、ある程度 Node.js に触れてきた今だからこそ当時やり残した Node.js への貢献に挑戦してみました。

何に取り組むのか決める

まず最初は何に取り組むのか決めるために good first issue を調べていました。しかし、意外と初心者には難しそうなものが多かったり、既に誰かがやる流れになっていたりして良さげなものは見つかりませんでした。そこで、OSS 貢献はドキュメントやテストから始めると良いと言われているのを思い出して調べ直すと次の issue が見つかりました。

Documentation for http.message.url suggests using deprecated url API #30048

内容は http API についてのドキュメントの message.url にある URL をパースする例の中で非推奨となっている url API が紹介されているというものでした。

こんな内容です。

$ node
> require('url').parse('/status?name=ryan')
Url {
  protocol: null,
  slashes: null,
  auth: null,
  host: null,
  port: null,
  hostname: null,
  hash: null,
  search: '?name=ryan',
  query: 'name=ryan',
  pathname: '/status',
  path: '/status?name=ryan',
  href: '/status?name=ryan' }

この issue で指摘されているように v11.0.0 からは DEP0116: Legacy URL API に書かれているように、require('url') で使用する API が非推奨になって、代わりに WHATWG URL API の使用を推奨しています。

修正点がわかりやすかったので、この issue に取り組むことにしました。

image

取り組み始めるまで

取り組みたい旨を伝えたのは良いのですが、始めるまではしばらく待つ必要がありました。理由はおそらく修正内容をどうするべきか @nodejs/url で議論されていたからだと思われます。待っている間は少し不安な気持ちになったのですが、3週間程度経つと「こんな感じで修正するといいよ」とアドバイスを頂けました。

(ちなみにこの間に shisama さんNode.jsへのコントリビュート解説、そしてOSSへ貢献するということ が公開されたので手を合わせながら読んでいました。)

PR を送る

PR を送る前には、大津さんが書かれた CONTRIBUTING の翻訳CODE_OF_CONDUCT を読んで最低限のマナーを把握するようにしていました。OSS に貢献するつもりが逆に迷惑をかけていたら悲しいですよね…😢

実際にドキュメントの修正を始めてからは以下のことに気をつけました。

  • 既存の内容と齟齬がないか
  • 英語は適切か
  • コミットメッセージはわかりやすいか

今回修正する箇所は API 自体が変更されているので内容が変わるのは仕方ありませんが、本来書かれているべきことを消してしまうのは修正ではなくてただの削除です。もとの非推奨な API でできたことを新しい API でもできることを確認してから修正しました。

また、Node.js に限らず多くの OSS は英語が標準です。Issue や PR での議論やコミットメッセージは英語で書かなければなりません。正直ここまでなら多少雑だったり間違えたりしていても大丈夫ですが、ドキュメントで間違った英語を使うのはさすがにマズイのでいつも以上に Google 翻訳で意味が伝わるか確かめていました。

送った PR: doc: update message.url example in http.IncomingMessage #30830

まだ Merge はされていないため貢献できた!とは言えないのですが、ついに一歩踏み出すことができました 🎉

追記 (2019/12/18) Merge されました!

おわりに

もともと OSS 活動は熟練したプロの開発者がしているイメージを持っていたので「自分なんか…」と敬遠していた時期もありましたが、今回は思い切って PR を送ってみました。修正箇所はごくわずかでも Node.js という広く使われている OSS に関われたのは素直に嬉しいです。

周りの学生の話を聞くと、やはり僕と同じように OSS 活動にハードルを感じている人は多いようです。しかし、少し勇気を振り絞るだけで世界中の優秀なプログラマと一緒に活動できるのは素晴らしいことですよね。

いきなりコードベースでの貢献をするのは難しくても、僕のようにドキュメントの修正から始めることもできます。もし、本エントリを読んで OSS 活動に興味を持たれた方がいたら一緒に挑戦しましょう!

Wantedly のサマーインターンに参加してきた

はじめに

どうも、さぃと(@saitoeku3)です。
大学生はもう夏休みが終わる時期ですが、皆さんは楽しい夏を過ごせたでしょうか? 僕は 8/19 - 8/30 の 2 週間 Wantedly のサマーインターン (フロントエンドコース) に参加してきました。
自分にとっては初めての就業形インターンだったこともあり、多くの刺激や学びを得られたので簡単に紹介させていただきます。

www.wantedly.com

会社の印象

サポーターズの面談や選考の面談では技術の話が中心だったので、参加前は技術ゴリゴリゴリラの息苦しい企業かもしれないと思っていたのですが、参加してみると大きく印象が変わりました。
実際に訪れると話しやすい人が多くて過ごしやすかったです。
知り合いの学生も何人か来ていたので、勝手にオフ会みたいな気分にもなっていました。

あとミーティングで使う部屋等に JOJO 関連の名前が付けられているのが個人的に好きでした。 ゴールドエクスペリエンスでランチを食べたり、ブチャラティMacBook の画面を映したりしていました。

インターンでやったこと

僕は Wantedly Visit で企業が利用する管理画面の開発をしていました。 現在 Visit ではマイクロサービス化やフロントエンドの刷新を行っている最中で、次のようなモダンな技術を採用しています。

  • TypeScript
  • React (Hooks, Function Component)
  • GraphQL (Apollo Client)
  • styled-components

GraphQL を除けば、どれも僕が普段から使っている技術だったこともあってスムーズに開発を進められました。 そのおかげで初日に PR を出して翌日にリリースすることができたのは嬉しかったです。

https://www.wantedly.com/companies/wantedly/post_articles/182854

他には「募集作成ページのバリデーション周り」や「スカウトページのコンポーネント作成」をしていました。 既にリリースされているものもあるので、Visit の管理画面を触る気機会があれば僕を感じながら使ってみてください!

参加してよかったこと

高度なコードレビュー

就業型のインターンやプロダクションレベルの開発が初めてだったため、プロのコードレビューを受けたのは初めてでした。レビューをしていただけたお陰で、自覚できていなかった未熟な部分を知れたのが嬉しかったです。

様々な方と協力しながらの開発

今回のインターンでは Zeplin というデザインツールを用いてコンポーネントの開発をしていました。(デスクトップ版だとデザインを透過して重ねながら確認できるので体験がよい)

普段は脳内妄想や軽いメモをもとに雑に実装するのですが、プロのデザインは疎かにできないので慎重に実装していました。ホバー時やクリック時の変化など Zeplin を見るだけでは判断しづらい部分もあったので、デザイナーさんに相談しながら実装することも多々ありました。

また、Wantedly Visit は海外にも展開しているので UI のラベルの翻訳も必要でした。Wantedly では GitHub 上で翻訳を依頼する基盤が整っており、僕も実際に翻訳を依頼していました。

GraphQL を用いた快適な開発

GraphQLFacebook が開発した「スキーマファーストの Web API を実装するためのクエリ言語」です。僕がジョインしたプロジェクトでは、GraphQL のクエリをもとに BFF (Backend For Frontend) からのレスポンスの型定義を自動で生成する仕組みがあったので、驚くほど快適に開発できました。

詳しくは

www.wantedly.com

に書かれています。

自由に作業できる環境

僕は毎日大好きな音楽や KMNZRADIO を聴きながらコードを書いていました。眠たいときはお昼寝をして良いとも言われていたので、ちゃんとしたアウトプットがあれば割と自由に過ごせる印象を受けました。

ランチが美味しかった

オフィスが白金台にあるので毎日 1000 円超えが当たり前の美味しいランチを食べていました。特に麹屋は 2 週間で 3 回行くほど最高なので皆さんも足を運んでみてください。

以下ランチの写真

f:id:sathoeku:20190915221011j:plain

f:id:sathoeku:20190915221058j:plain

f:id:sathoeku:20190915221201j:plain

f:id:sathoeku:20190915221235j:plain

f:id:sathoeku:20190915221414j:plain

おわりに

わずか 2 週間 (他の学生より 1 週間短い) という期間でしたが、温かく迎え入れていただいた皆さんのお陰で楽しく濃い毎日を過ごすことができました! 同じチームの方々 (特にメンターの富岡さんと沢山レビューをしてくださった原さん) や人事の方々には感謝でいっぱいです。

一緒だったインターンのみんなもありがとう!!!!

他の参加者のブログ

pixiv の梅雨インターンシップで最高の体験をしてきた

f:id:sathoeku:20190529175758p:plain

はじめに

どうも、さぃと(@saitoeku3)です。
5/25 - 5/26 の2日間、pixiv さんの梅雨インターンシップ(Web コース)に参加してきたので、内容や感想をざっくり紹介します。

インターンの概要

今回のインターンは、就業型ではなくハッカソン形式でした。
参加者はメンターとペアでチームを組み、「あなたの考える最高の pixiv を開発する」というお題に沿って2日間で1つのアプリを作り上げます。
期間中は実際に運用されている pixiv の API を使うことができました。

エントリーした経緯と選考

このインターンTwitter を見ているときに知りました。
他の企業は夏のインターンの募集を開始する中、pixiv さんは他と被らないような時期にも開催するようだったので、迷わずエントリーすることを決めました。(メンターの中に知っているエンジニアがいたのも決め手だったりする)

選考では、書類選考を選びました。GitHub 選考でも良かったのですが、どちらを選んでも GitHub を見られる気がしたという理由があります。実際に、面談中は GitHub を見せながら話をしていたので、大は小を兼ねるという意味でも書類選考で間違いなかったです。

インターン中の様子

僕は subal(@f_subal)さんとチームを組んで React + TypeScript を用いた Web アプリケーション開発を行いました。普段から愛用している構成だったこともあり、スムーズに始められてよかったです。(事前にプロジェクトの雛形を用意してくださった subal さんに感謝)

開発環境が整うと、すぐにアイデア出しを始めました。今回のインターンで一番よかった体験はここで、僕がぼんやりと考えていたアイデア言語化できるまで subal さんは何度もアイデアの詳細や実装したい理由等を問いかけてくれました。

このサポートのおかげで、最後までブレずに開発を続けられたと言っても過言ではありません。 そして、以下のような技術的な部分でも何度もサポートしていただき、無事に作りたかったアプリを形にすることができました。

  • createPortal を用いたコンポーネントの構築
  • useRefuseEffect の効果的な使い方
  • border-image の使い方
  • コンポーネントを共通化するときのプラクティス
  • マウスカーソルの座標計算を利用したリッチな UI の実現(キラキラが追従するアレではない)
  • Firebase Functions を用いた SSR による OGP 画像の生成(頭がおかしくなって最後の1時間で強引に実装した)

作ったもの

著作権の都合上、以降の画像に含まれるイラストにモザイクをかけています

f:id:sathoeku:20190529171706p:plain
よかったイラストをシェアするアプリ「yokatta」

アプリを作るときのコンセプトは、「シェアをキッカケにして pixiv 上のまだ見ぬ素晴らしいイラストや絵師に出会うという体験を生み出す」 にしました。

イデアのもとは Apple Music のプレイリストです。僕は以前、大学の先輩からシェアされたプレイリストがキッカケで今まで知らなかったアーティストに出会って幸せになったことがありました。同じ体験を pixiv でも生み出したいと言う思いからコンセプトを決定しました。

コンセプトを満たすために、アプリの設計ではユーザーの流れがアプリ内で完結しないことを気をつけました。具体的には、全てのページでイラストを見られるようにして、気になったイラストがあればすぐに pixiv のイラストやユーザーのページに移動できるようにしています。

ユーザーは自分が見てよいと思ったイラストをリストにして他の人にシェアすることができます。OGP 画像を設定しているので、ユーザーはアプリを開く前からイラストを楽しむことができます。(これが音楽では不可能な強みだったりする)

f:id:sathoeku:20190529171841p:plain
使用例(伝われ!)

結果

2日間の努力の結果、なんと優勝することができました!!

話をうかがうと、発表中に説明した制作意図が明確かつ実現できていることを評価していただけたようでした。最初の段階でアイデアのベースを固めて、その実現のために妥協をしなかったおかげです。

一方で、発表後のフィードバックでアプリの改善点をいくつか知ることができました。その中で強く改善したいのは、「ユーザーがより楽しめる UI/UX の構築」です。他のチームのプロダクトには、豊かなアニメーション表現やデザインやレイアウトの工夫が多く見られましたが、yokatta には明らかに不足していました。今後の開発の課題です。

感想

今回のインターンでは subal さんを始めとした多くの方々のサポートのおかげで、最高の体験をすることができました。
また、他の参加者を見ているとみんな素晴らしい技術と発想力を持っていてキラキラしていました。皆これからどんどん伸びていくと思うので、(競うわけではないけど)僕も負けたくないです。
2日間貴重な体験をさせていただき、本当にありがとうございます!!