8.1 项目结构设计

8.1.1 标准项目结构

一个典型的 TypeScript 项目结构应该清晰、可维护且易于扩展:

my-typescript-project/
├── src/                    # 源代码目录
│   ├── components/         # 组件
│   ├── services/          # 服务层
│   ├── utils/             # 工具函数
│   ├── types/             # 类型定义
│   ├── constants/         # 常量
│   ├── hooks/             # 自定义钩子(React项目)
│   ├── stores/            # 状态管理
│   ├── assets/            # 静态资源
│   ├── styles/            # 样式文件
│   ├── tests/             # 测试文件
│   └── index.ts           # 入口文件
├── dist/                  # 编译输出目录
├── docs/                  # 文档
├── scripts/               # 构建脚本
├── public/                # 公共资源
├── .vscode/               # VS Code 配置
├── node_modules/          # 依赖包
├── package.json           # 项目配置
├── tsconfig.json          # TypeScript 配置
├── tsconfig.build.json    # 构建配置
├── jest.config.js         # 测试配置
├── .eslintrc.js          # ESLint 配置
├── .prettierrc           # Prettier 配置
├── .gitignore            # Git 忽略文件
├── README.md             # 项目说明
└── CHANGELOG.md          # 变更日志

8.1.2 模块化组织

// src/types/index.ts - 统一类型导出
export * from './user';
export * from './api';
export * from './common';

// src/types/user.ts
export interface User {
    id: string;
    name: string;
    email: string;
    role: UserRole;
    createdAt: Date;
    updatedAt: Date;
}

export enum UserRole {
    ADMIN = 'admin',
    USER = 'user',
    MODERATOR = 'moderator'
}

export interface CreateUserRequest {
    name: string;
    email: string;
    role?: UserRole;
}

export interface UpdateUserRequest extends Partial<CreateUserRequest> {
    id: string;
}

// src/types/api.ts
export interface ApiResponse<T = any> {
    success: boolean;
    data?: T;
    error?: string;
    message?: string;
}

export interface PaginatedResponse<T> extends ApiResponse<T[]> {
    pagination: {
        page: number;
        limit: number;
        total: number;
        totalPages: number;
    };
}

export interface ApiError {
    code: string;
    message: string;
    details?: Record<string, any>;
}

// src/types/common.ts
export type ID = string | number;

export interface BaseEntity {
    id: ID;
    createdAt: Date;
    updatedAt: Date;
}

export type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

export type RequiredFields<T, K extends keyof T> = T & Required<Pick<T, K>>;

// src/constants/index.ts - 常量管理
export const API_ENDPOINTS = {
    USERS: '/api/users',
    AUTH: '/api/auth',
    POSTS: '/api/posts'
} as const;

export const HTTP_STATUS = {
    OK: 200,
    CREATED: 201,
    BAD_REQUEST: 400,
    UNAUTHORIZED: 401,
    FORBIDDEN: 403,
    NOT_FOUND: 404,
    INTERNAL_SERVER_ERROR: 500
} as const;

export const VALIDATION_RULES = {
    EMAIL_REGEX: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
    PASSWORD_MIN_LENGTH: 8,
    USERNAME_MIN_LENGTH: 3,
    USERNAME_MAX_LENGTH: 20
} as const;

// src/utils/index.ts - 工具函数
export * from './validation';
export * from './formatting';
export * from './api';
export * from './storage';

// src/utils/validation.ts
import { VALIDATION_RULES } from '../constants';

export class ValidationUtils {
    static isValidEmail(email: string): boolean {
        return VALIDATION_RULES.EMAIL_REGEX.test(email);
    }
    
    static isValidPassword(password: string): boolean {
        return password.length >= VALIDATION_RULES.PASSWORD_MIN_LENGTH;
    }
    
    static isValidUsername(username: string): boolean {
        return username.length >= VALIDATION_RULES.USERNAME_MIN_LENGTH &&
               username.length <= VALIDATION_RULES.USERNAME_MAX_LENGTH;
    }
    
    static validateRequired<T>(value: T, fieldName: string): T {
        if (value === null || value === undefined || value === '') {
            throw new Error(`${fieldName} is required`);
        }
        return value;
    }
}

// src/utils/formatting.ts
export class FormattingUtils {
    static formatDate(date: Date, locale = 'en-US'): string {
        return new Intl.DateTimeFormat(locale).format(date);
    }
    
    static formatCurrency(amount: number, currency = 'USD', locale = 'en-US'): string {
        return new Intl.NumberFormat(locale, {
            style: 'currency',
            currency
        }).format(amount);
    }
    
    static truncateText(text: string, maxLength: number): string {
        if (text.length <= maxLength) return text;
        return text.substring(0, maxLength - 3) + '...';
    }
    
    static capitalizeFirst(text: string): string {
        return text.charAt(0).toUpperCase() + text.slice(1).toLowerCase();
    }
}

// src/services/base.service.ts - 基础服务类
import { ApiResponse, ApiError } from '../types';

export abstract class BaseService {
    protected baseUrl: string;
    
    constructor(baseUrl: string) {
        this.baseUrl = baseUrl;
    }
    
    protected async request<T>(
        endpoint: string,
        options: RequestInit = {}
    ): Promise<ApiResponse<T>> {
        try {
            const response = await fetch(`${this.baseUrl}${endpoint}`, {
                headers: {
                    'Content-Type': 'application/json',
                    ...options.headers
                },
                ...options
            });
            
            const data = await response.json();
            
            if (!response.ok) {
                throw new Error(data.message || 'Request failed');
            }
            
            return data;
        } catch (error) {
            throw this.handleError(error);
        }
    }
    
    protected handleError(error: unknown): ApiError {
        if (error instanceof Error) {
            return {
                code: 'REQUEST_ERROR',
                message: error.message
            };
        }
        
        return {
            code: 'UNKNOWN_ERROR',
            message: 'An unknown error occurred'
        };
    }
}

8.2 TypeScript 配置详解

8.2.1 tsconfig.json 完整配置

{
  "compilerOptions": {
    // 基本选项
    "target": "ES2020",                    // 编译目标
    "module": "ESNext",                    // 模块系统
    "lib": ["ES2020", "DOM", "DOM.Iterable"], // 包含的库
    "allowJs": true,                       // 允许编译 JS 文件
    "checkJs": false,                      // 检查 JS 文件
    "jsx": "react-jsx",                    // JSX 处理方式
    "declaration": true,                   // 生成声明文件
    "declarationMap": true,                // 生成声明文件的 source map
    "sourceMap": true,                     // 生成 source map
    "outDir": "./dist",                    // 输出目录
    "rootDir": "./src",                    // 根目录
    "removeComments": false,               // 移除注释
    "noEmit": false,                       // 不生成输出文件
    "incremental": true,                   // 增量编译
    "tsBuildInfoFile": "./.tsbuildinfo",   // 增量编译信息文件
    
    // 模块解析选项
    "moduleResolution": "node",            // 模块解析策略
    "baseUrl": "./",                       // 基础路径
    "paths": {                             // 路径映射
      "@/*": ["src/*"],
      "@/components/*": ["src/components/*"],
      "@/services/*": ["src/services/*"],
      "@/utils/*": ["src/utils/*"],
      "@/types/*": ["src/types/*"]
    },
    "typeRoots": ["./node_modules/@types", "./src/types"], // 类型根目录
    "types": ["node", "jest"],             // 包含的类型包
    "allowSyntheticDefaultImports": true,  // 允许合成默认导入
    "esModuleInterop": true,               // ES 模块互操作
    "preserveSymlinks": true,              // 保留符号链接
    "allowUmdGlobalAccess": false,         // 允许 UMD 全局访问
    
    // 严格检查选项
    "strict": true,                        // 启用所有严格检查
    "noImplicitAny": true,                 // 不允许隐式 any
    "strictNullChecks": true,              // 严格空值检查
    "strictFunctionTypes": true,           // 严格函数类型检查
    "strictBindCallApply": true,           // 严格 bind/call/apply 检查
    "strictPropertyInitialization": true,  // 严格属性初始化检查
    "noImplicitThis": true,                // 不允许隐式 this
    "alwaysStrict": true,                  // 总是以严格模式解析
    
    // 额外检查选项
    "noUnusedLocals": true,                // 检查未使用的局部变量
    "noUnusedParameters": true,            // 检查未使用的参数
    "exactOptionalPropertyTypes": true,    // 精确可选属性类型
    "noImplicitReturns": true,             // 检查函数返回值
    "noFallthroughCasesInSwitch": true,    // 检查 switch 语句的 fallthrough
    "noUncheckedIndexedAccess": true,      // 检查索引访问
    "noImplicitOverride": true,            // 检查隐式重写
    "noPropertyAccessFromIndexSignature": true, // 禁止从索引签名访问属性
    
    // 高级选项
    "skipLibCheck": true,                  // 跳过库文件检查
    "forceConsistentCasingInFileNames": true, // 强制文件名大小写一致
    "resolveJsonModule": true,             // 解析 JSON 模块
    "isolatedModules": true,               // 隔离模块
    "useDefineForClassFields": true,       // 使用 define 定义类字段
    "experimentalDecorators": true,        // 实验性装饰器
    "emitDecoratorMetadata": true,         // 发出装饰器元数据
    "downlevelIteration": true,            // 降级迭代
    "importHelpers": true,                 // 导入辅助函数
    "newLine": "lf"                        // 换行符
  },
  
  "include": [
    "src/**/*",
    "tests/**/*",
    "scripts/**/*"
  ],
  
  "exclude": [
    "node_modules",
    "dist",
    "build",
    "coverage",
    "**/*.spec.ts",
    "**/*.test.ts"
  ],
  
  "references": [
    { "path": "./tsconfig.build.json" }
  ]
}

8.2.2 多配置文件管理

// tsconfig.build.json - 构建配置
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "noEmit": false,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": false,
    "removeComments": true
  },
  "exclude": [
    "**/*.test.ts",
    "**/*.spec.ts",
    "tests/**/*",
    "src/**/*.stories.ts"
  ]
}

// tsconfig.test.json - 测试配置
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "noEmit": true,
    "types": ["node", "jest", "@testing-library/jest-dom"]
  },
  "include": [
    "src/**/*",
    "tests/**/*",
    "**/*.test.ts",
    "**/*.spec.ts"
  ]
}

// tsconfig.node.json - Node.js 配置
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "lib": ["ES2020"],
    "types": ["node"]
  },
  "include": [
    "scripts/**/*",
    "server/**/*"
  ]
}

8.2.3 路径映射和模块解析

// 配置路径映射后的导入示例

// 传统导入方式
// import { UserService } from '../../../services/user.service';
// import { ValidationUtils } from '../../../utils/validation';
// import { User } from '../../../types/user';

// 使用路径映射后
import { UserService } from '@/services/user.service';
import { ValidationUtils } from '@/utils/validation';
import { User } from '@/types/user';

// 创建路径映射配置工具
class PathMappingConfig {
    static generatePaths(srcDir: string = 'src'): Record<string, string[]> {
        return {
            '@/*': [`${srcDir}/*`],
            '@/components/*': [`${srcDir}/components/*`],
            '@/services/*': [`${srcDir}/services/*`],
            '@/utils/*': [`${srcDir}/utils/*`],
            '@/types/*': [`${srcDir}/types/*`],
            '@/constants/*': [`${srcDir}/constants/*`],
            '@/hooks/*': [`${srcDir}/hooks/*`],
            '@/stores/*': [`${srcDir}/stores/*`],
            '@/assets/*': [`${srcDir}/assets/*`],
            '@/styles/*': [`${srcDir}/styles/*`]
        };
    }
    
    static generateWebpackAlias(srcDir: string = 'src'): Record<string, string> {
        const path = require('path');
        const basePath = path.resolve(__dirname, srcDir);
        
        return {
            '@': basePath,
            '@/components': path.resolve(basePath, 'components'),
            '@/services': path.resolve(basePath, 'services'),
            '@/utils': path.resolve(basePath, 'utils'),
            '@/types': path.resolve(basePath, 'types'),
            '@/constants': path.resolve(basePath, 'constants'),
            '@/hooks': path.resolve(basePath, 'hooks'),
            '@/stores': path.resolve(basePath, 'stores'),
            '@/assets': path.resolve(basePath, 'assets'),
            '@/styles': path.resolve(basePath, 'styles')
        };
    }
}

8.3 代码质量工具

8.3.1 ESLint 配置

// .eslintrc.js
module.exports = {
  root: true,
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 2020,
    sourceType: 'module',
    ecmaFeatures: {
      jsx: true
    },
    project: './tsconfig.json'
  },
  plugins: [
    '@typescript-eslint',
    'react',
    'react-hooks',
    'import',
    'jsx-a11y',
    'prettier'
  ],
  extends: [
    'eslint:recommended',
    '@typescript-eslint/recommended',
    '@typescript-eslint/recommended-requiring-type-checking',
    'plugin:react/recommended',
    'plugin:react-hooks/recommended',
    'plugin:import/errors',
    'plugin:import/warnings',
    'plugin:import/typescript',
    'plugin:jsx-a11y/recommended',
    'prettier'
  ],
  env: {
    browser: true,
    es6: true,
    node: true,
    jest: true
  },
  settings: {
    react: {
      version: 'detect'
    },
    'import/resolver': {
      typescript: {
        alwaysTryTypes: true,
        project: './tsconfig.json'
      }
    }
  },
  rules: {
    // TypeScript 规则
    '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
    '@typescript-eslint/explicit-function-return-type': 'warn',
    '@typescript-eslint/explicit-module-boundary-types': 'warn',
    '@typescript-eslint/no-explicit-any': 'warn',
    '@typescript-eslint/no-non-null-assertion': 'warn',
    '@typescript-eslint/prefer-const': 'error',
    '@typescript-eslint/prefer-readonly': 'warn',
    '@typescript-eslint/prefer-nullish-coalescing': 'error',
    '@typescript-eslint/prefer-optional-chain': 'error',
    '@typescript-eslint/strict-boolean-expressions': 'warn',
    '@typescript-eslint/switch-exhaustiveness-check': 'error',
    '@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
    '@typescript-eslint/consistent-type-imports': [
      'error',
      { prefer: 'type-imports' }
    ],
    
    // 导入规则
    'import/order': [
      'error',
      {
        groups: [
          'builtin',
          'external',
          'internal',
          'parent',
          'sibling',
          'index'
        ],
        'newlines-between': 'always',
        alphabetize: {
          order: 'asc',
          caseInsensitive: true
        }
      }
    ],
    'import/no-unresolved': 'error',
    'import/no-cycle': 'error',
    'import/no-self-import': 'error',
    'import/no-duplicate-imports': 'error',
    
    // React 规则
    'react/react-in-jsx-scope': 'off',
    'react/prop-types': 'off',
    'react/jsx-uses-react': 'off',
    'react/jsx-uses-vars': 'error',
    'react-hooks/rules-of-hooks': 'error',
    'react-hooks/exhaustive-deps': 'warn',
    
    // 通用规则
    'no-console': ['warn', { allow: ['warn', 'error'] }],
    'no-debugger': 'error',
    'no-alert': 'error',
    'no-var': 'error',
    'prefer-const': 'error',
    'prefer-arrow-callback': 'error',
    'arrow-spacing': 'error',
    'object-shorthand': 'error',
    'prefer-template': 'error',
    'template-curly-spacing': 'error',
    'yield-star-spacing': 'error',
    'yoda': 'error',
    
    // Prettier 集成
    'prettier/prettier': 'error'
  },
  overrides: [
    {
      files: ['**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts', '**/*.spec.tsx'],
      env: {
        jest: true
      },
      rules: {
        '@typescript-eslint/no-explicit-any': 'off',
        '@typescript-eslint/no-non-null-assertion': 'off'
      }
    },
    {
      files: ['scripts/**/*'],
      rules: {
        'no-console': 'off'
      }
    }
  ]
};

8.3.2 Prettier 配置

// .prettierrc
{
  "semi": true,
  "trailingComma": "es5",
  "singleQuote": true,
  "printWidth": 80,
  "tabWidth": 2,
  "useTabs": false,
  "quoteProps": "as-needed",
  "bracketSpacing": true,
  "bracketSameLine": false,
  "arrowParens": "avoid",
  "endOfLine": "lf",
  "embeddedLanguageFormatting": "auto",
  "htmlWhitespaceSensitivity": "css",
  "insertPragma": false,
  "jsxSingleQuote": true,
  "proseWrap": "preserve",
  "requirePragma": false,
  "vueIndentScriptAndStyle": false
}
// .prettierignore
dist/
build/
coverage/
node_modules/
*.min.js
*.min.css
package-lock.json
yarn.lock
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

8.3.3 Husky 和 lint-staged 配置

// package.json
{
  "scripts": {
    "prepare": "husky install",
    "lint": "eslint src --ext .ts,.tsx --fix",
    "lint:check": "eslint src --ext .ts,.tsx",
    "format": "prettier --write src/**/*.{ts,tsx,json,css,md}",
    "format:check": "prettier --check src/**/*.{ts,tsx,json,css,md}",
    "type-check": "tsc --noEmit",
    "test": "jest",
    "test:coverage": "jest --coverage"
  },
  "lint-staged": {
    "*.{ts,tsx}": [
      "eslint --fix",
      "prettier --write",
      "git add"
    ],
    "*.{json,css,md}": [
      "prettier --write",
      "git add"
    ]
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged",
      "pre-push": "npm run type-check && npm run test",
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  }
}
# .husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx lint-staged
# .husky/pre-push
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm run type-check
npm run test
# .husky/commit-msg
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx --no -- commitlint --edit "$1"

8.4 构建和打包

8.4.1 Webpack 配置

// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const webpack = require('webpack');

module.exports = (env, argv) => {
  const isProduction = argv.mode === 'production';
  const isDevelopment = !isProduction;
  
  return {
    entry: './src/index.ts',
    
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: isProduction 
        ? '[name].[contenthash].js' 
        : '[name].js',
      chunkFilename: isProduction 
        ? '[name].[contenthash].chunk.js' 
        : '[name].chunk.js',
      publicPath: '/',
      clean: true
    },
    
    resolve: {
      extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
      alias: {
        '@': path.resolve(__dirname, 'src'),
        '@/components': path.resolve(__dirname, 'src/components'),
        '@/services': path.resolve(__dirname, 'src/services'),
        '@/utils': path.resolve(__dirname, 'src/utils'),
        '@/types': path.resolve(__dirname, 'src/types'),
        '@/constants': path.resolve(__dirname, 'src/constants'),
        '@/hooks': path.resolve(__dirname, 'src/hooks'),
        '@/stores': path.resolve(__dirname, 'src/stores'),
        '@/assets': path.resolve(__dirname, 'src/assets'),
        '@/styles': path.resolve(__dirname, 'src/styles')
      }
    },
    
    module: {
      rules: [
        {
          test: /\.(ts|tsx)$/,
          exclude: /node_modules/,
          use: [
            {
              loader: 'ts-loader',
              options: {
                transpileOnly: true,
                configFile: 'tsconfig.json'
              }
            }
          ]
        },
        {
          test: /\.(css|scss|sass)$/,
          use: [
            isProduction ? MiniCssExtractPlugin.loader : 'style-loader',
            {
              loader: 'css-loader',
              options: {
                modules: {
                  auto: true,
                  localIdentName: isProduction 
                    ? '[hash:base64:8]' 
                    : '[name]__[local]--[hash:base64:5]'
                },
                sourceMap: isDevelopment
              }
            },
            {
              loader: 'postcss-loader',
              options: {
                sourceMap: isDevelopment
              }
            },
            {
              loader: 'sass-loader',
              options: {
                sourceMap: isDevelopment
              }
            }
          ]
        },
        {
          test: /\.(png|jpe?g|gif|svg|webp)$/i,
          type: 'asset',
          parser: {
            dataUrlCondition: {
              maxSize: 8 * 1024 // 8kb
            }
          },
          generator: {
            filename: 'assets/images/[name].[hash][ext]'
          }
        },
        {
          test: /\.(woff|woff2|eot|ttf|otf)$/i,
          type: 'asset/resource',
          generator: {
            filename: 'assets/fonts/[name].[hash][ext]'
          }
        }
      ]
    },
    
    plugins: [
      new CleanWebpackPlugin(),
      
      new HtmlWebpackPlugin({
        template: './public/index.html',
        filename: 'index.html',
        inject: true,
        minify: isProduction ? {
          removeComments: true,
          collapseWhitespace: true,
          removeRedundantAttributes: true,
          useShortDoctype: true,
          removeEmptyAttributes: true,
          removeStyleLinkTypeAttributes: true,
          keepClosingSlash: true,
          minifyJS: true,
          minifyCSS: true,
          minifyURLs: true
        } : false
      }),
      
      new ForkTsCheckerWebpackPlugin({
        typescript: {
          configFile: 'tsconfig.json'
        },
        eslint: {
          files: './src/**/*.{ts,tsx}'
        }
      }),
      
      new webpack.DefinePlugin({
        'process.env.NODE_ENV': JSON.stringify(argv.mode),
        'process.env.VERSION': JSON.stringify(process.env.npm_package_version)
      }),
      
      ...(isProduction ? [
        new MiniCssExtractPlugin({
          filename: '[name].[contenthash].css',
          chunkFilename: '[name].[contenthash].chunk.css'
        })
      ] : [])
    ],
    
    optimization: {
      minimize: isProduction,
      splitChunks: {
        chunks: 'all',
        cacheGroups: {
          vendor: {
            test: /[\\/]node_modules[\\/]/,
            name: 'vendors',
            chunks: 'all'
          },
          common: {
            name: 'common',
            minChunks: 2,
            chunks: 'all',
            enforce: true
          }
        }
      },
      runtimeChunk: {
        name: 'runtime'
      }
    },
    
    devServer: {
      static: {
        directory: path.join(__dirname, 'public')
      },
      compress: true,
      port: 3000,
      hot: true,
      open: true,
      historyApiFallback: true,
      client: {
        overlay: {
          errors: true,
          warnings: false
        }
      }
    },
    
    devtool: isDevelopment ? 'eval-source-map' : 'source-map',
    
    performance: {
      hints: isProduction ? 'warning' : false,
      maxEntrypointSize: 512000,
      maxAssetSize: 512000
    }
  };
};

8.4.2 Vite 配置

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
import { visualizer } from 'rollup-plugin-visualizer';
import { createHtmlPlugin } from 'vite-plugin-html';

export default defineConfig(({ command, mode }) => {
  const isProduction = mode === 'production';
  
  return {
    plugins: [
      react(),
      
      createHtmlPlugin({
        inject: {
          data: {
            title: 'TypeScript App',
            description: 'A modern TypeScript application'
          }
        }
      }),
      
      ...(isProduction ? [
        visualizer({
          filename: 'dist/stats.html',
          open: true,
          gzipSize: true,
          brotliSize: true
        })
      ] : [])
    ],
    
    resolve: {
      alias: {
        '@': resolve(__dirname, 'src'),
        '@/components': resolve(__dirname, 'src/components'),
        '@/services': resolve(__dirname, 'src/services'),
        '@/utils': resolve(__dirname, 'src/utils'),
        '@/types': resolve(__dirname, 'src/types'),
        '@/constants': resolve(__dirname, 'src/constants'),
        '@/hooks': resolve(__dirname, 'src/hooks'),
        '@/stores': resolve(__dirname, 'src/stores'),
        '@/assets': resolve(__dirname, 'src/assets'),
        '@/styles': resolve(__dirname, 'src/styles')
      }
    },
    
    css: {
      modules: {
        localsConvention: 'camelCase',
        generateScopedName: isProduction 
          ? '[hash:base64:8]' 
          : '[name]__[local]--[hash:base64:5]'
      },
      preprocessorOptions: {
        scss: {
          additionalData: `@import "@/styles/variables.scss";`
        }
      }
    },
    
    build: {
      target: 'es2020',
      outDir: 'dist',
      assetsDir: 'assets',
      sourcemap: true,
      minify: 'terser',
      terserOptions: {
        compress: {
          drop_console: isProduction,
          drop_debugger: isProduction
        }
      },
      rollupOptions: {
        output: {
          manualChunks: {
            vendor: ['react', 'react-dom'],
            utils: ['lodash', 'date-fns']
          }
        }
      },
      chunkSizeWarningLimit: 1000
    },
    
    server: {
      port: 3000,
      open: true,
      cors: true,
      proxy: {
        '/api': {
          target: 'http://localhost:8000',
          changeOrigin: true,
          rewrite: (path) => path.replace(/^\/api/, '')
        }
      }
    },
    
    preview: {
      port: 4173,
      open: true
    },
    
    define: {
      __APP_VERSION__: JSON.stringify(process.env.npm_package_version),
      __BUILD_TIME__: JSON.stringify(new Date().toISOString())
    },
    
    optimizeDeps: {
      include: ['react', 'react-dom'],
      exclude: ['@vite/client', '@vite/env']
    }
  };
});

8.4.3 构建脚本

// scripts/build.ts
import { execSync } from 'child_process';
import { rmSync, existsSync } from 'fs';
import { resolve } from 'path';
import chalk from 'chalk';

class BuildScript {
    private readonly distDir = resolve(__dirname, '../dist');
    
    async run(): Promise<void> {
        console.log(chalk.blue('🚀 Starting build process...'));
        
        try {
            // 清理输出目录
            this.cleanDist();
            
            // 类型检查
            await this.typeCheck();
            
            // 代码检查
            await this.lint();
            
            // 运行测试
            await this.test();
            
            // 构建项目
            await this.build();
            
            // 分析包大小
            await this.analyzeBundleSize();
            
            console.log(chalk.green('✅ Build completed successfully!'));
        } catch (error) {
            console.error(chalk.red('❌ Build failed:'), error);
            process.exit(1);
        }
    }
    
    private cleanDist(): void {
        console.log(chalk.yellow('🧹 Cleaning dist directory...'));
        
        if (existsSync(this.distDir)) {
            rmSync(this.distDir, { recursive: true, force: true });
        }
    }
    
    private async typeCheck(): Promise<void> {
        console.log(chalk.yellow('🔍 Running type check...'));
        
        try {
            execSync('npx tsc --noEmit', { stdio: 'inherit' });
            console.log(chalk.green('✅ Type check passed'));
        } catch (error) {
            throw new Error('Type check failed');
        }
    }
    
    private async lint(): Promise<void> {
        console.log(chalk.yellow('🔍 Running ESLint...'));
        
        try {
            execSync('npx eslint src --ext .ts,.tsx', { stdio: 'inherit' });
            console.log(chalk.green('✅ Linting passed'));
        } catch (error) {
            throw new Error('Linting failed');
        }
    }
    
    private async test(): Promise<void> {
        console.log(chalk.yellow('🧪 Running tests...'));
        
        try {
            execSync('npm test -- --coverage --watchAll=false', { stdio: 'inherit' });
            console.log(chalk.green('✅ Tests passed'));
        } catch (error) {
            throw new Error('Tests failed');
        }
    }
    
    private async build(): Promise<void> {
        console.log(chalk.yellow('📦 Building project...'));
        
        try {
            execSync('npx webpack --mode production', { stdio: 'inherit' });
            console.log(chalk.green('✅ Build completed'));
        } catch (error) {
            throw new Error('Build failed');
        }
    }
    
    private async analyzeBundleSize(): Promise<void> {
        console.log(chalk.yellow('📊 Analyzing bundle size...'));
        
        try {
            execSync('npx webpack-bundle-analyzer dist/stats.json --mode static --report dist/bundle-report.html --no-open', { 
                stdio: 'inherit' 
            });
            console.log(chalk.green('✅ Bundle analysis completed'));
        } catch (error) {
            console.warn(chalk.yellow('⚠️ Bundle analysis failed, but build continues'));
        }
    }
}

// 运行构建脚本
if (require.main === module) {
    const buildScript = new BuildScript();
    buildScript.run();
}

export { BuildScript };
// package.json scripts
{
  "scripts": {
    "dev": "vite",
    "build": "ts-node scripts/build.ts",
    "build:prod": "NODE_ENV=production npm run build",
    "build:analyze": "npm run build && npx webpack-bundle-analyzer dist/stats.json",
    "preview": "vite preview",
    "type-check": "tsc --noEmit",
    "type-check:watch": "tsc --noEmit --watch",
    "lint": "eslint src --ext .ts,.tsx --fix",
    "lint:check": "eslint src --ext .ts,.tsx",
    "format": "prettier --write src/**/*.{ts,tsx,json,css,md}",
    "format:check": "prettier --check src/**/*.{ts,tsx,json,css,md}",
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage",
    "clean": "rimraf dist coverage .tsbuildinfo",
    "clean:all": "npm run clean && rimraf node_modules package-lock.json"
  }
}

8.5 环境配置管理

8.5.1 环境变量管理

// src/config/env.ts
interface EnvironmentConfig {
    NODE_ENV: 'development' | 'production' | 'test';
    API_BASE_URL: string;
    API_TIMEOUT: number;
    ENABLE_ANALYTICS: boolean;
    LOG_LEVEL: 'debug' | 'info' | 'warn' | 'error';
    VERSION: string;
    BUILD_TIME: string;
}

class EnvironmentManager {
    private static instance: EnvironmentManager;
    private config: EnvironmentConfig;
    
    private constructor() {
        this.config = this.loadConfig();
        this.validateConfig();
    }
    
    static getInstance(): EnvironmentManager {
        if (!this.instance) {
            this.instance = new EnvironmentManager();
        }
        return this.instance;
    }
    
    private loadConfig(): EnvironmentConfig {
        return {
            NODE_ENV: (process.env.NODE_ENV as any) || 'development',
            API_BASE_URL: process.env.REACT_APP_API_BASE_URL || 'http://localhost:8000',
            API_TIMEOUT: parseInt(process.env.REACT_APP_API_TIMEOUT || '5000', 10),
            ENABLE_ANALYTICS: process.env.REACT_APP_ENABLE_ANALYTICS === 'true',
            LOG_LEVEL: (process.env.REACT_APP_LOG_LEVEL as any) || 'info',
            VERSION: process.env.REACT_APP_VERSION || '1.0.0',
            BUILD_TIME: process.env.REACT_APP_BUILD_TIME || new Date().toISOString()
        };
    }
    
    private validateConfig(): void {
        const requiredVars = ['API_BASE_URL'];
        const missingVars = requiredVars.filter(varName => !this.config[varName as keyof EnvironmentConfig]);
        
        if (missingVars.length > 0) {
            throw new Error(`Missing required environment variables: ${missingVars.join(', ')}`);
        }
        
        // 验证 URL 格式
        try {
            new URL(this.config.API_BASE_URL);
        } catch {
            throw new Error('Invalid API_BASE_URL format');
        }
        
        // 验证超时时间
        if (this.config.API_TIMEOUT <= 0) {
            throw new Error('API_TIMEOUT must be a positive number');
        }
    }
    
    get<K extends keyof EnvironmentConfig>(key: K): EnvironmentConfig[K] {
        return this.config[key];
    }
    
    getAll(): Readonly<EnvironmentConfig> {
        return { ...this.config };
    }
    
    isDevelopment(): boolean {
        return this.config.NODE_ENV === 'development';
    }
    
    isProduction(): boolean {
        return this.config.NODE_ENV === 'production';
    }
    
    isTest(): boolean {
        return this.config.NODE_ENV === 'test';
    }
}

// 导出单例实例
export const env = EnvironmentManager.getInstance();

// 类型安全的环境变量访问
export const config = {
    api: {
        baseUrl: env.get('API_BASE_URL'),
        timeout: env.get('API_TIMEOUT')
    },
    app: {
        version: env.get('VERSION'),
        buildTime: env.get('BUILD_TIME'),
        enableAnalytics: env.get('ENABLE_ANALYTICS')
    },
    logging: {
        level: env.get('LOG_LEVEL')
    }
} as const;

8.5.2 多环境配置文件

# .env.development
REACT_APP_API_BASE_URL=http://localhost:8000
REACT_APP_API_TIMEOUT=10000
REACT_APP_ENABLE_ANALYTICS=false
REACT_APP_LOG_LEVEL=debug

# .env.production
REACT_APP_API_BASE_URL=https://api.example.com
REACT_APP_API_TIMEOUT=5000
REACT_APP_ENABLE_ANALYTICS=true
REACT_APP_LOG_LEVEL=error

# .env.test
REACT_APP_API_BASE_URL=http://localhost:3001
REACT_APP_API_TIMEOUT=3000
REACT_APP_ENABLE_ANALYTICS=false
REACT_APP_LOG_LEVEL=warn

# .env.local (本地开发覆盖)
REACT_APP_API_BASE_URL=http://localhost:9000
REACT_APP_ENABLE_ANALYTICS=false
// src/config/feature-flags.ts
interface FeatureFlags {
    enableNewUI: boolean;
    enableBetaFeatures: boolean;
    enableExperimentalAPI: boolean;
    maxFileUploadSize: number;
    enableRealTimeUpdates: boolean;
}

class FeatureFlagManager {
    private flags: FeatureFlags;
    
    constructor() {
        this.flags = this.loadFlags();
    }
    
    private loadFlags(): FeatureFlags {
        // 可以从环境变量、远程配置或本地存储加载
        return {
            enableNewUI: process.env.REACT_APP_ENABLE_NEW_UI === 'true',
            enableBetaFeatures: process.env.REACT_APP_ENABLE_BETA === 'true',
            enableExperimentalAPI: process.env.REACT_APP_ENABLE_EXPERIMENTAL_API === 'true',
            maxFileUploadSize: parseInt(process.env.REACT_APP_MAX_FILE_SIZE || '10485760', 10), // 10MB
            enableRealTimeUpdates: process.env.REACT_APP_ENABLE_REALTIME === 'true'
        };
    }
    
    isEnabled(flag: keyof FeatureFlags): boolean {
        return Boolean(this.flags[flag]);
    }
    
    getValue<K extends keyof FeatureFlags>(flag: K): FeatureFlags[K] {
        return this.flags[flag];
    }
    
    // 运行时更新功能标志(用于A/B测试等)
    updateFlag<K extends keyof FeatureFlags>(flag: K, value: FeatureFlags[K]): void {
        this.flags[flag] = value;
        
        // 可以持久化到本地存储
        localStorage.setItem(`feature_flag_${flag}`, JSON.stringify(value));
    }
}

export const featureFlags = new FeatureFlagManager();

// 使用示例
export const useFeatureFlag = (flag: keyof FeatureFlags) => {
    return featureFlags.isEnabled(flag);
};

8.6 本章练习

练习 1:创建项目脚手架

// 创建一个 TypeScript 项目脚手架生成器
// 支持以下功能:
// 1. 选择项目类型(React、Node.js、Library)
// 2. 选择包管理器(npm、yarn、pnpm)
// 3. 选择代码质量工具(ESLint、Prettier、Husky)
// 4. 选择测试框架(Jest、Vitest)
// 5. 自动生成配置文件

interface ScaffoldOptions {
    projectName: string;
    projectType: 'react' | 'node' | 'library';
    packageManager: 'npm' | 'yarn' | 'pnpm';
    includeESLint: boolean;
    includePrettier: boolean;
    includeHusky: boolean;
    testFramework: 'jest' | 'vitest' | 'none';
    includeCI: boolean;
}

class ProjectScaffold {
    // 你的实现
}

// 使用示例
const scaffold = new ProjectScaffold();

await scaffold.generate({
    projectName: 'my-awesome-project',
    projectType: 'react',
    packageManager: 'npm',
    includeESLint: true,
    includePrettier: true,
    includeHusky: true,
    testFramework: 'jest',
    includeCI: true
});

练习 2:构建性能优化器

// 实现一个构建性能优化器
// 支持以下功能:
// 1. 分析构建时间
// 2. 检测大文件和重复依赖
// 3. 建议优化方案
// 4. 自动应用优化

interface BuildAnalysis {
    buildTime: number;
    bundleSize: number;
    chunkSizes: Record<string, number>;
    duplicateDependencies: string[];
    largeAssets: Array<{ name: string; size: number }>;
    suggestions: string[];
}

class BuildOptimizer {
    // 你的实现
}

// 使用示例
const optimizer = new BuildOptimizer();

const analysis = await optimizer.analyze('./dist');
console.log('Build analysis:', analysis);

const optimizations = optimizer.getSuggestions(analysis);
const applied = await optimizer.applyOptimizations(optimizations);

console.log('Applied optimizations:', applied);

练习 3:配置管理系统

// 实现一个配置管理系统
// 支持以下功能:
// 1. 多环境配置
// 2. 配置验证
// 3. 配置热更新
// 4. 配置加密
// 5. 配置版本管理

interface ConfigSchema {
    [key: string]: {
        type: 'string' | 'number' | 'boolean' | 'object' | 'array';
        required?: boolean;
        default?: any;
        validator?: (value: any) => boolean;
        encrypted?: boolean;
    };
}

class ConfigManager {
    // 你的实现
}

// 使用示例
const configManager = new ConfigManager();

configManager.defineSchema({
    apiUrl: {
        type: 'string',
        required: true,
        validator: (value) => value.startsWith('https://')
    },
    apiKey: {
        type: 'string',
        required: true,
        encrypted: true
    },
    timeout: {
        type: 'number',
        default: 5000,
        validator: (value) => value > 0
    }
});

const config = await configManager.load('production');
console.log('Loaded config:', config);

// 监听配置变化
configManager.onConfigChange((newConfig) => {
    console.log('Config updated:', newConfig);
});

8.7 本章总结

本章详细介绍了 TypeScript 项目的工程化配置,包括:

  1. 项目结构设计:学习了标准的项目结构和模块化组织方式
  2. TypeScript 配置详解:掌握了 tsconfig.json 的完整配置和多配置文件管理
  3. 代码质量工具:配置了 ESLint、Prettier 和 Git hooks
  4. 构建和打包:学习了 Webpack 和 Vite 的配置和优化
  5. 环境配置管理:实现了环境变量管理和功能标志系统

良好的工程化配置是项目成功的基础,它能够提高开发效率、保证代码质量、简化部署流程。通过本章的学习,你应该能够搭建一个完整、可维护的 TypeScript 项目。

下一章我们将学习测试策略,了解如何为 TypeScript 项目编写全面的测试。