flowtypeの導入を断念しました。とはいえ数週間ほどやって知見が溜まったので、それをメモがてら残しておきます。
前提
前提としてFlow Commentsで使用しています。
これは良いか悪いかさておき、実装としてのコードと、型を補完するコードを分けることができるので、融通が利くというのでFlow Commentsを使用しています。つまりはこういうことです。
var awslambda/*: Lambda */ = new AWS.Lambda();
var lambda = Bluebird.promisifyAll(lambda, {suffix: 'Promise'});
/*::
lambda = {};
lambda.addPermissionPromise = Bluebird.promisify(awslambda.addPermission);
lambda.createEventSourceMappingPromise = Bluebird.promisify(awslambda.createEventSourceMapping);
lambda.createFunctionPromise = Bluebird.promisify(awslambda.createFunction);
lambda.deleteEventSourceMappingPromise = Bluebird.promisify(awslambda.deleteEventSourceMapping);
lambda.deleteFunctionPromise = Bluebird.promisify(awslambda.deleteFunction);
lambda.getEventSourceMappingPromise = Bluebird.promisify(awslambda.getEventSourceMapping);
lambda.getFunctionPromise = Bluebird.promisify(awslambda.getFunction);
lambda.getFunctionConfigurationPromise = Bluebird.promisify(awslambda.getFunctionConfiguration);
lambda.getPolicyPromise = Bluebird.promisify(awslambda.getPolicy);
lambda.invokePromise = Bluebird.promisify(awslambda.invoke);
lambda.invokeAsyncPromise = Bluebird.promisify(awslambda.invokeAsync);
lambda.listEventSourceMappingsPromise = Bluebird.promisify(awslambda.listEventSourceMappings);
lambda.listFunctionsPromise = Bluebird.promisify(awslambda.listFunctions);
lambda.removePermissionPromise = Bluebird.promisify(awslambda.removePermission);
lambda.updateEventSourceMappingPromise = Bluebird.promisify(awslambda.updateEventSourceMapping);
lambda.updateFunctionCodePromise = Bluebird.promisify(awslambda.updateFunctionCode);
lambda.updateFunctionConfigurationPromise = Bluebird.promisify(awslambda.updateFunctionConfiguration);
*/
(ツラいですが!ツラいですが!!!
まぁ、型のためのコードをコメントとして残せるので、ツラいことに目をつぶればどうにでも出来るというのはメリットです。
外部宣言ファイルの読み込み
declare
で行う宣言は外部ファイルで定義して、checkするときにそのファイルを読み込むということができます。
外部宣言したファイルを読み込む方法として2つあり、flow check --lib [path]
で指定する方法と、.flowconfig
で[libs]
を定義する2つの方法があります。
どちらを使うかは好き好きかなと思います。
declare
flowtypeではDeclarationsとして型を宣言する機能があります。これとImportを組み合わせることで3rdパーティのモジュールを型定義して読み込むことができます。
declare module
存在しないモジュールを読み込む構文としてdeclare module
があります。
declare class C { x: string; } declare module M { declare function foo(c: C): void; }
declare moduleで宣言したあとに
var M = require('M'); M.foo(new C());
存在しないモジュールをrequire
、またはimport
を行うとdeclare module
で宣言したモジュールを受けとることができます。
注意点として、declare module
構文はモジュールを定義するための構文ではなく、require
(またはimport
)できないモジュールを定義するものになるということを気をつけてください。自分はモジュールを定義するための構文だと思ってハマりました。
使い所としては結構難しく、思いつくものとしては
- 開発中のモジュールのインターフェースを読み込む
ぐらいしか思いつきません。それ以外に使い道が全く思いつかないので、こういうときに使うというのがあれば教えてください。
declare var, function, class
declare module
以外の宣言の場合は
import /*:: type */ Package from 'Package';
のようにimport type
構文を使って、モジュールを読み込み、それに対してdeclareで宣言した型を付与します。(単純に型を読み込むだけなら外部宣言ファイルの読み込みを行えば使えるようになります)
ただし、aws-sdkのようにクラス内クラスはflowtypeでは宣言できない(たぶん)ため、少し手間をかける必要があります。
declare class Lambda {
addPermission(request: AddPermissionRequest, callback: (err: any, response: AddPermissionResponse) => void): void;
createEventSourceMapping(request: CreateEventSourceMappingRequest, callback: (err: any, response: EventSourceMappingConfiguration) => void): void;
createFunction(request: CreateFunctionRequest, callback: (err: any, response: FunctionConfiguration) => void): void;
deleteEventSourceMapping(request: DeleteEventSourceMappingRequest, callback: (err: any, response: EventSourceMappingConfiguration) => void): void;
deleteFunction(request: DeleteFunctionRequest, callback: (err: any, response: void) => void): void;
getEventSourceMapping(request: GetEventSourceMappingRequest, callback: (err: any, response: EventSourceMappingConfiguration) => void): void;
getFunction(request: GetFunctionRequest, callback: (err: any, response: GetFunctionResponse) => void): void;
getFunctionConfiguration(request: GetFunctionConfigurationRequest, callback: (err: any, response: FunctionConfiguration) => void): void;
getPolicy(request: GetPolicyRequest, callback: (err: any, response: GetPolicyResponse) => void): void;
invoke(request: InvocationRequest, callback: (err: any, response: InvocationResponse) => void): void;
invokeAsync(request: InvokeAsyncRequest, callback: (err: any, response: InvokeAsyncResponse) => void): void;
listEventSourceMappings(request: ListEventSourceMappingsRequest, callback: (err: any, response: ListEventSourceMappingsResponse) => void): void;
listFunctions(request: ListFunctionsRequest, callback: (err: any, response: ListFunctionsResponse) => void): void;
removePermission(request: RemovePermissionRequest, callback: (err: any, response: void) => void): void;
updateEventSourceMapping(request: UpdateEventSourceMappingRequest, callback: (err: any, response: EventSourceMappingConfiguration) => void): void;
updateFunctionCode(request: UpdateFunctionCodeRequest, callback: (err: any, response: FunctionConfiguration) => void): void;
updateFunctionConfiguration(request: UpdateFunctionConfigurationRequest, callback: (err: any, response: FunctionConfiguration) => void): void;
}
上記のようにAWSオブジェクト内部のクラスを単独で宣言し
import AWS from 'aws-sdk';
var lambda/*: Lambda */ = new AWS.Lambda();
AWSオブジェクトを通常のimportで読み込んだあとに変数に型定義を付与してあげます。
理想を言えば
declare class AWS {
static Lambda: Lambda;
}
みたいに宣言をできたら良かったのですが。。。
というか、もっと言えば、d.tsと同じ構文で宣言したかったです。
おわりに
取り敢えず、たかだが宣言とインポートをやりたかっただけなのに、ここまで理解するのに一体何個の地雷を踏んだんだろうというぐらいツラい道のりでした。
ES6+flowtype構成とか実用するには、まだ早すぎました・・・。