Roy Lopez
PersistDev.blog
#typescript

Understanding Modules and Scripts in TypeScript

Understanding Modules and Scripts in TypeScript
0 views
4 min read
#typescript

In TypeScript, files can be categorized in two ways: modules or scripts. This distinction determines how TypeScript scopes variables, functions, and types within a file.

Modules: Scoped and Explicit

A module is a self-contained piece of code with its own scope. To share functionality, modules use import and export statements. Anything defined in a module remains inaccessible to other files unless explicitly exported.

For example, consider this settings.ts module that defines a constant:

// settings.ts
const MAX_USERS = 100;

Without importing MAX_USERS, other files cannot access it:

// dashboard.ts
console.log(MAX_USERS);
// Error: Cannot find name 'MAX_USERS'.

To use the MAX_USERS constant in dashboard.ts, you must export it in settings.ts and import it where needed:

// settings.ts
export const MAX_USERS = 100;

// dashboard.ts
import { MAX_USERS } from "./settings";

console.log(MAX_USERS); // 100

TypeScript treats files with import or export statements as modules by default.

Scripts: Global Scope and Legacy Behavior

In contrast, a script operates in the global scope. All variables, functions, and types declared in a script file are accessible everywhere in the project. This behavior resembles traditional JavaScript, where variables declared in one <script> tag are accessible in another.

If a file has no import or export statements, TypeScript considers it a script. For example:

// settings.ts
const MAX_USERS = 100;

This MAX_USERS constant is now accessible globally:

// dashboard.ts
console.log(MAX_USERS); // 100

While convenient, relying on the global scope can lead to unintended collisions, especially in larger projects.

Why TypeScript Distinguishes Between Modules and Scripts

TypeScript’s Legacy

TypeScript predates the inclusion of import and export in JavaScript. It was initially used to create scripts, and as a result, TypeScript infers whether a file is a module or a script based on its content.

Execution Environment Matters

The decision to treat a file as a module or script depends on the environment in which the code executes. For instance:

  • In a browser, adding the type="module" attribute to a <script> tag ensures the file is treated as a module:

    <script type="module" src="dashboard.js"></script>
  • Without the type="module" attribute, the file is treated as a script.

This automatic inference can lead to errors when TypeScript guesses incorrectly.

Common Errors and Fixes

“Cannot redeclare block-scoped variable”

Suppose you declare a variable in a new file:

// helpers.ts
const user = "John";

You might encounter this error:

Cannot redeclare block-scoped variable 'user'.

This happens because TypeScript treats helpers.ts as a script, placing user in the global scope. If there’s already a global variable named user—such as one defined by the DOM—TypeScript prevents redeclaration.

To resolve this, make the file a module by adding an empty export statement:

// helpers.ts
const user = "John";

export {};

Now, user is scoped to the helpers.ts module, avoiding global conflicts.

Best Practices for TypeScript Projects

  • Always Use Modules: Explicitly define imports and exports to avoid global scope collisions and improve code maintainability.
  • Enable Module Resolution: Use a modern module system (e.g., ES modules or CommonJS) in your project configuration.
  • Set module in tsconfig.json: Ensure your project is set up to use modules by default:
    {
      "compilerOptions": {
        "module": "ESNext"
      }
    }

By treating all files as modules, you sidestep many potential issues with scripts and global scope, ensuring a cleaner and more robust TypeScript codebase.


Understanding the distinction between modules and scripts is essential for writing clear, maintainable TypeScript code. Modules offer the isolation and explicitness modern development demands, while scripts harken back to JavaScript’s early days. Adopting a module-first approach is the best way forward in most TypeScript projects.

Loading...