Modules and Declaration Files

The 6-Hour "Module Not Found" Nightmare

Friday, 3:45 PM. Deploy time for our new feature. I ran npm run build:

Error: Cannot find module '@utils/helpers'
Error: Cannot find module '@services/api'
Error: Cannot find module '@components/Button'
... 47 more errors

The code worked perfectly on my machine. TypeScript compiled fine locally. But CI/CD failed.

After 6 hours of debugging, I found the issue:

// tsconfig.json on my machine
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@utils/*": ["src/utils/*"],
      "@services/*": ["src/services/*"],
      "@components/*": ["src/components/*"]
    }
  }
}

TypeScript resolved the paths fine. But tsc doesn't rewrite import paths in the output. The compiled JavaScript still had:

Node.js doesn't understand TypeScript's path mapping. Production was broken.

The fix required understanding modules deeply:

And switching to relative imports for critical paths:

Result: Build worked. Deploy succeeded. Learned modules the hard way.

This article covers module systems, path resolution, and declaration files – the unglamorous foundation that prevents 6-hour debugging sessions.


ES Modules

Modern JavaScript module system.

Basic Export/Import

Default Exports

Mixed Exports


CommonJS

Node.js traditional module system.

Basic Module.exports


Module Resolution

How TypeScript finds modules.

Classic Strategy (Deprecated)

Node Strategy (Default)


Path Mapping

Configure custom module paths.

Basic Path Mapping

Wildcard Mapping

Warning: TypeScript resolves these for type checking, but doesn't rewrite imports in output. Need a bundler (webpack, rollup) or runtime loader.


Declaration Files (.d.ts)

Type definitions without implementation.

Basic Declaration File

Ambient Declarations

Module Augmentation


@types Packages

DefinitelyTyped community type definitions.

Installing @types

How @types Works

Custom Types


Triple-Slash Directives

Special comments for compiler instructions.

Reference Types

Reference Path

Reference Lib


Namespace vs Modules

Namespaces (Old Style)

Avoid namespaces in modern TypeScript. Use modules instead.

Modules (Modern)


Real-World Patterns

1. Barrel Exports

2. Type-Only Imports

3. Dynamic Imports


Project Structure

Monorepo Structure

Shared Types


Common Mistakes I Made

1. Forgetting to Export

Fix: Add export

2. Circular Dependencies

Fix: Extract shared types

3. Path Mapping Without Build Tool

Fix: Use bundler (webpack, rollup, esbuild) or relative imports for Node.js


Your Challenge

Build a modular plugin system:


Key Takeaways

  1. ES modules - modern standard with import/export

  2. CommonJS - Node.js traditional with require/module.exports

  3. Module resolution - Classic vs Node strategies

  4. Path mapping - custom module paths (needs bundler for output)

  5. Declaration files - .d.ts for types without implementation

  6. @types packages - community type definitions

  7. Barrel exports - clean imports from directories

  8. Type-only imports - import type for types


What I Learned

The 6-hour module nightmare taught me: TypeScript's module resolution is for type checking, not runtime.

The mistake:

TypeScript was happy. Types checked. Compiled without errors.

But the output:

TypeScript doesn't rewrite import paths. It only uses them for type resolution.

The fix depends on the environment:

For bundled apps (webpack/vite/rollup):

  • Path mapping works - bundler resolves it

  • Keep the convenience

For Node.js libraries:

  • Use relative imports

  • Or set up package.json exports

  • Runtime must resolve paths

The lesson: TypeScript modules are two systems:

  1. Compile-time: TypeScript's type resolution

  2. Runtime: JavaScript's module loader

They must both work. TypeScript helps with #1, but you own #2.

Know your target runtime. Test the compiled output. Module errors are silent until runtime.


Next: TypeScript Configuration

In the next article, we'll explore tsconfig.json and compiler options. You'll learn how a single compiler flag saved us from 200+ potential runtime errors.

Last updated