Skip to main content
ESLint Interlace
Plugin: modularityRules

no-mutable-exports

Disallow mutable let/var declarations on exported bindings

Keywords: mutable export, live binding, let, var, const, ESLint rule, JavaScript, TypeScript, auto-fix, LLM-optimized

Disallow export let and export var declarations — they create shared live bindings that all importers observe. This rule is part of eslint-plugin-modularity.

Quick Summary

AspectDetails
SeverityWarning (code quality)
Auto-Fix✅ Yes (replaces let/var with const)
CategoryModularity
ESLint MCP✅ Optimized for ESLint MCP integration
Best ForAll JavaScript/TypeScript codebases with ES module exports

Rule Details

ES modules export live bindings. When you write export let count = 0, every importer gets a reference to the same binding — if the exporting module reassigns count, all importers immediately see the new value. This creates invisible coupling across module boundaries and makes behavior hard to reason about.

The auto-fix replaces let or var with const. If the variable is later reassigned inside the module you will need to refactor that logic (e.g. encapsulate mutation behind an exported function), but the fix surfaces the issue immediately.

export const, export function, and export class are all fine and are never flagged.

Why This Matters

IssueImpactSolution
🔗 Implicit couplingAll importers share the same mutable bindingUse const or encapsulate state
🐛 Spooky actionRemote reassignment surprises importersExport functions instead
🔄 PredictabilityModule interface becomes statefulKeep exports immutable

Examples

❌ Incorrect

// Mutable export creates a live binding
export let count = 0;
export var config = {};

✅ Correct

// Immutable exports
export const count = 0;
export const config = Object.freeze({});
export function increment() { return count + 1; }
export class Counter {}

Auto-fix

ESLint can fix this automatically. Running eslint --fix replaces the mutable keyword with const.

Before

export let count = 0;
export var config = {};

After

export const count = 0;
export const config = {};

If the variable is reassigned inside the module, TypeScript or the runtime will surface the error — which is exactly the intent. Refactor the mutation behind an exported function rather than using a mutable live binding.

Configuration Examples

Basic Usage

{
  rules: {
    'modularity/no-mutable-exports': 'warn'
  }
}

Further Reading

Known False Negatives

The following patterns are not detected due to static analysis limitations:

Re-exported Mutable Bindings

Why: When a mutable binding is imported from another module and then re-exported, the rule cannot trace the original declaration.

// ❌ NOT DETECTED - re-export of a mutable binding
import { count } from './state';
export { count }; // still a live binding if count was declared with let

Mitigation: Apply the rule in the originating module and review re-export chains manually.

Namespace Exports

Why: export * from './module' bulk-re-exports all bindings; the rule cannot inspect the source module statically.

// ❌ NOT DETECTED - bulk re-export
export * from './mutable-state';

Mitigation: Avoid export * for modules that contain mutable state; prefer explicit named exports.