Project Architecture Design
SPA and MPA Architecture Design
Single Page Application (SPA) Architecture:
// src/router.ts (Bun-based SPA routing implementation)
import { serve } from 'bun'
const app = serve({
port: 3000,
fetch(request) {
const url = new URL(request.url)
const pathname = url.pathname
// Dynamically import page components (code splitting)
const getPage = async () => {
switch (pathname) {
case '/':
return (await import('./pages/Home')).default
case '/about':
return (await import('./pages/About')).default
default:
return (await import('./pages/404')).default
}
}
try {
const Page = await getPage()
const html = await Page.render()
return new Response(html, {
headers: { 'Content-Type': 'text/html' }
})
} catch (err) {
console.error('Routing error:', err)
return new Response('Not Found', { status: 404 })
}
}
})
console.log('SPA running on http://localhost:3000')Multi-Page Application (MPA) Architecture:
// src/server.ts (Bun-based MPA server)
import { serve } from 'bun'
import fs from 'fs/promises'
import path from 'path'
const app = serve({
port: 3000,
fetch(request) {
const url = new URL(request.url)
const pathname = url.pathname
// MPA route mapping
const pageMap = {
'/': '/pages/home.html',
'/about': '/pages/about.html',
'/contact': '/pages/contact.html'
}
if (pageMap[pathname]) {
return serveStaticFile(pageMap[pathname])
}
return new Response('Not Found', { status: 404 })
}
})
async function serveStaticFile(filePath: string) {
const fullPath = path.join(process.cwd(), filePath)
try {
const content = await fs.readFile(fullPath, 'utf8')
return new Response(content, {
headers: { 'Content-Type': getContentType(filePath) }
})
} catch {
return new Response('Not Found', { status: 404 })
}
}
function getContentType(filePath: string) {
const ext = path.extname(filePath)
const mimeTypes = {
'.html': 'text/html',
'.css': 'text/css',
'.js': 'application/javascript'
}
return mimeTypes[ext] || 'application/octet-stream'
}
console.log('MPA running on http://localhost:3000')Modular and Component-Based Design
Modular Architecture Example:
src/
├── modules/
│ ├── auth/ # Authentication module
│ │ ├── api.ts # API requests
│ │ ├── hooks.ts # Custom hooks
│ │ └── types.ts # Type definitions
│ ├── user/ # User module
│ └── product/ # Product module
├── shared/ # Shared module
│ ├── components/ # Common components
│ ├── utils/ # Utility functions
│ └── constants.ts # Constant definitionsComponent-Based Design Principles:
// src/shared/components/Button.tsx
import type { ButtonProps } from './types'
export const Button = ({
variant = 'primary',
size = 'medium',
...props
}: ButtonProps) => {
const baseClasses = 'rounded font-medium transition-colors'
const variantClasses = {
primary: 'bg-blue-500 text-white hover:bg-blue-600',
secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300'
}
const sizeClasses = {
small: 'px-3 py-1 text-sm',
medium: 'px-4 py-2 text-base',
large: 'px-6 py-3 text-lg'
}
return (
<button
className={`${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]}`}
{...props}
/>
)
}
// Type definitions (src/shared/components/types.ts)
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary'
size?: 'small' | 'medium' | 'large'
}State Management Architecture
Zustand State Management Example:
// src/stores/counterStore.ts
import { create } from 'zustand'
interface CounterState {
count: number
increment: () => void
decrement: () => void
reset: () => void
}
export const useCounterStore = create<CounterState>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 })
}))
// Usage in component
import { useCounterStore } from '../stores/counterStore'
export const Counter = () => {
const { count, increment, decrement } = useCounterStore()
return (
<div>
<button onClick={decrement}>-</button>
<span>{count}</span>
<button onClick={increment}>+</button>
</div>
)
}Redux Toolkit Integration:
// src/stores/store.ts
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './counterSlice'
export const store = configureStore({
reducer: {
counter: counterReducer
}
})
// src/stores/counterSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
interface CounterState {
value: number
}
const initialState: CounterState = {
value: 0
}
export const counterSlice = createSlice({
name: 'counter',
initialState,
reducers: {
increment: (state) => {
state.value += 1
},
decrement: (state) => {
state.value -= 1
}
}
})
export const { increment, decrement } = counterSlice.actions
export default counterSlice.reducerRouting Architecture Design
Dynamic Routing Implementation:
// src/router/dynamicRoutes.ts
type RouteConfig = {
path: string
component: () => Promise<{ default: React.ComponentType }>
children?: RouteConfig[]
}
export const routes: RouteConfig[] = [
{
path: '/',
component: () => import('../pages/Home')
},
{
path: '/products/:id',
component: () => import('../pages/ProductDetail'),
children: [
{
path: 'reviews',
component: () => import('../components/ProductReviews')
}
]
}
]
// Route matcher
export function matchRoute(url: string): RouteConfig | null {
const pathSegments = url.split('/').filter(Boolean)
for (const route of routes) {
const routeSegments = route.path.split('/').filter(Boolean)
if (pathSegments.length !== routeSegments.length) continue
let match = true
const params: Record<string, string> = {}
for (let i = 0; i < routeSegments.length; i++) {
if (routeSegments[i].startsWith(':')) {
params[routeSegments[i].slice(1)] = pathSegments[i]
} else if (routeSegments[i] !== pathSegments[i]) {
match = false
break
}
}
if (match) {
return { ...route, params }
}
}
return null
}Project Directory Conventions
Recommended Directory Structure:
my-bun-app/
├── public/ # Static assets
│ ├── favicon.ico
│ └── assets/
├── src/
│ ├── app.ts # Application entry point
│ ├── components/ # Common components
│ ├── layouts/ # Layout components
│ ├── pages/ # Page components
│ ├── routes/ # Route configurations
│ ├── stores/ # State management
│ ├── styles/ # Global styles
│ ├── types/ # Type definitions
│ ├── utils/ # Utility functions
│ └── api/ # API request encapsulation
├── tests/ # Test files
├── bunfig.toml # Bun configuration
├── package.json
└── tsconfig.jsonFile Naming Conventions:
- Component files:
PascalCase.tsx(e.g.,Button.tsx) - Utility functions:
camelCase.ts(e.g.,formatDate.ts) - Type definitions:
PascalCase.d.ts(e.g.,User.d.ts) - Configuration files:
kebab-case(e.g.,bunfig.toml)
Engineering Toolchain
Advanced Bun.js Configuration
bunfig.toml Configuration Example:
# Cache configuration
cache_dir = "~/.bun_cache"
# Installation configuration
install_global_bin_dir = "~/.bun_bin"
# Build configuration
[build]
target = "browser" # or "node"
minify = true
sourcemap = true
splitting = true # Enable code splitting
# Development server configuration
[serve]
port = 3000
cors = true
https = false
# Environment variables
[env]
NODE_ENV = "development"
API_BASE_URL = "http://localhost:8080"Environment Variable Management:
// src/config.ts
type Env = 'development' | 'production' | 'test'
const getEnv = (): Env => {
return (process.env.NODE_ENV as Env) || 'development'
}
export const config = {
env: getEnv(),
apiBaseUrl: process.env.API_BASE_URL || 'http://localhost:8080',
isDev: getEnv() === 'development'
}
// Usage example
import { config } from './config'
console.log(config.apiBaseUrl)Webpack Deep Configuration
Bun vs. Webpack Configuration Comparison:
// webpack.config.js (traditional configuration)
module.exports = {
entry: './src/index.tsx',
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader', 'postcss-loader']
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html'
}),
new CleanWebpackPlugin()
],
optimization: {
splitChunks: {
chunks: 'all'
}
}
}
// Equivalent Bun configuration (via bunfig.toml)
[build]
target = "browser"
minify = true
sourcemap = true
splitting = true
[install]
external = ["react", "react-dom"] # Similar to Webpack's externalsTypeScript Integration
tsconfig.json Configuration:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["DOM", "DOM.Iterable", "ES2020"],
"moduleResolution": "Node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
"types": ["bun-types"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}Type Safety Practices:
// src/types/api.ts
export interface User {
id: number
name: string
email: string
}
export type ApiResponse<T> = {
data: T
error?: {
code: number
message: string
}
}
// src/api/userApi.ts
import type { User, ApiResponse } from '../types/api'
export const fetchUser = async (id: number): Promise<ApiResponse<User>> => {
const response = await fetch(`/api/users/${id}`)
if (!response.ok) {
throw new Error('Failed to fetch user')
}
return response.json()
}Code Linting Tools
ESLint Configuration:
// .eslintrc.js
module.exports = {
env: {
browser: true,
es2020: true,
node: true
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:@typescript-eslint/recommended'
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true
},
ecmaVersion: 2020,
sourceType: 'module'
},
plugins: ['react', '@typescript-eslint'],
rules: {
'react/react-in-jsx-scope': 'off',
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }]
}
}Prettier Configuration:
// .prettierrc
{
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"quoteProps": "as-needed",
"jsxSingleQuote": false,
"trailingComma": "all",
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "always"
}Testing Framework Integration
Vitest Configuration:
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./src/test/setup.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html']
}
}
})Test Example:
// src/components/Button.test.tsx
import { describe, it, expect } from 'vitest'
import { render, screen } from '@testing-library/react'
import { Button } from './Button'
describe('Button component', () => {
it('renders correctly', () => {
render(<Button>Click me</Button>)
expect(screen.getByText('Click me')).toBeInTheDocument()
})
it('calls onClick handler', () => {
const handleClick = vi.fn()
render(<Button onClick={handleClick}>Click me</Button>)
screen.getByText('Click me').click()
expect(handleClick).toHaveBeenCalledTimes(1)
})
})Performance Optimization and Deployment
Code Splitting and Lazy Loading
Dynamic Import Implementation:
// src/utils/lazyLoad.ts
export function lazyLoad<T extends React.ComponentType<any>>(
importFn: () => Promise<{ default: T }>
) {
return React.lazy(importFn)
}
// Usage example
const LazyComponent = lazyLoad(() => import('./HeavyComponent'))
export const MyComponent = () => (
<React.Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</React.Suspense>
)Route-Level Code Splitting:
// src/routes/dynamicRoutes.ts
export const routes = [
{
path: '/dashboard',
component: () => import('../pages/Dashboard'), // Automatic code splitting
children: [
{
path: 'analytics',
component: () => import('../pages/Analytics') // Nested route code splitting
}
]
}
]Asset Compression and Caching
Bun Static Asset Handling:
// src/server/staticAssets.ts
import { serve } from 'bun'
const assetServer = serve({
port: 3001,
fetch(request) {
const url = new URL(request.url)
const filePath = path.join(process.cwd(), 'public', url.pathname)
return Bun.file(filePath).response
}
})
// Enable compression middleware in production
if (process.env.NODE_ENV === 'production') {
app.use(async (ctx, next) => {
await next()
if (ctx.response.body && ctx.response.headers.get('content-type')?.includes('text/')) {
ctx.response.headers.set('content-encoding', 'gzip')
ctx.response.body = await compressGzip(ctx.response.body)
}
})
}CDN Integration Configuration:
// src/config/cdn.ts
export const CDN_CONFIG = {
enabled: process.env.NODE_ENV === 'production',
baseUrl: 'https://cdn.example.com/assets',
version: process.env.CDN_VERSION || '1.0.0'
}
// Usage example
const imageUrl = `${CDN_CONFIG.baseUrl}/images/logo.png?v=${CDN_CONFIG.version}`SSR and SSG Implementation
Server-Side Rendering Example:
// src/server/render.ts
import { renderToString } from 'react-dom/server'
import App from '../app'
export async function renderApp(req: Request) {
const html = renderToString(<App />)
return `
<!DOCTYPE html>
<html>
<head><title>SSR App</title></head>
<body>
<div id="root">${html}</div>
<script src="/client.js"></script>
</body>
</html>
`
}
// Server entry point
serve({
port: 3000,
fetch(request) {
if (request.url === '/server-render') {
return renderApp(request)
}
// ...other route handling
}
})Static Site Generation:
// scripts/generateStatic.ts
import fs from 'fs/promises'
import path from 'path'
import { renderToString } from 'react-dom/server'
import App from '../src/app'
async function generatePage(route: string) {
const html = renderToString(<App initialRoute={route} />)
const outputPath = path.join(process.cwd(), 'dist', route, 'index.html')
await fs.mkdir(path.dirname(outputPath), { recursive: true })
await fs.writeFile(outputPath, `
<!DOCTYPE html>
<html>
<head><title>${route}</title></head>
<body>
<div id="root">${html}</div>
</body>
</html>
`)
}
// Generate predefined routes
['/', '/about', '/contact'].forEach(generatePage)Performance Monitoring Solutions
Web Vitals Integration:
// src/utils/performance.ts
import { getCLS, getFID, getLCP } from 'web-vitals'
export function setupPerformanceMonitoring() {
if (process.env.NODE_ENV === 'production') {
getCLS(console.log)
getFID(console.log)
getLCP(console.log)
// Custom metric monitoring
const navigationTiming = performance.getEntriesByType('navigation')[0]
console.log('Page load time:', navigationTiming.domContentLoadedEventEnd)
}
}
// Call at application entry
setupPerformanceMonitoring()Lighthouse CI Configuration:
# .github/workflows/lighthouse.yml
name: Lighthouse Audit
on: [push]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- run: npm ci
- run: npm run build
- run: npm install -g @lhci/cli
- run: lhci autorun --collect.url=https://your-production-url.comCI/CD and Docker
GitHub Actions Configuration:
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- run: bun install
- run: bun build
- uses: azure/webapps-deploy@v2
with:
app-name: 'my-bun-app'
publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE }}
package: ./distDockerfile Optimization:
# Multi-stage build
FROM node:18-alpine as builder
WORKDIR /app
COPY . .
RUN bun install --production
RUN bun build
# Production image
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json .
RUN bun install --production
EXPOSE 3000
CMD ["bun", "run", "--bun", "dist/app.js"]
# Security hardening
RUN apk add --no-cache tini
ENTRYPOINT ["/sbin/tini", "--"]



