Cybozu Frontend Monthly #5
イベント概要
サイボウズフロントエンドマンスリー は、サイボウズ社内で行っているフロントエンド情報共有会「フロントエンドウィークリー」の公開版です。
その月に気になったフロントエンドの情報を、サイボウズのフロントエンドエキスパートチームのメンバーが共有していきます。
このイベントのハッシュタグは #サイボウズフロントエンドマンスリー です。
※フロントエンドウィークリーとは
毎週火曜の 17:00 〜 18:00
で社内向けに行っているフロントエンドの気になる記事を紹介する会です。2016年3月15日から行われています。
ハッシュタグ
#サイボウズフロントエンドウィークリー
で実況しています。
zennのpublicationにてウィークリーのまとめも投稿していますので、ぜひこちらもご覧ください。 https://zenn.dev/p/cybozu_frontend
開催日
2020年11月24日
イベントページ
https://cybozu.connpass.com/event/196103/
配信URL
https://www.youtube.com/watch?v=x6mhjoYoGMo
メンバー
コンテンツ
We rendered a million web pages to learn how the web breaks
- 共有者:b4h0_c4t
Web ページがどのようにして壊れるかを学ぶため、実際に発生しているエラーを調査、考察した記事。
トップ 100 ドメインのルートページ 100 万件をレンダリングするスクリプトを使用して出力される未処理エラーを調査した結果、85%が ReferenceError
TypeError
SyntaxError
。
また、上記のエラーが実際に発生している理由として、そのほとんどがリソースの読み込み失敗に起因していると述べています。
- How to resolve ReferenceError
- ReferenceError の多くはライブラリが生成したグローバル変数を読み込む時に発生している。
- What causes TypeError on live web site?
- TypeError の 97%は null もしくは undefined に起因している。
- What causes SyntaxError on live web site?
- 開発中に遭遇する SyntaxError のほとんどは typo。デプロイされている Web サイトの場合はほとんどがネットワーク障害か JS のプログラム生成の失敗に起因している。
ほとんどのエラーと強く関連しているコードは webpack
で、サンプルにした Web ページの 12%が 1 件以上のエラーを発生させている。
エラーの出にくい Web サイトを作るためには静的検査が欲しいが、遅延バインディングの必要性もあってなかなか難しい。 その点、TypeScript は検査時と実行時で性質を完全に分離しているので良いトレードオフかもしれない。 最近は WebAssembly が登場し、JS レスで静的検査に寄せられる土台ができつつあるが、実行時にランタイム外との対話が必要なことにわ変わりなさそう。
ランタイムの動的性質を許可しつつ、部分的に静的検査によって安全性を担保することが壊れにくい Web ページを作る鍵になるかもしれないと締めていました。
Performance · microsoft/TypeScript Wiki
- 共有者:sakito
この wiki 自体は TypeScript チームがコンパイル速度や実装の体験をよくするための情報として、去年ごろに公開していました。
今年の秋ごろ「Writing Easy-to-Compile Code」の項目が追加されています。 この項目はコンパイルパフォーマンスがいい書き方を示してくれています。
項目は下記 3 つに別れています。
- Preferring Interfaces Over Intersections
- Type を使った Intersection Types よりも interface の extends のほうがいいとのこと。
- Using Type Annotations
- 関数の戻り値の型は、明示的に型を定義したほうがコンパイル時間の節約になる。型推論は便利なので、コンパイルのボトルネックとなってる場合には試してねとのこと。
- Preferring Base Types Over Unions
- Union Type を使用するよりも sub types を使った方がよいとのこと(例のコードを見た方がわかりやすい)
interface と Type に関する記事は最近「TypeScript: Prefer Interfaces」も出ており、 Type はインライン化されるので d.ts のサイズが肥大化するから interface を推奨するという内容の記事になっています。 これは d.ts を書く場合に覚えておくとよさそうです。
TypeScript の Type か interface を使うか、型推論に任せるかは時と場合によるというのを前提での紹介でした!
Vue Ref の糖衣構文と Svelte
- 共有者: nakajmg
vue の rfc にref
の糖衣構文である(Svelte inspired な)ref:
構文についての rfcが出されました。この記事ではメリデメや Svelte との比較などについて書かれています。
rfc の背景
vue ではリアクティブな変数を定義するためにref()
という関数を使います。
import { createComponent, ref } from "vue";
export default createComponent({
setup() {
const count = ref(1);
function inc() {
count.value++;
}
console.log(count.value);
return {
count,
inc,
};
},
});
ref()
で作成した変数の取得/更新はcount.value
のように.value
を経由する必要があります。TypeScript を採用していれば.value
を経由しないで参照しようとするとエラーなりが出るので問題ないですが、そうでない場合はref()
で作られた変数と通常の(reactive じゃない)変数を区別する必要があります。
提案された構文
この.value
を使うのが面倒で冗長だよねというところから次のような糖衣構文が提案されています。
<script setup>
// declaring a variable that compiles to a ref ref: count = 1 function inc(){" "}
{
// the variable can be used like a plain value
count++
}
// access the raw ref object by prefixing with $ console.log($count.value)
</script>
ラベル付き文の構文を使って reactive な変数を定義し、.value
なしに参照しています。
このコードはコンパイルされて 👆 にあるようなコードに変換されます。
JavaScript のセマンティクスとトレードオフ
.value
を経由するようになっているのは JavaScript の言語的な制約からきているもので、vue は JavaScript のセマンティクスを変更/拡張することなくリアクティブを実現するためにこうしています。
今回の rfc は、JavaScript のセマンティクスから逸れてると認識しながらも、開発者の体験を向上させるためにトレードオフを行う余地があるのではという考えから提出されました。
感想
自分は次の理由からあまりいい提案ではないように思います。
- vue 独自の構文である
- どんどん増えていきそう
- JavaScript エコシステムへの負荷(Linter とかエディタ)
- 本来のラベル付き文から逸脱している
- コンパイル前後のコードに差異が結構ある
この糖衣構文によって開発者の体験が短期的には向上するかもしれませんが、解決したい課題に対してデメリットが上回っていると感じています。(この rfc に限らず vue が<script setup>
など、独自の糖衣構文的なものを追加してくる傾向があること自体にあまりいい印象を持っていないです。)
How Web Apps Work | Mark’s Dev Blog
- 共有者:toshi-toma
Redux のメンテーであるMark Eriksonによる、Web 開発の概念や用語、ツール、技術について説明するシリーズ。Web 開発は様々な技術が出てくるので、それらがなぜ必要なのか、どう関係してるのかを説明してる。そして、必要に応じて深く調べれるように別記事への導線も用意されている。
個人的に、Redux のドキュメントの英語読みやすいなーと思ってたので、網羅的に書かれていて、かつ読みやすい記事でありがたいなと思いました。
テーマごとに全 5 記事ある
- HTTP and Servers
- HTTP とアプリケーションサーバーの話
- 触れてる内容: HTTP, IP アドレス, DNS, HTTP リクエスト, HTTP レスポンス, ヘッダー, HTTP メソッド, ステータスコード, クッキー, アプリケーションサーバー(リクエストの処理、ルーティング、静的ファイル、動的な Web アプリケーションサーバー)
- Client Development and Deployment
- JS メインのフロントエンド開発の話
- 触れてる内容: JS の歴史, JS モジュールフォーマット, コンパイル(トランスパイル), バンドル, 開発環境ツール(Node.js), デプロイ, ポリフィル, Code Splitting
- Browsers, HTML, and CSS
- ブラウザと HTML, CSS の話
- 触れてる内容: ブラウザ, メジャーブラウザ, HTML, タグや属性, セマンティクス, CSS, 構文とセレクタ, ページレイアウト(ボックスモデル, display, position, flexbox, grid), レスポンシブ
- JavaScript and the DOM
- JS の幅広い基本的な内容と DOM の話
- 触れてる内容: ECMAScript, JS の概要(構文, データタイプ, 組み込み関数, this, Class, タイマーとイベントループ, 非同期処理), イミュータブル, DOM, jQuery
- AJAX, APIs, and Data Transfer
- クライアントとサーバー間でデータをやり取りする話
- 触れてる内容: JSON, Ajax, ポーリング, WebSockets, CORS, REST, RPC, GraphQL, ブラウザストレージ, クライアントルーティング
tc39/proposal-js-module-blocks
- 共有者: @sosukesuzuki
JS Modules Blocks は先日の TC39 ミーティングで Stage 1 になった プロポーザルです。
このプロポーザルは、ファイルを隔てずにモジュールを定義できるインラインモジュール式(InlineModuleExpression
)という新しい構文を導入します。
const m = module {
export const foo = "hello";
}
この module {}
という部分が新しく導入される構文です。式なので、結果をそのまま値として扱うことが可能です。
ここで定義されたm
は import(...)
で import できます。
const m = module {
export const foo = "hello";
}
const m1 = await import(m);
console.log(m1.foo); // hello
まだ Stage 1 なのでこれから仕様が大幅に変更される可能性はありますが、実用性もあり面白い提案なので、注目しておくと良さそうです。
WebAssembly ハンズオン: 実際に動かして基礎を学ぶ(翻訳)
- 共有者: @zaki___yama
原文は 8 月頃に投稿された Hands-on WebAssembly: Try the basics — Martian Chronicles, Evil Martians’ team blog という記事。
ハンズオンと書いてますが手を動かす成分は少なめ。環境構築も Docker で提供されてるものを使うので楽です。(何をインストールしてるのかわかりづらいというのはある)
内容としては
clang
を使った素朴なやり方で C のソースを wasm にコンパイルするwasm-objdump
で wasm の中身を確認する- Bynarien (
wasm-opt
) で wasm のバイナリサイズを圧縮する - WebAssembly Binary toolkit (wabt) で wasm を wat に変換し、中身を確認する
- Emscripten でコンパイルしてみる
- 同じことを Rust でやってみる(ライブラリを使ったやり方)
という感じで、C メインで Rust はおまけ程度。
大部分は C で wasm 書く予定がなければ必要ない情報かなーと思いましたが、個人的には
- 「コンパイラの即席入門(Compilers 101)」で説明されてるコンパイラの概要 (コンパイラはソースコードをパースして一旦内部表現(IR)に変換する。WebAssembly はどんなブラウザでも理解できる内部表現という位置づけ)
- 「ドラゴンのはらわた(Dragon guts)」
- テキスト形式の
.wat
ファイルの読み方
- テキスト形式の
あたりが面白かったです。
元の C の関数
int sign(int x) { return (x % 2) * (2 - (x % 4)); }
を wat にすると、
(func (;1;) (type 0) (param i32) (result i32)
i32.const 2
local.get 0
i32.const 4
i32.rem_s
i32.sub
local.get 0
i32.const 2
i32.rem_s
i32.mul)
となり、これは
- 整数値
2
をスタックに push する(i32.const 2
) - 関数の最初のパラメーターをスタックに push(
local.get 0
) - 整数値
4
をスタックに push(i32.const 4
) - スタックから 2 つの値を削除して 1 番目の値を 2 番目の値で割った余りをスタックに push する(
i32.rem_s
)
が x % 4
に相当します。
Dropping Support For IE11 Is Progressive Enhancement · The Ethically-Trained Programmer
- 共有者: @pirosikick
IE11 をサポートする労力を JavaScript を無効にしているユーザーのために使ったほうがいいんじゃないかという話。
- IE11 は減少傾向だが、JS 無し環境は無くならない
- JS をダウンロードしている間はみんな JS 無し環境
- 広告ブロッカーの普及によって、JS 無し・JS が壊れている人は一定数居る
- QA チームがない場合、問い合わせが無いだけで IE11 で壊れているかも
- Babel/Autoprefixer などによるトラスパイルだけでは不完全で、API の polyfill、IE11 特有のバグへの対応など、IE11 対応するなら IE11 で QA する必要がある
- IE11 サポートする代わりに、script タグの module/nomodule を使って Progressive Enhancement しよう
- nomodule 側はできる限る JS を少なくするべき。
- 理想は全く無しだが、実際は広告や Analitics だけ JS 使うことになりそう
- Analitics に IE11 ユーザーが出続ける限り、ちゃんと動いているはず
- JS 無しユーザーのために特別な実装するほうが大変じゃない?
- 基本は、JS 無しユーザー用のクラス(
.has-old-js
)を使って、要素の表示・非表示するだけ - 最近の JS は十分にモジュール化されているはず
- 基本は、JS 無しユーザー用のクラス(
- 筆者の具体例
- モーダルが IE11 では出ない、静的なレイアウトへの切り替え、検索機能は Google に送信するフォームへ切り替え
※余談:ブログが「The Ethically-Trained Programmer(倫理的に訓練されたプログラマ)」で favicon が「倫」で笑った
Use CSS Variables instead of React Context | Epic React by Kent C. Dodds
- 共有者: @pirosikick
CSS in JS の React Context を使ったテーマ機能(例:styled-components の ThemeProvider)より、CSS Variables を使おうという話。
- 開発者体験的には違いはない(個人的には CSS Variables のほうがシンプルで好き)
- テーマ切替時に再描画するコンポーネント数が React Context のほうが多い
Building a type checked url router from scratch
- 共有者: @shisama_
SPA のルーティングに設定する URL の型を TypeScript 4.1 で新しく追加された Template Literal Types を使って検査する方法の紹介です。
Template Literal Types についてはAnnouncing TypeScript 4.1の記事で紹介されています。
記事中の型宣言から一部抜き出すと次のように Template Literal で文字列から infer で値を抜き出すことが出来ます。
type TemplateVariables<T> = T extends `${infer Start}/:${infer Variable}`
? TemplateVariables<Start> | Variable
: never;
type param = TemplateVariables<"/api/users/:id">;
// param = "id"
この記事の最終的な目的はコンポーネントに紐付けたパスのパラメータとコンポーネントの Props の型が同じになるようにしたいということです。
記事中から一部抜粋した例をあげます。 以下のようなコンポーネントがあったとき、
type MyDogProps = {
name: string
breed: string
}
const MyDog = ({ name, breed }: MyDogProps): string =>
`<p>My dog's name is ${name} and it's a ${breed}</p>
<p>${Link({ route: routes.home, children: "Back to home" })}</p>`
以下のようなルーティング定義をしておきます。
const routes = {
home: route([Home, "/"]),
dog: route([MyDog, "my-dog/:name/:breed"]),
search: route([Search, "search/:query"]),
};
注目してほしいのが、my-dog/:name/:breed
のname
とbreed
です。これえは MyDogProps のキーと同じです。なので、たとえば以下のように MyDogProps のキー名が違う場合エラーになります。
type MyDogProps = {
name: string;
breed_name: string;
};
const routes = {
dog: route([MyDog, "my-dog/:name/:breed"]),
};
// breed というプロパティはないためエラー
また、Props には定義されているのにパスには存在しない場合もエラーになります。
type MyDogProps = {
name: string;
breed: string;
age: string;
};
const routes = {
dog: route([MyDog, "my-dog/:name/:breed"]),
};
// age がないためエラー
逆に Props にないパラメータをパスに含めてもエラーになります。
type MyDogProps = {
name: string;
breed: string;
};
const routes = {
dog: route([MyDog, "my-dog/:name/:breed/:age"]),
};
// Propsにageはないためエラー
このようにコンポーネントが期待するパラメータが正しく渡ってくることを事前にチェックすることができます。