TSConfig Package (@schwab/tsconfig)
Overview
The @schwab/tsconfig package provides shared TypeScript configuration files that ensure consistent compilation settings, strict type checking, and optimized build configurations across all applications and packages in the Charles Schwab monorepo.
Architecture
Configuration Files
1. Base Configuration (base.json)
Core TypeScript Configuration
{
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"allowImportingTsExtensions": true,
"esModuleInterop": true,
"incremental": true,
"isolatedModules": true,
"lib": ["ES2022", "DOM", "DOM.Iterable", "ES2022.String"],
"module": "ESNext",
"moduleResolution": "Bundler",
"moduleDetection": "force",
"noUncheckedIndexedAccess": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
"target": "ES2022",
"allowSyntheticDefaultImports": true,
"allowUnreachableCode": false,
"experimentalDecorators": true,
"noImplicitAny": false,
"removeComments": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"jsx": "react-jsx",
"noLib": false,
"sourceMap": true,
"emitDecoratorMetadata": true,
"allowJs": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"paths": {
"@/apps/*": ["../../apps/*"]
}
},
"include": [
"next-env.d.ts",
"global.d.ts",
"**/*.ts",
"**/*.tsx",
"**/**/*.ts",
"**/**/*.tsx",
"src"
],
"exclude": ["dist", "build", "node_modules", "**/*.js"]
}
2. Next.js Configuration (next.json)
Next.js Optimized Configuration
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Next.js",
"extends": "./base.json",
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "Bundler"
}
}
Key Features
1. Strict Type Safety
Type Safety Configuration Benefits
// Enhanced type safety with strict settings
interface UserProfile {
id: string;
name: string;
email: string;
preferences?: UserPreferences;
}
// noUncheckedIndexedAccess prevents index signature issues
const users: Record<string, UserProfile> = {};
function getUser(id: string): UserProfile | undefined {
// TypeScript enforces proper handling of potentially undefined values
return users[id]; // Returns UserProfile | undefined (not UserProfile)
}
// Usage requires proper null/undefined checking
const user = getUser('123');
if (user) {
console.log(user.name); // Safe access after null check
}
// noUnusedLocals and noUnusedParameters catch dead code
function processData(data: any[], _unusedParam: string): any[] {
// This would cause a compilation error due to unused parameter
return data.filter(item => item.active);
}
// forceConsistentCasingInFileNames prevents import issues
import { Component } from './MyComponent'; // Must match actual file casing
2. Modern ES Features
ES2022 and Modern JavaScript Features
// ES2022 features enabled by target and lib settings
// Top-level await support
const config = await import('./config.json');
// Private class fields
class DataService {
#apiKey: string;
#baseUrl: string;
constructor(apiKey: string, baseUrl: string) {
this.#apiKey = apiKey;
this.#baseUrl = baseUrl;
}
async #makeRequest(endpoint: string): Promise<Response> {
return fetch(`${this.#baseUrl}${endpoint}`, {
headers: {
'Authorization': `Bearer ${this.#apiKey}`
}
});
}
async getData(id: string): Promise<any> {
const response = await this.#makeRequest(`/data/${id}`);
return response.json();
}
}
// Logical assignment operators
class UserSettings {
private _theme: string | null = null;
get theme(): string {
// Nullish coalescing assignment
return this._theme ??= 'default';
}
set theme(value: string) {
this._theme = value;
}
}
// Array methods with proper typing
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2); // Properly typed as number[]
const found = numbers.find(n => n > 3); // Properly typed as number | undefined
3. Path Mapping and Module Resolution
Module Resolution and Path Mapping
// Path mapping configuration enables clean imports
// Instead of relative imports:
// import { Component } from '../../../apps/client-central/src/components/Component';
// Use clean path mappings:
import { Component } from '@/apps/client-central/src/components/Component';
// Module resolution examples
export interface PathMappingExample {
// Apps can import from packages using clean paths
fetchData: typeof import('@schwab/fetch').fetchDrupalData;
// Packages can reference other packages
validateSchema: typeof import('@schwab/schema').validateResponse;
// Type-only imports for better tree-shaking
utilities: typeof import('@schwab/utilities');
}
// Bundler module resolution supports modern import patterns
export const dynamicImport = async (moduleName: string) => {
switch (moduleName) {
case 'utils':
return await import('@schwab/utilities');
case 'schema':
return await import('@schwab/schema');
default:
throw new Error(`Unknown module: ${moduleName}`);
}
};
4. JSX and React Integration
React JSX Configuration
// Modern React JSX transform (no need to import React)
interface ComponentProps {
title: string;
children?: React.ReactNode;
className?: string;
}
// JSX without React import (react-jsx transform)
export function MyComponent({ title, children, className }: ComponentProps) {
return (
<div className={className}>
<h1>{title}</h1>
{children}
</div>
);
}
// Fragment shorthand syntax support
export function ListComponent({ items }: { items: string[] }) {
return (
<>
{items.map((item, index) => (
<div key={index}>{item}</div>
))}
</>
);
}
// Experimental decorators support for class components
function withLogging<T extends { new(...args: any[]): {} }>(constructor: T) {
return class extends constructor {
constructor(...args: any[]) {
console.log('Creating instance of', constructor.name);
super(...args);
}
};
}
@withLogging
class LegacyComponent extends React.Component<{}, {}> {
render() {
return <div>Legacy component with decorator</div>;
}
}
Build Optimizations
1. Incremental Compilation
Build Performance Features
// Incremental compilation support with .tsbuildinfo files
export interface BuildOptimizations {
// Declaration files and maps for better IDE support
generateDeclarations: true;
// Source maps for debugging
enableSourceMaps: true;
// Skip type checking for node_modules
skipLibCheck: true;
// Isolated modules for better parallel processing
isolatedModules: true;
}
// Example of optimized module structure
export class OptimizedService {
constructor(private config: ServiceConfig) {}
// Methods designed to be tree-shakeable
async fetchData(id: string): Promise<Data> {
// Implementation
return {} as Data;
}
// Separate method for different functionality
async updateData(id: string, data: Partial<Data>): Promise<void> {
// Implementation
}
}
interface ServiceConfig {
apiUrl: string;
timeout: number;
}
interface Data {
id: string;
name: string;
value: number;
}
2. Type Checking Performance
Performance-Optimized Type Checking
// Configuration optimizes type checking performance
// Using branded types for better performance
type UserId = string & { readonly __brand: unique symbol };
type AccountId = string & { readonly __brand: unique symbol };
export function createUserId(id: string): UserId {
return id as UserId;
}
export function createAccountId(id: string): AccountId {
return id as AccountId;
}
// Type-only imports reduce bundle size
export type { ComponentProps } from '@schwab/ui';
export type { ValidationSchema } from '@schwab/schema';
// Interface merging for extensible configurations
export interface GlobalConfig {
apiUrl: string;
timeout: number;
}
// This can be augmented in other files
declare global {
namespace NodeJS {
interface ProcessEnv {
NODE_ENV: 'development' | 'production' | 'test';
API_URL?: string;
DATABASE_URL?: string;
}
}
}
Usage Examples
1. Application Configuration
app/tsconfig.json
{
"extends": "@schwab/tsconfig/next.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@/components/*": ["./src/components/*"],
"@/lib/*": ["./src/lib/*"],
"@/types/*": ["./src/types/*"]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules",
".next",
"out",
"build"
]
}
2. Package Configuration
packages/ui/tsconfig.json
{
"extends": "@schwab/tsconfig/base.json",
"compilerOptions": {
"baseUrl": ".",
"rootDir": "./src",
"outDir": "./dist",
"declaration": true,
"declarationMap": true,
"paths": {
"@schwab/ui": ["./src/index.ts"],
"@schwab/ui/*": ["./src/*"]
}
},
"include": [
"src/**/*"
],
"exclude": [
"dist",
"node_modules",
"**/*.test.ts",
"**/*.test.tsx",
"**/*.stories.ts",
"**/*.stories.tsx"
]
}
3. Custom Configuration Extensions
Advanced Configuration Patterns
// apps/custom-app/types/tsconfig-extensions.d.ts
// Extend global types for app-specific needs
declare global {
namespace App {
interface Locals {
user?: User;
session?: Session;
}
interface PageData {
title: string;
meta: Record<string, string>;
}
}
}
// Module augmentation for third-party libraries
declare module '@schwab/fetch' {
interface FetchOptions {
customTimeout?: number;
retryAttempts?: number;
}
}
// Custom utility types leveraging strict configuration
export type RequiredFields<T, K extends keyof T> = T & Required<Pick<T, K>>;
export type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
// Configuration validation using strict types
export interface AppConfig extends RequiredFields<BaseConfig, 'apiUrl' | 'version'> {
features: {
enableAnalytics: boolean;
enableChat: boolean;
enableNotifications: boolean;
};
performance: {
enableCaching: boolean;
cacheTimeout: number;
};
}
export function validateAppConfig(config: unknown): AppConfig {
// Runtime validation would go here
return config as AppConfig;
}
Best Practices
1. Configuration Inheritance
Proper Configuration Inheritance
// Recommended inheritance pattern
// Base config: @schwab/tsconfig/base.json
// ↓
// Framework config: @schwab/tsconfig/next.json
// ↓
// App-specific config: apps/my-app/tsconfig.json
export interface ConfigurationStrategy {
// Always extend from @schwab/tsconfig configs
baseConfig: '@schwab/tsconfig/base.json';
// Use framework-specific configs when available
frameworkConfig: '@schwab/tsconfig/next.json';
// Add app-specific overrides minimally
appOverrides: {
paths?: Record<string, string[]>;
include?: string[];
exclude?: string[];
};
}
2. Type Safety Guidelines
Type Safety Best Practices
// Leverage the strict configuration for better code quality
// Use exact types instead of any
export interface ApiResponse<T = unknown> {
data: T;
success: boolean;
message?: string;
}
// Prefer union types over loose typing
export type Theme = 'light' | 'dark' | 'auto';
export type Environment = 'development' | 'staging' | 'production';
// Use const assertions for immutable data
export const API_ENDPOINTS = {
users: '/api/users',
accounts: '/api/accounts',
transactions: '/api/transactions',
} as const;
export type ApiEndpoint = typeof API_ENDPOINTS[keyof typeof API_ENDPOINTS];
// Implement proper error handling with typed errors
export class TypedError<T = Record<string, unknown>> extends Error {
constructor(
message: string,
public code: string,
public metadata?: T
) {
super(message);
this.name = 'TypedError';
}
}
// Use generic constraints for flexible yet safe APIs
export function processData<T extends Record<string, unknown>>(
data: T[],
processor: (item: T) => T
): T[] {
return data.map(processor);
}
The TSConfig package ensures consistent, optimized TypeScript compilation across the Charles Schwab monorepo, enabling strict type safety, modern JavaScript features, and efficient build processes while maintaining developer productivity and code quality.