Skip to Content
Course content

159: Non-Modules and Script Files

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was helping a junior dev migrate a legacy project to TypeScript. He had created two separate files: analytics.ts and app.ts. In both files, he'd defined a simple constant called apiEndpoint. He wasn't importing one into the other; they were just two files in the same folder. When he ran the compiler, he was baffled to see a wall of red errors screaming "Duplicate identifier 'apiEndpoint'". He kept insisting, "But they're different files! How does the compiler even know about the other one?"

What he had stumbled into is one of the most confusing quirks for people moving from a "module-first" mindset: the difference between a TypeScript module and a script file.

The Global Scope Trap

In TypeScript, any file that does not contain an import or export statement is treated as a script file, not a module. This means everything you declare at the top level of that file—variables, functions, classes—is dumped into the global namespace.

If you have ten different .ts files and none of them use import or export, they all share the same global space. It's essentially like putting one giant <script> tag after another in an old HTML page. If you define const user = 'Alice' in fileA.ts and const user = 'Bob' in fileB.ts, TypeScript will throw a compile-time error because it thinks you're trying to declare the same variable twice in the same global environment.

// fileA.ts
const appVersion = "1.0.0"; // Global scope

// fileB.ts
const appVersion = "2.0.0"; // Error: Duplicate identifier 'appVersion'

I'll be honest: this behavior feels archaic. But it exists to maintain compatibility with the way JavaScript worked for decades before ES modules became the standard.

Forcing Module Behavior

The fix is usually simpler than you'd expect. To tell TypeScript, "Treat this file as its own isolated module, not a global script," you just need to add an export statement. Even if you don't actually have anything specific to export, you can use an empty export. This is a common "hack" in the TS community.

By adding export {} to the bottom of your file, you're signaling to the compiler that the file is a module. Suddenly, those variables are scoped to the file itself, and the duplicate identifier errors vanish.

// fileA.ts
const appVersion = "1.0.0";
export {}; // Now this file is a module!

// fileB.ts
const appVersion = "2.0.0"; 
export {}; // No more conflict with fileA.ts

Of course, the "correct" way to handle this in a modern project is to actually export the things you need and import them where they're used. But when you're dealing with small utility scripts or legacy migrations, export {} is a lifesaver that saves you from having to rename every single variable just to satisfy the compiler.




📋 Practical Task

Fixing Global Namespace Clashes in Session Management

You are working on a project with two files that are currently treated as scripts. Because they both use common naming conventions, the compiler is failing. Your goal is to isolate these files so they no longer clash, without changing the variable names.

File 1: auth-setup.ts

const sessionTimeout = 3600;
function initializeSession() {
    console.log("Session started with timeout: " + sessionTimeout);
}
initializeSession();

File 2: guest-setup.ts

const sessionTimeout = 0;
function initializeSession() {
    console.log("Guest session started.");
}
initializeSession();

Your Task: Modify both files so that the "Duplicate identifier" errors for sessionTimeout and initializeSession disappear, while ensuring the files remain independent and do not import/export logic from one another.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.