本文へスキップ

ファイル署名(Types-First)

Flowは、依存関係の順序で各ファイルを個別に処理することにより、コードベースをチェックします。チェックプロセスにとって重要な型情報を含むファイルごとに、署名を抽出してメインメモリに格納する必要があります。これは、依存するファイルで使用されます。Flowは、これらの署名を構築するために、ファイルの境界にあるアノテーションに依存しています。このFlowアーキテクチャの要件をTypes-Firstと呼びます。

このアーキテクチャの利点は2つあります。

  1. 特に再チェックに関して、パフォーマンスを劇的に向上させます。Flowがその依存関係をまだチェックしていないファイル`foo.js`をチェックしたいとします。Flowは、エクスポート周辺のアノテーションを見るだけで、依存関係の署名を抽出します。このプロセスはほとんど構文的なものであり、そのため、レガシーバージョンのFlow(v0.125以前)が署名を生成するために実行していた完全な型推論よりもはるかに高速です。

  2. エラーの信頼性を向上させます。推論された型は複雑になることが多く、下流のファイルで、実際のソースから離れた場所でエラーが報告される可能性があります。ファイルのファイル境界における型アノテーションは、そのようなエラーを局所化し、それらを紹介したファイルで対処するのに役立ちます。

このパフォーマンス上の利点のためのトレードオフは、コードのエクスポートされた部分が型でアノテーションされる必要があるか、型を自明に推論できる式(数値や文字列など)である必要があることです。

Types-Firstアーキテクチャの詳細については、この記事をご覧ください。

コードベースをTypes-Firstにアップグレードする方法

注:Types-firstはv0.134以降デフォルトモードであり、v0.143以降は唯一利用可能なモードです。それ以降は、有効にするために`.flowconfig`オプションは必要ありません。古いバージョンからコードベースをアップグレードする場合は、いくつかの便利なツールがあります。

Flowバージョンのアップグレード

Types-firstモードはバージョン0.125で正式にリリースされましたが、バージョン0.102から実験的な状態で使用できました。現在、古いFlowバージョンを使用している場合は、最初にFlowをアップグレードする必要があります。最新のFlowバージョンを使用すると、上記のメリットを最大限に活用できます。

コードベースをTypes-First用に準備する

Types-firstは、ファイルの型シグネチャを構築するために、モジュール境界でアノテーションが必要です。これらのアノテーションがない場合、`signature-verification-failure`が生成され、コードの該当部分のエクスポートされた型は`any`になります。

コードベースをTypes-first対応にするために必要な型を確認するには、`.flowconfig`ファイルの`[options]`セクションに次の行を追加します。

well_formed_exports=true

たとえば、`foo`という関数呼び出しをエクスポートする`foo.js`というファイルを考えてみましょう。

declare function foo<T>(x: T): T;
module.exports = foo(1);

関数呼び出しの戻り値型は、現在(多相性、オーバーロードなどにより)自明に推論できません。結果をアノテーションする必要があり、次のエラーが表示されます。

Cannot build a typed interface for this module. You should annotate the exports
of this module with types. Cannot determine the type of this call expression. Please
provide an annotation, e.g., by adding a type cast around this expression.
(`signature-verification-failure`)

4│ module.exports = foo(1);
^^^^^^

これを解決するには、次のようなアノテーションを追加できます。

declare function foo<T>(x: T): T;
module.exports = foo(1) as number;

注:バージョン0.134以降、Types-firstはデフォルトモードです。このモードでは`well_formed_exports`が自動的に有効になるため、このフラグを明示的に設定しなくてもこれらのエラーが表示されます。このアップグレードの部分では、`types_first=false`を設定することをお勧めします。

中間結果を確定する

コードベースに型を追加する作業を進めるにつれて、新しいコードがコミットされるまで、ディレクトリを含めてリグレッションが発生しないようにすることができます。これは、`.flowconfig`に次の行を追加することで実行できます。

well_formed_exports.includes=<PROJECT_ROOT>/path/to/directory

警告:これは正規表現ではなく部分文字列チェックです(パフォーマンス上の理由により)。

大規模コードベース向けのコードモッド

大規模コードベースに必要なアノテーションを追加することは、非常に面倒な場合があります。この負担を軽減するために、Flowの推論に基づいたコードモッドを提供しており、これを使用して複数のファイルをまとめてアノテーションできます。詳細については、このチュートリアルを参照してください。

Types-firstフラグを有効にする

署名検証エラーを排除したら、`.flowconfig`ファイルの`[options]`セクションに次の行を追加して、Types-firstモードを有効にできます。

types_first=true

`flow check`または`flow start`コマンドに`--types-first`を渡すこともできます。

以前の`well_formed_exports`フラグは`types_first`によって暗示されます。このプロセスが完了し、Types-firstが有効になったら、`well_formed_exports`を削除できます。

残念ながら、リポジトリの一部に対してTypes-firstモードを有効にすることはできません。このスイッチは、現在の`.flowconfig`によって管理されるすべてのファイルに影響します。

注:上記のフラグは、`experimental.`プレフィックスを使用してFlow `>=0.102`のバージョンで使用できます(v0.128以前は、`includes`の代わりに`whitelist`を使用していました)。

experimental.well_formed_exports=true
experimental.well_formed_exports.whitelist=<PROJECT_ROOT>/path/to/directory
experimental.types_first=true

注:Types-firstがデフォルトで有効になっているバージョン(つまり`>=0.134`)を使用している場合は、コードモッドを実行中に`.flowconfig`で`types_first=false`を設定してください。

新しく導入されたエラーに対処する

従来のモードとTypes-firstモードを切り替えると、前に説明した署名検証エラー以外にも、いくつかの新しいFlowエラーが発生する可能性があります。これらのエラーは、アノテーションに基づく型が解釈される方法と、それぞれの推論された型との違いによるものです。

一般的なエラーパターンとその解決策を以下に示します。

エクスポートで配列タプルが通常の配列として扱われる

Types-firstでは、エクスポート位置にある配列リテラル

module.exports = [e1, e2];

は、タプル型`[t1, t2]`ではなく、`e1`と`e2`の型が`t1`と`t2`である`Array<t1 | t2>`型を持つものとして扱われます。

従来のモードでは、推論された型は同時に両方の型を包含していました。これにより、たとえば、インポートの最初の位置に型`t1`があることを期待するファイルのインポート時にエラーが発生する可能性があります。

修正:タプル型が期待される場合は、エクスポート側に`[t1, t2]`アノテーションを明示的に追加する必要があります。

エクスポートでの間接的なオブジェクト代入

Flowは次のコードを許可します。

function foo(): void {}
foo.x = () => {};
foo.x.y = 2;
module.exports = foo;

しかし、Types-firstでは、エクスポートされた型は次のようになります。

{
(): void;
x: () => void;
}

つまり、`y`の更新は考慮されません。

修正:エクスポートされた型に`y`の更新を含めるには、エクスポートに次の型のアノテーションを追加する必要があります。

{
(): void;
x: { (): void; y: number; };
};

これは、次のようなより複雑な代入パターンにも当てはまります。

function foo(): void {}
Object.assign(foo, { x: 1});
module.exports = foo;

`{ (): void; x: number }`でエクスポートを手動でアノテーションするか、関数定義の前に代入を行う必要があります。

foo.x = 1;
function foo(): void {}
module.exports = foo;

最後の例では、定義の後であれば、Flow Types-firstは静的な更新を検出することに注意してください。

function foo(): void {}
foo.x = 1;
module.exports = foo;

更新されたエクスポートされた変数

Types-firstシグネチャ抽出器は、エクスポートされた`let`バインド変数の後続の更新を検出しません。次の例を考えてみましょう。

let foo: number | string = 1;
foo = "blah";
module.exports = foo;

従来のモードでは、エクスポートされた型は`string`になります。Types-firstでは`number | string`になり、下流の型付けがより正確な型に依存している場合、エラーが発生する可能性があります。

修正:更新時に新しい変数を導入し、それをエクスポートします。たとえば、

const foo1: number | string = 1;
const foo2 = "blah";
module.exports = foo2;