プログラミング2021年振り返り
プログラミング勉強
全体
自分はフロントエンド得意だと思っていたのですが、「標準のHTMLのフォームでデータ送信するときってどんな感じでやり取りするんだっけ?」「mimeTypeやHeadersって知っているけどどういう仕組みだっけ?」「Web Workerって?シングルスレッドって?」「PWAってどういうものだっけ?」と基礎的なことで詰まっていたため、改めてMDNのHTTPのドキュメントを読んでサンプル書いて勉強していました。
ブラウザで使えるAPIについても目について面白そうなJavaScriptのAPIやCSSをCodesanboxで書いて試していました。 Codesandboxを使った理由として、WindowsやiPhoneなどの別環境での挙動確認だったりHTTPSでしか動かないAPIを試すために毎回デプロイするのは面倒なので書いてすぐ公開、Webブラウザ上(=閲覧環境)で編集できるためです。
JSのライブラリを使わず素のJSで書いて解決できるとありがたい場面は業務でも時々あるのでこういう素振りは役に立ちました。
アウトプットのリポジトリ
業務では経験できないことをちゃんと学習しようと思ってフロントエンドを中心にサンプルをザクザク作っていました。
Vite と Redux の学習のために作ったリポジトリ。 このあたりからViteを使い始めたのですが、既存プロジェクト以外はViteで開発するのが主力になるんじゃないかと思う程ビルドが高速でConfigも楽で快適だと思いました。
去年Next.jsで作った映画を公開順で閲覧するためのサイトですが、GitHub Actions で CI を回せるようにしました。 CI/CD に入門するためにGitHub Actionsを使いましたが、本当に無料でいいの?というぐらい機能が豊富で簡単です。 公式ドキュメントを読んでもいいし、テンプレートを選んでそこからカスタムしてもOK。
Next.jsのStatic Generateを学習するためにSpace XのAPIを使って作ったサイトです。 外部リンクからの画像最適化ってどうすればいいんだっけ?と頭を悩ませましたがVercelにデプロイする前提で解決しました。 Next.jsはReactベースのフレームワークで、ドキュメントに従って書けばなんでもよしなにやってくれてかなり便利な反面、ECMAScriptの挙動やTypeScript、ReactのHooksの書き方など、基礎的な箇所は意識して勉強しないといいコードにはなりませんね。
仕事
現在の会社に転職して2年8ヶ月経過しました。 転職前は2年半在籍していたのが最長だったので、今まで一番長い期間在籍しています。
前々からTwitterで呟いていたのですが、転職を考えています。
理由としては自分がフロントエンドが比較的得意なのですがそれを活かせる場面がなく、テストコードやCIもなく開発体制もかなり古いというのがずっと続いているためです。 自分である程度社内を改善しなきゃダメだと思って社内研修をするなど頑張っていましたが、あんまり改善できなかったのと、上司から評価されなくて、疲れて諦め気味です。
30歳になったのですが、まともな業務での開発経験がRedmineとWordPressのプラグイン、VBAぐらいしかありません。 そのため、実務でフロントエンドができていないことがコンプレックスになりかけています。
そういう状況で趣味の学習を元手にフロントエンドの開発職を狙うのは絶望的かもしれませんが、頑張っていきたいです。
2022年やりたいこと
- フロントエンドのトレンドのキャッチアップ
- Rustの学習入門、ライブラリ作成
- npmのライブラリの公開パイプラインの構築
- BFFの構築とGraphQL入門
- AWSやGCPなどのインフラ構築
- テストコードいっぱい書く
Rustに関してはJavaScriptのツールチェインがRustベースで書かれていることが最近増えてきたのと、TypeScriptは便利だけどそれ以外の型付きの言語を学習してみたいのと、WASMのパフォーマンスを調べてみたいためです。
npmに関してはnpm publishコマンドで公開してみたのですが、ヒューマンエラーが起きそうで怖いのでGitHub Actionsでmainブランチにマージされたらpublishできるパイプラインを構築して本番に近い形で公開できる方法を練習したいです。 ただ個人で開発しているライブラリならnpm publishで公開になりそうです。
テストコードですが個人プロジェクトだと書く気がどうしても起きないのですが、業務で保守性の高いコードを書くには必要なので、個人勉強の範囲で練習して慣れておきたいです。
まとめ
フロントエンドは移り変わりが激しいと言われていたのは昔で大体ReactかVueかその派生フレーワークを使うことがトレンドで固まっていて、今後もその流れは強くなると思います。 ただReact 18の新機能やSvelteの登場、Web版PhotoshopでWeb ComponentsのライブラリであるLitが使われているそうなので、最先端の部分では動きがあるんじゃないかと思っています。
ただ自分はまだ業務では素のJSかjQueryしか書いたことがないので、Reactを使う現場に転職できるように頑張ります。
もしこれを読んでいるをReactを使ったフロントエンドエンジニアを募集しているところがあったらお声掛けしていただけるとありがたいです。
良いお年を!
Inputタグにアップされた画像ファイルのプレビューを オブジェクト URLと データ URL でやる方法で書き比べてみた
動機
アップロードした画像を使って画像を加工するなどの処理の場合、どのように取り出して取り扱うか、というのが全然整理できていなかったのでその整理を込めてサンプルコードを書いた。
File オブジェクトや addEventListener
もあんまり書いたことがなかったので、ライブラリなしの Vanilla JS で書いてみた。
やりたいこと
input[type="file"]
に入力された画像はファイル名しか表示されないので、画像がプレビューされてほしい- fileを取り出した後に
<img>
タグに表示するためにURLに変換する必要があるが、アプローチとしてURL.createObjectURL()
を使う、FileReader
のreadAsDataURL()
を使うアプローチがあった
TL;DR
Codesandbox で試せる環境を作った。勉強も兼ねてVanilla + TypeScriptで型をつけながら書いています。
- 動作的にはどっちでプレビューしても殆ど変わりはない
URL.createObjectURL()
してオブジェクトURLを生成すると、開放しない限りはメモリ上に残り続ける- なのでオブジェクトURLが不要になったタイミング、今回の場合だと画像の読み込みが終わったタイミングで
URL.revokeObjectURL()
を実行する必要がある - 開放するとそのオブジェクトURLを使って画像を表示、というのができなくなるので、複数箇所で同じ画像を表示するなら
URL.revokeObjectURL()
を実行するタイミングを考える必要がある - データURLはURL自体にデータを持っているようなものなのでオブジェクトURLよりは比較的取り回しが楽に感じた
解説
入力したファイルの取り出し
const fileForm = document.getElementById("userFile"); function handleFiles(event: Event) { const target = event.target as HTMLInputElement; const files = target.files; // ... } fileForm?.addEventListener("change", handleFiles, false);
input[type="file"]
に対して document.getElementById("userFile").files
という形で取り出せますが、フォームへの入力が変化したタイミングで処理を行いたいので EventListener 経由でアクセスして取り出しています。
取り出したファイルデータは複数の File データを持つ FileList オブジェクトとして取り出されます。
フォームから受け取りデータが単一の File オブジェクトではなく FileListオブジェクトなのは、フォームにて複数のファイルが入力する場合があるためです。
- FileList
- change イベントでの選択されたファイルへのアクセス
file からオブジェクトURLの変換
Array.from(files).forEach((file) => { // if you would like to watch uploaded file info, remove comment // console.log(file) const image = new Image(300); image.addEventListener("load", (event: Event) => { const target = event.target as HTMLImageElement; URL.revokeObjectURL(target.src); }); const imageUrl = URL.createObjectURL(file); image.src = imageUrl; image.title = file.name; previewPoint?.appendChild(image); });
まず new Image()
にてプレビュー画像を表示するための <img>
タグを生成しています。width=300 としたいのでコンストラクターに引数として渡しています。
オブジェクトURLは不要になったら開放する必要があるため、 <img>
タグにて画像の読み込みが終わった時点で URL.revokeObjectURL()
するように EventListener で登録します。
取得したFileを URL.createObjectURL()
を通してオブジェクトURLを発行し、それを <img>
タグの src に入力し、画像の表示を行います。
最後にプレビューするポイントにプレビュー画像のタグを付与する。
- URL.createObjectURL()
- URL.revokeObjectURL()
file からデータURLの変換
Array.from(files).forEach((file) => { const reader = new FileReader(); reader.addEventListener( "load", (event: Event) => { const image = new Image(300); image.title = file.name; image.src = reader.result as string; previewPoint2?.appendChild(image); }, false ); reader.readAsDataURL(file); });
FileReader.readAsDataURL()
でファイルデータを読み込むとデータURLとして変換され、 FileReader.result
にて取り出すことが出来る。
FileReaderがFileを読み込んだタイミングで FileReader.result
にデータURLが入るため EventListener にて Event を登録する。
画像のタグを付与するあたりはオブジェクトURLと一緒。
実はこの箇所はMDNのサンプルコードのままではある。
- FileReader.readAsDataURL()
- データ URL
最後に
フォームで入力した画像を取り出して取り扱う方法を学習することができた。
動機としては React Image Crop というライブラリを使おうと思った際に、当然のようにオブジェクトURLとbase64 data が登場したが、曖昧な理解だと今後も詰まりそうだと思ったのでWeb APIの標準を通じて学習した。
https://github.com/DominicTobias/react-image-crop
上記のオブジェクトURLを使ったサンプルコードを書いた後に、MDNのサンプルに自分がやりたいのとほぼ同じ物があったのでぐぬぬ…となったが、TypeScriptで型付けるという形で発展させた。
Next.js で SG したサイトがなぜあんなに早いのか、 Chrome DevTools とコードリーディングで調べる
はじめに
静的コンテンツをCDNに乗せておくとアクセス時間が早い、という話ではなく、 Next.js を SG してブラウザで閲覧した時の挙動に絞っています。
TL;DR
- SGした際に、
getStaticProps()
経由で渡したコンテンツは_next/data/
ディレクトリ配下に JSON 形式として書き出される - 該当するローティングに達すると、 JSON を読み込み、クライアントサイドでページを書き換える
- すべてのコンテンツを一気に読み込むのではなく、ページへのリンクが表示されたら、そのリンク先のコンテンツの JSON を先読みしている
- つまり、クリックする前に既にコンテンツは読み込まれているので、クライアントサイドの書き換えしか時間がかからない
Chrome DevTools で見てみる
Next.js の公式サイトは SG で構築されています。これを例として見ていきます。 もし初めて見て場合は各リンクをクリックしてみてください。 どのページも一瞬で遷移するのを感じると思います。
Chrome の DevTools を開き、 Network タブを開き、フィルター欄に json
を入力し、一旦更新します。
するとJSONファイルが読み込まれているのがわかります。
Headers タブを開いて Request URL 見ると、サイトのルートの /_next/data/
ディレクトリ配下にこのJSONファイルが格納されているのがわかります。
Preview タブに切り替えてJSONの中身を見るとページの情報が格納されています。
実際に該当のページに遷移すると、 JSON に格納したデータと一致するのを確認できます。
つまりページ遷移 = 読み込んだ JSON を JS でページの内容を書き換えるクライアントサイドレンダリングをしていると推測できます。
コードベースで言えば下記でビルド時にファイルパスを指定したマニフェストを作成し、ページロード時のコンテンツのパスを指定していると思われます。
自作のサイトでも確認してみる
JSON の中の pageProps
の構造について確証が持てなかったので自作のサイトでも確認してみることにしました。
https://nextjs-demo-spacex-crew.vercel.app/
自分のソースコードと比較すると、 pageProps
中の構造は Next.js の getStaticProps()
で渡している構造と一致します。
つまり、ビルド時に渡したコンテンツのデータはJSONとして保存されていることが推測できます。
基本的にページコンテンツ必要になったら読み込む
ではブログの一覧画面のようなページが有り、リンク先が大量にあるようなケースでは読み込まれるJSONファイルの数が増えて、初期表示が遅れるのでは?と思いますが、その心配はありません。
Network タブを開いたまま、ページを上下にスクロールすると読み込まれる JSON ファイルが増えているのを確認できます。
ゆっくりスクロールするとわかりますが、ページのリンク が表示されたらその 遷移先のコンテンツのJSON が読み込まれます。
ユーザーが遷移先のリンクを閲覧する必要性が出てくるタイミングとして 「リンクが表示された段階」 を想定しており、そこで初めてJSONを読み込むように設計していることが推測されます。
リンクが表示された段階で既にコンテンツは読み込まれており、リンクをクリックするとクライアントサイドで書き換え、ページ内の画像やアセットの読み込みが実行されます。
これによって一気に読み込まれるJSONファイルの数を減らし、低速回線でも快適に閲覧できます。
コードベースで言えば next/link
のコードを見ると Intersection Observer API を使って監視をしているのが確認できます。
(useIntersection
という Hooks 経由で使用しています)
※ちょっと脱線しますが、 next/image
も useIntersection
を使って監視しています。
最後に
Next.js は基本的にマニュアルどおりに作成すれば適宜最適化され、表示速度の早い Web サイトを簡単に作成できます。
素の軽い HTML ページに比べて、初回の表示は微妙に劣ることはありえますが、2ページ目以降は先読みした JSON を元にクライアントサイドレンダリングするのが基本のため、ページ切り替えが早いです。
コードリーディングした箇所についてはあまり自身がないので、間違っていたら指摘お願います。
Next.js で SG したサイトがなぜあんなに早いのか、 Chrome DevTools とコードリーディングで調べる
はじめに
静的コンテンツをCDNに乗せておくとアクセス時間が早い、という話ではなく、 Next.js を SG してブラウザで閲覧した時の挙動に絞っています。
TL;DR
- SGした際に、
getStaticProps()
経由で渡したコンテンツは_next/data/
ディレクトリ配下に JSON 形式として書き出される - 該当するローティングに達すると、 JSON を読み込み、クライアントサイドでページを書き換える
- すべてのコンテンツを一気に読み込むのではなく、ページへのリンクが表示されたら、そのリンク先のコンテンツの JSON を先読みしている
- つまり、クリックする前に既にコンテンツは読み込まれているので、クライアントサイドの書き換えしか時間がかからない
Chrome DevTools で見てみる
Next.js の公式サイトは SG で構築されています。これを例として見ていきます。 もし初めて見て場合は各リンクをクリックしてみてください。 どのページも一瞬で遷移するのを感じると思います。
Chrome の DevTools を開き、 Network タブを開き、フィルター欄に json
を入力し、一旦更新します。
するとJSONファイルが読み込まれているのがわかります。
Headers タブを開いて Request URL 見ると、サイトのルートの /_next/data/
ディレクトリ配下にこのJSONファイルが格納されているのがわかります。
Preview タブに切り替えてJSONの中身を見るとページの情報が格納されています。
実際に該当のページに遷移すると、 JSON に格納したデータと一致するのを確認できます。
つまりページ遷移 = 読み込んだ JSON を JS でページの内容を書き換えるクライアントサイドレンダリングをしていると推測できます。
コードベースで言えば下記でビルド時にファイルパスを指定したマニフェストを作成し、ページロード時のコンテンツのパスを指定していると思われます。
自作のサイトでも確認してみる
JSON の中の pageProps
の構造について確証が持てなかったので自作のサイトでも確認してみることにしました。
https://nextjs-demo-spacex-crew.vercel.app/
自分のソースコードと比較すると、 pageProps
中の構造は Next.js の getStaticProps()
で渡している構造と一致します。
つまり、ビルド時に渡したコンテンツのデータはJSONとして保存されていることが推測できます。
基本的にページコンテンツ必要になったら読み込む
ではブログの一覧画面のようなページが有り、リンク先が大量にあるようなケースでは読み込まれるJSONファイルの数が増えて、初期表示が遅れるのでは?と思いますが、その心配はありません。
Network タブを開いたまま、ページを上下にスクロールすると読み込まれる JSON ファイルが増えているのを確認できます。
ゆっくりスクロールするとわかりますが、ページのリンク が表示されたらその 遷移先のコンテンツのJSON が読み込まれます。
ユーザーが遷移先のリンクを閲覧する必要性が出てくるタイミングとして 「リンクが表示された段階」 を想定しており、そこで初めてJSONを読み込むように設計していることが推測されます。
リンクが表示された段階で既にコンテンツは読み込まれており、リンクをクリックするとクライアントサイドで書き換え、ページ内の画像やアセットの読み込みが実行されます。
これによって一気に読み込まれるJSONファイルの数を減らし、低速回線でも快適に閲覧できます。
コードベースで言えば next/link
のコードを見ると Intersection Observer API を使って監視をしているのが確認できます。
(useIntersection
という Hooks 経由で使用しています)
※ちょっと脱線しますが、 next/image
も useIntersection
を使って監視しています。
最後に
Next.js は基本的にマニュアルどおりに作成すれば適宜最適化され、表示速度の早い Web サイトを簡単に作成できます。
素の軽い HTML ページに比べて、初回の表示は微妙に劣ることはありえますが、2ページ目以降は先読みした JSON を元にクライアントサイドレンダリングするのが基本のため、ページ切り替えが早いです。
コードリーディングした箇所についてはあまり自身がないので、間違っていたら指摘お願います。
Next.js と外部 API で作成した静的サイトをデプロイしようとしたら、画像最適化を考えて結局 Vercel にデプロイした
TL;DR
経由
Next.jsの getStaticProps
と getStaticPaths
を使ったStatic Generationの素振りをするために作成しました。
- 作ったもの
- リポジトリ
悩んだ末に結局 Vercel デプロイ
最初は Vercel にロックインはしたくないと思い、 next export
で静的に書き出して firebase
や Netlify
にアップしようとしたしたのですが、 next export
を使用する時は next/image
が基本的に使えません。
なので、<img>
タグで置き換えようかと思ったのですが、結局下記の理由で Vercel にロックインしてもいいので Vercel にデプロイすることにしました。
- 今回のように外部 API を利用する場合はビルド時に画像を最適化するのは難しい
- せっかく
Next.js
で高速な静的サイトを作成するのに、画像の表示がネックになるのは意味がないのでは? - Vercel のドキュメントを読んだら、外部 URL の画像も最適化して配信されることが判明
- Vercel にデプロイするなら、
next export
しないのでnext/image
がそのまま使えて楽
Next.js は Vercel が作っているから親和性がいいのだろうと思っていましたが、真面目に考えると悩ましい画像の最適化・配信も担ってくれるので本当にありがたいです。
外部の API に含まれている画像 URL を next/image
で使用する時の注意点
- 表示する URL のドメインを調べて
next.config.js
に追記しておく必要があります。 - もし画像のアップロード先のドメインが複数ある場合はそれらを列挙する必要があります。
- もしドメインがない場合はエラーが出るので
next.config.js
に追記して dev server を再起動してください。
公式ドキュメント
https://nextjs.org/docs/api-reference/next/image#domains
今回、自分の場合は下記のように書きました。
/** @type {import('next').NextConfig} */ module.exports = { reactStrictMode: true, images: { domains: ['imgur.com', 'i.imgur.com', 'images2.imgbox.com'], }, }
JavaScript で小数点以下を桁数指定して四捨五入するなら、parseFloat() と toFixed() を組み合わせればよい
TL;DR
第一引数に四捨五入する値を、第二引数に小数点以下の桁数を指定する関数を定義する。
/** * Rounding number with digit * Example: * round(3.14159265359, 2) = 3.14 * @param {number} value * @param {number} digits default : 0 * @returns {number} rounded number */ function round(value, digits = 0){ return parseFloat(value.toFixed(digits)) } // Math.E = 3.141592653589793 round(Math.PI, 3) // expected output: 3.142 round(Math.PI) // expected output: 3 Math.round(Math.PI) // expected output: 3
解説
JavaScriptで四捨五入するための関数として Math.round()
があった。
ただしこれは、引数として与えられた数字を四捨五入して整数部分しか表示できない。
四捨五入後に、整数部分だけでなく、小数点以下の桁数取り扱う場合は下記の関数を組み合わせる必要がある。
まず toFixed()
で桁数を指定して整形する。このとき、戻り値が文字列なので、 parseFloat()
によって小数点を含んだ文字列に変換する。
3.141592653589793.toFixed(3) // expected output: "3.142" parseFloat("3.142") // expected output: 3.142
厳密にやる場合はparseFloat()
の値に対して Number.isNaN()
で判定すれば良いと思われる。
余談
自分がこの方法を調べたとき、元の数字に100を掛けて Math.round()
した後に再び100で割る、という方法がかなり出てきて直感的に「なんかスマートじゃない」と思った。
なので、調べてみたところ、Math.jsが提供している round()
関数では自分がやりたいことに近かったので、該当する実装箇所 を参考にした。
他にも参考になりそうな実装があるので数値処理をしたい方は Math.js のソースを読めば良いと思います。
「Reactを自作しよう」をやってみた & Babelの設定の復習
動機
以前から React を自作しよう を見かけてやりたいと思っていたが GW でやっとまとまった時間が取れたのでじっくり取り込んだ。
React をよく個人的に使うので、その設計と実装を経験したほうが React への理解が深まるのでは?と思った。
TL;DR
作業結果は下記のリポジトリにアップした。
https://github.com/ryokryok/practice-diy-react
感想
記事の中で実装したのは下記の React の関数を簡素化したものだった。
React.createElement
React.useState
ReactDOM.render
実際手を動かした見た所感は下記。
- 本当に 1 からの実装して数百行のコードで React の関数を置き換えるので面白かった
/** @jsx Didact.createElement *
とコメントを書くだけで JSX の変換関数が置き換わるのを知ったFiber
という作業単位によって効率よくレンダリングするというのが理解できた- JSX が取りうる構造を想定しながらそれを実装に落とし込む方法が理解できた
- HTML の要素が親子関係、または兄弟関係を想定するなど
- どのように差分検出するのか、というのを理解できた
- 更新か、追加か、削除かなどを判定するためにどのような実装
- 型がない JavaScript での実装なので、作業中にどのような Object の実装を目指しているのかちょっとわかりにくかった
環境構築
翻訳版にも元記事にも環境構築はなく、悩んだが Babel で JSX を JS に変換したときの挙動を確認する手順があったため、開発用にBabel + Webpack でセットアップして、変換確認用に script を設定した。
Babel のプラグインに関してはただ単純に変換した結果を見たいため、@babel/preset-react
だけ入れた。
通常なら "@babel/preset-env"
も入れるが今回はパス。
{ "presets": ["@babel/preset-react"] }
package.json
にて scripts を追加して yarn transform
すればJSX を JS の関数に変換した結果が temp
ディレクトリーに展開される。
"scripts": { "transform": "babel src -d temp", "dev": "webpack serve", "build:dev": "webpack --mode=development", "build": "webpack --mode=production" },
記事中にもあったがコメントで @jsx Didact.createElement
と書くと JSX の変換用関数を別のものに指定できる。
(標準では React.createElement
が指定される)
公式では下記に記載がある。例として Preact の変換用関数の記載がある。
@babel/plugin-transform-react-jsx
後は Webpack を入れて、 babel-loader で変換するようにするだけ。Webpack の設定は素朴なので詳細はリポジトリを見てください。
自分以外にやってみた人の記事を見ると create-react-app
したのを eject していた。
https://blog.shibayu36.org/entry/2021/04/15/173000