Module System Rules
CRITICAL: Never use default exports. Always use named exports.
CRITICAL: Never use default exports. Always use named exports.
**CRITICAL:** Never use default exports. Always use named exports.
// GOOD: Named exports
export const createUser = async (deps: Dependencies, args: CreateUserArgs): Promise<User> => {
// ...
};
export interface User {
readonly id: string;
readonly email: string;
}
export type UserRole = 'admin' | 'user' | 'guest';
// BAD: Default exports
export default createUser; // NEVER
export default function createUser() { /* ... */ } // NEVER
export default class User { /* ... */ } // NEVER**Why:** Named exports provide:
**CRITICAL:** Never use CommonJS modules. Always use ES module syntax.
// GOOD: ES modules
import { createUser } from './user';
import type { User } from './types';
export { updateUser } from './user';
// BAD: CommonJS
const { createUser } = require('./user'); // NEVER
module.exports = createUser; // NEVER
exports.createUser = createUser; // NEVER
module.exports.createUser = createUser; // NEVER**Why:** ES modules are:
**CRITICAL:** All `index.ts` files must contain ONLY imports and exports. Never put actual code or logic in index files.
// GOOD: index.ts with only exports
export { createUser } from './createUser';
export { updateUser } from './updateUser';
export { deleteUser } from './deleteUser';
export type { CreateUserArgs, CreateUserResult } from './createUser';
export type { UpdateUserArgs, UpdateUserResult } from './updateUser';
// BAD: Logic in index.ts
export const createUser = async (deps, args) => {
// Implementation here - WRONG!
};
const helper = () => { /* ... */ }; // WRONG!**Why:** Index files should be pure re-export modules for clean public APIs. Logic belongs in dedicated files.
**CRITICAL:** Never bypass a module's `index.ts` file. Always import from the module's public API.
// GOOD: Import from module's public API
import { createUser, updateUser } from '../user';
import type { User, UserRole } from '../user';
// BAD: Bypassing index.ts
import { createUser } from '../user/createUser'; // NEVER
import { User } from '../user/types'; // NEVER
import { helper } from '../user/internal/helper'; // NEVER**Why:** This enforces:
**Example module structure:**
user/
├── index.ts # Public API - import from here
├── createUser.ts # Implementation - don't import directly
├── updateUser.ts # Implementation - don't import directly
├── types.ts # Types - don't import directly
└── internal/ # Internal helpers - definitely don't import directly
└── validator.ts**CRITICAL:** Inside a module, nothing should ever import from its own `index.ts`. All imports within a module must use relative paths. The barrel is the module's public API for *external* consumers only. For nested modules, the same barrel rules apply.
// Given this structure:
// user/
// ├── index.ts ← barrel for external consumers
// ├── create_user.ts
// ├── update_user.ts
// ├── types.ts
// └── validation/
// ├── index.ts
// └── validate_email.ts
// In user/create_user.ts:
// GOOD: relative path to sibling file
import type { User } from './types';
// GOOD: relative path to nested sub-module barrel
import { validateEmail } from './validation';
// BAD: importing from own module's barrel
import type { User } from '.'; // circular
import { validateEmail } from './index'; // circular**Why:** Importing from the module's own `index.ts` creates circular dependencies and defeats the purpose of the barrel (which is the module's public contract for external consumers, not internal wiring).
**CRITICAL:** Never include file extensions in import statements.
// GOOD: No extensions
import { createUser } from './user';
import { config } from '@/lib/config';
// BAD: Extensions in imports
import { createUser } from './user.js'; // NEVER
import { Component } from './Component.tsx'; // NEVER**CRITICAL:** Use `@/` path alias instead of long relative paths.
// GOOD: Path alias for deep imports
import { createLogger } from '@/lib/logger';
import { parseArgs } from '@/lib/args';
import { handleSpec } from '@/commands/spec';
// BAD: Deep relative imports
import { createLogger } from '../../../lib/logger'; // NEVER
import { parseArgs } from '../../lib/args'; // NEVER**When to use path aliases:**
// GOOD: Relative for nearby files
import { validate } from './validate';
import { types } from '../types';
// GOOD: Alias for distant files
import { logger } from '@/lib/logger';**tsconfig.json setup:**
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}// GOOD: Separate type imports from value imports
import { createLogger } from '@/lib/logger';
import type { Logger } from '@/lib/logger';
import { validateArgs } from '@/lib/schema-validator';
import type { CommandResult, GlobalOptions } from '@/lib/args';
// GOOD: Pure type imports when no values are needed
import type { HookInput, PostToolUseHookOutput } from '@/types/config';
import type { SddConfig, Component } from '@/types/settings';
// BAD: Importing types without the type keyword
import { Logger } from '@/lib/logger'; // Looks like a value import
import { SddConfig } from '@/types/settings'; // Bundler can't tree-shake**Why**: `import type` is erased at compile time, ensuring types never appear in output bundles. It also makes the intent explicit — readers know immediately this import is for types only.