Azure SDKのStorage移行解説:polyfillSuffixから#platform/* importsへ何が変わる?

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.jsonimports フィールドを使う #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.jsonimports 条件で決まる構成になります。PRの差分にも、utils.node.js への直接importを #platform/utils/utils へ変更する例が含まれています。(GitHub)

対象になる7つのStorageパッケージ

PRのコミットメッセージでは、移行対象として以下の7パッケージが挙げられています。@azure/storage-blob-changefeed はNode.js onlyとして扱われている点に注意が必要です。(GitHub)

パッケージ主な利用シーン確認すべきポイント
@azure/storage-commonStorage系SDKの共通処理他パッケージへの波及が大きいため、依存関係の更新後に全体テストを実行する
@azure/storage-blobBlob Storage操作Browser・React Native・Node.jsのビルド差分を確認する
@azure/storage-queueQueue Storage操作Node/Browser向けテストで同じAPIが動くか確認する
@azure/storage-file-shareAzure Files操作ファイル・ストリーム周りの環境差分を確認する
@azure/storage-file-datalakeData Lake Storage Gen2操作Blob系依存との組み合わせを確認する
@azure/storage-internal-avro内部的なAvro処理内部パッケージのため、SDKをforkしている場合は差分追従が重要
@azure/storage-blob-changefeedBlob Change FeedNode.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-blobpackage.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.jsonimports 条件で解決
コード上のimportutils.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.jsonexportsimports を解決するときに追加で成功させる条件を指定するオプションです。公式ドキュメントでも、customConditionsexports / imports フィールド参照時の条件解決に使われると説明されています。(TypeScript)

PRブランチの @azure/storage-blob では、ルートの tsconfig.jsonconfig/tsconfig.src.esm.jsonconfig/tsconfig.src.cjs.jsonconfig/tsconfig.src.browser.jsonconfig/tsconfig.src.react-native.json などを参照する構成になっています。(GitHub)

確認すべきポイントは次のとおりです。

確認項目見る場所失敗しやすい症状
imports 条件対象パッケージの package.jsonBrowser向けbundleなのにNode向け実装が入る
TypeScriptの条件解決tsconfig、共通tsconfig、customConditionsIDEでは通るがCIの tsc で解決できない
.mts の扱いbundler、テストランナー、lintBrowser/RN向けファイルだけ解決できない
package scriptspackage.jsontestbuild:sampleslint旧tsconfigパスを参照してCIが失敗する
ESLint設定eslint.config.mjslint対象に .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生成など
Browserbundle成功、CORS設定、Blob/File操作、不要なNode組み込みモジュールの混入

Browser向けbundleで fspathstream など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.jsonimports を定義する

次に、パッケージ内部で使う #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後の警告、依存解析、実行時エラーを見ます。fsstream などのNode組み込みモジュールに関するエラーが出た場合は、条件付き解決の設定を疑いましょう。

React NativeをBrowserと同じ扱いにしてしまう

PRではReact Native用のstubやAPI surfaceの追加も変更点として挙げられています。React NativeはBrowserに近い部分もありますが、完全に同じではありません。browser 条件だけを前提にすると、Metroやテスト環境で解決がずれる場合があります。(GitHub)

React Nativeで確認すべき点は、次の3つです。

確認項目理由
react-native 条件が選ばれるかbrowserdefault に落ちると意図しない実装を読む可能性がある
.mts ファイルを処理できるかSDK側の環境別ファイルで .mts が使われる
実機・エミュレーターで動くかMetro上の解決と実行時の挙動が一致しないことがある

PR段階の情報をリリース済み仕様として扱う

今回の情報は、GitHub PR上の変更内容がベースです。確認時点でPRはOpenで、レビュー待ちの状態が示されています。つまり、最終的なマージ内容やリリース版では細部が変わる可能性があります。(GitHub)

本番環境で対応方針を決める場合は、PRだけでなく、利用しているパッケージのリリースノート、npm上の公開バージョン、lockfile上の実際の解決バージョンも確認してください。

変更を受けた実務チェックリスト

SDK更新前後で、次のチェックを行うと安全です。

チェックコマンド・確認方法判断基準
Storage SDKの利用有無npm lspnpm why対象7パッケージが入っているか
deep importの有無grep -R "@azure/storage-.*\/dist" src test見つかったら公開APIへ置換
Browser bundle通常の本番ビルドNode専用モジュールの警告がないか
React Native buildMetro、実機、エミュレーターreact-native 条件で解決されるか
TypeScripttsc --noEmit#platform/*.mts 解決で落ちないか
テストNode/Browser/RNの対象環境別に実行特定環境だけ失敗しないか
CI設定package scripts、pipeline設定旧tsconfig名を直接参照していないか

特に、CIだけが失敗するケースでは、ローカルIDEのTypeScript解決とCIの tsc 実行条件が違っていることがあります。customConditionsmoduleResolution、パッケージマネージャーの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.jsonimportsconfig/ 配下tsconfig、.mts ファイル、条件付き解決へ追従する計画を立てましょう。

今回のポイントは「公開APIの変更」ではなく「プラットフォーム別実装の解決方法の変更」です。Storage SDKを使う側は公開API利用を徹底し、SDKを作る・拡張する側は #platform/* とTypeScriptの条件解決を正しく扱うことが、移行時のトラブルを減らす近道です。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次