← Back to list

Understanding Typescript Configuration: How tsconfig.json,

You’ve just tried to push your code, but the pre-commit hook is blocking you. The error message says your imports aren’t in alphabetical…

Kaleesh · 2026-06-19 12:29 · 15 claps · 3.7 min read
#typescript #debugging #project-management #eslint-configuration #tsconfig
Open on Medium ↗
Wiki topics: BIZ · Business Strategy 💻 · Programming 🌐 · Web Development

Understanding Typescript Configuration: How tsconfig.json, ESLint, and Import Ordering Work Together

You’ve just tried to push your code, but the pre-commit hook is blocking you. The error message says your imports aren’t in alphabetical order. You check your tsconfig.json—nothing about imports there. So where's this rule coming from? And why can't you push without running npm run lint?

This article will demystify the entire workflow: what tsconfig.json actually controls, how ESLint enforces import ordering, and how pre-commit hooks tie everything together to create a seamless (or frustrating) development experience

Part 1: What is Typescript and Why Do We Need Configuration?

The Problem Typescript Solves

JavaScript is dynamically typed. You can pass a string where a number is expected, call methods on undefined, and the code will run until it crashes at runtime. Typescript adds a type system on top of JavaScript:

// JavaScript - no error until runtime
const users = fetchUsers();
const firstUser = users[0];
firstUser.email.toLowerCase(); // 💥 Runtime error if users is null

// TypeScript - error caught immediately
const users: User[] = fetchUsers();
const firstUser = users[0];
firstUser.email.toLowerCase(); // ✅ Type-safe

But Typescript isn’t JavaScript. Browsers and Node.js can’t run .ts files directly. They need to be compiled into .js. This is where tsconfig.json comes in.

What is tsconfig.json?

tsconfig.json is a blueprint for the Typescript compiler. It tells the tsc (Typescript compiler) how to transform your .ts files into .js files that Node.js or browsers can execute.

Think of it like a recipe:

  • Ingredients: your Typescript source files
  • Instructions: settings in tsconfig.json
  • Output: compiled JavaScript

Part 2: Anatomy of tsconfig.json

A Real-World Example

{
  "compilerOptions": {
    // Output target - what version of JavaScript to produce
    "target": "ES2020",

    // Module system - how imports/exports work
    "module": "ESNext",

    // Include built-in types for ES2020 features and DOM APIs
    "lib": ["ES2020", "DOM"],

    // Where to find Typescript source files
    "rootDir": "./src",

    // Where to write compiled JavaScript files
    "outDir": "./dist",

    // Enforce strict type checking
    "strict": true,

    // Enable emit of declaration files (.d.ts)
    "declaration": true,

    // Skip checking node_modules type definitions (speeds up compilation)
    "skipLibCheck": true,

    // Ensure consistent casing in file names
    "forceConsistentCasingInFileNames": true,

    // Support CommonJS imports in ES modules
    "esModuleInterop": true,

    // Allow importing JSON files
    "resolveJsonModule": true
  },

  // Which files to include in compilation
  "include": ["src/**/*"],

  // Which files to exclude
  "exclude": ["node_modules", "dist", "**/*.test.ts"],

  // Type definitions from packages
  "typeRoots": ["./node_modules/@types"]
}

Part 3: How the Typescript Compiler Actually Works

The Compilation Pipeline

When you run tsc or npm run build, here's what happens internally:

Step 1: Scanning ├─ Read tsconfig.json ├─ Identify all .ts files in “include” paths └─ Exclude files in “exclude” paths

Step 2: Parsing ├─ Convert each .ts file into an Abstract Syntax Tree (AST) ├─ AST represents the structure: variables, functions, types, etc. └─ Example: “import express from ‘express’” becomes a node in the tree

Step 3: Type Checking ├─ Analyze the entire AST ├─ Check that types match: function arguments, return values, etc. ├─ Report errors if types don’t align └─ This is why Typescript catches bugs before runtime

Step 4: Transformation ├─ Remove all type annotations (they don’t exist in JavaScript) ├─ Transform modern syntax to target JavaScript version ├─ Example: Arrow functions stay arrow functions if target is ES2020 └─ But async/await → generators if target is ES5

Step 5: Emit ├─ Write .js files to outDir ├─ Optionally write .d.ts type definition files └─ Optionally write .js.map source maps for debugging

Step 6: Reporting └─ Output errors, warnings, or success message

Part 4: ESLint and the Import Ordering Mystery

Now here’s where most developers get confused. Import ordering has NOTHING to do with tsconfig.json.

Your alphabetically-sorted imports are enforced by ESLint, not Typescript.

What is ESLint?

ESLint is a code quality and style linter. Unlike Typescript (which checks types), ESLint checks:

  • Code style consistency
  • Potential bugs (unused variables, unreachable code)
  • Code structure (import ordering, naming conventions)
  • Custom rules your team defines

ESLint Configuration

Your project likely has an .eslintrc.json or eslint.config.js

{
  "env": {
    "node": true,
    "es2020": true
  },
  "extends": ["eslint:recommended"],
  "plugins": ["import"],
  "rules": {
    "import/order": [
      "error",
      {
        "groups": [
          "builtin",
          "external",
          "internal",
          "parent",
          "sibling",
          "index"
        ],
        "alphabeticalOrder": true,
        "newlines-between": "always"
      }
    ],
    "no-unused-vars": "warn",
    "no-console": "warn"
  }
}

Understanding the import/order Rule

This rule enforces a specific structure for your imports:

// ✅ CORRECT ORDER
// 1. Built-in Node.js modules
import fs from 'fs';
import path from 'path';

// 2. External packages (alphabetically sorted)
import axios from 'axios';
import dotenv from 'dotenv';
import express from 'express';

// 3. Internal project imports (alphabetically sorted)
import { AuthService } from './services/auth';
import { UserController } from './controllers/user';
import { validateEmail } from './utils/validators';

// ❌ WRONG ORDER (mixed external and internal)
import express from 'express';
import { AuthService } from './services/auth';
import axios from 'axios';

Part 5: The Pre-Commit Hook — The Final Gatekeeper

Now we have Typescript and ESLint. The last piece of the puzzle is the pre-commit hook — the reason you can’t push without running lint.

What is a Pre-Commit Hook?

A Git hook is a script that automatically runs at specific Git events. A pre-commit hook runs every time you try to commit, BEFORE the commit happens.

Your project likely uses Husky, a tool that manages Git hooks:

# .husky/pre-commit (or similar)
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npm run lint
npm run type-check

Your package.json Scripts

{
  "scripts": {
    "lint": "eslint src --fix",
    "lint:check": "eslint src --no-fix",
    "type-check": "tsc --noEmit",
    "build": "tsc",
    "dev": "ts-node src/index.ts"
  }
}

What each does:

  • npm run lint: Run ESLint and auto-fix violations
  • npm run lint:check: Run ESLint without auto-fixing (for CI/CD)
  • npm run type-check: Run Typescript compiler to check types only (don't emit .js)
  • npm run build: Full Typescript compilation

메타데이터
post_id
a87c63c4965e
slug
understanding-typescript-configuration-how-tsconfig-json-a87c63c4965e
url
https://medium.com/@kaleeshp96/understanding-typescript-configuration-how-tsconfig-json-a87c63c4965e
canonical_url
https://medium.com/@kaleeshp96/understanding-typescript-configuration-how-tsconfig-json-a87c63c4965e
author_url
https://medium.com/@kaleeshp96
status
ok
fetched_at
2026-08-07 01:46:55