Azure SDK documentation update: Migrate storage packages from polyfillSuffix to #platform/* imports は、Azure SDK for JavaScriptのStorage系パッケージで、プラットフォーム別実装の解決方法を旧来の polyfillSuffix 中心の方式から、#platform/* のサブパスimportへ移す変更です。結論から言うと、アプリ側で @azure/storage-blob などの公開APIだけを通常利用している場合、すぐに大きなコード修正が必要になる可能性は高くありません。一方で、Azure SDKをソースからビルドしているチーム、Storageパッケージをforkしているチーム、BrowserやReact Native向けのバンドル設定を細かく管理しているチームは、ビルド・型解決・テスト設定を確認すべき変更です。PRでは公開APIの破壊的変更はないと説明されていますが、対象はStorage系7パッケージに広がっています。(GitHub)
Azure SDKのStorageパッケージで何が変わるのか
今回の変更は、Azure SDK for JavaScriptリポジトリ内のStorage系パッケージを、polyfillSuffix を使った旧来のビルドパターンから、package.json の imports フィールドを使う #platform/* import設計へ移行するものです。PRの要約では、対象パッケージに config/ フォルダー配下のtsconfigを追加し、polyfillSuffix を #platform/* wildcard importsへ置き換える方針が示されています。(GitHub)
変更の中心は、Node.js、Browser、React Nativeといった実行環境ごとの差分を「実行時に判定する」のではなく、「ビルド時・型解決時に適切な実装へ解決する」方向へ寄せることです。PRのコミットメッセージでも、isNodeLike チェックと polyfillSuffix によるビルド時置換を使う方式から、#platform/* wildcard import subpathsによるコンパイル時のプラットフォーム解決へ移行すると説明されています。(GitHub)
たとえば、Storage関連コードでは次のように、Node専用の相対importを #platform/* に置き換える変更が見られます。
// 旧方式の例
import { bodyToString } from "./utils/utils.node.js";
// 新方式の例
import { bodyToString } from "#platform/utils/utils";
この書き換えにより、呼び出し側のコードは #platform/utils/utils という共通のspecifierを使い、実際にどのファイルを読むかは package.json の imports 条件で決まる構成になります。PRの差分にも、utils.node.js への直接importを #platform/utils/utils へ変更する例が含まれています。(GitHub)
対象になる7つのStorageパッケージ
PRのコミットメッセージでは、移行対象として以下の7パッケージが挙げられています。@azure/storage-blob-changefeed はNode.js onlyとして扱われている点に注意が必要です。(GitHub)
| パッケージ | 主な利用シーン | 確認すべきポイント |
|---|---|---|
@azure/storage-common | Storage系SDKの共通処理 | 他パッケージへの波及が大きいため、依存関係の更新後に全体テストを実行する |
@azure/storage-blob | Blob Storage操作 | Browser・React Native・Node.jsのビルド差分を確認する |
@azure/storage-queue | Queue Storage操作 | Node/Browser向けテストで同じAPIが動くか確認する |
@azure/storage-file-share | Azure Files操作 | ファイル・ストリーム周りの環境差分を確認する |
@azure/storage-file-datalake | Data Lake Storage Gen2操作 | Blob系依存との組み合わせを確認する |
@azure/storage-internal-avro | 内部的なAvro処理 | 内部パッケージのため、SDKをforkしている場合は差分追従が重要 |
@azure/storage-blob-changefeed | Blob Change Feed | Node.js only前提で、Browser対応と誤解しない |
通常のアプリ開発者は、これらのパッケージ名を見て「自分のコードで直接importしているか」を確認すれば十分です。逆に、SDKの内部ファイルや dist/ 配下を直接参照している場合は、今回のような内部構成変更の影響を受けやすくなります。
#platform/* importsとは何か
#platform/* は、アプリケーションから使う公開API名ではありません。パッケージ内部のコードが、環境別ファイルへ安全に到達するための内部import名です。
Node.jsの package.json には imports フィールドがあり、これは現在のパッケージ内で使うサブパスimportを定義するための仕組みです。Node.jsのドキュメントでは、imports のエントリは # で始まる文字列である必要があり、現在のパッケージ内のimport解決に使われると説明されています。(Node.js)
PRブランチ上の @azure/storage-blob の package.json では、次のような imports 設定が確認できます。Browser、React Native、defaultの条件ごとに、読み込むソースファイルを分ける設計です。(GitHub)
"imports": {
"#platform/*": {
"browser": "./src/*-browser.mts",
"react-native": "./src/*-react-native.mts",
"default": "./src/*.ts"
},
"#test-utils": {
"browser": "./test/utils/index-browser.mts",
"react-native": "./test/utils/index-browser.mts",
"default": "./test/utils/index.ts"
}
}
この設定の意味はシンプルです。SDK内部で #platform/foo をimportしたとき、Browser向けなら foo-browser.mts、React Native向けなら foo-react-native.mts、それ以外なら通常の foo.ts を参照する、という解決が可能になります。
旧方式と新方式の違い
今回の変更を理解するうえで重要なのは、「実行時の分岐」から「解決時の分岐」へ比重が移ることです。
| 観点 | 旧方式:polyfillSuffix中心 | 新方式:#platform/* imports |
|---|---|---|
| プラットフォーム差分の扱い | polyfillSuffix や環境判定に依存 | package.json の imports 条件で解決 |
| コード上のimport | utils.node.js など環境別ファイルを直接参照しやすい | #platform/utils/utils のような共通specifierを使う |
| TypeScript設定 | ルート直下のtsconfigに依存する構成が多い | config/ フォルダー配下のターゲット別tsconfigへ整理 |
| Browser/RN対応 | 実行環境判定やビルド置換の影響を受けやすい | 条件付き解決で環境別実装を選ぶ |
| レビュー時の見通し | 「どの環境でどのファイルが使われるか」を追いにくい | imports とtsconfigを見れば解決ルートを確認しやすい |
PRでは、config/ ディレクトリの作成、index.ts / index-browser.mts から indexPlatform パターンへのリネーム、React Native用stubの追加、ESLint設定の更新、package.json scriptsの更新、旧root-level tsconfigの削除などが主な変更として挙げられています。(GitHub)
すぐ対応が必要な人、様子見でよい人
公開APIに破壊的変更はないとPRでは説明されています。ただし、これは「すべての利用者が何も確認しなくてよい」という意味ではありません。依存パッケージの内部構造に近い使い方をしているほど、確認の優先度は上がります。(GitHub)
| 読者・チーム | 対応の優先度 | 具体的な確認内容 |
|---|---|---|
@azure/storage-blob などを通常の公開APIで使うアプリ開発者 | 低 | SDK更新後に通常のビルド・単体テスト・E2Eテストを実行する |
| Browser向けにAzure Storage SDKをバンドルしているチーム | 中 | browser 条件が正しく選ばれるか、Node専用APIが混入しないか確認する |
| React NativeでStorage SDKを使うチーム | 中〜高 | react-native 条件、Metro設定、.mts の扱い、RN用stubの動作を確認する |
| Azure SDK for JSをforkしているチーム | 高 | polyfillSuffix、旧tsconfig、相対importを新方式へ追従する |
| SDK内部ファイルをdeep importしているチーム | 高 | 公開API経由へ置き換える。内部パス依存は今後も壊れやすい |
| CIでSDKをソースビルドしているチーム | 高 | config/ 配下tsconfig、package scripts、lint設定の変更をCIに反映する |
特に注意したいのは、#platform/* を「便利なエイリアス」と誤解して、アプリ側から直接importしようとするケースです。これはパッケージ内部の解決用であり、アプリケーションの公開利用口ではありません。外部アプリでは、従来どおり @azure/storage-blob などの公開エントリポイントからimportするのが安全です。
移行後に確認すべきビルド設定
今回の変更では、TypeScriptの型解決とpackage.jsonの条件付き解決がより重要になります。TypeScriptの customConditions は、package.json の exports や imports を解決するときに追加で成功させる条件を指定するオプションです。公式ドキュメントでも、customConditions は exports / imports フィールド参照時の条件解決に使われると説明されています。(TypeScript)
PRブランチの @azure/storage-blob では、ルートの tsconfig.json が config/tsconfig.src.esm.json、config/tsconfig.src.cjs.json、config/tsconfig.src.browser.json、config/tsconfig.src.react-native.json などを参照する構成になっています。(GitHub)
確認すべきポイントは次のとおりです。
| 確認項目 | 見る場所 | 失敗しやすい症状 |
|---|---|---|
imports 条件 | 対象パッケージの package.json | Browser向けbundleなのにNode向け実装が入る |
| TypeScriptの条件解決 | tsconfig、共通tsconfig、customConditions | IDEでは通るがCIの tsc で解決できない |
.mts の扱い | bundler、テストランナー、lint | Browser/RN向けファイルだけ解決できない |
| package scripts | package.json の test、build:samples、lint | 旧tsconfigパスを参照してCIが失敗する |
| ESLint設定 | eslint.config.mjs | lint対象に .mts が含まれず差分を検出できない |
| テスト環境 | Node、Browser、React Native | 特定環境だけStorage操作やモックが失敗する |
アプリ開発者が行うべき確認手順
Azure SDKを通常利用しているアプリ開発者は、内部移行の詳細をすべて追う必要はありません。ただし、SDK更新時に以下の確認をしておくと、BrowserやReact Nativeでの事故を避けやすくなります。
依存しているStorageパッケージを確認する
まず、プロジェクトで使っているStorage系パッケージを洗い出します。
npm ls @azure/storage-blob @azure/storage-queue @azure/storage-file-share @azure/storage-file-datalake
pnpmを使っている場合は、次のように確認できます。
pnpm why @azure/storage-blob
pnpm why @azure/storage-queue
直接依存していなくても、別のライブラリ経由でStorage SDKが入っている場合があります。BrowserアプリやReact Nativeアプリでは、間接依存でもbundle結果に影響することがあります。
deep importしていないか確認する
次に、Azure SDKの内部ファイルを直接importしていないか確認します。
grep -R "@azure/storage-.*\/dist" src test
grep -R "@azure/storage-.*\/src" src test
次のようなimportが見つかった場合は、公開APIへ置き換えるべきです。
// 避けたい例
import { something } from "@azure/storage-blob/dist/esm/some-internal-file";
// 推奨される考え方
import { BlobServiceClient } from "@azure/storage-blob";
内部ファイルへの依存は、今回のようなビルド構成変更で壊れやすくなります。公開APIが提供していない機能を使いたい場合でも、まずはSDKのissueや正式な拡張ポイントを確認する方が安全です。
BrowserとNodeの両方でテストする
フロントエンドとバックエンドの両方でStorage SDKを使っている場合、片方だけのテストでは不十分です。最低限、次の2系統を確認しましょう。
| 環境 | 確認内容 |
|---|---|
| Node.js | 認証、アップロード、ダウンロード、ストリーム処理、SAS生成など |
| Browser | bundle成功、CORS設定、Blob/File操作、不要なNode組み込みモジュールの混入 |
Browser向けbundleで fs、path、stream などNode専用モジュールに関するエラーが出る場合は、条件付き解決が意図どおり働いていない可能性があります。
SDKをfork・拡張している場合の移行手順
Azure SDK for JSをforkしている、またはStorageパッケージの内部構成に近い形で拡張している場合は、今回のPRを単なるドキュメント更新ではなく、ビルド設計の変更として扱うべきです。
polyfillSuffix 依存を洗い出す
まず、対象コード内に旧方式の痕跡がないか確認します。
grep -R "polyfillSuffix" .
grep -R "isNodeLike" sdk/storage
grep -R "\.node\.js\|\.browser\.js\|\.browser\.mts\|\.react-native\.mts" sdk/storage
isNodeLike がすべて悪いわけではありません。ただし、プラットフォーム別実装を選ぶ目的で多用している場合は、#platform/* による解決へ寄せられるか検討します。
package.json に imports を定義する
次に、パッケージ内部で使う #platform/* を package.json に定義します。
{
"imports": {
"#platform/*": {
"browser": "./src/*-browser.mts",
"react-native": "./src/*-react-native.mts",
"default": "./src/*.ts"
}
}
}
ポイントは、すべてのファイルを無理にBrowser/RN対応させることではありません。Node専用の機能であれば、defaultだけにする、Browser向けstubを用意する、公開API上で利用不可にするなど、実態に合った設計にする必要があります。
import文を共通specifierへ置き換える
実装側では、環境別ファイルを直接importしている箇所を #platform/* に置き換えます。
// 変更前
import { streamToText } from "./utils/utils.node.js";
// 変更後
import { streamToText } from "#platform/utils/utils";
このとき、Node版、Browser版、React Native版で同じ名前の関数や型をexportしているかを確認してください。環境別ファイルのAPI形状がずれていると、ある環境だけで型エラーや実行時エラーが発生します。
config/ 配下にtsconfigを整理する
今回のPRでは、ターゲット別tsconfigを config/ フォルダーへ移す構成が採用されています。たとえば @azure/storage-blob のルート tsconfig.json は、src ESM、src CJS、Browser、React Native、Node test、Browser test、snippets用の設定を参照する形です。(GitHub)
{
"references": [
{ "path": "./config/tsconfig.src.esm.json" },
{ "path": "./config/tsconfig.src.cjs.json" },
{ "path": "./config/tsconfig.src.browser.json" },
{ "path": "./config/tsconfig.src.react-native.json" },
{ "path": "./config/tsconfig.test.node.json" },
{ "path": "./config/tsconfig.test.browser.json" },
{ "path": "./config/tsconfig.snippets.json" }
],
"files": []
}
CIで tsc -p tsconfig.src.json のような旧パスを直接呼び出している場合は、ここで失敗します。package scripts、GitHub Actions、Azure Pipelines、社内CIのいずれかに旧tsconfig名が残っていないか確認しましょう。
失敗しやすいポイント
#platform/* をアプリ側のimportに使ってしまう
#platform/* はAzure SDKパッケージ内部の解決用です。アプリケーションコードで次のように書くべきではありません。
// 避ける
import { something } from "#platform/utils/utils";
外部アプリでは、公開されているパッケージのエントリポイントを使います。
// 通常はこちら
import { BlobServiceClient } from "@azure/storage-blob";
Node.jsの imports は現在のパッケージ内のサブパスimportを定義する仕組みであり、外部利用者向けの公開APIを増やすものではありません。(Node.js)
Browser条件が選ばれずNode向けコードが混入する
Browser向けアプリでStorage SDKを使う場合、bundle結果にNode向け実装が入ると、実行時にエラーになることがあります。たとえば、Storageのダウンロード処理やストリーム処理でNode専用APIが混入すると、Browserでは動作しません。
確認方法としては、bundle後の警告、依存解析、実行時エラーを見ます。fs や stream などのNode組み込みモジュールに関するエラーが出た場合は、条件付き解決の設定を疑いましょう。
React NativeをBrowserと同じ扱いにしてしまう
PRではReact Native用のstubやAPI surfaceの追加も変更点として挙げられています。React NativeはBrowserに近い部分もありますが、完全に同じではありません。browser 条件だけを前提にすると、Metroやテスト環境で解決がずれる場合があります。(GitHub)
React Nativeで確認すべき点は、次の3つです。
| 確認項目 | 理由 |
|---|---|
react-native 条件が選ばれるか | browser や default に落ちると意図しない実装を読む可能性がある |
.mts ファイルを処理できるか | SDK側の環境別ファイルで .mts が使われる |
| 実機・エミュレーターで動くか | Metro上の解決と実行時の挙動が一致しないことがある |
PR段階の情報をリリース済み仕様として扱う
今回の情報は、GitHub PR上の変更内容がベースです。確認時点でPRはOpenで、レビュー待ちの状態が示されています。つまり、最終的なマージ内容やリリース版では細部が変わる可能性があります。(GitHub)
本番環境で対応方針を決める場合は、PRだけでなく、利用しているパッケージのリリースノート、npm上の公開バージョン、lockfile上の実際の解決バージョンも確認してください。
変更を受けた実務チェックリスト
SDK更新前後で、次のチェックを行うと安全です。
| チェック | コマンド・確認方法 | 判断基準 |
|---|---|---|
| Storage SDKの利用有無 | npm ls、pnpm why | 対象7パッケージが入っているか |
| deep importの有無 | grep -R "@azure/storage-.*\/dist" src test | 見つかったら公開APIへ置換 |
| Browser bundle | 通常の本番ビルド | Node専用モジュールの警告がないか |
| React Native build | Metro、実機、エミュレーター | react-native 条件で解決されるか |
| TypeScript | tsc --noEmit | #platform/* や .mts 解決で落ちないか |
| テスト | Node/Browser/RNの対象環境別に実行 | 特定環境だけ失敗しないか |
| CI設定 | package scripts、pipeline設定 | 旧tsconfig名を直接参照していないか |
特に、CIだけが失敗するケースでは、ローカルIDEのTypeScript解決とCIの tsc 実行条件が違っていることがあります。customConditions、moduleResolution、パッケージマネージャーのlockfileをあわせて確認しましょう。
今回の変更をどう捉えるべきか
今回の polyfillSuffix から #platform/* importsへの移行は、利用者向けの新機能追加というより、Azure SDK for JavaScriptの内部品質を上げるための構成整理と見るのが自然です。
実務上のメリットは、次の3点です。
| 観点 | 期待できる効果 |
|---|---|
| 保守性 | プラットフォーム別実装の参照先が imports に集約され、差分を追いやすくなる |
| 型解決 | TypeScriptの条件解決と連動しやすくなり、環境別の型チェックを整理しやすい |
| テスト | Node、Browser、React Nativeごとのテスト対象を分けやすくなる |
一方で、内部構成が整理されるほど、非公開パスに依存したコードは壊れやすくなります。Azure SDKを安全に使う基本は、公開APIからimportし、SDK内部のファイル構造に依存しないことです。
次に取るべき行動
アプリ開発者は、まず自分のプロジェクトで対象Storageパッケージを使っているか確認してください。通常の公開APIだけを使っているなら、SDK更新後にNode・Browser・React Nativeなど自分の対象環境でテストを回すことが主な対応です。
SDKをforkしている、または内部ファイルを直接参照している場合は、polyfillSuffix、旧tsconfig、環境別相対importを洗い出し、package.json の imports、config/ 配下tsconfig、.mts ファイル、条件付き解決へ追従する計画を立てましょう。
今回のポイントは「公開APIの変更」ではなく「プラットフォーム別実装の解決方法の変更」です。Storage SDKを使う側は公開API利用を徹底し、SDKを作る・拡張する側は #platform/* とTypeScriptの条件解決を正しく扱うことが、移行時のトラブルを減らす近道です。

コメント