メルマガ全文サンプル
2022年05月26日に発行された、CodeGrid 488号のメルマガ全文のサンプルです。
CodeGrid 488号 (2022年05月26日発行) ━━━━━━━━━━━━━━━━━ CodeGrid 488号 (2022年05月26日発行) フロントエンドに関わる人々のガイド https://www.codegrid.net/ ━━━━━━━━━━━━━━━━━ このメールはCodeGridの購読手続きをしていただいた方へ配信しています。 ――――――――――――――――― 【 お知らせ 】直近の人気シリーズ一覧ができました ――――――――――――――――― CodeGridのWebアプリに、人気シリーズの一覧ページができました。 https://www.codegrid.net/popular-series/monthly/ これは、直近1ヶ月間または1年間で「お気に入り数」が多いシリーズが順に並んでいるものです。 「どれから読んだらいいかな」「他の購読者はどんな記事に興味を持っているのかな」というときに、ぜひ参考にしてください。 ――――――――――――――――― 【 お知らせ 】2022年6月の配信予定 ――――――――――――――――― 6月の配信日は、次の4回になります。 6月2日(木)、9日(木)、16日(木)、23日(木) 6月30日(木)の配信はありません。ご了承ください。 ――――――――――――――――― 目次 ――――――――――――――――― 【 HTML/CSS > HTML/CSSの実践 】 工程を説明するUIの制作:順番の数字と画像、説明文を配置する https://www.codegrid.net/articles/2022-making-procedure-ui-1/ 手順を追って説明するUIのHTMLとCSSを考えます。工程ごとの順番を示す数字は、CSSのルールによって増減して、その回数をカウントできるCSSカウンターを利用してみましょう。 (フロントエンド・エンジニア 坂巻 翔大郎) 【 JavaScript > ECMAScript 】 複数の非同期処理を扱う場合の4つのメソッドの違い(最終回):Promise.race() https://www.codegrid.net/articles/2022-multiple-promise-3/ JavaScriptで非同期処理を組み合わせて使うための4つのメソッドのうち、Promise.race()を解説します。応用例として、タイムアウトの処理を取り上げます。 (フロントエンド・エンジニア 宇野 陽太) 【 Jamstack > ホスティング 】 ホスティングサービスCloudflare Pages(第3回):公開サイトの振る舞い https://www.codegrid.net/articles/2021-cloudflare-pages-3/ Cloudflare Pagesの公開サイトの振る舞いと、実際に一般公開するサイトのための設定について解説します。公開サイトの振る舞いの中には、知っていると便利な機能だけでなく、知らないと戸惑う動作もあります。 (Jamstackエンジニア 中村 享介) ================= 【 HTML/CSS > HTML/CSSの実践 】 工程を説明するUIの制作:順番の数字と画像、説明文を配置する ================= 手順を追って説明するUIのHTMLとCSSを考えます。工程ごとの順番を示す数字は、CSSのルールによって増減して、その回数をカウントできるCSSカウンターを利用してみましょう。 (フロントエンド・エンジニア 坂巻 翔大郎) ◆アプリURL◆ https://www.codegrid.net/articles/2022-making-procedure-ui-1/ ◆目次◆ - はじめに - 作成するもの - 工程部分のHTML - 見出しと各工程を横並びにするCSS - 工程ごとの順を示す数字のCSS - おまけ:任意の開始番号を指定する - まとめ ――――――――――――――――― はじめに ――――――――――――――――― この記事では、「手順を追って説明するUI」にはどのようなHTMLとCSSが適しているのかを考え、解説していきます。例として「料理の工程」を取り上げますが、サービスの登録フローを説明や道順などにも応用できます。 ――――――――――――――――― 作成するもの ――――――――――――――――― 作成するもののデザインは、次のとおりです。 /* 図 */ https://cdn.codegrid.net/2022-making-procedure-ui/assets/img/01.png 「作り方」という見出しのあとに、料理の工程を一覧します。各工程では順番を示す数字・調理の様子がわかる画像・説明文があります。工程の部分は料理レシピサイトでよく見られる構成になっています。サイトによっては順番を示す数字の近くに、その工程の概要になるような小見出しが入る場合もありますが、今回は入らないものにしました。 簡単ですが、要件をまとめておきます。 * 工程の数は制限なしで、最小は1工程 * 各工程の順番を示す数字は1から始まる * 各工程の画像はない場合がある * 画面幅に応じた列数になり、最大3列。画面幅を狭めると画面幅に収まるだけの列数になる * 説明文の長さは制限なし 特に難しいことはないと思うかもしれませんが、どのような点に気をつけてHTML/CSSを実装すればよいか解説していきます。 ――――――――――――――――― 工程部分のHTML ――――――――――――――――― まずはHTMLの全体像を見てみましょう。 ```html <h2>作り方</h2> <ol> <li> <img src="./img/recipe-01.png" alt="写真:10cmに切られた大根" width="200" height="151"> <span>大根を10cm程度に切ります。</span> </li> <li> <img src="./img/recipe-02.png" alt="写真:皮をむかれた大根とむかれた皮" width="200" height="151"> <span>大根の皮を剥きます。</span> </li> <li> <img src="./img/recipe-03.png" alt="写真:縦に4等分された大根" width="200" height="151"> <span>縦に4等分に切ります。</span> </li> <li> <img src="./img/recipe-04.png" alt="写真:5mm幅に切られた大根" width="200" height="151"> <span>切り口を下にして、端から5mmくらいの幅で均等に切ります。</span> </li> <li> <span>大根のいちょう切りの完成です</span> </li> <!-- 以下工程が続く... --> </ol> ``` 順を追って解説します。 ## 見出しのマークアップ ```html <h2>作り方</h2> ``` 「作り方」の部分はh2要素を使用しますが、周りの文脈次第で適切な見出しレベルに調整します。 ## 工程のマークアップ 次に工程を一覧する部分です。 ```html <ol> <li> <img src="./img/recipe-01.png" alt="写真:10cmに切られた大根" width="200" height="151"> <span>大根を10cm程度に切ります。</span> </li> ``` この工程の一覧は、一から順を追って説明するものですから、順序付きリスト要素であるol要素を使用します。 各工程の画像には適切なalt属性・width属性・height属性を指定します。alt属性値には写っているものを客観的に捉えた文章を考え指定しました。width属性とheight属性は画像が読み込まれたときにガタつかないように、なるべく指定するようにしています。 説明文はなんらかのスタイルをあてる可能性を考慮して、span要素で囲っておきます。 HTMLは以上ですが、デザインにある「工程ごとの順を示す数字」はHTMLには含めていません。その数字は後述するCSSカウンターを使用して出します。 スタイルがあたっていない状態をデモで確認しておきましょう。 /* デモ:スタイルを当てていない状態 */ ソースコード: https://github.com/codegrid/2022-making-procedure-ui/blob/main/1.html https://codegrid.github.io/2022-making-procedure-ui/1.html 次はCSSを考えていきます。 ――――――――――――――――― 見出しと各工程を横並びにするCSS ――――――――――――――――― CSSでは、見出し・各工程を横並びにする部分と、順番を示す数字部分を分けて解説します。 まずは見出し・各工程を横並びにする部分です。 ```css /* 見出し */ h2 { margin: 0 auto 20px; font-size: 28px; max-width: 680px; /* 列幅 * 3 + 列間のgap * 2 */ } /* 工程の一覧 */ ol { display: grid; grid-template-columns: repeat(auto-fill, 200px); gap: 40px; list-style: none; max-width: 680px; /* 列幅 * 3 + 列間のgap * 2 */ margin: 0 auto; padding: 0; } /* 各工程 */ li { display: flex; flex-direction: column; gap: 10px; } /* 各工程:画像 */ li img { border-radius: 4px; } /* 各工程:説明文 */ li span { font-size: 14px; line-height: 1.5; } ``` 順を追って解説します。 ## 見出しのスタイル まずは見出しの部分です。 ```css /* 見出し */ h2 { margin: 0 auto 20px; font-size: 28px; max-width: 680px; /* 列幅 * 3 + 列間のgap * 2 */ } ``` 見出しの頭の位置と、工程の頭の位置を揃えるために、後述の工程の一覧と同じ最大幅を指定して中央寄せにしています。 ## 一覧のスタイル 次に、工程の一覧部分です。 ```css /* 工程の一覧 */ ol { display: grid; grid-template-columns: repeat(auto-fill, 200px); gap: 40px; list-style: none; max-width: 680px; /* 列幅 * 3 + 列間のgap * 2 */ margin: 0 auto; padding: 0; } ``` CSS Gridを使用して200pxの列が複数できるようにしています。最大幅を680pxとしているため、最大で3列収まります。画面幅によっては2列、1列と変化します。ol要素の持つリスト番号や余白は不要なので消しておきます。 ## 各工程のスタイル そして、各工程の部分です。 ```css /* 各工程 */ li { display: flex; flex-direction: column; gap: 10px; } /* 各工程:画像 */ li img { border-radius: 4px; } /* 各工程:説明文 */ li span { font-size: 14px; line-height: 1.5; } ``` 各工程の部分では画像やテキストが縦方向に1列で並んでいるので、Flexboxを使用します。各要素間の余白はgapプロパティで指定します *。 ここまでの様子をデモで確認しましょう。 // * 均等な余白を想定 もし工程にほかの要素が増えたときに上下間の余白が他の要素と異なるようなことがある場合は、それぞれの要素に余白を指定したほうが良いでしょう。今回は均等な余白を想定しているのでFlexboxとgapプロパティを使用しています。 // /* デモ:各工程を並べた状態 */ ソースコード: https://github.com/codegrid/2022-making-procedure-ui/blob/main/2.html https://codegrid.github.io/2022-making-procedure-ui/2.html //// Flexboxでgapプロパティが使えない場合 Flexboxでgapプロパティが使えるようになるのはSafari 14.1以降です。それ以前の環境に対応する必要がある場合は、CSS Gridで次のようにしても良いでしょう。 ```css li { display: grid; align-content: start; gap: 10px; } ``` //// ――――――――――――――――― 工程ごとの順を示す数字のCSS ――――――――――――――――― li要素(display: list-itemの要素)は、デフォルトでビュレットやリスト番号などの記号や文字を持っています。それらは::marker疑似要素*を使うことで色や大きさを調整できますが、適用されるプロパティには制限があります。ですので、デフォルトのリスト番号は消してしまい、CSSカウンターを使用して改めてリスト番号を出します。 // ::marker疑似要素 ::marker疑似要素は、display: list-itemが指定されている要素に対してのみ使用できる疑似要素です。文字列や画像以外の指定 | contentプロパティ再入門(#1)にも登場します。 #1: https://www.codegrid.net/articles/2019-css-content-2/#toc-5 // 具体的には次のようなCSSです。 ```css ol { /* 省略 */ counter-reset: item; /* 追記1 */ } li { /* 省略 */ counter-increment: item; /* 追記2 */ } li::before { content: counter(item); /* 追記3 */ } ``` 新たに追記したものはCSSカウンターというものです。CSSカウンターはCSSが管理する変数のようなもので、CSSのルールによって増減し、その回数をカウントしています。 CSSカウンターに関連するプロパティはいくつかありますが、counter-resetプロパティとcounter-incrementプロパティをおさえておけば問題ないでしょう。counter-resetプロパティを使うことで任意のカウンター(変数)を初期化します(デフォルトでは0が入る)。そしてcounter-incrementプロパティで任意のカウンターの数値を増減できます。そのカウンターを画面に表示するためにはcounter関数を使用します。 上記のコードを解説すると、次のようになります(「追記1」「追記2」「追記3」は、コード中のコメントの箇所に対応)。 * 追記1:ol要素が登場したらitemカウンターが0になる * 追記2:li要素が登場するたびにitemカウンターが1増える * 追記3:itemカウンターを画面に表示させるためには、疑似要素のcontentプロパティの値としてcounter関数を使用する 次のように表示されます。 /* デモ:工程ごとの数字を出した */ ソースコード: https://github.com/codegrid/2022-making-procedure-ui/blob/main/3.html https://codegrid.github.io/2022-making-procedure-ui/3.html これで、あとは番号の部分の背景色などのスタイルをつければ完成です。 ```css li::before { align-self: flex-start; display: flex; align-items: center; justify-content: center; border-radius: 4px; padding: 10px 12px; line-height: 1; background-color: #518300; color: #fff; content: counter(item); } ``` /* デモ:工程ごとの数字にスタイルを当てた */ ソースコード: https://github.com/codegrid/2022-making-procedure-ui/blob/main/4.html https://codegrid.github.io/2022-making-procedure-ui/4.html 冒頭で挙げた要件を満たしていることはもちろん、ol要素を使っていることでアクセシビリティに配慮できています。支援技術を使ってこの「作り方」を見ていくと、全行程がいくつあり、いまどの工程を見ているかがわかります。 もしdiv要素を使って同じものを作った場合それはわかりません。ですので、適切な要素を選択することは重要なことです。 /* 動画 */ https://www.youtube.com/embed/RK1H1SpckGM ――――――――――――――――― おまけ:任意の開始番号を指定する ――――――――――――――――― これで要件に沿ったUIが完成しましたが、カウンターについてもう少し見てみましょう。 ol要素ではstart属性が利用でき、start属性を使うとリストの開始番号をその属性値に変更できます。たとえば、<ol start="3">とすればリストの番号は3から開始されます。 これまでの解説のように自身で任意のカウンターを定義した場合は、start属性を指定しても開始番号がその値になりません。ですが、ol要素によって順序付きリストが作成される場合、list-itemというカウンターが暗黙のうちに利用でき、そのlist-itemカウンターを利用すれば、start属性の値を利用できます。 次のCSSとデモを見てみましょう。 ```html <h2>list-itemカウンターを利用する</h2> <ol> <li>aaaa</li> <li>bbbb</li> <li>cccc</li> </ol> <ol start="3"> <li>aaaa</li> <li>bbbb</li> <li>cccc</li> </ol> ``` ```css ol { list-style: none; } li::before { content: "[" counter(list-item) "]"; } ``` /* デモ:list-itemカウンターを使用する */ ソースコード: https://github.com/codegrid/2022-making-procedure-ui/blob/main/5.html https://codegrid.github.io/2022-making-procedure-ui/5.html 気をつけることとして、li要素のdisplayプロパティ値が初期値のlist-item以外だと、次のようにうまくいきません。 ```css ol { list-style: none; } li { display: flex; } li::before { content: "[" counter(list-item) "]"; } ``` /* デモ:liのdisplayプロパティ値をflex(list-item以外)にした */ ソースコード: https://github.com/codegrid/2022-making-procedure-ui/blob/main/6.html https://codegrid.github.io/2022-making-procedure-ui/6.html この場合は、改めてli要素でlist-itemカウンターを増減させるようにしておくとうまくいきます。ol要素でのcounter-resetは開始番号はstart属性が決めるため不要です。 ```css ol { list-style: none; } li { display: flex; counter-increment: list-item; } li::before { content: "[" counter(list-item) "] "; } ``` /* デモ:liで改めてcounter-incrementした */ ソースコード: https://github.com/codegrid/2022-making-procedure-ui/blob/main/7.html https://codegrid.github.io/2022-making-procedure-ui/7.html start属性に3を指定して、3から始まり、順番にカウンターが増えるようになっています。 ――――――――――――――――― まとめ ――――――――――――――――― 今回は「手順を説明するもの」として料理の工程のHTMLとCSSを解説しました。冒頭にもありますが、サービスの登録フローを説明や道順などにも応用できる内容ですので、適切な利用シーンがあれば活用してみてください。 CSSカウンターを利用したため、疑似要素を使って各工程の番号が表示されています。ですが、その番号のテキストは、疑似要素で表示しているため基本的には選択してコピーできません。もし、テキストとしてコピーさせたい場合は素直にHTMLに番号を書くほうが良いでしょう。その場合は番号をJavaScriptやテンプレートエンジンを使って自動的に振られるようにしておくと間違いがないでしょう。 CSSカウンターやリストに関連するCSSプロパティはCSS Lists and Counters Module Level 3(#1)という仕様にまとまっています。興味がある方はご覧になってください。CodeGridでも要望があれば、より詳しく解説したいと思います。 #1: https://www.w3.org/TR/css-lists-3/ ================= 【 JavaScript > ECMAScript 】 複数の非同期処理を扱う場合の4つのメソッドの違い(最終回):Promise.race() ================= JavaScriptで非同期処理を組み合わせて使うための4つのメソッドのうち、Promise.race()を解説します。応用例として、タイムアウトの処理を取り上げます。 (フロントエンド・エンジニア 宇野 陽太) ◆アプリURL◆ https://www.codegrid.net/articles/2022-multiple-promise-3/ ◆目次◆ - はじめに - Promise.race()の基本 - Promise.race()の実践例 - まとめ ――――――――――――――――― はじめに ――――――――――――――――― JavaScriptには、非同期処理を組み合わせて使うためのメソッドとして、執筆現在次の4種類が用意されています。 * Promise.all() * Promise.allSettled() * Promise.any() * Promise.race() これら4つのメソッドについて、例を交えながら解説していきます。前回までで、Promise.all()、Promise.allSettled()、Promise.any()と解説してきました。今回は、最後のPromise.race()です。 ――――――――――――――――― Promise.race()の基本 ――――――――――――――――― Promise.race()は、引数に渡したいずれかの処理が完了したときに値が決まります。完了時点での成否は問わず、いずれか1つでも成功、または失敗すると、Promise.race()が返すPromiseオブジェクトもその値で決定されます。 次のコードを実行すると、即座に"a"という値がコンソールに出力されます。 いずれも成功となるPromiseオブジェクトの場合 ```js const promiseA = Promise.resolve("a"); const promiseB = new Promise((resolve) => setTimeout(() => resolve("b"), 1000) ); const promiseC = new Promise((resolve) => setTimeout(() => resolve("c"), 2000) ); console.time("03-1 time"); Promise.race([ promiseA, promiseB, promiseC, ]).then((result) => console.timeLog("03-1 time", result)); ``` 実行開始するとすぐにpromiseAが完了します。その後promiseBやpromiseCも完了となりますが、その完了を待つことなく、promiseAの結果である"a"が出力されます。 * デモ:実行開始後すぐに値が出力される(#1)(別タブまたは別ウインドウで開き、DevToolsなどのコンソールを見てください)ソースコード(#2) #1: https://codegrid.github.io/2022-multiple-promise/03-1/ #2: https://github.com/codegrid/2022-multiple-promise/blob/main/03-1/main.js /* 図 */ https://cdn.codegrid.net/2022-multiple-promise/assets/img/03-1.png 前回解説したPromise.any()は、引数で渡したいずれかの処理が成功で完了した時に値が決定されました。Promise.race()においても、上記コードのよう即座に成功で処理が完了する場合や、すべての処理が成功する場合はPromise.any()と同様の挙動になります。 次に失敗となる例を見てみましょう。次のコードを実行すると、即座にa failedというエラーがコンソールに出力されます。 1つ目のPromiseオブジェクトが失敗する場合 ```js const promiseA = Promise.reject(new Error("a failed")); const promiseB = new Promise((resolve) => setTimeout(() => resolve("b"), 1000) ); const promiseC = new Promise((resolve) => setTimeout(() => resolve("c"), 2000) ); console.time("03-2 time"); Promise.race([promiseA, promiseB, promiseC]) .then((result) => console.timeLog("03-2 time", result)) .catch((error) => console.timeLog("03-2 time", error.message)); ``` 今度は、実行開始するとすぐにpromiseAが失敗します。 Promise.race()は結果の成否を問わないため、すぐにpromiseAが失敗した場合は、その失敗理由であるエラーをコンソールに出力するというわけです。 promiseAが失敗したあとに、promiseBやpromiseCが成功しますが、この結果を待つことはありません。 * デモ:実行開始後すぐにエラーが出力される(#1)(別タブまたは別ウインドウで開き、DevToolsなどのコンソールを見てください)ソースコード(#2) #1: https://codegrid.github.io/2022-multiple-promise/03-2/ #2: https://github.com/codegrid/2022-multiple-promise/blob/main/03-2/main.js /* 図 */ https://cdn.codegrid.net/2022-multiple-promise/assets/img/03-2.png では、今度は失敗となるPromisオブジェクトの順番を入れ替えてみましょう。約1秒後にpromiseBが失敗となる次のコードを実行した場合、コンソールにはいつ、何が出力されるでしょう。 2つ目のPromiseオブジェクトが失敗する場合にはいつ何が出力される? ```js const promiseA = Promise.resolve("a"); const promiseB = new Promise((resolve) => setTimeout(() => resolve(new Error("b failed")), 1000) ); const promiseC = new Promise((resolve) => setTimeout(() => resolve("c"), 2000) ); console.time("03-3 time"); Promise.race([promiseA, promiseB, promiseC]) .then((result) => console.timeLog("03-3 time", result)) .catch((error) => console.timeLog("03-3 time", error.message) ); ``` ここまで読んできた方ならもうわかりますね。実行すると即座に"a"という文字列がコンソールに出力されます。 なぜならPromise.race()は、一番最初に完了した値で決定されます。後続の処理が成功しようが失敗しようが関係ありません。よって、一番最初に完了するpromiseAの結果である"a"がコンソールに出力されます。 * デモ:実行開始後すぐに値が出力される(#1)(別タブまたは別ウインドウで開き、DevToolsなどのコンソールを見てください)ソースコード(#2) #1: https://codegrid.github.io/2022-multiple-promise/03-3/ #2: https://github.com/codegrid/2022-multiple-promise/blob/main/03-3/main.js /* 図 */ https://cdn.codegrid.net/2022-multiple-promise/assets/img/03-3.png ――――――――――――――――― Promise.race()の実践例 ――――――――――――――――― この特性を実際の開発に応用する例としては、次のようなタイムアウトの処理が挙げられます。 Promise.race()を使ったタイムアウト ```js const fetchExtremelyHeavyData = (time) => new Promise((resolve) => setTimeout(() => resolve("すごく大きいデータ"), time)); const main = async () => { const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error("タイムアウト")), 5000) ); try { const data = await Promise.race([ fetchExtremelyHeavyData(4000), timeout, ]); showData(data); } catch (error) { showError(error); } }; main(); // => Success! ``` fetchExtremelyHeavyData()は、なにかとても大きなデータを取得する処理をイメージした関数です。処理が完了するまでに、多くの時間がかかります。 データが大きいとは言え、いつまでもユーザーを待たせるわけにはいかないため、5秒でタイムアウトさせたいと思います。実行開始から5秒後に失敗するtimeoutを作り、fetchExtremelyHeavyData()と一緒にPromise.race()へと渡します。 まずは、タイムアウトする前にデータが返ってくる場合を想定して、fetchExtremelyHeavyData()の待ち時間を4秒に設定してみます。 この場合、fetchExtremelyHeavyData()は4秒後、timeoutは5秒後に完了するため、Promise.race()の値は先に完了するfetchExtremelyHeavyData()の値で決定されます。 次に、非常に処理時間がかかってしまう場合を想定して、fetchExtremelyHeavyData()の待ち時間を10秒に設定してみます。 データを取得する処理の待ち時間は10秒、タイムアウトは5秒で設定 ```js const fetchExtremelyHeavyData = (time) => new Promise((resolve) => setTimeout(() => resolve("すごく大きいデータ"), time)); const main = async () => { const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error("タイムアウト")), 5000) ); try { const data = await Promise.race([ fetchExtremelyHeavyData(10000), timeout, ]); showData(data); } catch (error) { showError(error); } }; main(); // => Error! ``` この場合、fetchExtremelyHeavyData()は10秒後、timeoutは5秒後に完了するため、Promise.race()の値は先に完了するtimeoutの値で決定されます。 つまり、fetchExtremelyHeavyData()の完了を待つことなく、ユーザーにはタイムアウトしたことを伝えられるというわけです。 以下のデモでは、上記サンプルのfetchExtremelyHeavyData()の処理時間を変更して確認することができます。inputに5000未満の値を入力した場合は成功の画面となり、5000以上の値を入力した場合は失敗の画面となります。 /* デモ:Promise.race()を使ったデモ */ ソースコード: https://github.com/codegrid/2022-multiple-promise/blob/main/03-4/main.js https://codegrid.github.io/2022-multiple-promise/03-4/ このようにPromise.race()を使うことで、いずれかの処理が完了したとき、言い換えると、一番最初に成功もしくは失敗となった結果を使って、処理を進めるようなコードを書くことができます。 ――――――――――――――――― まとめ ――――――――――――――――― このシリーズでは、複数の非同期処理を、並列に実行するためのメソッド4つを解説しました。 ここまで解説したように、それぞれのメソッドは似ているようで明確な違いがあります。 たとえば、対象とする数について、allがついている2つは、すべての処理を対象とし、そうでない残りの2つは、いずれかの処理を対象とします。 また、値の決まり方について、Promise.all()/Promise.any()は対象の処理が成功した場合であるのに対し、Promise.allSettled()/Promise.race()は成否を問わず完了した場合に値が決まります。 これらをまとめると次のようになります。 :成功したときに決まる:完了したときに決まる すべての処理:Promise.all():Promise.allSettled() いずれかの処理:Promise.any():Promise.race() 自分の覚えやすい方法で覚えておき、是非開発の際に役立ててください。 最後にMDNのリンクを貼っておきますので、より詳しい情報を知りたい方は参考にしてみてください。 * Promise.all() https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/all * Promise.allSettled() https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled * Promise.any() https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/any * Promise.race() https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/race ================= 【 Jamstack > ホスティング 】 ホスティングサービスCloudflare Pages(第3回):公開サイトの振る舞い ================= Cloudflare Pagesの公開サイトの振る舞いと、実際に一般公開するサイトのための設定について解説します。公開サイトの振る舞いの中には、知っていると便利な機能だけでなく、知らないと戸惑う動作もあります。 (Jamstackエンジニア 中村 享介) ◆アプリURL◆ https://www.codegrid.net/articles/2021-cloudflare-pages-3/ ◆目次◆ - はじめに - URLの正規化とファイルの対応 - SPAと404 - リダイレクト - ヘッダーの変更 - カスタムドメインの設定 - まとめ ――――――――――――――――― はじめに ――――――――――――――――― Cloudflare Pagesでは、Jamstack構成のWebサイトやSPAでできたWebアプリケーションのような、最近よく使われる手法で作られたサイトを効率よく公開・配信できるようになっています。 その公開サイトの振る舞いの中には知っていると便利な機能だけでなく、知らないと戸惑う動作もあります。 今回はその振る舞いと、実際に一般公開するサイトのための設定について解説します。 ――――――――――――――――― URLの正規化とファイルの対応 ――――――――――――――――― まずはURLと公開ファイルの構造の関係からみていきましょう。 Pagesで配信されるサイトのURLは、ApacheのようなWebサーバーで配信する場合のデフォルト動作と少し違います。同じコンテンツが複数のURLで表示されないよう、正規化されています。URLの正規化のルールをみていきます。 まず、HTMLの拡張子は省略したパスになります。たとえば、サイトがexample.comというドメインだとして、サイトのルートに/about.htmlがあると、URLは https://example.com/about になります。https://example.com/about.htmlにアクセスしても、https://example.com/aboutにリダイレクトされます。 * ファイル: /about.html * 正規URL: https://example.com/about * リダイレクトされるURL: https://example.com/about.html また、ディレクトリのパスはindex.htmlというファイルを探して表示されるのはApacheと同様ですが、パスは常にindex.htmlを省略した形になります。つまり、example.comで/service/index.htmlだとすると、https://example.com/service/でアクセスできます。 もちろん、https://example.com/service/index.htmlでもアクセスできますが、index.htmlも省略したURLにリダイレクトされるのです。なお、/service.htmlが設置されていなければ、https://example.com/serviceもリダイレクトされます。 * ファイル: /service/index.html * 正規URL: https://example.com/service/ * リダイレクトされるURL: * https://example.com/service/index.html * https://example.com/service(/service.htmlがない場合) そのため、作成するサイトの内部リンクや、canonicalに指定するURLなど、この正規化されたURLのルールに合わせておくと良いでしょう。拡張子付きの内部リンクでもリダイレクトされるため動作はしますが、リダイレクトを挟まないほうがページ遷移はスムーズです。 なお、W3Cの公開するCool URIs don't change(#1)(日本語訳(#2))の中でもURLに拡張子は含めるべきでないとされているので、多くの場合は好ましい振る舞いといえます。 #1: https://www.w3.org/Provider/Style/URI #2: https://www.kanzaki.com/docs/Style/URI.html しかし、この機能を無効化する設定などはないので、「厳密に今までのサイトとURLを合わせたい」といった場合は困るかもしれません。 ――――――――――――――――― SPAと404 ――――――――――――――――― PagesはデフォルトでSPA(シングルページアプリケーション)をデプロイする動作になっています。 これはつまり、どんなURLでアクセスした場合でも、基本は/index.htmlをステータスコード200で返します。SPAのルーティングに合わせた動作です。また、画像やJSファイルなどのアセットのように、対応するファイルがあれば、/index.htmlではなく、そのファイルを返します。そのため、SPAを出力するフレームワークはそのままデプロイするだけで多くの場合、望んだ通りに動作します。 しかし、複数ページで構成される普通のWebサイトの場合は、/index.htmlを200で返すのではなく、ファイルが見つからなかった404エラーページを404のステータスコードで返してほしいかもしれません。 404を返すようにするには、ルートに404.htmlを用意します。/404.htmlがあるとPagesは対応するファイルが存在しないURLにアクセスした際、ステータスコード404で/404.htmlを返すようになります。 404はディレクトリ単位で別のものを用意することもでき、たとえば/about/404.htmlを用意すれば/about/以下のディレクトリではこちらの404が使われるようになります。 ――――――――――――――――― リダイレクト ――――――――――――――――― Pagesでは、_redirectsファイルによるリダイレクト設定ができます。これは、_redirectsという名前のテキストファイルを用意し、1行ずつ、リダイレクト元、リダイレクト先、ステータスコードをスペースで区切って書いていくだけの簡単な設定方法です。ファイルは公開ディレクトリのルートにおくことで有効になります。Netlifyを利用したことがある人にはお馴染みの設定かもしれません。 // Netlifyのリダイレクト機能 Netlifyでリダイレクトを設定するには、_redirectsファイルを公開ディレクトリのルートに置いて設定する方法と、通常プロジェクトのルートに設置するnetlify.tomlに設定する方法の2通りがあります。詳しくは次の記事を参照ください。 * リダイレクトとリライト | Netlifyのリダイレクトとリライトの機能 https://www.codegrid.net/articles/2022-netlify-redirects-1/ // たとえば、/about.htmlを/about/へ、/contact.htmlを/contact/へ301リダイレクトするには次のように内容になります。 ``` /about.html /about/ 301 /contact.html /contact/ 301 ``` ステータスコードを指定しないと302リダイレクトとなります。302は一時的な移動を表しますので、URLが変更された場合などは301を明示しましょう。 そのほか、複雑なリダイレクトでなければ使えると思っていてよいでしょう。たとえば、ディレクトリの名前を変えたときなどによく使う*と:splatを使ったリダイレクト設定も可能です。マッチするパスすべてをリダイレクト設定するなら、*(アスタリスク)で指定します。 ``` # recruit配下のコンテンツにアクセスされたらcareersのトップにリダイレクト /recruit/* /careers/ 301 ``` また、そのパスを維持したまま、新しいパスでも展開するならリダイレクト先のほうで:splatを使います。 ``` # リネームされたのでrecruitをcareersに変えてそのパス以下はそのままリダイレクトする /recruit/* /careers/:splat 301 ``` こうすれば、/recruit/infoは/careers/infoへ、/recruit/jobs/engineerは/careers/jobs/engineerへとリダイレクトされます。なお、単純な静的なリダイレクトは2000件まで、*などを使うリダイレクトは100件まで、合計2100件という制限があります。 // リダイレクトは100件までだった 以前はリダイレクトは全部で100件まででしたが、2022年4月7日に静的リダイレクト1000件と*を使ったリダイレクト100件の合計、1100件に緩和されたとTwitterでアナウンス(#1)がありました。 #1: https://twitter.com/chatsidhartha/status/1511809202829860874 その後、アナウンスは見つけられませんが、Discordで簡単なやりとり(#1)があり、2022年5月3日にドキュメントが2000に更新(#2)されています。 #1: https://discord.com/channels/595317990191398933/789155108529111069/975170585472679936 #2: https://github.com/cloudflare/cloudflare-docs/pull/4277 // また、URLのディレクトリの親子を入れ替える場合に使うプレースホルダーも使えます。たとえば、/blog/2020/events/seminarのようなURLを/news/events/2020/seminarへリダイレクトするには次のようになります。 ``` /blog/:year/:category/:slug /news/:category/:year/:slug 301 ``` しかし、Netlifyと比べると、Pagesの_redirectsでサポートされている設定は少なくなっているので注意が必要です。リライトに当たる動作、たとえば、/a/というURLのまま、/b.htmlというHTMLを200で返すような指定はできません。他のドメインにあるページをプロキシすることもできません。アクセスされたら特定のURLに自動で移動するステータスコード30xのリダイレクトだけが設定できます。 また、クエリーパラメーターを元にURLを作ったり、アクセス元の国や言語を元にリダイレクトすることもできません。 何が使えるのかは、公式ドキュメントのRedirects(#1)に記載されている表で確認してください。 #1: https://developers.cloudflare.com/pages/platform/redirects/ これらの制限以上の高度なリダイレクトには、Cloudflare WorkersやBulk Redirects(#1)というCloudflareが提供する別のサービスを利用することを推奨しています。 #1: https://blog.cloudflare.com/maximum-redirects-minimum-effort-announcing-bulk-redirects/ ――――――――――――――――― ヘッダーの変更 ――――――――――――――――― HTTPレスポンスヘッダーに関しては、_headersというテキストファイルで設定できます。これも、Netlifyと同じです。 _headersファイルがルートに置かれていると、そのテキストで書かれた設定に合わせてレスポンスヘッダーの内容を追加できます。たとえばjsonフォルダ以下はCORS制限に引っかからないよう、他のドメインからもアクセスできるようにするには以下のように設定します。 ``` /json/* Access-Control-Allow-Origin: * ``` カスタムドメインを設定した上、pages.devで提供されるドメインを検索エンジンによってインデックスされないようにするには、次のように記載します。 ``` https://myproject.pages.dev/* X-Robots-Tag: noindex ``` 複数のパスに違った設定を書くこともできます。詳しくは、その他の設定と合わせて公式ドキュメントのHeaders(#1)を参照してください。 #1: https://developers.cloudflare.com/pages/platform/headers/ ――――――――――――――――― カスタムドメインの設定 ――――――――――――――――― 本番環境として利用するには、通常、pages.devではなく、独自のドメインを割り当てて運用します。 独自ドメインを設定する方法は2種類あります。 1. Cloudflareのネームサーバーを設定する 2. CNAMEレコードを追加する Cloudflareで新規で取得するドメインなど、ドメインのネームサーバーをCloudflareにできるなら1のネームサーバーを設定する方法が簡単でしょう。 しかし、すでに運用されているドメインの場合、メールや他のサブドメインなど、さまざまなことを考慮すると面倒かもしれません。そういった場合は2のCNAMEで設定するのが便利でしょう。 CNAMEレコードの設定は管理画面の案内に従います。たとえば、www.example.comをcustom.pages.devに割り当てるとしたら次のようになります。 ``` CNAME www.example.com custom.pages.dev. ``` なお、社内ツールとして使うのであれば、カスタムドメインを割り当てず、pages.devのまま運用するという選択もありでしょう。ピクセルグリッドで作られた社内ツールのいくつかは、pages.devのまま運用されています。 ――――――――――――――――― まとめ ――――――――――――――――― 今回はCloudflare Pagesの公開サイトの振る舞いを中心に紹介しました。 404の動作は知らないと、どうすれば良いのかわからなくなりそうなポイントですが、知っていればSPAでも通常のサイトでも404.htmlを用意するかどうかで切り替えられえる便利なものです。 Cloudflare Pagesは今回の記事でもリダイレクトの件数が増えたことを書きましたが、2022年5月のPlatform Week(#1)でもGitリポジトリを介さず直接アップロードする機能などいくつかの機能が追加されている、積極的に開発されているサービスです。 #1: https://www.cloudflare.com/ja-jp/platform-week/ 次回はGitのメインブランチ以外で作られるプレビューサイトとアクセス制限について解説する予定です。 【 次号予告 】ホスティングサービスCloudflare Pages(第4回):Git以外の管理システムを使う場合 ※このシリーズは不定期連載です ================= 編集後記 ================= 眠い、眠いのです。(まる) だいたいのWebアプリって、基本的なところはあんまり考えずに、これってこう動くよね、という予想で動かせるようになっていますが、あらためてこれすごいなと思います。(soto) ================= ご意見、ご要望、ご質問などのお問い合わせ ================= CodeGrid フロントエンドに関わる人々のガイド https://www.codegrid.net/ このメールマガジンへのご意見・ご感想・ご質問は、こちらのお問い合わせまで。 https://www.codegrid.net/contact/ [email protected] 配信先のメールアドレスの変更・退会は、こちらでお手続きください。(ログインが必要です) https://www.codegrid.net/settings/ Twitter(@CodeGrid) https://twitter.com/CodeGrid 著作・発行:株式会社ピクセルグリッド https://www.pxgrid.com/ ━━━━━━━━━━━━━━━━━ 本メールの無断全文転載はご遠慮ください。