Modules

JavaScript projects in Flow are composed of “modules” which are just single files that encapsulate some logic. A module can “export” variables/functions/classes so that other modules can make use of them. Additionally they can “import” variables/functions/classes from other modules.

Flow supports both ES modules (recommended) and CommonJS modules. For an excellent explanation of how ES modules (sometimes referred to as “ES6 Modules”) work, check out the modules chapter of Exploring JS.

ES Modules#

Here’s a succinct example of ES modules in action:

// == `Math.js` == //

// This function is exported, so it's available for other modules to import
export function add(num1: number, num2: number): number {
  return num1 + num2;
};

// This function isn't exported, so it's only available in the local scope
// of this module
function sub(num1, num2) {
  return num1 - num2;
}

// Note that we can use both exported and non-exported items within this
// module
var two: number = add(1, 2);
var one: number = sub(2, 1);

Here we’ve defined a module by writing a file (Math.js). Both functions defined within this module are available for use within this file, but only add() can be imported by other modules (because it was exported).

1
2
3
4
5
6
7
// == `Calculator.js` == //

import {add} from "./Math.js";

import {sub} from "./Math.js"; // Error! `sub` is not an export of Math.js

var four: number = add(2, 2);
show Flow output hide Flow output
$> flow
3: import {add} from "./Math.js";
           ^^^ add. name is already bound
-21: export function add(num1: number, num2: number): number {
            ^ function add

Because Math.js exports its add() function, we are able to import it using an import statement. Similarly, because Math.js does not export its sub() function, attempting to import it from another module will result in an error. If we wish to import sub() into Calculator.js, we must export it from Math.js using the export keyword. Note that it is possible to export multiple things from one module by just using the export keyword on multiple things.

CommonJS Modules#

Flow also supports CommonJS modules as well. If you’re not familiar with CommonJS modules, you can read about them here – but note that we recommend using ES modules if you need to choose between the two options.

Here’s the CommonJS version of the example given above for ES modules:

// == `Math_CommonJS.js` == //

function add(num1: number, num2: number): number {
  return num1 + num2;
};
// This is how we export the `add()` function in CommonJS
exports.add = add;

function sub(num1, num2) {
  return num1 - num2;
}

var two: number = add(1, 2);
var one: number = sub(2, 1);
// == Calculator_CommonJS.js == //


var Math = require('./Math_CommonJS.js');

var four: number = Math.add(2, 2);

// Error! `sub` is not exported from Math_CommonJS.js

var one: number = Math.sub(2, 1);

ES Module <-> CommonJS Interoperability#

Some projects start out as CommonJS and wish to migrate to ES modules incrementally (or just need to pull in legacy code that uses CommonJS). Because of this, Flow supports a set of interoperability semantics between the two kinds of module systems. Note that this interop strategy is compatible with the strategy employed by Babel 6.

Importing from CommonJS -> ES Module#

Say we have the following CommonJS module:

// == CJSModule.js == //


class MyClass {}

module.exports = MyClass;

If you wish to import MyClass in to an ES module, Flow models this as a default export from CJSModule.js:

import MyClass from "./CJSModule.js";

(Note the lack of curly braces – showing that this a “default” import rather than a “named” import)

Now consider a different common pattern that is used in CommonJS modules:

// == CJSModule_MultExports.js == //
function util1() {}
function util2() {}

exports.util1 = util1;
exports.util2 = util2;

If you wish to import 1 or both of the functions exported by CJSModule_MultExports.js, you can do so using named import(s):

1
import {util1, util2} from "./CJSModule_MultExports.js";
show Flow output hide Flow output
$> flow
1: import {util1, util2} from "./CJSModule_MultExports.js";
           ^^^^^ util1. name is already bound
-9: function util1() {}
    ^^^^^^^^^^^^^^^^^^^ function util1

1: import {util1, util2} from "./CJSModule_MultExports.js";
                  ^^^^^ util2. name is already bound
-8: function util2() {}
    ^^^^^^^^^^^^^^^^^^^ function util2

If instead you wish to receive an object with util1 and util2 properties similar to what you might receive from a call to require(), you can do so via import * as:

import * as MultExports from "./CJSModule_MultExports.js";

Importing from ES Module -> CommonJS#

Sometimes you’re in the middle of converting your project to use ES modules and you need to make a change to a legacy module that hasn’t been converted yet. For this, Flow supports using require() to import from an ES module.

Say we have an ES module with “named” exports:

// == ES_NamedExports.js == //

export function util1() {}
export function util2() {}

You can require() this ES module from a CommonJS module as follows:

const ES_NamedExports = require('./ES_NamedExports.js');

ES_NamedExports.util1();
ES_NamedExports.util2();

If you have an ES module that has a “default” export:

// == ES_DefaultExport.js == //
export default function() {}

You can require() the function as follows:

const ES_DefaultExport = require('./ES_DefaultExport.js');

// Note that the default-export is stored as a property named `default`
ES_DefaultExport.default();

The one rough edge to this CommonJS <-> ES Module interoperability is the fact that “default” exports from an ES Module will show up as a default property on the object returned from require().

This is because, in ES modules, a “default” export is essentially just sugar for a “named” export whose name is default.

Module Resolution#

For both ES modules and CommonJS modules, Flow needs to understand how to look up the name of a module on disk. For this, Flow uses the same module resolution rules as Node.js. In other words, you most likely don’t have to learn or switch to a new module resolution system in order to use Flow.

Aliasing Module Names#

In general we recommend against deviating too far from the standard module resolution algorithm, but in some advanced environments it is useful to alias a module name with some other string before Flow performs the resolution.

For this we have the module.name_mapper config option that allows you to specify a regular expression template string and a replacement pattern that will be run on all imports and require()s in your project before trying to look things up on disk. We call these aliases “name mappers”.

The simplest example of a name mapper is one that will convert all references of the "Foo" module to references of the "Bar" module:

.flowconfig

[options]
module.name_mapper='^Foo$' -> 'Bar'

main.js

// @flow

// Because the string "Foo" matches the name mapper above, Flow will look
// for a module named "Bar" rather than "Foo" here.
import {something} from "Foo";

CSS Modules with Webpack#

A more common (and less trivial) example here is to configure Flow to understand CSS Modules:

CSSModule.js.flow

// @flow

// CSS modules have a `className` export which is a string
declare export var className: string;

.flowconfig

[options]
module.name_mapper='^\(.*\)\.css$' -> '<PROJECT_ROOT>/CSSModule.js.flow'

NOTE: You do not need to manually substitute anything for “<PROJECT_ROOT>”. This is a string token that Flow recognizes and will automatically replace with the path to the directory of your .flowconfig file.

main.js

// @flow

import {className} from "./SomeCSSFile.css"; // Works!

Note that the module.name_mapper config option uses the regular expression syntax as documented here.

Type Imports & Exports#

In addition to importing and exporting runtime variables between modules, it can also be useful to import and export types as well. For this, Flow has extended the ES module import and export syntax in a couple of ways:

export type#

If you wish to define a type alias or interface in a module that other modules will need access to as well, you can export it from the module using export type:

// == User.js == //

export type UserID = number;
export type User = {
  id: UserID,
  name: string,
};

type GuitarT = {
  type: string,
  color: string,
};

export let jimiGuitar: GuitarT = {
  type: "Stratocaster",
  color: "White",
};

export function getUser(id: UserID): User {
  return {
    id: id,
    name: "Jimi Hendrix",
    guitar: jimiGuitar,
  };
}

Here we’ve defined UserID and User as types in User.js that are exported for other modules to access.

import type#

In order for another module to import these types, it must use import type:

import type {UserID, User} from "./User.js";

When you use import type in a module, you are creating a local type alias to the type that you are importing from the other module. import type will work on type aliases, interfaces, and classes. It will not work on other kinds of variables like let/const/var because these do not represent types (they only represent values).

import typeof#

If you have a value that you’d like to import the type of, the most straightforward option would be to import the value and then use typeof to get its type:

import {jimiguitar} from "./User.js";


type GuitarT = typeof jimiguitar;

var myGuitar: GuitarT = {
  type: "Gibson",
  color: "Black",
};

Alternatively, you can also use the import typeof short-hand to make things simpler:

import typeof {jimiguitar as GuitarT} from "./User.js";

var myGuitar: GuitarT = {
  type: "Gibson",
  color: "Black",
};

Missing/Required Annotations#

Flow is able to infer most types in your program for you, but there is one restriction imposed on this rule: You must annotate the exports of a module explicitly.

Flow requires this for 2 reasons:

1) Placing a type annotation at the boundaries of a module reduces the amount of work Flow needs to do to infer the types that span modules across your project. This, in turn, removes a performance barrier from Flow’s internal engine while typechecking your project and makes Flow much faster.

2) In general, we’ve found that explicitly annotating module boundaries is a good habit to adopt because it helps document the ways in which the exports of a module are intended to be used.

Because of this restriction, you may occasionally see an error from Flow that states that you are Missing an annotation. To resolve this error, simply add type annotations for the export the error points at.

← Prev Next →

You can edit this page on GitHub and send us a pull request!