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
},
});
Web(XMLHttpRequest)で使用できるプロトコルに blob を追加
以下のように blob プロトコルでのリクエストが出来るようになりました。
axios.get("blob://hogehoge");
いつ使うのこんなのと思われるかも知れませんが、弊社は一度この不具合を踏んでハマったことがあります。
Web(XMLHttpRequest)で使用できるプロトコルに data を追加
前項とだいたい同じです。
axios.get("data:image/gif;base64,hogehoge");
Node で使用できるプロトコルに data を追加
前項とだいたい同じで、こちらは 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