Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I avoid repeating type definitions in module declarations?

Tags:

flowtype

Disclaimer: I'm still somewhat new Flow and static typing in general, so it's very possible I'm overlooking something easy/obvious with this question.

Say I have a library called my-library. The library exposes a single module to its users, index.js, which imports a couple other modules (moduleA.js and moduleB.js) for internal use.

I want to use Flow with this library, both for my own development internally, and in a index.js.flow file with a module declaration for users of the library who are also using Flow. So the file structure would look something like this:

- index.js
- index.js.flow (contains the module declaration for library users)
- moduleA.js
- moduleA.js.flow (just exported type definitions, no module declaration)
- moduleB.js
- moduleB.js.flow (just exported type definitions, no module declaration)

The issue is that the index.js.flow module declaration needs to use types from both moduleA.js.flow and moduleB.js.flow. (The reason I have moduleA.js.flow and moduleB.js.flow instead of just defining the types directly in the .js files is that the type definitions in the .js files will be stripped out by Babel, and I want them to still exist somewhere for library users).

I know that the following does not type check with external JS that imports my-module:

index.js.flow (this doesn't work)

import type { SomeType } from './moduleA'

declare module 'my-module' {
  declare function exports(): {
    someMethod: () => SomeType
  }
}

SomeType doesn't seem to be useable in the module declaration when it's imported, but defining it locally does work:

index.js.flow (this works)

export type SomeType = string

declare module 'my-module' {
  declare function exports(): {
    someMethod: () => SomeType
  }
}

So one solution is to just define and export all types from within index.js.flow, and just have moduleA.js and moduleB.js import them (and not include .js.flow files for moduleA and moduleB), but it seems weird to have all of the type definitions in the root flow file instead of in the .js.flow files matching the modules that those types originate from.

Alternatively, I know I could define the types in their respective modules for development, and just define them again in the index.js.flow module declaration, but I'd prefer not to have to repeat the type definitions in two different places if possible.

I would greatly appreciate any help figuring out how best to organize this. (And again, I know there's a pretty solid chance I'm doing something dumb or overlooking something obvious.)

like image 366
Shane Cavaliere Avatar asked Aug 15 '16 05:08

Shane Cavaliere


1 Answers

The purpose of .js.flow files is to act exactly like the corresponding .js files in your implementation, except that they are not translated by Babel. In particular, they import and export things just like the corresponding .js files do. Also, just like a .js file is automatically associated with a module based on where it resides in the file system, so is a .js.flow file.

Following the example in the question, let's say index.js's module.exports is a function that returns an object containing a property someMethod of type () => SomeType, where the type SomeType is exported by moduleA.js. Then we can have the following in index.js.flow:

// @flow

import type { SomeType } from './moduleA'

declare module.exports: () => {
  someMethod: () => SomeType;
};

And the following in moduleA.js.flow:

// @flow

export type SomeType = string;

Assuming that we put index.js.flow and moduleA.js.flow in src/node_modules/my-module/, we can test that our setup is correct by having the following in test.js in src/:

// @flow

var foo = require('my-module');

(foo().someMethod(): number); // error (string incompatible with number)
like image 68
Avik Chaudhuri Avatar answered Nov 07 '22 17:11

Avik Chaudhuri