axios は v1.0.0 でどう変わるのか
概要
本記事は、HTTP クライアントライブラリである axios の v1.0.0 が満を持してリリースされたため、何がどう変わったのか、マイグレーションしても良いのかについて個人的に調べてまとめた結果になります。
TL;DR
-
axios
のv1.0.0
は、パッケージのモダン化に向けた節目としてのバージョンともいえる -
v1.0.0
では多数のバグ修正と、いくつかの小規模の機能追加がまとめて取り込まれた - 破壊的変更や非推奨化は少なからずあるが、基本的な使い方や挙動を大きく変える規模の変更はない
- 一方で劇的に良くなる変化もないので、急いであげる理由もない
- 公式マイグレーションガイドは記事執筆時点では提供されていない
axios
について
axios は、JavaScript
向けの HTTP クライアントライブラリの一種で、この種のパッケージとしては比較的古くから普及している老舗ライブラリです。
大きな特徴として、扱いが難しい XMLHttpRequest を Promise
でラップして使いやすくした上、 Node
で実行する場合は fetch
API に切り替えることで、同じコードをブラウザでも Node
でも動くようにしたことがあげられます。
サンプルコード
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
v1.0.0
なのか
なぜ今になって 本パッケージは2016年という、ES2015
で Promise
が導入されてまもない頃にリリースされ、デファクトスタンダードに近い立ち位置を得てきたため、多くの方が一度は使っている、今も使い続けていることでしょう。
その割には今になっての v1.0.0
のリリースなので、 「axios って一生 v1.0.0 にならないのかと思ってた」 「というかまだ 0.x 系だったの知らなかった」 「開発続いていたんだ」 と驚いた方もいるでしょう。
以下 issue 及び discussions では、今後 axios
をどうしていきたいかが議論されており、モダン JavaScript のプラクティスに則ったライブラリを目指すための一環としての v1.0.0
のリリースを行うことがわかります。
なお、モダン化というならブラウザでも XMLHttpRequest
に代わる Fetch API に切り替えていくのかとも思いましたが、それについては以下のようなスタンスを持っているようです。
もし axios が Response と Request オブジェクトを使った fetch APIに移行したら、このプロジェクトの存在はかなり無意味になると思います。ユーザーは1つのリクエストに1つの Promise、そしてクロスプラットフォームが axios の最も重要な利点だと考えていますが、もしこれを実行すれば、文字通りインターセプターを持つ別のフェッチライブラリができてしまうことになります。そのようなプロジェクトはすでに存在しています。axiosは日々の仕事を楽にするために、ユーザーエクスペリエンスをシンプルにする独自の方法を見つけるべきでしょう。
これに対して XMLHttpRequest
を使い続けることに対するデメリットも議論されていますが、axios
ではレガシーブラウザや旧バージョンの Node
への互換性を重視する姿勢のようです。
事実、今回のリリースでも IE11
のサポートを終了しませんでした。(一部機能が動かないという話はある)
以上より、axios
の v1.0.0
化はモダン化に向けた大きな一歩目である表明にはなるものの、積極的に後方互換を壊すような意図はないようです。
ちなみに、本記事では扱いませんが、 v0.25
や v0.27
での破壊的変更はそれなりにあるので、それ未満のバージョンから上げる場合はご注意ください。
マイグレーションガイドについて
前項で互換性はなるべく維持する方針であることはわかりましたが、それでも v1.0.0
にはいくつかの破壊的変更が含まれています。
リリースノートを見た限りは大きな影響はありませんが、以下 Discussions によると、これからマイグレーションガイドは用意されるそうです。
本記事では以降で主な変更点を紹介しますが、語弊・誤解がないように、ここが Breaking Changes だよとは明言しません。必要に応じてリリースノートをチェックしたり、マイグレーションガイドをお待ち下さい。
主な変更点
ここでは、v1.0.0
で追加・修正された内容の一部を紹介します。
これらの変更は、記事執筆時点ではドキュメントサイトには反映されていないためご注意ください。
AxiosError オブジェクトへのスタックトレースの追加
エラー発生時にスタックトレースが AxiosError
オブジェクトに追加されます。
axios.get("/hoge").catch((e: AxiosError) => {
console.log(e.stack); // 今までは stack にスタックトレースが含まれていなかった
});
ただし、内部で利用されている Error.captureStackTrace
は非標準機能であるため、環境によって挙動が変わることがあります。
webpack
から rollup
に変更
ビルドツールを
ユーザーに直接影響はありませんが、ライブラリのビルドには Webpack よりも rollup が適しているとのことです。
事実、ビルド成果物のサイズが(minify後)2KB軽量化したと Issue には書かれています。
とはいえ以下で調べてみると、 0.27
から 1.0.0
でだいぶ増加してますが…。
追加されたインターセプタをすべて破棄するメソッドの追加
use
した interceptors
を一括で解除(eject
)するためのメソッドが追加されました。
const instance = axios.create();
instance.interceptors.request.use(() => {});
instance.interceptors.request.clear(); // ↑を解除
instance.interceptors.response.use(() => {});
instance.interceptors.response.clear(); // ↑を解除
ちなみに、これも例によって TypeScript
の対応が漏れていましたが、PR を出して v1.1.0
でリリースされました。
transformResponse
のコールバックに HTTPステータスを追加
transformResponse
は、レスポンス内容を書き換えるコールバック関数です。
ここに第三引数としてステータスコードが追加されました。
axios.get("https://dummyjson.com/products/1", {
transformResponse(data, headers, status) {
console.log(status); // 200
},
});
blob
を追加
Web(XMLHttpRequest)で使用できるプロトコルに
以下のように blob
プロトコルでのリクエストが出来るようになりました。
axios.get("blob://hogehoge");
いつ使うのこんなのと思われるかも知れませんが、弊社は一度この不具合を踏んでハマったことがあります。
data
を追加
Web(XMLHttpRequest)で使用できるプロトコルに 前項とだいたい同じです。
axios.get("data:image/gif;base64,hogehoge");
data
を追加
Node で使用できるプロトコルに 前項とだいたい同じで、こちらは Node
の話です。
axios.get("data:image/gif;base64,hogehoge");
toFormData
関数へのオプション追加
オブジェクトを FormData
形式に変換する toFormData
関数に formSerializer
オプションを指定できるようになり、シリアライズ時のいくつかの挙動を変更できるようになりました。
例えば dots: true
を指定した場合、配列はドット記法に変換されるようになります。
const obj = {
nested: {
arr: ["hello", "world"],
},
};
const form = axios.toFormData(obj, undefined, {
dots: true,
});
form.get("nested.arr.0"); // dots: true の場合はこっちが "hello" に
form.get("nested.arr[0]"); // dots: false の場合はこっちが "hello" に
HTMLFormElement
を指定可能に
リクエストペイロードに
以下のように Form 要素がある場合に
<form id="form">
<input type="text" name="foo" value="1" />
<input type="text" name="deep.prop" value="2" />
<input type="text" name="deep prop spaced" value="3" />
<input type="text" name="baz" value="4" />
<input type="text" name="baz" value="5" />
<select name="user.age">
<option value="value1">Value 1</option>
<option value="value2" selected>Value 2</option>
<option value="value3">Value 3</option>
</select>
</form>
対象の Form 要素を取得してペイロードして渡すことで
await axios.post("http://localhost:3000", document.querySelector("#form"));
Form の内容が multipart/form-data
形式に変換された状態でリクエストされます。
await axios.post("http://localhost:3000", document.querySelector("#form"));
------WebKitFormBoundaryD24ZKi5sunQBZrji
Content-Disposition: form-data; name="foo"
1
------WebKitFormBoundaryD24ZKi5sunQBZrji
Content-Disposition: form-data; name="deep.prop"
2
------WebKitFormBoundaryD24ZKi5sunQBZrji
Content-Disposition: form-data; name="deep prop spaced"
3
------WebKitFormBoundaryD24ZKi5sunQBZrji
Content-Disposition: form-data; name="baz"
4
------WebKitFormBoundaryD24ZKi5sunQBZrji
Content-Disposition: form-data; name="baz"
5
------WebKitFormBoundaryD24ZKi5sunQBZrji
Content-Disposition: form-data; name="user.age"
value2
------WebKitFormBoundaryD24ZKi5sunQBZrji--
また、以下のように Content-Type: application/json
を明示した場合は JSON
形式に自動で変換されます。
await axios.post("http://localhost:3000", document.querySelector("#form"));
{"foo":"1","deep":{"prop":{"spaced":"3"}},"baz":["4","5"],"user":{"age":"value2"}}
application/x-www-form-urlencoded への自動シリアライゼーション
(post|put|patch)Form
メソッドにて、 "content-type": "application/x-www-form-urlencoded"
を指定した場合、ペイロードを multipart/form-data
形式に自動で変換されます。
const data = {
x: 1,
arr: [1],
arr2: [[2]],
users: [{ name: "Peter", surname: "Griffin" }],
};
await axios.postForm("https://postman-echo.com/post", data, {
headers: { "content-type": "application/x-www-form-urlencoded" },
});
ペイロードは以下のように変換されてリクエストされます。
"x"
1
------WebKitFormBoundaryhtSCeXdfQVdT55OA
Content-Disposition: form-data; name="arr[]"
1
------WebKitFormBoundaryhtSCeXdfQVdT55OA
Content-Disposition: form-data; name="arr2[0][0]"
2
------WebKitFormBoundaryhtSCeXdfQVdT55OA
Content-Disposition: form-data; name="users[0][name]"
Peter
------WebKitFormBoundaryhtSCeXdfQVdT55OA
Content-Disposition: form-data; name="users[0][surname]"
Griffin
------WebKitFormBoundaryhtSCeXdfQVdT55OA--
(post|put|patch)Form
メソッドは v0.27.0
で追加されたショートカットメソッドで、'Content-Type': 'multipart/form-data'
を自動で付与してくれます。
本来は 'Content-Type': 'multipart/form-data'
の場合に自動でペイロードを変換する仕組みがありましたが、ヘッダーを明示的に application/x-www-form-urlencoded
で上書きされた場合でも同様の変換処理が走るようになったようです。
その他
ここで紹介した以外にも、いくつかの細かい修正が多く含まれています。
特に、TypeScript
の型情報の修正が多く、これまで TypeScript
では使用できなかった機能がいくつか使えるようになりました。(本記事では型レベルのみの修正の紹介は割愛)
ご興味がある方はリリースノート にも目を向けてみてください。
所感
最後に、調査する中での私の所感が以下になります。
-
axios
のような HTTPクライアントライブラリを深堀りすることで、Web や HTTP の学びを多く得られて良かった - 全体的に
TypeScript
の対応が弱いなと感じた-
JavaScript
で実装され、型定義ファイル(index.d.ts
)を別途作成する構成になっているので、抜け漏れが出やすいというのは仕方ない - とはいえ現代で
JavaScript
では使えるけどTypeScript
では使えないというのは厳しそう
-
-
FormData
を中心に、ペイロードの自動変換が強化された印象- 自動シリアライズや、シリアライズ用のヘルパー関数の提供あたりは嬉しいけど、どこまで HTTP クライアントライブラリの責務として扱うかは怪しい
-
FormData
自体を使う機会が最近は減ってきてる気もする
- 現代ではだんだん必要性が薄れてくるパッケージなのかなと思ったけど、シンプルな
Promise
ベースのインタフェースで、ブラウザ/Node ともに動くという強みは残ってそう- 今後は
Deno
やBun
のような他のランタイムやライブラリで動くことも期待されそう
- 今後は
- 歴史があってユーザー数が多い割にはメンテナ・コントリビュータが少ない
- メンテナーは実質一人の状態
- コントリビュータも限られているので、OSS 貢献のチャンスは眠ってる
- 実際、私もちょっと調査してて見つけた不具合を即日でマージしてもらえました
Discussion