メインコンテンツにスキップ

package:web への移行

Dart の package:web はブラウザ API へのアクセスを提供し、Dart アプリケーションと Web の間の相互運用を可能にします。ブラウザと対話し、DOM のオブジェクトや要素を操作するには package:web を使用してください。

dart
import 'package:web/web.dart';

void main() {
  final div = document.querySelector('div')!;
  div.text = 'Text set at ${DateTime.now()}';
}

package:web vs dart:html

#

package:web の目標は、既存の Dart Web ライブラリに関するいくつかの懸念事項に対処することで、Dart が Web API を公開する方法を改革することです。

  1. Wasm 互換性

    Wasm と互換性があるのは、dart:js_interop および dart:js_interop_unsafe を使用するパッケージのみです。package:webdart:js_interop に基づいているため、デフォルトで dart2wasm でサポートされます。

    dart:htmldart:svg などの Dart コア Web ライブラリは非推奨であり、Wasm にコンパイルする際にはサポートされません

  2. 最新の状態を維持する

    package:webWeb IDL を使用して、IDL の各宣言の 相互運用メンバー および 相互運用型 を自動生成します。dart:html の追加メンバーや抽象化とは対照的に、参照を直接生成することで、package:web はより簡潔で理解しやすく、一貫性があり、Web の将来の開発に追従しやすくなっています。

  3. バージョン管理

    パッケージであるため、package:webdart:html のようなライブラリよりも簡単にバージョン管理でき、進化してもユーザーコードを壊すのを避けることができます。また、コードを排他的でなくし、貢献を受け入れやすくします。開発者は独自の 代替相互運用宣言 を作成し、package:web と競合することなく一緒に使用できます。


これらの改善により、自然に package:webdart:html の間に実装の違いが生じます。IDL の 名前の変更型テスト のように、既存のパッケージに最も影響を与える変更は、後続の移行セクションで説明されています。簡潔にするために dart:html のみを参照しますが、同じ移行パターンは dart:svg のような他の Dart コア Web ライブラリにも適用されます。

dart:html からの移行

#

dart:html のインポートを削除し、package:web/web.dart に置き換えます。

dart
import 'dart:html' as html; // Remove
import 'package:web/web.dart' as web; // Add

pubspec の dependenciesweb を追加します。

dart pub add web

以下のセクションでは、dart:html から package:web への一般的な移行の問題のいくつかについて説明します。

その他の移行の問題については、dart-lang/web リポジトリを確認し、問題を報告してください。

名前の変更

#

dart:html の多くのシンボルは、Dart スタイルに合わせるために元の IDL 宣言から名前が変更されました。たとえば、appendChildappend に、HTMLElementHtmlElement になりました。

対照的に、混乱を減らすために、package:web は IDL 定義の元の名前を使用します。名前が変更された型を dart:htmlpackage:web の間で変換するための dart fix が利用可能です。

インポートを変更した後、名前が変更されたオブジェクトはすべて新しい「未定義」エラーになります。これらは次のいずれかの方法で対処できます。

  • CLI から dart fix --dry-run を実行します。
  • IDE で dart fix: 「〜に名前を変更」を選択します。

dart fix は、一般的な型の名前変更の多くをカバーしています。名前変更のための dart fix がない dart:html 型に遭遇した場合は、まず 問題を報告して知らせてください。

その後、既存の dart:html メンバーの package:web 型名を、定義を検索することで手動で検出できます。dart:html メンバー定義の @Native アノテーションの値は、コンパイラにその型の任意の JS オブジェクトを注釈付けられた Dart クラスとして扱うように指示します。たとえば、@Native アノテーションは、dart:htmlHtmlElement メンバーのネイティブ JS 名が HTMLElement であることを示しているため、package:web の名前も HTMLElement になります。

dart
@Native("HTMLElement")
class HtmlElement extends Element implements NoncedElement { }

未定義のメンバーの dart:html 定義を package:web で見つけるには、次のいずれかの方法を試してください。

  • IDE で未定義の名前を Ctrl または Command キーを押しながらクリックし、「定義へ移動」を選択します。
  • dart:html API ドキュメントで名前を検索し、そのページの「アノテーション」セクションを確認します。

同様に、未定義の package:web API があり、対応する dart:html メンバーの定義が native キーワードを使用している場合があります。定義が @JSName アノテーションを使用して名前変更されているかどうかを確認してください。アノテーションの値は、メンバーが package:web で使用する名前を示します。

dart
@JSName('appendChild')
Node append(Node node) native;

native は内部キーワードであり、このコンテキストでは external と同じ意味です。

型テスト

#

dart:html を使用するコードでは、is のような実行時チェックが利用されることがよくあります。dart:html オブジェクトで使用される場合、is および as は、オブジェクトが @Native アノテーション内の JS 型であることを検証します。対照的に、すべての package:web 型は JSObject に再定義されます。これは、実行時型テストが dart:html 型と package:web 型の間で異なる結果をもたらすことを意味します。

型テストを実行できるようにするには、is 型テストを使用する dart:html コードを、相互運用メソッド (instanceOfString など) またはより便利で型指定された isA ヘルパー (Dart 3.4 以降で利用可能) を使用するように移行してください。JS 型ページの「Compatibility, type checks, and casts」セクションでは、代替手段について詳しく説明しています。

dart
obj is Window; // Remove
obj.instanceOfString('Window'); // Add

型シグネチャ

#

dart:html の多くの API は、型シグネチャでさまざまな Dart 型をサポートしています。dart:js_interop型を制限するため、package:web の一部のメンバーでは、メンバーを呼び出す前に値を変換する必要があります。JS 型ページの「Conversions」セクションから、相互運用変換メソッドの使用方法を学びます。

dart
window.addEventListener('click', callback); // Remove
window.addEventListener('click', callback.toJS); // Add

一般的に、変換が必要なメソッドは、例外のバリエーションでフラグが付けられていることで見つけることができます。

A value of type '...' can't be assigned to a variable of type 'JSFunction?'

条件付きインポート

#

dart:html がサポートされているかどうかによって条件付きインポートを使用して、ネイティブと Web を区別するコードが一般的です。

dart
export 'src/hw_none.dart'
    if (dart.library.io) 'src/hw_io.dart'
    if (dart.library.html) 'src/hw_html.dart';

ただし、dart:html は非推奨であり、Wasm にコンパイルする際にはサポートされないため、ネイティブと Web を区別するには dart.library.js_interop を使用するのが正しい代替手段です。

dart
export 'src/hw_none.dart' // Stub implementation
    if (dart.library.io) 'src/hw_io.dart' // dart:io implementation
    if (dart.library.js_interop) 'src/hw_web.dart'; // package:web implementation

仮想ディスパッチとモッキング

#

dart:html クラスは仮想ディスパッチをサポートしていましたが、JS 相互運用は拡張型を使用するため、仮想ディスパッチは不可能です。同様に、package:web 型での dynamic 呼び出しは期待どおりには機能しません (または、偶然機能し続けるかもしれませんが、dart:html が削除されると停止します)。メンバーは静的にのみ利用可能であるためです。この問題を回避するために、仮想ディスパッチに依存するすべてのコードを移行してください。

仮想ディスパッチの 1 つのユースケースはモッキングです。dart:html クラスを implements するモッキングクラスがある場合、それを package:web 型の実装に使用することはできません。代わりに、JS オブジェクト自体をモックすることを推奨します。詳細については、モッキングチュートリアルを参照してください。

ネイティブではない API

#

dart:html クラスには、非自明な実装を持つ API が含まれている場合もあります。これらのメンバーは、package:web ヘルパーに存在する場合と存在しない場合があります。コードがその実装の具体性に依存している場合は、必要なコードをコピーできる可能性があります。ただし、それが実行不可能であると思われる場合、またはそのコードが他のユーザーにも有益であると思われる場合は、問題を報告するか、package:web にプルリクエストをアップロードして、そのメンバーをサポートすることを検討してください。

ゾーン

#

dart:html では、コールバックは自動的にゾーン化されます。package:web ではそうではありません。現在のゾーンでのコールバックの自動バインディングはありません。

これがアプリケーションにとって重要であれば、ゾーンを引き続き使用できますが、自分で記述してコールバックをバインドする必要があります。詳細については、#54507 を参照してください。自動的にこれを行うための変換 API または ヘルパー はまだありません。

ヘルパー

#

package:web のコアには external 相互運用メンバーが含まれていますが、dart:html がデフォルトで提供していた他の機能は提供していません。これらの違いを軽減するために、package:web には、コア相互運用では直接利用できない多くのユースケースの処理をサポートするための helpers が含まれています。ヘルパーライブラリには、Dart Web ライブラリの一部のレガシー機能を公開するためのさまざまなメンバーが含まれています。

たとえば、コア package:web はイベントリスナーの追加と削除のみをサポートしています。代わりに、コードを自分で記述することなく、Dart Stream を使用してイベントを簡単に購読できる ストリームヘルパー を使用できます。

dart
// Original dart:html version:
final htmlInput = InputElement();
await htmlInput.onBlur.first;

// Migrated package:web version:
final webInput = HTMLInputElement();
await webInput.onBlur.first;

すべてのヘルパーとそのドキュメントは、リポジトリの package:web/helpers で見つけることができます。移行を支援し、Web API の使用を容易にするために、継続的に更新されます。

#

以下は、dart:html から package:web に移行されたパッケージの例です。