Flow: Declare default export among other exports

Created on 18 May 2016  路  18Comments  路  Source: facebook/flow

Suppose there is an npm module like this:

// index.js

import React from 'react'

export default React.createClass({...})

export const foo = 'bar'

And I want to write a declaration file for this module. If it only has the default export I would write something like this:

// my-module.js.flow

declare module 'my-module' {
  declare var exports: React.Component
}

If it doesn't have the default export, I would do:

// my-module.js.flow

declare module 'my-module' {
  declare var foo: string
}

But in combination this doesn't work:

// my-module.js.flow

declare module 'my-module' {
   declare var foo: string
  declare var exports: React.Component // this overrides foo
}
bug

Most helpful comment

Ok, this has been fixed as of v0.28. You can now do the following:

declare module "my-module" {
  declare export default React.Component;
  declare export var foo: string;
}
import DefaultReactComponent from "my-module";
import {foo} from "my-module";

All 18 comments

Unfortunately this is a shortcoming of our declare module syntax at the moment: Right now all declare modules are interpreted as if they were CommonJS modules, and CommonJS modules only have a single export (so you there's no way to express both a default and named exports at the same time).

The plan to address this is to add export syntax to declare module. I will start to work on this today, thanks for the reminder!

That's great news that you're starting working on this. Thank you!

In the meantime, is there a way to add type info somehow for such module? I'm experimenting with using module.name_mapper to replace the file from which Flow gets type info for this module, and this seems to work. Still wondering if there is a better way.

@rpominov: If there is no way for you to change the upstream module directly right now then the only 2 workarounds I can think of are:

1) Use require() directly in the importing file until the Flow fix lands (if this is an option in your setup?). With this (assuming your system uses standard ES/CJS interop semantics like Babel does) you could define the interface as:

my-wrapper-module.js

declare module 'my-module' {
  declare module.exports: {
    default: React.Component,
    foo: string,
  };
}

2) Create an untyped wrapper module around the external module that hoists the default export to a named export. Then you can write a declaration file for that wrapper:

my-wrapper-module.js

export {default as component, foo} from "my-module";

my-wrapper-module.js.flow

declare module 'my-module' {
  declare var component: React.Component;
  declare var foo: string;
}

Thanks! Can't check at the moment, but I think both methods should work great in my case 馃憤

Ok, this has been fixed as of v0.28. You can now do the following:

declare module "my-module" {
  declare export default React.Component;
  declare export var foo: string;
}
import DefaultReactComponent from "my-module";
import {foo} from "my-module";

Is there any difference between these two?

declare module "my-module" {
  declare export var foo: string;
}
declare module "my-module" {
  declare var foo: string;
}

Is this supposed to work ? I tried on 0.30.0. But not of below works and I'm getting errors:

declare module "my-module" {
  declare export var exp: string;
  declare export default exp;
}

declare module "my-modules" {
  declare var exp: string;
  declare export default exp;
}

You should do it like this (only one line needed):

// @flow

declare module "my-modules" {
  declare export default string;
}
// @flow

import a from 'my-modules'
(a: number)
6: (a: number)
      ^ string. This type is incompatible with
  6: (a: number)
         ^^^^^^ number

To complete the answers above, you can do one of the following form to define your modules

// usage:
// import foo from 'some-module'
declare module 'some-module' {
  declare type Foo = {/*...*/}
  declare export default Foo
}

// usage: 
// import myFunc from 'some-module'
declare module 'some-module' {
  declare export default () => void 
}

// usage: 
// import { foo } from 'some-module'
declare module 'some-module' {
  declare type Foo = {/*...*/}
  declare export var foo: FooType 
}

// usage: 
// import Foo from 'some-module'
declare module 'some-module' {
  declare class Foo {}
  declare export default typeof Foo
}

One more that seems to be even compatible with eslint:

declare module 'some-module' {
  declare function f(): void;
  declare var exports : typeof f; // and all type alternatives
}

@tomitrescak: declare var exports is deprecated and will be going away soon. Seems we'll want to get declare export default supported in eslint ASAP

I'm new to flow but just an FYI related to this issue the EventEmitter defined in lib/node https://github.com/facebook/flow/blob/master/lib/node.js#L581 seems to need updating to this type of syntax since

import EventEmitter from 'events'
//or
import {EventEmitter} from 'events'

got it to work with suggestions above

declare module "events" {
  // TODO: See the comment above the events$EventEmitter declaration
  declare class EventEmitter {
    static EventEmitter: typeof EventEmitter;
    // deprecated
    static listenerCount(emitter: events$EventEmitter, event: string): number;

    addListener(event: string, listener: Function): events$EventEmitter;
    emit(event: string, ...args:Array<any>): boolean;
    listeners(event: string): Array<Function>;
    listenerCount(event: string): number;
    on(event: string, listener: Function): events$EventEmitter;
    once(event: string, listener: Function): events$EventEmitter;
    removeAllListeners(event?: string): events$EventEmitter;
    removeListener(event: string, listener: Function): events$EventEmitter;
    setMaxListeners(n: number): void;
    getMaxListeners(): number;
  }

  declare export var EventEmitter: typeof EventEmitter
  declare export default typeof EventEmitter
}

hopefully that saves someone some time

I get a parse error on:

declare export var ...

@peterhal: Are you writing it like this?

https://flowtype.org/try/#0CYUwxgNghgTiAEBbA9sArhBAiAsl+A3gFDzyiSwIgAeADsjAC7wBus8AdgFydqIBGIGAG4iAXyA

@jeffmo I'm also getting the parse error. Might have something to do with the babel parser for eslint?

For those using index.js.flow to type a class as module.exports (i.e. can be gotten via require('index.js') instead of require('index.js').default), I was able to do the following:

index.js.flow

// @flow

declare class MyClass

// Flow gripes that typeof MyClass isn't compatible with exports...yet it seems to do the
// proper typechecking with it everywhere else!
// flow-issue
declare var exports: typeof MyClass

Is there a way to import a default type from a CommonJS module?

I'm trying to do

declare module 'superagent' {
  declare class Superagent {
    (method: string, url: string): Request;
    get(url: string): Request;
    head(url: string): Request;
    put(url: string): Request;
    post(url: string): Request;
    delete(url: string): Request;
    patch(url: string): Request;
    options(url: string): Request;
    trace(url: string): Request;
  }

  declare module.exports: Superagent;
}

but then import type Superagent from 'superagent' doesn't work...

How would you export a variable with a hyphen with the declare export var syntax?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

STRML picture STRML  路  48Comments

TylerEich picture TylerEich  路  49Comments

gabro picture gabro  路  57Comments

opensrcery picture opensrcery  路  88Comments

xtinec picture xtinec  路  65Comments