Project Architecture Design
Single Page Application (SPA) and Multi-Page Application Architecture
Core Design of SPA Architecture:
// Typical SPA Project Structure
src/
├── assets/ # Static assets
├── components/ # Common components
├── pages/ # Page components
├── routes/ # Route configurations
├── store/ # State management
├── services/ # API services
├── utils/ # Utility functions
├── App.tsx # Root component
└── main.tsx # Entry file
// Route Configuration Example (React Router v6)
import { BrowserRouter, Routes, Route } from 'react-router-dom'
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="/dashboard" element={<PrivateRoute><Dashboard /></PrivateRoute>} />
</Routes>
</BrowserRouter>
)
}
Key Points of MPA Architecture Design:
// Multi-Page Application Configuration Example (Webpack)
module.exports = {
entry: {
home: './src/pages/home/index.tsx',
about: './src/pages/about/index.tsx'
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
},
// ...other configurations
}
// Page Entry File Example
// src/pages/home/index.tsx
import React from 'react'
import ReactDOM from 'react-dom'
import HomePage from '../../components/HomePage'
ReactDOM.render(<HomePage />, document.getElementById('root'))
Modular and Component-Based Design
Modular Design Principles:
// Modular Example: User Management Module
src/
├── modules/
│ ├── user/
│ │ ├── components/ # User-related components
│ │ ├── api/ # User API services
│ │ ├── types/ # User-related type definitions
│ │ ├── hooks/ # User-related custom hooks
│ │ └── index.ts # Module export entry
// Type Definition Example
// src/modules/user/types.ts
export interface User {
id: number
name: string
email: string
}
export type UserListResponse = PaginatedResponse<User>
// API Service Example
// src/modules/user/api.ts
import axios from 'axios'
import { User, UserListResponse } from './types'
export const fetchUsers = async (page: number): Promise<UserListResponse> => {
const response = await axios.get(`/api/users?page=${page}`)
return response.data
}
Component-Based Design Specifications:
// Component Design Example
// src/components/Button/index.tsx
import React from 'react'
import PropTypes from 'prop-types'
interface ButtonProps {
variant?: 'primary' | 'secondary'
size?: 'sm' | 'md' | 'lg'
onClick?: () => void
children: React.ReactNode
}
const Button: React.FC<ButtonProps> = ({
variant = 'primary',
size = 'md',
onClick,
children
}) => {
return (
<button
className={`btn btn-${variant} btn-${size}`}
onClick={onClick}
>
{children}
</button>
)
}
Button.propTypes = {
variant: PropTypes.oneOf(['primary', 'secondary']),
size: PropTypes.oneOf(['sm', 'md', 'lg']),
onClick: PropTypes.func,
children: PropTypes.node.isRequired
}
export default Button
State Management Architecture
Redux Toolkit Configuration Example:
// store/configureStore.ts
import { configureStore } from '@reduxjs/toolkit'
import userReducer from '../modules/user/slice'
export const store = configureStore({
reducer: {
user: userReducer
},
devTools: process.env.NODE_ENV !== 'production'
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
// Modular Slice Example
// modules/user/slice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { User } from '../types'
interface UserState {
currentUser: User | null
loading: boolean
error: string | null
}
const initialState: UserState = {
currentUser: null,
loading: false,
error: null
}
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
loginStart(state) {
state.loading = true
state.error = null
},
loginSuccess(state, action: PayloadAction<User>) {
state.currentUser = action.payload
state.loading = false
},
loginFailure(state, action: PayloadAction<string>) {
state.error = action.payload
state.loading = false
}
}
})
export const { loginStart, loginSuccess, loginFailure } = userSlice.actions
export default userSlice.reducer
MobX State Management Example:
// stores/UserStore.ts
import { makeAutoObservable } from 'mobx'
import { User } from '../modules/user/types'
import { fetchUser } from '../modules/user/api'
class UserStore {
currentUser: User | null = null
loading = false
error: string | null = null
constructor() {
makeAutoObservable(this)
}
async login(userId: number) {
this.loading = true
this.error = null
try {
const user = await fetchUser(userId)
this.currentUser = user
} catch (err) {
this.error = err.message
} finally {
this.loading = false
}
}
}
export const userStore = new UserStore()
Routing Architecture Design
Advanced React Router Configuration:
// routes/index.tsx
import { lazy, Suspense } from 'react'
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
import LoadingSpinner from '../components/LoadingSpinner'
const Home = lazy(() => import('../pages/Home'))
const About = lazy(() => import('../pages/About'))
const Dashboard = lazy(() => import('../pages/Dashboard'))
const AppRoutes = () => {
return (
<BrowserRouter>
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route
path="/dashboard"
element={
<RequireAuth>
<Dashboard />
</RequireAuth>
}
/>
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</Suspense>
</BrowserRouter>
)
}
// Authentication Control Component
const RequireAuth = ({ children }: { children: JSX.Element }) => {
const { currentUser } = useUserStore()
if (!currentUser) {
return <Navigate to="/login" replace />
}
return children
}
Vue Router Configuration Example:
// router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import Home from '../views/Home.vue'
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: () => import('../views/About.vue') // Lazy loading
},
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('../views/Dashboard.vue'),
meta: { requiresAuth: true }
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
// Global Navigation Guard
router.beforeEach((to, from, next) => {
const isAuthenticated = store.getters['user/isAuthenticated']
if (to.meta.requiresAuth && !isAuthenticated) {
next('/login')
} else {
next()
}
})
export default router
Project Directory Structure Specifications
Recommended Project Structure:
my-project/
├── .github/ # GitHub configurations
│ └── workflows/ # CI/CD workflows
├── .husky/ # Husky configurations
├── public/ # Static assets (not processed by Webpack)
│ ├── favicon.ico
│ └── index.html
├── src/
│ ├── assets/ # Processed static assets
│ ├── components/ # Common components
│ │ ├── ui/ # Basic UI components
│ │ └── layout/ # Layout components
│ ├── hooks/ # Custom hooks
│ ├── modules/ # Feature modules
│ │ ├── user/ # User module
│ │ └── product/ # Product module
│ ├── router/ # Route configurations
│ ├── services/ # API services
│ ├── store/ # State management
│ ├── types/ # Global type definitions
│ ├── utils/ # Utility functions
│ ├── App.tsx # Root component
│ └── main.tsx # Entry file
├── tests/ # Test files
├── .editorconfig # Editor configuration
├── .eslintrc.js # ESLint configuration
├── .gitignore # Git ignore file
├── .prettierrc # Prettier configuration
├── package.json # Project dependencies
├── tsconfig.json # TypeScript configuration
└── vite.config.ts # Vite configuration (or webpack.config.js)
Directory Structure Design Principles:
- Functional Modularization: Organize by business features
- Clear Layering: Separate components, state, routes, and services
- Centralized Type Management: Store global types separately
- Test-Friendly: Keep test files alongside source code
- Configuration Separation: Manage build configurations independently
Engineering Toolchain
Advanced TypeScript Configuration
In-Depth tsconfig Configuration:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["DOM", "DOM.Iterable", "ES2020"],
"moduleResolution": "Node",
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
"types": ["vite/client"],
"declaration": true,
"declarationDir": "dist/types",
"emitDeclarationOnly": false,
"composite": true,
"incremental": true,
"outDir": "dist",
"rootDir": "src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.spec.ts"]
}
Project References Configuration:
// tsconfig.json (root project)
{
"references": [
{ "path": "./packages/core" },
{ "path": "./packages/utils" }
]
}
// packages/core/tsconfig.json
{
"compilerOptions": {
"composite": true,
"declaration": true,
"outDir": "../../dist/core"
},
"include": ["src"]
}
// packages/utils/tsconfig.json
{
"compilerOptions": {
"composite": true,
"declaration": true,
"outDir": "../../dist/utils"
},
"include": ["src"]
}
Declaration File Generation
Automatic d.ts File Generation:
// tsconfig.json
{
"compilerOptions": {
"declaration": true,
"declarationDir": "dist/types",
"emitDeclarationOnly": false
}
}
// Generate Declaration Files Command
tsc --emitDeclarationOnly
// Third-Party Library Type Declaration Example
// types/custom.d.ts
declare module 'untyped-library' {
export function doSomething(): void
export const someValue: number
}
// Global Type Extension
// types/global.d.ts
declare global {
interface Window {
__MY_APP_CONFIG__: {
apiBaseUrl: string
}
}
}
Type Checking and Code Quality
ESLint + Prettier Configuration:
// .eslintrc.js
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
project: './tsconfig.json'
},
plugins: ['@typescript-eslint', 'prettier'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended'
],
rules: {
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'prettier/prettier': 'error'
}
}
// .prettierrc
{
"printWidth": 100,
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"bracketSpacing": true
}
Husky + lint-staged Configuration:
// package.json
{
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{ts,tsx}": [
"eslint --fix",
"prettier --write",
"git add"
]
}
}
Code Splitting and Lazy Loading
Dynamic Import Implementation:
// React Dynamic Import Example
import React, { Suspense, lazy } from 'react'
const LazyComponent = lazy(() => import('./components/HeavyComponent'))
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
)
}
// Vue Dynamic Import Example
const LazyComponent = () => import('./components/HeavyComponent.vue')
// Route-Level Code Splitting
const routes = [
{
path: '/dashboard',
component: () => import('./views/Dashboard.vue')
}
]
// Webpack Magic Comments
const LazyComponent = lazy(() => import(
/* webpackChunkName: "dashboard" */
/* webpackPrefetch: true */
'./components/Dashboard'
))
Vite Dynamic Import:
// Vite Dynamic Import (Automatic Code Splitting)
const module = await import('./path/to/module')
// Preloading Strategy
import.meta.glob('./modules/*.ts')
Type-Safe API Encapsulation
Axios Type-Safe Encapsulation:
// services/api.ts
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
interface ApiResponse<T> {
code: number
data: T
message: string
}
class ApiClient {
private instance: AxiosInstance
constructor(baseURL: string) {
this.instance = axios.create({
baseURL,
timeout: 10000
})
this.setupInterceptors()
}
private setupInterceptors() {
this.instance.interceptors.request.use(
(config) => {
// Add authentication token, etc.
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
(error) => Promise.reject(error)
)
this.instance.interceptors.response.use(
(response: AxiosResponse<ApiResponse<any>>) => {
if (response.data.code !== 200) {
// Handle business errors
return Promise.reject(new Error(response.data.message))
}
return response.data.data
},
(error) => {
// Handle HTTP errors
return Promise.reject(error)
}
)
}
public async get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
return this.instance.get<ApiResponse<T>>(url, config)
}
public async post<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
return this.instance.post<ApiResponse<T>>(url, data, config)
}
}
// Create API Client Instance
export const apiClient = new ApiClient(process.env.API_BASE_URL!)
// API Service Example
// services/userApi.ts
import { apiClient } from './api'
interface User {
id: number
name: string
email: string
}
export const userApi = {
getUsers: () => apiClient.get<User[]>('/users'),
getUserById: (id: number) => apiClient.get<User>(`/users/${id}`),
createUser: (userData: Omit<User, 'id'>) => apiClient.post<User>('/users', userData)
}
GraphQL Type-Safe Encapsulation:
// services/graphql.ts
import { ApolloClient, InMemoryCache, gql } from '@apollo/client/core'
const cache = new InMemoryCache()
export const client = new ApolloClient({
uri: 'https://api.example.com/graphql',
cache
})
// Type-Safe Query
interface User {
id: number
name: string
email: string
}
interface GetUsersResponse {
users: User[]
}
export const GET_USERS = gql`
query GetUsers {
users {
id
name
email
}
}
`
export async function fetchUsers(): Promise<GetUsersResponse> {
const { data } = await client.query<GetUsersResponse>({ query: GET_USERS })
return data
}
Performance Optimization and Deployment
Compilation Speed Optimization
Incremental Compilation Configuration:
// tsconfig.json
{
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": "./.tsbuildinfo"
}
}
// Project References for Build Optimization
{
"references": [
{ "path": "./packages/core" },
{ "path": "./packages/utils" }
]
}
// Optimized Build Command
tsc --build --watch
Vite Build Optimization:
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
build: {
minify: 'terser',
sourcemap: false,
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
utils: ['lodash', 'date-fns']
}
}
}
}
})
Type Checking Performance Optimization
Skipping Declaration File Checks:
// tsconfig.json
{
"compilerOptions": {
"skipLibCheck": true
}
}
// Exclude Specific Files from Checking
{
"exclude": ["**/*.d.ts", "**/node_modules/**"]
}
// Modular Checking
tsc --project tsconfig.core.json
tsc --project tsconfig.utils.json
Caching Strategy:
# Use swc instead of tsc for type checking (experimental)
SWC_NODE_PROJECT=./tsconfig.json swc src -d dist
# Custom Cache Implementation with TypeScript Compiler API
import * as ts from 'typescript'
import fs from 'fs'
function checkWithCache(file: string) {
const cacheFile = `.cache/${file}.json`
if (fs.existsSync(cacheFile)) {
const cached = JSON.parse(fs.readFileSync(cacheFile, 'utf8'))
if (cached.hash === getFileHash(file)) {
return cached.diagnostics
}
}
const program = ts.createProgram([file], {})
const diagnostics = program.getSemanticDiagnostics()
fs.writeFileSync(cacheFile, JSON.stringify({
hash: getFileHash(file),
diagnostics
}))
return diagnostics
}
Packaging and Distribution
Webpack Production Configuration:
// webpack.prod.js
const { merge } = require('webpack-merge')
const common = require('./webpack.common.js')
const TerserPlugin = require('terser-webpack-plugin')
module.exports = merge(common, {
mode: 'production',
devtool: false,
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true,
terserOptions: {
compress: {
drop_console: true
}
}
})
],
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
},
performance: {
hints: 'warning',
maxEntrypointSize: 512000,
maxAssetSize: 512000
}
})
Rollup Configuration Example:
// rollup.config.js
import typescript from '@rollup/plugin-typescript'
import { nodeResolve } from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import { terser } from 'rollup-plugin-terser'
export default {
input: 'src/index.ts',
output: [
{
file: 'dist/bundle.cjs.js',
format: 'cjs'
},
{
file: 'dist/bundle.esm.js',
format: 'esm'
}
],
plugins: [
nodeResolve(),
commonjs(),
typescript({
declaration: true,
declarationDir: 'dist/types'
}),
terser()
]
}
Type-Safe Deployment Process
CI/CD Type Checking Integration:
# .github/workflows/ci.yml
name: CI Pipeline
on: [push, pull_request]
jobs:
type-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '16'
- run: npm ci
- run: npm run type-check
build:
needs: type-check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '16'
- run: npm ci
- run: npm run build
- uses: actions/upload-artifact@v2
with:
name: dist
path: dist
deploy:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/download-artifact@v2
with:
name: dist
- run: npm run deploy
Docker Type-Safe Build:
# Dockerfile
FROM node:16-alpine as builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run type-check && npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Continuous Integration and Type Checking
Advanced GitHub Actions Configuration:
name: Advanced CI
on:
push:
branches: [main]
pull_request:
types: [opened, synchronize]
jobs:
setup:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- uses: actions/checkout@v2
- id: set-matrix
run: echo "::set-output name=matrix::{\"include\":[{\"module\":\"core\"},{\"module\":\"utils\"}]}"
type-check:
needs: setup
runs-on: ubuntu-latest
strategy:
matrix: ${{fromJson(needs.setup.outputs.matrix)}}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '16'
- run: npm ci
- run: npm run type-check -- --project packages/${{ matrix.module }}/tsconfig.json
test:
needs: type-check
runs-on: ubuntu-latest
strategy:
matrix: ${{fromJson(needs.setup.outputs.matrix)}}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '16'
- run: npm ci
- run: npm test -- packages/${{ matrix.module }}
deploy:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '16'
- run: npm ci
- run: npm run build
- run: npm run deploy
CircleCI Configuration Example:
# .circleci/config.yml
version: 2.1
jobs:
type-check:
docker:
- image: cimg/node:16.14
steps:
- checkout
- restore_cache:
keys: [v1-deps-{{ checksum "package-lock.json" }}]
- run: npm ci
- save_cache:
paths: [node_modules]
key: v1-deps-{{ checksum "package-lock.json" }}
- run: npm run type-check
build:
docker:
- image: cimg/node:16.14
steps:
- checkout
- restore_cache:
keys: [v1-deps-{{ checksum "package-lock.json" }}]
- run: npm ci
- run: npm run build
- save_cache:
paths: [dist]
key: v1-dist-{{ .Environment.CIRCLE_SHA1 }}
deploy:
docker:
- image: cimg/base:2021.09
steps:
- checkout
- restore_cache:
keys: [v1-dist-{{ .Environment.CIRCLE_SHA1 }}]
- run: npm run deploy
workflows:
version: 2
ci_pipeline:
jobs:
- type-check
- build:
requires: [type-check]
- deploy:
requires: [build]
filters:
branches:
only: main



