代表的な面接トピック

コーディング面接: TypeScript 5.9 の import defer で副作用の罠を回避するには?

コーディング難しい
Offer.cc 編集チーム公開日 更新日

質問

あるチームが、コストの高いモジュール初期化を遅延させるために TypeScript 5.9 の import defer の使用を検討しています。これが dynamic import とどう異なるのか、なぜ名前空間インポート(namespace import)のみが許可されるのか、そしてレガシーランタイム互換性とロールバックを確保しながらどのようにリリースするかを説明してください。

プロンプトとコンテキスト

フロントエンド基盤チームが大規模な TypeScript アプリケーションを保守しています。あるアナリティクスモジュールが、インポート時にグローバルリスナーを登録し、環境設定を読み込んでいるため、起動が遅延しています。チームは TypeScript 5.9 の import defer を使用してこれらの副作用を遅延させたいと考えていますが、その成果物は構文を理解できないレガシーランタイム上でも動作し続ける必要があります。ロード、評価、アクセスによるトリガー、および移行ゲートについて説明してください。

TypeScript 5.9 のノートでは、import defer は名前空間インポートのみを許可すると記載されています。モジュールとその依存関係は先にロードできますが、モジュールコードの評価は名前空間のメンバーにアクセスした際に行われます。TypeScript はこの構文をダウンレベル変換しないため、直接の保持は preserve または esnext モジュールモードを対象としています。

面接官が見ているポイント

面接官は、ロードと評価の違い、静的インポートと動的インポートの違い、およびトップレベルの副作用に関する理解を求めています。優れた回答では、import defer を単なる遅延読み込み(lazy loading)の短縮記法として扱うのではなく、バンドラー、レガシーブラウザ、SSR、プリロード、テストの分離、ロールバックについても網羅します。

最初に明確にすべき質問

  • ターゲットのランタイムとバンドラーは import defer をネイティブに解釈できるか?
  • モジュールの副作用は安全に後回しにできるか、それとも起動時に発生する必要があるか?
  • どのメンバーアクセスが評価のトリガーとなり、隠れたトップレベルの読み取りは存在しないか?
  • SSR、クライアントハイドレーション、プリロード、テストは決定論的な順序を必要としているか?
  • 移行が失敗した場合、通常の静的インポートや動的 import() に戻せるか?

30秒での回答

「私は import defer を動的ロードではなく『遅延評価(deferred evaluation)』として定義します。TypeScript 5.9 では名前空間インポートが必要であり、リソースはロードされる可能性がありますが、モジュールが評価されるのは最初の名前空間メンバーへのアクセス時です。コンパイラはレガシーランタイム向けの変換を提供しません。私ならトップレベルの副作用と SSR の順序を監査し、対応するバンドラーとランタイムで小規模な実験を行い、通常のインポートや動的 import() への切り替え手段を維持します。ビルド、ハイドレーション、パフォーマンス、副作用のテストに合格した後にのみ適用を拡大します。」

ステップバイステップの詳細解説

1. セマンティクスとバージョンを固定する

TypeScript 5.9 のバージョン、モジュールモード、TC39 プロポーザルのステータスを記録します。ロードと評価は別々の事象です。import defer は評価を遅延させるものであり、必ずしもネットワークリクエストを遅延させるわけではありません。起動時のウォーターフォールから実行を推測するのではなく、両方のタイムスタンプを計測します。

2. 構文の境界を明確にする

評価を遅延できるのは名前空間インポートのみであり、デフォルトインポートや名前付きインポートは無効です。名前空間のプロパティへのアクセスが評価のトリガーとなるため、その読み取りが観測可能な実行境界となります。

ts
import defer * as analytics from "./analytics.js";

// The module is loaded, but top-level registration has not run.
export function openPanel() {
  analytics.start(); // First member access triggers evaluation.
}

呼び出し側が analytics.start より前にグローバル登録を必要とする場合、遅延評価によって動作が変化します。通常のインポートを維持するか、初期化を明示的な関数に移行してください。

3. dynamic import と比較する

動的 import() は通常 Promise を返し、ロードと評価を非同期フローに配置します。import defer は静的なモジュール関係を維持し、リソースを早期にロードできる可能性があり、トップレベルの実行を遅延させます。エラーの伝播、プリロード、コード分割、SSR、テストのタイミングが異なるため、両者を同一視すると誤ったパフォーマンス比較につながります。

4. 副作用とアクセスグラフを監査する

イベントリスナー、シングルトンの登録、環境変数の読み取り、ポリフィル、テレメトリの初期化、キャッシュの初期投入など、トップレベルの副作用をリストアップします。ハイドレーション、ルートのプリフェッチ、テストのセットアップを含め、名前空間メンバーへのすべてのアクセスを追跡します。順序が重要な場合は、呼び出し側がタイミングを選択できるように副作用を明示的な initialize() に移行します。

5. ビルドとランタイムの互換性を処理する

TypeScript は import defer をダウンレベル変換しません。レガシーランタイムの場合、バンドラーがそれを変換できるか、あるいは拒否するかを確認します。それができなければ通常のインポートまたは動的 import() の実装を維持します。CI では、対象ブラウザ、Node SSR、開発サーバー、本番バンドル、ソースマップ、コード分割、エラーバウンダリをカバーする必要があります。

6. ロールアウトとロールバックのゲートを設定する

遅延、静的、動的パス用にフィーチャーフラグを使用します。初回インタラクションのレイテンシ、評価時間、重複初期化、ハイドレーションエラーを記録します。順序の変更、レガシーランタイムの構文エラー、メトリクスの悪化が発生した場合はフラグを無効にし、プロポーザルやツールチェーンが進化している間は安定したインポートパスに戻します。

模範回答

まず TypeScript 5.9、バンドラー、および各ターゲットランタイムのサポートマトリクスを確認し、次にトップレベルの副作用を洗い出します。import defer は名前空間インポートをサポートし、評価前にモジュールをロードでき、最初のメンバーアクセス時に評価します。TypeScript はダウンレベル変換を提供しません。私ならロードと評価を個別に計測し、SSR、ハイドレーション、レガシーブラウザ、バンドル、テスト分離、重複初期化をテストし、フィーチャーフラグによる通常のインポートへのフォールバックを維持します。順序、成果物、パフォーマンスデータが安定した後にのみ移行を進めます。

よくある間違い

  • import defer を動的 import() として扱う → 静的依存関係と Promise のタイミングを見落とす → ロード、評価、エラーを個別に計測する。
  • デフォルトまたは名前付きインポートを使用する → TypeScript 5.9 の構文境界に違反する → 名前空間インポートを使用し、アクセス時にトリガーする。
  • TypeScript が書き換えてくれると思い込む → レガシーランタイムで構文エラーが発生する可能性がある → バンドラーのサポートを確認し、フォールバックを維持する。
  • トップレベルの副作用を無視する → リスナー、ポリフィル、テレメトリの順序が変わる → 初期化を明示的にするか、通常のインポートを維持する。
  • 起動時のネットワークのみを見る → 早期ロードは早期実行を意味しない → 両方のタイムスタンプを記録する。

関連する追加質問

なぜ名前空間インポートが必要なのですか?

名前空間オブジェクトは、明確なプロパティアクセス境界を提供するためです。デフォルトインポートや名前付きインポートはセットアップ中に具体的なバインディングを公開するため、「最初のメンバーアクセス時に評価する」という統一ルールを維持できません。

コード分割(code splitting)と同じですか?

いいえ。コード分割はリソースのパッケージ化とロードの方法を制御します。import defer は主にモジュールコードが評価されるタイミングを変更します。リソース自体はすでにロードまたはプリフェッチされている可能性があります。

SSR ではどのように処理すべきですか?

サーバーとクライアントの評価順序の互換性を維持します。クライアントが登録していない状態でサーバーがグローバル状態を登録すると、ハイドレーションの不整合(不一致)が発生する可能性があります。必要に応じてサーバー側では静的インポートを維持するか、初期化を明示的かつ反復可能にします。

1回限りの副作用をどのようにテストしますか?

分離されたテストプロセスにおいて、アクセスなし、初回アクセス、反復アクセス、同時アクセス、破棄(teardown)にわたるモジュール初期化をカウントします。シングルトンやリスナーが重複して登録されていないことをアサートします。

どのような場合に避けるべきですか?

ランタイムやバンドラーのサポートがない場合、起動時の副作用を後回しにできない場合、SSR の順序を変更できない場合、またはパフォーマンス向上が再現できない場合は避けてください。代わりに通常のインポートまたは動的 import() を使用します。

公開情報ソース

関連する質問

関連面接ツール

コーディング問題にはスクリーンショットを使用

問題をキャプチャし、制約条件、解法アプローチ、コード、エッジケース、計算量の順に進めます。

ツールを見る