Skip to main content

Strict ES Module Import/Export Lints

These lint rules enforce stricter usage of ES module import and export syntax. They all require the experimental.strict_es6_import_export option to be set to true in your .flowconfig.

default-import-access

Triggers when you access the default export of a module indirectly, rather than importing it directly. This includes accessing the default property on an import * namespace object (via member access or destructuring), and using import { default as ... } syntax.

Accessing default on a namespace import via member access:

import * as Foo from './foo';

Foo.default; // Error
Foo['default']; // Error

Destructuring default from a namespace import, including in variable declarations and assignments:

import * as Foo from './foo';

const {default: renamed1} = Foo; // Error
const {'default': renamed2} = Foo; // Error

let x;
({default: x} = Foo); // Error
({'default': x} = Foo); // Error

Using default in import destructuring:

import {default as renamed} from './foo'; // Error

Instead, import the default export directly:

import Foo from './foo'; // Ok

export-renamed-default

Triggers when you set the default export of a module by renaming a named export to default in an export specifier, rather than using export default directly. This applies to both local exports and re-exports.

Renaming a local binding to default in an export specifier:

class Foo {}

export {Foo as default}; // Error

Instead, use export default directly:

class Foo {}

export default Foo; // Ok

Re-exporting a named export as default:

export {named1 as default} from './foo'; // Error

Re-exporting the default property from another module:

export {default} from './foo'; // Error

For re-exports, import the value first and then use export default:

import {named1} from './foo';
export default named1; // Ok

invalid-import-star-use

Triggers when an import * namespace object is used in a way other than accessing one of its named exports. The namespace object can only be used with member access (e.g. Foo.bar), string literal member access (e.g. Foo['bar']), or simple destructuring (e.g. const {bar} = Foo).

Using the namespace object directly as a value, calling it, or passing it around is not allowed:

import * as Utils from './utils';

const copy = Utils; // Error
Utils(); // Error

Using the namespace object in a type position without qualification, or with unqualified typeof, is not allowed:

import * as Utils from './utils';

type T = Utils; // Error
type U = typeof Utils; // Error

Qualified type access and qualified typeof are allowed:

import * as Utils from './utils';

type T = Utils.SomeType; // Ok
type U = typeof Utils.someValue; // Ok

Destructuring with rest elements or computed properties is not allowed, because these patterns do not access specific named exports:

import * as Utils from './utils';

const {foo, ...rest} = Utils; // Error
const {foo, [expr]: computed} = Utils; // Error

Simple destructuring with known property names is allowed:

import * as Utils from './utils';

const {greeting, count} = Utils; // Ok
const {'greeting': renamed} = Utils; // Ok

mixed-import-and-require

Triggers when a file uses both ES module import statements and CommonJS require calls at the top level. Mixing these two module systems in a single file can lead to confusing semantics and interop issues. Choose one style per file.

Using both import and require in the same file:

import {named1} from './foo';

const {named2} = require('./foo'); // Error

The error points to the require call and references the import statement. Only the first of each is reported, regardless of how many import or require statements appear.

Accessing a member on a require call also triggers the lint:

import {named1} from './foo';

const named2 = require('./foo').named2; // Error

Type-only imports (using import type or import typeof) do not count as value imports and do not trigger this lint when combined with require:

import type {MyType} from './foo';
import typeof {MyClass} from './foo';

const Foo = require('./foo'); // Ok

To fix the error, use one module system consistently. Prefer ES module import statements:

import {named1} from './foo';
import {named2} from './foo'; // Ok

non-const-var-export

Triggers when you export a variable declared with var or let. Exported variables should be declared with const to prevent reassignment after export, which can lead to unpredictable behavior across modules.

Exporting a let or var declaration directly:

export let count: number = 0; // Error
export var name: string = 'foo'; // Error

export const MAX: number = 100; // Ok

Exporting let or var variables via export specifiers:

let count: number = 0;
var name: string = 'foo';
const MAX: number = 100;

export {count, name}; // Error on `count` and `name`
export {MAX}; // Ok

To fix the error, change the variable declaration to const:

export const count: number = 0; // Ok
export const name: string = 'foo'; // Ok

this-in-exported-function

Triggers when you use this in an exported function that does not have a this parameter annotation. Without a this annotation, the binding of this is unknown and can lead to unexpected behavior when the function is called from another module.

Using this in a directly exported function declaration:

export function greet() {
return this.name; // Error
}

Using this in exported function expressions and arrow functions:

export const greet = function() {
return this.name; // Error
};

export const greetArrow = () => {
return this.name; // Error
};

Using this in a function exported via an export specifier:

function greet() {
return this.name; // Error
}

export { greet };

Using this in a default-exported function:

export default function() {
return this.name; // Error
}

To fix the error, add a this parameter annotation to the function to explicitly declare the expected this type:

export function greet(this: {name: string}) {
return this.name; // Ok
}

Note that this in nested function declarations, function expressions, or class bodies within an exported function does not trigger this lint, since those have their own this binding:

export function outer() {
// `this` inside a nested function does not trigger the lint
function inner() {
this; // Ok (not flagged by this lint)
}

// `this` inside a class body does not trigger the lint
class C {
method() {
this; // Ok
}
}
}