@alepot55/chessboardjs 2.2.0 → 2.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +227 -0
- package/.github/instructions/copilot-instuctions.md +1671 -0
- package/README.md +127 -403
- package/assets/themes/alepot/theme.json +42 -0
- package/assets/themes/default/theme.json +42 -0
- package/chessboard.bundle.js +782 -119
- package/config/jest.config.js +15 -0
- package/config/rollup.config.js +35 -0
- package/dist/chessboard.cjs.js +10476 -0
- package/dist/chessboard.css +197 -0
- package/dist/chessboard.esm.js +10407 -0
- package/dist/chessboard.iife.js +10481 -0
- package/dist/chessboard.umd.js +10482 -0
- package/jest.config.js +2 -7
- package/package.json +18 -3
- package/rollup.config.js +2 -11
- package/{chessboard.move.js → src/components/Move.js} +3 -3
- package/src/components/Piece.js +273 -0
- package/{chessboard.square.js → src/components/Square.js} +60 -7
- package/src/constants/index.js +15 -0
- package/src/constants/positions.js +62 -0
- package/src/core/Chessboard.js +1930 -0
- package/src/core/ChessboardConfig.js +458 -0
- package/src/core/ChessboardFactory.js +385 -0
- package/src/core/index.js +141 -0
- package/src/errors/ChessboardError.js +133 -0
- package/src/errors/index.js +15 -0
- package/src/errors/messages.js +189 -0
- package/src/index.js +103 -0
- package/src/services/AnimationService.js +180 -0
- package/src/services/BoardService.js +156 -0
- package/src/services/CoordinateService.js +355 -0
- package/src/services/EventService.js +807 -0
- package/src/services/MoveService.js +594 -0
- package/src/services/PieceService.js +303 -0
- package/src/services/PositionService.js +237 -0
- package/src/services/ValidationService.js +673 -0
- package/src/services/index.js +14 -0
- package/src/styles/animations.css +46 -0
- package/{chessboard.css → src/styles/board.css} +3 -0
- package/src/styles/index.css +4 -0
- package/src/styles/pieces.css +66 -0
- package/src/utils/animations.js +37 -0
- package/{chess.js → src/utils/chess.js} +16 -16
- package/src/utils/coordinates.js +62 -0
- package/src/utils/cross-browser.js +150 -0
- package/src/utils/logger.js +422 -0
- package/src/utils/performance.js +311 -0
- package/src/utils/validation.js +458 -0
- package/tests/unit/chessboard-config-animations.test.js +106 -0
- package/tests/unit/chessboard-robust.test.js +163 -0
- package/tests/unit/chessboard.test.js +183 -0
- package/chessboard.config.js +0 -147
- package/chessboard.js +0 -981
- package/chessboard.piece.js +0 -115
- package/test/chessboard.test.js +0 -128
- /package/{alepot_theme → assets/themes/alepot}/bb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/bw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/kb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/kw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/nb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/nw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/pb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/pw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/qb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/qw.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/rb.svg +0 -0
- /package/{alepot_theme → assets/themes/alepot}/rw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/bb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/bw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/kb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/kw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/nb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/nw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/pb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/pw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/qb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/qw.svg +0 -0
- /package/{default_pieces → assets/themes/default}/rb.svg +0 -0
- /package/{default_pieces → assets/themes/default}/rw.svg +0 -0
- /package/{.babelrc → config/.babelrc} +0 -0
|
@@ -0,0 +1,1671 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: "**/Chessboardjs/**"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# 🏗️ Chessboard.js - Guida Completa per Ingegneri Esperti
|
|
6
|
+
|
|
7
|
+
## 📋 Panoramica del Progetto
|
|
8
|
+
|
|
9
|
+
**Chessboard.js** è un pacchetto NPM di livello enterprise per scacchiere interattive in JavaScript vanilla, progettato per massime performance, modularità e manutenibilità. Il progetto implementa pattern architetturali moderni e segue rigorosamente tutte le best practices dell'industria.
|
|
10
|
+
|
|
11
|
+
### 🎯 Principi Architetturali
|
|
12
|
+
|
|
13
|
+
- **Separation of Concerns**: Architettura modulare con responsabilità ben definite
|
|
14
|
+
- **SOLID Principles**: Applicazione rigorosa dei principi di design object-oriented
|
|
15
|
+
- **Clean Architecture**: Dipendenze invertite e layer ben separati
|
|
16
|
+
- **DDD Patterns**: Domain-driven design per la logica di business
|
|
17
|
+
- **Performance First**: Ottimizzazioni per rendering e interazioni in tempo reale
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 🏛️ Architettura del Sistema
|
|
22
|
+
|
|
23
|
+
### Core Architecture Layers
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
27
|
+
│ Presentation Layer │
|
|
28
|
+
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐│
|
|
29
|
+
│ │ Components │ │ Event System │ │ UI Adapters ││
|
|
30
|
+
│ │ - Square.js │ │ - Observers │ │ - Themes ││
|
|
31
|
+
│ │ - Piece.js │ │ - Handlers │ │ - Animations ││
|
|
32
|
+
│ │ - Move.js │ │ - Dispatchers │ │ - Renderers ││
|
|
33
|
+
│ └─────────────────┘ └─────────────────┘ └─────────────────┘│
|
|
34
|
+
└─────────────────────────────────────────────────────────────┘
|
|
35
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
36
|
+
│ Application Layer │
|
|
37
|
+
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐│
|
|
38
|
+
│ │ Use Cases │ │ Controllers │ │ Services ││
|
|
39
|
+
│ │ - Move Logic │ │ - Game Ctrl │ │ - Asset Mgmt ││
|
|
40
|
+
│ │ - Validation │ │ - UI Ctrl │ │ - Theme Svc ││
|
|
41
|
+
│ │ - Animation │ │ - Event Ctrl │ │ - Config Svc ││
|
|
42
|
+
│ └─────────────────┘ └─────────────────┘ └─────────────────┘│
|
|
43
|
+
└─────────────────────────────────────────────────────────────┘
|
|
44
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
45
|
+
│ Domain Layer │
|
|
46
|
+
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐│
|
|
47
|
+
│ │ Entities │ │ Value Objects │ │ Domain Svc ││
|
|
48
|
+
│ │ - Chessboard │ │ - Position │ │ - Move Engine ││
|
|
49
|
+
│ │ - Game State │ │ - Coordinates │ │ - Rules Eng ││
|
|
50
|
+
│ │ - Config │ │ - Square │ │ - Validators ││
|
|
51
|
+
│ └─────────────────┘ └─────────────────┘ └─────────────────┘│
|
|
52
|
+
└─────────────────────────────────────────────────────────────┘
|
|
53
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
54
|
+
│ Infrastructure Layer │
|
|
55
|
+
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐│
|
|
56
|
+
│ │ Utilities │ │ External Deps │ │ Platform ││
|
|
57
|
+
│ │ - DOM Utils │ │ - Chess.js │ │ - Browser API ││
|
|
58
|
+
│ │ - Math Utils │ │ - Animations │ │ - Web Workers ││
|
|
59
|
+
│ │ - Performance │ │ - Asset Load │ │ - Storage ││
|
|
60
|
+
│ └─────────────────┘ └─────────────────┘ └─────────────────┘│
|
|
61
|
+
└─────────────────────────────────────────────────────────────┘
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 📁 Struttura Directory Ottimizzata
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
Chessboardjs/
|
|
68
|
+
├── 📁 src/ # Codice sorgente principale
|
|
69
|
+
│ ├── 📁 core/ # Layer del dominio
|
|
70
|
+
│ │ ├── 📄 Chessboard.js # Entity principale
|
|
71
|
+
│ │ ├── 📄 ChessboardConfig.js # Configuration management
|
|
72
|
+
│ │ ├── 📄 GameState.js # State management
|
|
73
|
+
│ │ └── 📄 index.js # Core exports
|
|
74
|
+
│ ├── 📁 components/ # UI Components (Presentation)
|
|
75
|
+
│ │ ├── 📄 Square.js # Square component
|
|
76
|
+
│ │ ├── 📄 Piece.js # Piece component
|
|
77
|
+
│ │ ├── 📄 Move.js # Move component
|
|
78
|
+
│ │ ├── 📄 Board.js # Board container
|
|
79
|
+
│ │ └── 📄 index.js # Components barrel
|
|
80
|
+
│ ├── 📁 services/ # Application Services
|
|
81
|
+
│ │ ├── 📄 AssetService.js # Asset loading/caching
|
|
82
|
+
│ │ ├── 📄 ThemeService.js # Theme management
|
|
83
|
+
│ │ ├── 📄 AnimationService.js # Animation orchestration
|
|
84
|
+
│ │ ├── 📄 EventService.js # Event management
|
|
85
|
+
│ │ └── 📄 ValidationService.js # Input validation
|
|
86
|
+
│ ├── 📁 controllers/ # Application Controllers
|
|
87
|
+
│ │ ├── 📄 GameController.js # Game logic control
|
|
88
|
+
│ │ ├── 📄 UIController.js # UI state control
|
|
89
|
+
│ │ └── 📄 EventController.js # Event coordination
|
|
90
|
+
│ ├── 📁 domain/ # Domain Logic
|
|
91
|
+
│ │ ├── 📄 MoveEngine.js # Move calculation
|
|
92
|
+
│ │ ├── 📄 RulesEngine.js # Chess rules
|
|
93
|
+
│ │ ├── 📄 PositionValidator.js # Position validation
|
|
94
|
+
│ │ └── 📄 GameRules.js # Game rule definitions
|
|
95
|
+
│ ├── 📁 value-objects/ # Value Objects
|
|
96
|
+
│ │ ├── 📄 Position.js # Board position
|
|
97
|
+
│ │ ├── 📄 Coordinates.js # Square coordinates
|
|
98
|
+
│ │ ├── 📄 Move.js # Move representation
|
|
99
|
+
│ │ └── 📄 Color.js # Color value object
|
|
100
|
+
│ ├── 📁 utils/ # Infrastructure Utilities
|
|
101
|
+
│ │ ├── 📄 dom.js # DOM manipulation
|
|
102
|
+
│ │ ├── 📄 animations.js # Animation helpers
|
|
103
|
+
│ │ ├── 📄 coordinates.js # Coordinate calculations
|
|
104
|
+
│ │ ├── 📄 performance.js # Performance monitoring
|
|
105
|
+
│ │ ├── 📄 validation.js # Input validation
|
|
106
|
+
│ │ ├── 📄 debounce.js # Debouncing utilities
|
|
107
|
+
│ │ └── 📄 logger.js # Logging system
|
|
108
|
+
│ ├── 📁 styles/ # CSS Modules
|
|
109
|
+
│ │ ├── 📄 index.css # Main stylesheet
|
|
110
|
+
│ │ ├── 📄 board.css # Board styling
|
|
111
|
+
│ │ ├── 📄 pieces.css # Piece styling
|
|
112
|
+
│ │ ├── 📄 animations.css # Animation definitions
|
|
113
|
+
│ │ ├── 📄 themes.css # Theme variables
|
|
114
|
+
│ │ └── 📄 responsive.css # Responsive design
|
|
115
|
+
│ ├── 📁 types/ # Type definitions
|
|
116
|
+
│ │ ├── 📄 index.d.ts # Main type exports
|
|
117
|
+
│ │ ├── 📄 chessboard.d.ts # Chessboard types
|
|
118
|
+
│ │ ├── 📄 config.d.ts # Configuration types
|
|
119
|
+
│ │ └── 📄 events.d.ts # Event types
|
|
120
|
+
│ └── 📄 index.js # Main entry point
|
|
121
|
+
├── 📁 assets/ # Static Assets
|
|
122
|
+
│ ├── 📁 themes/ # Tema collections
|
|
123
|
+
│ │ ├── 📁 default/ # Default theme
|
|
124
|
+
│ │ │ ├── 📄 theme.json # Theme metadata
|
|
125
|
+
│ │ │ └── 📁 pieces/ # Piece assets
|
|
126
|
+
│ │ ├── 📁 alepot/ # Custom theme
|
|
127
|
+
│ │ └── 📁 [theme-name]/ # Additional themes
|
|
128
|
+
│ ├── 📁 pieces/ # Universal piece sets
|
|
129
|
+
│ │ ├── 📁 svg/ # SVG pieces (preferred)
|
|
130
|
+
│ │ ├── 📁 png/ # PNG fallbacks
|
|
131
|
+
│ │ └── 📁 webp/ # WebP optimized
|
|
132
|
+
│ └── 📁 sounds/ # Audio assets
|
|
133
|
+
├── 📁 dist/ # Build Output
|
|
134
|
+
│ ├── 📄 chessboard.esm.js # ES Modules
|
|
135
|
+
│ ├── 📄 chessboard.cjs.js # CommonJS
|
|
136
|
+
│ ├── 📄 chessboard.umd.js # UMD
|
|
137
|
+
│ ├── 📄 chessboard.iife.js # IIFE (browsers)
|
|
138
|
+
│ ├── 📄 chessboard.min.js # Minified version
|
|
139
|
+
│ ├── 📄 chessboard.css # Compiled CSS
|
|
140
|
+
│ └── 📁 types/ # TypeScript declarations
|
|
141
|
+
├── 📁 tests/ # Test Suites
|
|
142
|
+
│ ├── 📁 unit/ # Unit tests
|
|
143
|
+
│ │ ├── 📁 core/ # Core logic tests
|
|
144
|
+
│ │ ├── 📁 components/ # Component tests
|
|
145
|
+
│ │ ├── 📁 services/ # Service tests
|
|
146
|
+
│ │ └── 📁 utils/ # Utility tests
|
|
147
|
+
│ ├── 📁 integration/ # Integration tests
|
|
148
|
+
│ │ ├── 📄 game-flow.test.js # Game flow tests
|
|
149
|
+
│ │ ├── 📄 ui-interaction.test.js # UI interaction tests
|
|
150
|
+
│ │ └── 📄 api-integration.test.js # API integration tests
|
|
151
|
+
│ ├── 📁 e2e/ # End-to-end tests
|
|
152
|
+
│ │ ├── 📄 user-scenarios.test.js # User journey tests
|
|
153
|
+
│ │ ├── 📄 performance.test.js # Performance tests
|
|
154
|
+
│ │ └── 📄 accessibility.test.js # A11y tests
|
|
155
|
+
│ ├── 📁 fixtures/ # Test data
|
|
156
|
+
│ │ ├── 📄 positions.json # Sample positions
|
|
157
|
+
│ │ ├── 📄 games.pgn # Sample games
|
|
158
|
+
│ │ └── 📄 configs.json # Test configurations
|
|
159
|
+
│ └── 📁 helpers/ # Test utilities
|
|
160
|
+
├── 📁 docs/ # Documentation
|
|
161
|
+
│ ├── 📄 README.md # Project overview
|
|
162
|
+
│ ├── 📄 API.md # Complete API reference
|
|
163
|
+
│ ├── 📄 ARCHITECTURE.md # Architecture guide
|
|
164
|
+
│ ├── 📄 CONTRIBUTING.md # Contribution guidelines
|
|
165
|
+
│ ├── 📄 PERFORMANCE.md # Performance guide
|
|
166
|
+
│ ├── 📁 api/ # Generated API docs
|
|
167
|
+
│ ├── 📁 examples/ # Usage examples
|
|
168
|
+
│ │ ├── 📄 basic-usage.html # Basic setup
|
|
169
|
+
│ │ ├── 📄 advanced-config.html # Advanced configuration
|
|
170
|
+
│ │ ├── 📄 custom-themes.html # Custom theming
|
|
171
|
+
│ │ ├── 📄 game-integration.html # Chess.js integration
|
|
172
|
+
│ │ └── 📄 performance-demo.html # Performance showcase
|
|
173
|
+
│ └── 📁 guides/ # Development guides
|
|
174
|
+
│ ├── 📄 getting-started.md # Quick start guide
|
|
175
|
+
│ ├── 📄 customization.md # Customization guide
|
|
176
|
+
│ ├── 📄 theming.md # Theming guide
|
|
177
|
+
│ ├── 📄 performance.md # Performance optimization
|
|
178
|
+
│ └── 📄 troubleshooting.md # Common issues
|
|
179
|
+
├── 📁 tools/ # Development Tools
|
|
180
|
+
│ ├── 📄 build.js # Build orchestration
|
|
181
|
+
│ ├── 📄 dev-server.js # Development server
|
|
182
|
+
│ ├── 📄 theme-validator.js # Theme validation
|
|
183
|
+
│ ├── 📄 performance-monitor.js # Performance monitoring
|
|
184
|
+
│ └── 📄 asset-optimizer.js # Asset optimization
|
|
185
|
+
├── 📁 config/ # Configuration Files
|
|
186
|
+
│ ├── 📄 rollup.config.js # Rollup configuration
|
|
187
|
+
│ ├── 📄 jest.config.js # Jest configuration
|
|
188
|
+
│ ├── 📄 eslint.config.js # ESLint rules
|
|
189
|
+
│ ├── 📄 prettier.config.js # Prettier formatting
|
|
190
|
+
│ ├── 📄 babel.config.js # Babel configuration
|
|
191
|
+
│ └── 📄 typescript.config.json # TypeScript configuration
|
|
192
|
+
├── 📁 benchmarks/ # Performance Benchmarks
|
|
193
|
+
│ ├── 📄 rendering-bench.js # Rendering performance
|
|
194
|
+
│ ├── 📄 animation-bench.js # Animation performance
|
|
195
|
+
│ └── 📄 memory-bench.js # Memory usage tests
|
|
196
|
+
└── 📁 scripts/ # Automation Scripts
|
|
197
|
+
├── 📄 release.js # Release automation
|
|
198
|
+
├── 📄 version-bump.js # Version management
|
|
199
|
+
├── 📄 changelog-gen.js # Changelog generation
|
|
200
|
+
└── 📄 deploy.js # Deployment automation
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## 🔧 Configurazione Ambiente di Sviluppo
|
|
206
|
+
|
|
207
|
+
### Prerequisiti Tecnici
|
|
208
|
+
|
|
209
|
+
```json
|
|
210
|
+
{
|
|
211
|
+
"engines": {
|
|
212
|
+
"node": ">=18.0.0",
|
|
213
|
+
"npm": ">=9.0.0"
|
|
214
|
+
},
|
|
215
|
+
"browserslist": [
|
|
216
|
+
"> 1%",
|
|
217
|
+
"last 2 versions",
|
|
218
|
+
"not dead",
|
|
219
|
+
"not ie 11"
|
|
220
|
+
]
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Dipendenze di Sviluppo Raccomandate
|
|
225
|
+
|
|
226
|
+
```json
|
|
227
|
+
{
|
|
228
|
+
"devDependencies": {
|
|
229
|
+
// Build & Bundling
|
|
230
|
+
"rollup": "^4.0.0",
|
|
231
|
+
"@rollup/plugin-node-resolve": "^15.0.0",
|
|
232
|
+
"@rollup/plugin-commonjs": "^25.0.0",
|
|
233
|
+
"@rollup/plugin-terser": "^0.4.0",
|
|
234
|
+
"@rollup/plugin-babel": "^6.0.0",
|
|
235
|
+
"rollup-plugin-postcss": "^4.0.0",
|
|
236
|
+
"rollup-plugin-analyzer": "^4.0.0",
|
|
237
|
+
|
|
238
|
+
// TypeScript Support
|
|
239
|
+
"typescript": "^5.0.0",
|
|
240
|
+
"@rollup/plugin-typescript": "^11.0.0",
|
|
241
|
+
"tslib": "^2.5.0",
|
|
242
|
+
|
|
243
|
+
// Testing Framework
|
|
244
|
+
"jest": "^29.0.0",
|
|
245
|
+
"jest-environment-jsdom": "^29.0.0",
|
|
246
|
+
"@testing-library/jest-dom": "^6.0.0",
|
|
247
|
+
"@testing-library/dom": "^9.0.0",
|
|
248
|
+
"jest-performance": "^1.0.0",
|
|
249
|
+
|
|
250
|
+
// E2E Testing
|
|
251
|
+
"playwright": "^1.40.0",
|
|
252
|
+
"@playwright/test": "^1.40.0",
|
|
253
|
+
|
|
254
|
+
// Code Quality
|
|
255
|
+
"eslint": "^8.50.0",
|
|
256
|
+
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
|
257
|
+
"@typescript-eslint/parser": "^6.0.0",
|
|
258
|
+
"eslint-config-prettier": "^9.0.0",
|
|
259
|
+
"eslint-plugin-import": "^2.28.0",
|
|
260
|
+
"eslint-plugin-jest": "^27.0.0",
|
|
261
|
+
"prettier": "^3.0.0",
|
|
262
|
+
|
|
263
|
+
// CSS Processing
|
|
264
|
+
"postcss": "^8.4.0",
|
|
265
|
+
"autoprefixer": "^10.4.0",
|
|
266
|
+
"cssnano": "^6.0.0",
|
|
267
|
+
"postcss-custom-properties": "^13.0.0",
|
|
268
|
+
|
|
269
|
+
// Documentation
|
|
270
|
+
"jsdoc": "^4.0.0",
|
|
271
|
+
"typedoc": "^0.25.0",
|
|
272
|
+
"docsify-cli": "^4.4.0",
|
|
273
|
+
|
|
274
|
+
// Performance & Monitoring
|
|
275
|
+
"lighthouse": "^11.0.0",
|
|
276
|
+
"bundlesize": "^0.18.0",
|
|
277
|
+
"size-limit": "^9.0.0",
|
|
278
|
+
|
|
279
|
+
// Automation
|
|
280
|
+
"husky": "^8.0.0",
|
|
281
|
+
"lint-staged": "^14.0.0",
|
|
282
|
+
"commitizen": "^4.3.0",
|
|
283
|
+
"semantic-release": "^21.0.0"
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Script NPM Ottimizzati
|
|
289
|
+
|
|
290
|
+
```json
|
|
291
|
+
{
|
|
292
|
+
"scripts": {
|
|
293
|
+
// Development
|
|
294
|
+
"dev": "rollup -c config/rollup.config.js --watch --environment NODE_ENV:development",
|
|
295
|
+
"dev:debug": "rollup -c config/rollup.config.js --watch --environment NODE_ENV:development,DEBUG:true",
|
|
296
|
+
"dev:server": "node tools/dev-server.js",
|
|
297
|
+
|
|
298
|
+
// Building
|
|
299
|
+
"build": "npm run clean && npm run build:lib && npm run build:types && npm run build:css",
|
|
300
|
+
"build:lib": "rollup -c config/rollup.config.js --environment NODE_ENV:production",
|
|
301
|
+
"build:types": "tsc --emitDeclarationOnly",
|
|
302
|
+
"build:css": "postcss src/styles/index.css -o dist/chessboard.css",
|
|
303
|
+
"build:analyze": "npm run build && rollup-plugin-analyzer",
|
|
304
|
+
|
|
305
|
+
// Testing
|
|
306
|
+
"test": "jest --config config/jest.config.js",
|
|
307
|
+
"test:watch": "jest --config config/jest.config.js --watch",
|
|
308
|
+
"test:coverage": "jest --config config/jest.config.js --coverage",
|
|
309
|
+
"test:unit": "jest --config config/jest.config.js tests/unit",
|
|
310
|
+
"test:integration": "jest --config config/jest.config.js tests/integration",
|
|
311
|
+
"test:e2e": "playwright test",
|
|
312
|
+
"test:performance": "node benchmarks/performance-suite.js",
|
|
313
|
+
|
|
314
|
+
// Code Quality
|
|
315
|
+
"lint": "eslint src/ tests/ --ext .js,.ts",
|
|
316
|
+
"lint:fix": "eslint src/ tests/ --ext .js,.ts --fix",
|
|
317
|
+
"format": "prettier --write \"src/**/*.{js,ts,css}\" \"tests/**/*.{js,ts}\"",
|
|
318
|
+
"typecheck": "tsc --noEmit",
|
|
319
|
+
|
|
320
|
+
// Documentation
|
|
321
|
+
"docs:build": "jsdoc src/ -c config/jsdoc.config.json -d docs/api",
|
|
322
|
+
"docs:serve": "docsify serve docs",
|
|
323
|
+
"docs:types": "typedoc src/ --out docs/types",
|
|
324
|
+
|
|
325
|
+
// Quality Assurance
|
|
326
|
+
"validate": "npm run lint && npm run typecheck && npm run test",
|
|
327
|
+
"validate:ci": "npm run validate && npm run build && npm run test:e2e",
|
|
328
|
+
"size-check": "size-limit",
|
|
329
|
+
"perf-audit": "lighthouse --config-path=config/lighthouse.config.js",
|
|
330
|
+
|
|
331
|
+
// Maintenance
|
|
332
|
+
"clean": "rimraf dist/ coverage/ .nyc_output/",
|
|
333
|
+
"clean:all": "npm run clean && rimraf node_modules/ package-lock.json",
|
|
334
|
+
"deps:check": "ncu --interactive --format group",
|
|
335
|
+
"deps:update": "ncu -u && npm install",
|
|
336
|
+
|
|
337
|
+
// Release
|
|
338
|
+
"version:patch": "npm version patch",
|
|
339
|
+
"version:minor": "npm version minor",
|
|
340
|
+
"version:major": "npm version major",
|
|
341
|
+
"prepublishOnly": "npm run validate:ci",
|
|
342
|
+
"release": "semantic-release",
|
|
343
|
+
|
|
344
|
+
// Git Hooks
|
|
345
|
+
"prepare": "husky install",
|
|
346
|
+
"pre-commit": "lint-staged",
|
|
347
|
+
"commit": "git-cz"
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## 🏗️ Patterns Architetturali Implementati
|
|
355
|
+
|
|
356
|
+
### 1. Observer Pattern per Eventi
|
|
357
|
+
|
|
358
|
+
```javascript
|
|
359
|
+
// src/services/EventService.js
|
|
360
|
+
export class EventService {
|
|
361
|
+
constructor() {
|
|
362
|
+
this.observers = new Map();
|
|
363
|
+
this.eventQueue = [];
|
|
364
|
+
this.isProcessing = false;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
subscribe(eventType, callback, priority = 0) {
|
|
368
|
+
if (!this.observers.has(eventType)) {
|
|
369
|
+
this.observers.set(eventType, []);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const observer = { callback, priority, id: crypto.randomUUID() };
|
|
373
|
+
this.observers.get(eventType).push(observer);
|
|
374
|
+
|
|
375
|
+
// Sort by priority (higher first)
|
|
376
|
+
this.observers.get(eventType).sort((a, b) => b.priority - a.priority);
|
|
377
|
+
|
|
378
|
+
return observer.id;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async emit(eventType, data) {
|
|
382
|
+
const event = {
|
|
383
|
+
type: eventType,
|
|
384
|
+
data,
|
|
385
|
+
timestamp: Date.now(),
|
|
386
|
+
id: crypto.randomUUID()
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
this.eventQueue.push(event);
|
|
390
|
+
|
|
391
|
+
if (!this.isProcessing) {
|
|
392
|
+
await this.processQueue();
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
async processQueue() {
|
|
397
|
+
this.isProcessing = true;
|
|
398
|
+
|
|
399
|
+
while (this.eventQueue.length > 0) {
|
|
400
|
+
const event = this.eventQueue.shift();
|
|
401
|
+
await this.processEvent(event);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
this.isProcessing = false;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### 2. Strategy Pattern per Animazioni
|
|
410
|
+
|
|
411
|
+
```javascript
|
|
412
|
+
// src/services/AnimationService.js
|
|
413
|
+
export class AnimationService {
|
|
414
|
+
constructor() {
|
|
415
|
+
this.strategies = new Map();
|
|
416
|
+
this.activeAnimations = new Map();
|
|
417
|
+
this.setupDefaultStrategies();
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
setupDefaultStrategies() {
|
|
421
|
+
this.registerStrategy('ease', new EaseAnimationStrategy());
|
|
422
|
+
this.registerStrategy('linear', new LinearAnimationStrategy());
|
|
423
|
+
this.registerStrategy('bounce', new BounceAnimationStrategy());
|
|
424
|
+
this.registerStrategy('elastic', new ElasticAnimationStrategy());
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
registerStrategy(name, strategy) {
|
|
428
|
+
this.strategies.set(name, strategy);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
async animate(element, properties, options = {}) {
|
|
432
|
+
const strategy = this.strategies.get(options.easing || 'ease');
|
|
433
|
+
|
|
434
|
+
if (!strategy) {
|
|
435
|
+
throw new Error(`Animation strategy '${options.easing}' not found`);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const animation = await strategy.animate(element, properties, options);
|
|
439
|
+
this.activeAnimations.set(animation.id, animation);
|
|
440
|
+
|
|
441
|
+
animation.finished.then(() => {
|
|
442
|
+
this.activeAnimations.delete(animation.id);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
return animation;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### 3. Factory Pattern per Componenti
|
|
451
|
+
|
|
452
|
+
```javascript
|
|
453
|
+
// src/components/ComponentFactory.js
|
|
454
|
+
export class ComponentFactory {
|
|
455
|
+
constructor(services) {
|
|
456
|
+
this.services = services;
|
|
457
|
+
this.componentRegistry = new Map();
|
|
458
|
+
this.registerDefaults();
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
registerDefaults() {
|
|
462
|
+
this.register('square', SquareComponent);
|
|
463
|
+
this.register('piece', PieceComponent);
|
|
464
|
+
this.register('move', MoveComponent);
|
|
465
|
+
this.register('board', BoardComponent);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
register(type, ComponentClass) {
|
|
469
|
+
this.componentRegistry.set(type, ComponentClass);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
create(type, config = {}) {
|
|
473
|
+
const ComponentClass = this.componentRegistry.get(type);
|
|
474
|
+
|
|
475
|
+
if (!ComponentClass) {
|
|
476
|
+
throw new Error(`Component type '${type}' not registered`);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return new ComponentClass({
|
|
480
|
+
...config,
|
|
481
|
+
services: this.services
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
createBoard(config) {
|
|
486
|
+
const board = this.create('board', config);
|
|
487
|
+
|
|
488
|
+
// Create 64 squares
|
|
489
|
+
for (let rank = 0; rank < 8; rank++) {
|
|
490
|
+
for (let file = 0; file < 8; file++) {
|
|
491
|
+
const square = this.create('square', {
|
|
492
|
+
rank,
|
|
493
|
+
file,
|
|
494
|
+
color: (rank + file) % 2 === 0 ? 'light' : 'dark'
|
|
495
|
+
});
|
|
496
|
+
board.addSquare(square);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return board;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### 4. Command Pattern per Azioni
|
|
506
|
+
|
|
507
|
+
```javascript
|
|
508
|
+
// src/domain/commands/MoveCommand.js
|
|
509
|
+
export class MoveCommand {
|
|
510
|
+
constructor(from, to, piece, capturedPiece = null) {
|
|
511
|
+
this.from = from;
|
|
512
|
+
this.to = to;
|
|
513
|
+
this.piece = piece;
|
|
514
|
+
this.capturedPiece = capturedPiece;
|
|
515
|
+
this.timestamp = Date.now();
|
|
516
|
+
this.id = crypto.randomUUID();
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
execute(board) {
|
|
520
|
+
// Store previous state for undo
|
|
521
|
+
this.previousState = board.getState();
|
|
522
|
+
|
|
523
|
+
// Execute the move
|
|
524
|
+
board.movePiece(this.from, this.to);
|
|
525
|
+
|
|
526
|
+
if (this.capturedPiece) {
|
|
527
|
+
board.removePiece(this.to);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Emit move event
|
|
531
|
+
board.emit('move', {
|
|
532
|
+
command: this,
|
|
533
|
+
from: this.from,
|
|
534
|
+
to: this.to,
|
|
535
|
+
piece: this.piece
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
undo(board) {
|
|
540
|
+
if (!this.previousState) {
|
|
541
|
+
throw new Error('Cannot undo command that was never executed');
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
board.setState(this.previousState);
|
|
545
|
+
|
|
546
|
+
board.emit('move-undone', {
|
|
547
|
+
command: this,
|
|
548
|
+
restoredState: this.previousState
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
canExecute(board) {
|
|
553
|
+
return board.isValidMove(this.from, this.to);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
---
|
|
559
|
+
|
|
560
|
+
## 🎯 Best Practices di Sviluppo
|
|
561
|
+
|
|
562
|
+
### Convenzioni di Codice
|
|
563
|
+
|
|
564
|
+
#### 1. Naming Conventions
|
|
565
|
+
|
|
566
|
+
```javascript
|
|
567
|
+
// Classes: PascalCase
|
|
568
|
+
class ChessboardController { }
|
|
569
|
+
class MoveValidationService { }
|
|
570
|
+
|
|
571
|
+
// Methods e Variables: camelCase
|
|
572
|
+
const currentPosition = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR';
|
|
573
|
+
function validateMoveInput(from, to) { }
|
|
574
|
+
|
|
575
|
+
// Constants: UPPER_SNAKE_CASE
|
|
576
|
+
const DEFAULT_ANIMATION_DURATION = 200;
|
|
577
|
+
const PIECE_TYPES = ['p', 'r', 'n', 'b', 'q', 'k'];
|
|
578
|
+
|
|
579
|
+
// Private methods: underscore prefix
|
|
580
|
+
class Chessboard {
|
|
581
|
+
_initializeBoard() { }
|
|
582
|
+
_setupEventListeners() { }
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Files: PascalCase for classes, camelCase for utilities
|
|
586
|
+
// ChessboardController.js
|
|
587
|
+
// animationHelpers.js
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
#### 2. Struttura Metodi
|
|
591
|
+
|
|
592
|
+
```javascript
|
|
593
|
+
class ComponentBase {
|
|
594
|
+
/**
|
|
595
|
+
* Public method template
|
|
596
|
+
*/
|
|
597
|
+
async publicMethod(param1, param2, options = {}) {
|
|
598
|
+
// 1. Input validation
|
|
599
|
+
this._validateInputs(param1, param2, options);
|
|
600
|
+
|
|
601
|
+
// 2. Pre-processing
|
|
602
|
+
const normalizedOptions = this._normalizeOptions(options);
|
|
603
|
+
|
|
604
|
+
try {
|
|
605
|
+
// 3. Core logic
|
|
606
|
+
const result = await this._executeOperation(param1, param2, normalizedOptions);
|
|
607
|
+
|
|
608
|
+
// 4. Post-processing
|
|
609
|
+
return this._formatResult(result);
|
|
610
|
+
|
|
611
|
+
} catch (error) {
|
|
612
|
+
// 5. Error handling
|
|
613
|
+
this._handleError(error, { param1, param2, options });
|
|
614
|
+
throw error;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Private validation method
|
|
620
|
+
*/
|
|
621
|
+
_validateInputs(param1, param2, options) {
|
|
622
|
+
if (!param1) {
|
|
623
|
+
throw new ValidationError('param1 is required');
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
if (typeof param2 !== 'string') {
|
|
627
|
+
throw new ValidationError('param2 must be a string');
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
#### 3. Error Handling Strategy
|
|
634
|
+
|
|
635
|
+
```javascript
|
|
636
|
+
// src/utils/errors.js
|
|
637
|
+
export class ChessboardError extends Error {
|
|
638
|
+
constructor(message, code, context = {}) {
|
|
639
|
+
super(message);
|
|
640
|
+
this.name = 'ChessboardError';
|
|
641
|
+
this.code = code;
|
|
642
|
+
this.context = context;
|
|
643
|
+
this.timestamp = new Date().toISOString();
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
export class ValidationError extends ChessboardError {
|
|
648
|
+
constructor(message, field, value) {
|
|
649
|
+
super(message, 'VALIDATION_ERROR', { field, value });
|
|
650
|
+
this.name = 'ValidationError';
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
export class ConfigurationError extends ChessboardError {
|
|
655
|
+
constructor(message, configKey, configValue) {
|
|
656
|
+
super(message, 'CONFIG_ERROR', { configKey, configValue });
|
|
657
|
+
this.name = 'ConfigurationError';
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Usage in components
|
|
662
|
+
class ChessboardConfig {
|
|
663
|
+
validate() {
|
|
664
|
+
if (!this.id) {
|
|
665
|
+
throw new ValidationError(
|
|
666
|
+
'Chessboard ID is required',
|
|
667
|
+
'id',
|
|
668
|
+
this.id
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
if (!['w', 'b'].includes(this.orientation)) {
|
|
673
|
+
throw new ConfigurationError(
|
|
674
|
+
'Invalid orientation value',
|
|
675
|
+
'orientation',
|
|
676
|
+
this.orientation
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
### Performance Optimization
|
|
684
|
+
|
|
685
|
+
#### 1. Lazy Loading e Code Splitting
|
|
686
|
+
|
|
687
|
+
```javascript
|
|
688
|
+
// src/services/AssetService.js
|
|
689
|
+
export class AssetService {
|
|
690
|
+
constructor() {
|
|
691
|
+
this.cache = new Map();
|
|
692
|
+
this.loadingPromises = new Map();
|
|
693
|
+
this.preloadQueue = [];
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
async loadTheme(themeName) {
|
|
697
|
+
if (this.cache.has(themeName)) {
|
|
698
|
+
return this.cache.get(themeName);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if (this.loadingPromises.has(themeName)) {
|
|
702
|
+
return this.loadingPromises.get(themeName);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const promise = this._loadThemeData(themeName);
|
|
706
|
+
this.loadingPromises.set(themeName, promise);
|
|
707
|
+
|
|
708
|
+
try {
|
|
709
|
+
const theme = await promise;
|
|
710
|
+
this.cache.set(themeName, theme);
|
|
711
|
+
return theme;
|
|
712
|
+
} finally {
|
|
713
|
+
this.loadingPromises.delete(themeName);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
async _loadThemeData(themeName) {
|
|
718
|
+
// Dynamic import for code splitting
|
|
719
|
+
const { default: themeModule } = await import(`../assets/themes/${themeName}/theme.js`);
|
|
720
|
+
|
|
721
|
+
// Parallel loading of assets
|
|
722
|
+
const [pieces, metadata] = await Promise.all([
|
|
723
|
+
this._loadPieces(themeName),
|
|
724
|
+
this._loadThemeMetadata(themeName)
|
|
725
|
+
]);
|
|
726
|
+
|
|
727
|
+
return {
|
|
728
|
+
...themeModule,
|
|
729
|
+
pieces,
|
|
730
|
+
metadata
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
#### 2. Memory Management
|
|
737
|
+
|
|
738
|
+
```javascript
|
|
739
|
+
// src/core/Chessboard.js
|
|
740
|
+
export class Chessboard {
|
|
741
|
+
constructor(config) {
|
|
742
|
+
this.eventListeners = new Set();
|
|
743
|
+
this.animationFrames = new Set();
|
|
744
|
+
this.timers = new Set();
|
|
745
|
+
this.observers = new Set();
|
|
746
|
+
|
|
747
|
+
// WeakMap for DOM associations
|
|
748
|
+
this.elementData = new WeakMap();
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
destroy() {
|
|
752
|
+
// Clean up event listeners
|
|
753
|
+
this.eventListeners.forEach(listener => {
|
|
754
|
+
listener.element.removeEventListener(listener.event, listener.handler);
|
|
755
|
+
});
|
|
756
|
+
this.eventListeners.clear();
|
|
757
|
+
|
|
758
|
+
// Cancel animations
|
|
759
|
+
this.animationFrames.forEach(frameId => {
|
|
760
|
+
cancelAnimationFrame(frameId);
|
|
761
|
+
});
|
|
762
|
+
this.animationFrames.clear();
|
|
763
|
+
|
|
764
|
+
// Clear timers
|
|
765
|
+
this.timers.forEach(timerId => {
|
|
766
|
+
clearTimeout(timerId);
|
|
767
|
+
});
|
|
768
|
+
this.timers.clear();
|
|
769
|
+
|
|
770
|
+
// Disconnect observers
|
|
771
|
+
this.observers.forEach(observer => {
|
|
772
|
+
observer.disconnect();
|
|
773
|
+
});
|
|
774
|
+
this.observers.clear();
|
|
775
|
+
|
|
776
|
+
// Clear DOM references
|
|
777
|
+
this.element = null;
|
|
778
|
+
this.squares = null;
|
|
779
|
+
this.pieces = null;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
#### 3. Rendering Optimization
|
|
785
|
+
|
|
786
|
+
```javascript
|
|
787
|
+
// src/utils/rendering.js
|
|
788
|
+
export class RenderOptimizer {
|
|
789
|
+
constructor() {
|
|
790
|
+
this.pendingUpdates = new Map();
|
|
791
|
+
this.updateScheduled = false;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
scheduleUpdate(element, updateFn) {
|
|
795
|
+
this.pendingUpdates.set(element, updateFn);
|
|
796
|
+
|
|
797
|
+
if (!this.updateScheduled) {
|
|
798
|
+
this.updateScheduled = true;
|
|
799
|
+
requestAnimationFrame(() => this.flushUpdates());
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
flushUpdates() {
|
|
804
|
+
// Batch DOM reads first
|
|
805
|
+
const measurements = new Map();
|
|
806
|
+
for (const [element] of this.pendingUpdates) {
|
|
807
|
+
measurements.set(element, {
|
|
808
|
+
rect: element.getBoundingClientRect(),
|
|
809
|
+
computedStyle: getComputedStyle(element)
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Then batch DOM writes
|
|
814
|
+
for (const [element, updateFn] of this.pendingUpdates) {
|
|
815
|
+
const measurement = measurements.get(element);
|
|
816
|
+
updateFn(element, measurement);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
this.pendingUpdates.clear();
|
|
820
|
+
this.updateScheduled = false;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
---
|
|
826
|
+
|
|
827
|
+
## 🧪 Testing Strategy Completa
|
|
828
|
+
|
|
829
|
+
### 1. Unit Testing (Jest)
|
|
830
|
+
|
|
831
|
+
```javascript
|
|
832
|
+
// tests/unit/core/Chessboard.test.js
|
|
833
|
+
import { Chessboard } from '../../../src/core/Chessboard.js';
|
|
834
|
+
import { ValidationError } from '../../../src/utils/errors.js';
|
|
835
|
+
|
|
836
|
+
describe('Chessboard Core', () => {
|
|
837
|
+
let chessboard;
|
|
838
|
+
let mockContainer;
|
|
839
|
+
|
|
840
|
+
beforeEach(() => {
|
|
841
|
+
mockContainer = document.createElement('div');
|
|
842
|
+
mockContainer.id = 'test-board';
|
|
843
|
+
document.body.appendChild(mockContainer);
|
|
844
|
+
|
|
845
|
+
chessboard = new Chessboard({
|
|
846
|
+
id: 'test-board',
|
|
847
|
+
position: 'start'
|
|
848
|
+
});
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
afterEach(() => {
|
|
852
|
+
chessboard?.destroy();
|
|
853
|
+
document.body.removeChild(mockContainer);
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
describe('Initialization', () => {
|
|
857
|
+
it('should create chessboard with valid config', () => {
|
|
858
|
+
expect(chessboard).toBeInstanceOf(Chessboard);
|
|
859
|
+
expect(chessboard.element).toBe(mockContainer);
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
it('should throw ValidationError for invalid config', () => {
|
|
863
|
+
expect(() => {
|
|
864
|
+
new Chessboard({ id: null });
|
|
865
|
+
}).toThrow(ValidationError);
|
|
866
|
+
});
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
describe('Move Handling', () => {
|
|
870
|
+
it('should execute valid move', async () => {
|
|
871
|
+
const moveResult = await chessboard.move('e2', 'e4');
|
|
872
|
+
|
|
873
|
+
expect(moveResult.success).toBe(true);
|
|
874
|
+
expect(chessboard.get('e4')).toBe('pw');
|
|
875
|
+
expect(chessboard.get('e2')).toBe(null);
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
it('should reject invalid move', async () => {
|
|
879
|
+
const moveResult = await chessboard.move('e2', 'e5');
|
|
880
|
+
|
|
881
|
+
expect(moveResult.success).toBe(false);
|
|
882
|
+
expect(moveResult.error).toBeDefined();
|
|
883
|
+
});
|
|
884
|
+
});
|
|
885
|
+
});
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
### 2. Integration Testing
|
|
889
|
+
|
|
890
|
+
```javascript
|
|
891
|
+
// tests/integration/game-flow.test.js
|
|
892
|
+
import { Chessboard } from '../../src/core/Chessboard.js';
|
|
893
|
+
import { Chess } from 'chess.js';
|
|
894
|
+
|
|
895
|
+
describe('Game Flow Integration', () => {
|
|
896
|
+
let board;
|
|
897
|
+
let chess;
|
|
898
|
+
|
|
899
|
+
beforeEach(() => {
|
|
900
|
+
const container = document.createElement('div');
|
|
901
|
+
container.id = 'integration-test';
|
|
902
|
+
document.body.appendChild(container);
|
|
903
|
+
|
|
904
|
+
chess = new Chess();
|
|
905
|
+
board = new Chessboard({
|
|
906
|
+
id: 'integration-test',
|
|
907
|
+
position: chess.fen(),
|
|
908
|
+
onMove: (move) => {
|
|
909
|
+
const result = chess.move(move);
|
|
910
|
+
if (result) {
|
|
911
|
+
board.position(chess.fen());
|
|
912
|
+
return true;
|
|
913
|
+
}
|
|
914
|
+
return false;
|
|
915
|
+
}
|
|
916
|
+
});
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
it('should play complete game with chess.js integration', async () => {
|
|
920
|
+
const moves = [
|
|
921
|
+
['e2', 'e4'], ['e7', 'e5'],
|
|
922
|
+
['g1', 'f3'], ['b8', 'c6'],
|
|
923
|
+
['f1', 'c4'], ['f8', 'c5']
|
|
924
|
+
];
|
|
925
|
+
|
|
926
|
+
for (const [from, to] of moves) {
|
|
927
|
+
const result = await board.move(from, to);
|
|
928
|
+
expect(result.success).toBe(true);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
expect(chess.history().length).toBe(6);
|
|
932
|
+
expect(board.fen()).toBe(chess.fen());
|
|
933
|
+
});
|
|
934
|
+
});
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
### 3. E2E Testing (Playwright)
|
|
938
|
+
|
|
939
|
+
```javascript
|
|
940
|
+
// tests/e2e/user-interaction.spec.js
|
|
941
|
+
import { test, expect } from '@playwright/test';
|
|
942
|
+
|
|
943
|
+
test.describe('Chessboard User Interactions', () => {
|
|
944
|
+
test.beforeEach(async ({ page }) => {
|
|
945
|
+
await page.goto('/examples/basic-usage.html');
|
|
946
|
+
await page.waitForSelector('#chessboard');
|
|
947
|
+
});
|
|
948
|
+
|
|
949
|
+
test('should allow drag and drop moves', async ({ page }) => {
|
|
950
|
+
const board = page.locator('#chessboard');
|
|
951
|
+
|
|
952
|
+
// Drag white pawn from e2 to e4
|
|
953
|
+
await page.dragAndDrop(
|
|
954
|
+
'[data-square="e2"] .piece',
|
|
955
|
+
'[data-square="e4"]'
|
|
956
|
+
);
|
|
957
|
+
|
|
958
|
+
// Verify move was executed
|
|
959
|
+
await expect(page.locator('[data-square="e4"] .piece')).toBeVisible();
|
|
960
|
+
await expect(page.locator('[data-square="e2"] .piece')).not.toBeVisible();
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
test('should show legal move hints', async ({ page }) => {
|
|
964
|
+
// Click on knight
|
|
965
|
+
await page.click('[data-square="g1"] .piece');
|
|
966
|
+
|
|
967
|
+
// Check that legal move squares are highlighted
|
|
968
|
+
await expect(page.locator('[data-square="f3"].hint')).toBeVisible();
|
|
969
|
+
await expect(page.locator('[data-square="h3"].hint')).toBeVisible();
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
test('should handle invalid moves gracefully', async ({ page }) => {
|
|
973
|
+
// Try to move pawn backwards
|
|
974
|
+
await page.dragAndDrop(
|
|
975
|
+
'[data-square="e2"] .piece',
|
|
976
|
+
'[data-square="e1"]'
|
|
977
|
+
);
|
|
978
|
+
|
|
979
|
+
// Piece should return to original position
|
|
980
|
+
await expect(page.locator('[data-square="e2"] .piece')).toBeVisible();
|
|
981
|
+
await expect(page.locator('[data-square="e1"] .piece')).not.toBeVisible();
|
|
982
|
+
});
|
|
983
|
+
});
|
|
984
|
+
```
|
|
985
|
+
|
|
986
|
+
### 4. Performance Testing
|
|
987
|
+
|
|
988
|
+
```javascript
|
|
989
|
+
// benchmarks/rendering-performance.js
|
|
990
|
+
import { performance } from 'perf_hooks';
|
|
991
|
+
import { Chessboard } from '../src/core/Chessboard.js';
|
|
992
|
+
|
|
993
|
+
class PerformanceBenchmark {
|
|
994
|
+
async runRenderingBenchmark() {
|
|
995
|
+
const iterations = 1000;
|
|
996
|
+
const times = [];
|
|
997
|
+
|
|
998
|
+
for (let i = 0; i < iterations; i++) {
|
|
999
|
+
const start = performance.now();
|
|
1000
|
+
|
|
1001
|
+
const board = new Chessboard({
|
|
1002
|
+
id: 'benchmark-board',
|
|
1003
|
+
position: 'start',
|
|
1004
|
+
size: 400
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
await board.build();
|
|
1008
|
+
|
|
1009
|
+
const end = performance.now();
|
|
1010
|
+
times.push(end - start);
|
|
1011
|
+
|
|
1012
|
+
board.destroy();
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
return {
|
|
1016
|
+
mean: times.reduce((a, b) => a + b) / times.length,
|
|
1017
|
+
min: Math.min(...times),
|
|
1018
|
+
max: Math.max(...times),
|
|
1019
|
+
p95: this.percentile(times, 95),
|
|
1020
|
+
p99: this.percentile(times, 99)
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
percentile(arr, p) {
|
|
1025
|
+
const sorted = arr.sort((a, b) => a - b);
|
|
1026
|
+
const index = (p / 100) * (sorted.length - 1);
|
|
1027
|
+
return sorted[Math.round(index)];
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
```
|
|
1031
|
+
|
|
1032
|
+
---
|
|
1033
|
+
|
|
1034
|
+
## 📚 Sistema di Documentazione
|
|
1035
|
+
|
|
1036
|
+
### 1. JSDoc Standards
|
|
1037
|
+
|
|
1038
|
+
```javascript
|
|
1039
|
+
/**
|
|
1040
|
+
* Represents a chess piece on the board
|
|
1041
|
+
* @class
|
|
1042
|
+
* @memberof Components
|
|
1043
|
+
* @since 2.0.0
|
|
1044
|
+
*
|
|
1045
|
+
* @example
|
|
1046
|
+
* const piece = new Piece({
|
|
1047
|
+
* type: 'pawn',
|
|
1048
|
+
* color: 'white',
|
|
1049
|
+
* square: 'e2'
|
|
1050
|
+
* });
|
|
1051
|
+
*/
|
|
1052
|
+
export class Piece {
|
|
1053
|
+
/**
|
|
1054
|
+
* Creates a new chess piece
|
|
1055
|
+
* @param {Object} config - Piece configuration
|
|
1056
|
+
* @param {string} config.type - Piece type (pawn, rook, knight, bishop, queen, king)
|
|
1057
|
+
* @param {string} config.color - Piece color (white, black)
|
|
1058
|
+
* @param {string} config.square - Current square position
|
|
1059
|
+
* @param {Object} [config.element] - DOM element for the piece
|
|
1060
|
+
* @throws {ValidationError} When configuration is invalid
|
|
1061
|
+
*
|
|
1062
|
+
* @example
|
|
1063
|
+
* const piece = new Piece({
|
|
1064
|
+
* type: 'queen',
|
|
1065
|
+
* color: 'black',
|
|
1066
|
+
* square: 'd8'
|
|
1067
|
+
* });
|
|
1068
|
+
*/
|
|
1069
|
+
constructor(config) {
|
|
1070
|
+
this._validateConfig(config);
|
|
1071
|
+
|
|
1072
|
+
/**
|
|
1073
|
+
* Piece type
|
|
1074
|
+
* @type {string}
|
|
1075
|
+
* @readonly
|
|
1076
|
+
*/
|
|
1077
|
+
this.type = config.type;
|
|
1078
|
+
|
|
1079
|
+
/**
|
|
1080
|
+
* Piece color
|
|
1081
|
+
* @type {string}
|
|
1082
|
+
* @readonly
|
|
1083
|
+
*/
|
|
1084
|
+
this.color = config.color;
|
|
1085
|
+
|
|
1086
|
+
/**
|
|
1087
|
+
* Current square position
|
|
1088
|
+
* @type {string}
|
|
1089
|
+
* @private
|
|
1090
|
+
*/
|
|
1091
|
+
this._square = config.square;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
/**
|
|
1095
|
+
* Moves the piece to a new square
|
|
1096
|
+
* @param {string} targetSquare - Target square notation (e.g., 'e4')
|
|
1097
|
+
* @param {Object} [options={}] - Move options
|
|
1098
|
+
* @param {boolean} [options.animate=true] - Whether to animate the move
|
|
1099
|
+
* @param {number} [options.duration=200] - Animation duration in milliseconds
|
|
1100
|
+
* @returns {Promise<boolean>} True if move was successful
|
|
1101
|
+
*
|
|
1102
|
+
* @example
|
|
1103
|
+
* await piece.moveTo('e4', { animate: true, duration: 300 });
|
|
1104
|
+
*/
|
|
1105
|
+
async moveTo(targetSquare, options = {}) {
|
|
1106
|
+
// Implementation...
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
```
|
|
1110
|
+
|
|
1111
|
+
### 2. TypeScript Definitions
|
|
1112
|
+
|
|
1113
|
+
```typescript
|
|
1114
|
+
// src/types/chessboard.d.ts
|
|
1115
|
+
export interface ChessboardConfig {
|
|
1116
|
+
/** HTML element ID where the board will be rendered */
|
|
1117
|
+
id: string;
|
|
1118
|
+
|
|
1119
|
+
/** Board position as FEN string or predefined position */
|
|
1120
|
+
position?: string | 'start' | 'empty';
|
|
1121
|
+
|
|
1122
|
+
/** Board orientation */
|
|
1123
|
+
orientation?: 'white' | 'black' | 'w' | 'b';
|
|
1124
|
+
|
|
1125
|
+
/** Board size in pixels or 'auto' */
|
|
1126
|
+
size?: number | 'auto';
|
|
1127
|
+
|
|
1128
|
+
/** Enable drag and drop */
|
|
1129
|
+
draggable?: boolean;
|
|
1130
|
+
|
|
1131
|
+
/** Show legal move hints */
|
|
1132
|
+
hints?: boolean;
|
|
1133
|
+
|
|
1134
|
+
/** Enable click-to-move */
|
|
1135
|
+
clickable?: boolean;
|
|
1136
|
+
|
|
1137
|
+
/** Colors allowed to move */
|
|
1138
|
+
movableColors?: 'white' | 'black' | 'both' | 'none';
|
|
1139
|
+
|
|
1140
|
+
/** Highlight last move */
|
|
1141
|
+
moveHighlight?: boolean;
|
|
1142
|
+
|
|
1143
|
+
/** Animation easing function */
|
|
1144
|
+
moveAnimation?: AnimationEasing;
|
|
1145
|
+
|
|
1146
|
+
/** Animation duration */
|
|
1147
|
+
moveTime?: AnimationDuration | number;
|
|
1148
|
+
|
|
1149
|
+
/** What happens when piece is dropped off board */
|
|
1150
|
+
dropOffBoard?: 'snapback' | 'trash';
|
|
1151
|
+
|
|
1152
|
+
/** Only allow legal moves */
|
|
1153
|
+
onlyLegalMoves?: boolean;
|
|
1154
|
+
|
|
1155
|
+
/** Piece theme configuration */
|
|
1156
|
+
pieceTheme?: string | PieceThemeFunction;
|
|
1157
|
+
|
|
1158
|
+
/** Board color scheme */
|
|
1159
|
+
whiteSquare?: string;
|
|
1160
|
+
blackSquare?: string;
|
|
1161
|
+
|
|
1162
|
+
// Event handlers
|
|
1163
|
+
onMove?: MoveHandler;
|
|
1164
|
+
onMoveEnd?: MoveEndHandler;
|
|
1165
|
+
onChange?: ChangeHandler;
|
|
1166
|
+
onDragStart?: DragStartHandler;
|
|
1167
|
+
onDragMove?: DragMoveHandler;
|
|
1168
|
+
onDrop?: DropHandler;
|
|
1169
|
+
onSnapbackEnd?: SnapbackEndHandler;
|
|
1170
|
+
onSquareClick?: SquareClickHandler;
|
|
1171
|
+
onSquareOver?: SquareOverHandler;
|
|
1172
|
+
onSquareOut?: SquareOutHandler;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
export type AnimationEasing = 'ease' | 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out';
|
|
1176
|
+
|
|
1177
|
+
export type AnimationDuration = 'fast' | 'normal' | 'slow';
|
|
1178
|
+
|
|
1179
|
+
export interface MoveEvent {
|
|
1180
|
+
from: string;
|
|
1181
|
+
to: string;
|
|
1182
|
+
piece: string;
|
|
1183
|
+
captured?: string;
|
|
1184
|
+
promotion?: string;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
export type MoveHandler = (move: MoveEvent) => boolean | Promise<boolean>;
|
|
1188
|
+
|
|
1189
|
+
export type MoveEndHandler = (move: MoveEvent) => void;
|
|
1190
|
+
|
|
1191
|
+
export interface ChessboardAPI {
|
|
1192
|
+
// Position methods
|
|
1193
|
+
position(): string;
|
|
1194
|
+
position(fen: string, animate?: boolean): void;
|
|
1195
|
+
|
|
1196
|
+
// Piece methods
|
|
1197
|
+
get(square: string): string | null;
|
|
1198
|
+
put(piece: string, square: string, animate?: boolean): void;
|
|
1199
|
+
remove(square: string, animate?: boolean): string | null;
|
|
1200
|
+
|
|
1201
|
+
// Board control
|
|
1202
|
+
clear(animate?: boolean): void;
|
|
1203
|
+
reset(animate?: boolean): void;
|
|
1204
|
+
flip(animate?: boolean): void;
|
|
1205
|
+
|
|
1206
|
+
// Moves
|
|
1207
|
+
move(from: string, to: string, animate?: boolean): Promise<MoveResult>;
|
|
1208
|
+
|
|
1209
|
+
// Utilities
|
|
1210
|
+
resize(size: number | 'auto'): void;
|
|
1211
|
+
destroy(): void;
|
|
1212
|
+
|
|
1213
|
+
// Chess.js integration
|
|
1214
|
+
fen(): string;
|
|
1215
|
+
turn(): 'w' | 'b';
|
|
1216
|
+
moves(options?: MovesOptions): string[];
|
|
1217
|
+
history(options?: HistoryOptions): string[] | MoveEvent[];
|
|
1218
|
+
isGameOver(): boolean;
|
|
1219
|
+
isCheckmate(): boolean;
|
|
1220
|
+
isDraw(): boolean;
|
|
1221
|
+
}
|
|
1222
|
+
```
|
|
1223
|
+
|
|
1224
|
+
---
|
|
1225
|
+
|
|
1226
|
+
## 🚀 Build e Deployment
|
|
1227
|
+
|
|
1228
|
+
### 1. Configurazione Rollup Avanzata
|
|
1229
|
+
|
|
1230
|
+
```javascript
|
|
1231
|
+
// config/rollup.config.js
|
|
1232
|
+
import { defineConfig } from 'rollup';
|
|
1233
|
+
import resolve from '@rollup/plugin-node-resolve';
|
|
1234
|
+
import commonjs from '@rollup/plugin-commonjs';
|
|
1235
|
+
import terser from '@rollup/plugin-terser';
|
|
1236
|
+
import babel from '@rollup/plugin-babel';
|
|
1237
|
+
import typescript from '@rollup/plugin-typescript';
|
|
1238
|
+
import postcss from 'rollup-plugin-postcss';
|
|
1239
|
+
import { visualizer } from 'rollup-plugin-visualizer';
|
|
1240
|
+
import filesize from 'rollup-plugin-filesize';
|
|
1241
|
+
|
|
1242
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
1243
|
+
const isDevelopment = process.env.NODE_ENV === 'development';
|
|
1244
|
+
|
|
1245
|
+
const baseConfig = {
|
|
1246
|
+
input: 'src/index.js',
|
|
1247
|
+
external: ['chess.js'],
|
|
1248
|
+
plugins: [
|
|
1249
|
+
resolve({
|
|
1250
|
+
browser: true,
|
|
1251
|
+
preferBuiltins: false
|
|
1252
|
+
}),
|
|
1253
|
+
commonjs(),
|
|
1254
|
+
typescript({
|
|
1255
|
+
tsconfig: './config/tsconfig.json',
|
|
1256
|
+
declaration: true,
|
|
1257
|
+
declarationDir: './dist/types'
|
|
1258
|
+
}),
|
|
1259
|
+
babel({
|
|
1260
|
+
babelHelpers: 'bundled',
|
|
1261
|
+
exclude: 'node_modules/**',
|
|
1262
|
+
configFile: './config/babel.config.js'
|
|
1263
|
+
}),
|
|
1264
|
+
postcss({
|
|
1265
|
+
extract: true,
|
|
1266
|
+
minimize: isProduction,
|
|
1267
|
+
sourceMap: !isProduction
|
|
1268
|
+
}),
|
|
1269
|
+
...(isProduction ? [
|
|
1270
|
+
terser({
|
|
1271
|
+
compress: {
|
|
1272
|
+
drop_console: true,
|
|
1273
|
+
drop_debugger: true,
|
|
1274
|
+
pure_funcs: ['console.log', 'console.debug']
|
|
1275
|
+
},
|
|
1276
|
+
format: {
|
|
1277
|
+
comments: false
|
|
1278
|
+
}
|
|
1279
|
+
}),
|
|
1280
|
+
visualizer({
|
|
1281
|
+
filename: 'dist/bundle-analysis.html',
|
|
1282
|
+
open: false,
|
|
1283
|
+
gzipSize: true
|
|
1284
|
+
}),
|
|
1285
|
+
filesize()
|
|
1286
|
+
] : [])
|
|
1287
|
+
]
|
|
1288
|
+
};
|
|
1289
|
+
|
|
1290
|
+
export default defineConfig([
|
|
1291
|
+
// ES Modules
|
|
1292
|
+
{
|
|
1293
|
+
...baseConfig,
|
|
1294
|
+
output: {
|
|
1295
|
+
file: 'dist/chessboard.esm.js',
|
|
1296
|
+
format: 'esm',
|
|
1297
|
+
sourcemap: !isProduction
|
|
1298
|
+
}
|
|
1299
|
+
},
|
|
1300
|
+
|
|
1301
|
+
// CommonJS
|
|
1302
|
+
{
|
|
1303
|
+
...baseConfig,
|
|
1304
|
+
output: {
|
|
1305
|
+
file: 'dist/chessboard.cjs.js',
|
|
1306
|
+
format: 'cjs',
|
|
1307
|
+
exports: 'auto',
|
|
1308
|
+
sourcemap: !isProduction
|
|
1309
|
+
}
|
|
1310
|
+
},
|
|
1311
|
+
|
|
1312
|
+
// UMD
|
|
1313
|
+
{
|
|
1314
|
+
...baseConfig,
|
|
1315
|
+
output: {
|
|
1316
|
+
file: 'dist/chessboard.umd.js',
|
|
1317
|
+
format: 'umd',
|
|
1318
|
+
name: 'Chessboard',
|
|
1319
|
+
globals: {
|
|
1320
|
+
'chess.js': 'Chess'
|
|
1321
|
+
},
|
|
1322
|
+
sourcemap: !isProduction
|
|
1323
|
+
}
|
|
1324
|
+
},
|
|
1325
|
+
|
|
1326
|
+
// IIFE (Browser)
|
|
1327
|
+
{
|
|
1328
|
+
...baseConfig,
|
|
1329
|
+
output: {
|
|
1330
|
+
file: 'dist/chessboard.iife.js',
|
|
1331
|
+
format: 'iife',
|
|
1332
|
+
name: 'Chessboard',
|
|
1333
|
+
globals: {
|
|
1334
|
+
'chess.js': 'Chess'
|
|
1335
|
+
},
|
|
1336
|
+
sourcemap: !isProduction
|
|
1337
|
+
}
|
|
1338
|
+
},
|
|
1339
|
+
|
|
1340
|
+
// Minified versions for production
|
|
1341
|
+
...(isProduction ? [
|
|
1342
|
+
{
|
|
1343
|
+
...baseConfig,
|
|
1344
|
+
output: {
|
|
1345
|
+
file: 'dist/chessboard.min.js',
|
|
1346
|
+
format: 'iife',
|
|
1347
|
+
name: 'Chessboard',
|
|
1348
|
+
globals: {
|
|
1349
|
+
'chess.js': 'Chess'
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
] : [])
|
|
1354
|
+
]);
|
|
1355
|
+
```
|
|
1356
|
+
|
|
1357
|
+
### 2. CI/CD Pipeline (GitHub Actions)
|
|
1358
|
+
|
|
1359
|
+
```yaml
|
|
1360
|
+
# .github/workflows/ci.yml
|
|
1361
|
+
name: CI/CD Pipeline
|
|
1362
|
+
|
|
1363
|
+
on:
|
|
1364
|
+
push:
|
|
1365
|
+
branches: [main, develop]
|
|
1366
|
+
pull_request:
|
|
1367
|
+
branches: [main]
|
|
1368
|
+
|
|
1369
|
+
env:
|
|
1370
|
+
NODE_VERSION: '18'
|
|
1371
|
+
|
|
1372
|
+
jobs:
|
|
1373
|
+
lint:
|
|
1374
|
+
name: Code Quality
|
|
1375
|
+
runs-on: ubuntu-latest
|
|
1376
|
+
steps:
|
|
1377
|
+
- uses: actions/checkout@v4
|
|
1378
|
+
|
|
1379
|
+
- name: Setup Node.js
|
|
1380
|
+
uses: actions/setup-node@v4
|
|
1381
|
+
with:
|
|
1382
|
+
node-version: ${{ env.NODE_VERSION }}
|
|
1383
|
+
cache: 'npm'
|
|
1384
|
+
|
|
1385
|
+
- name: Install dependencies
|
|
1386
|
+
run: npm ci
|
|
1387
|
+
|
|
1388
|
+
- name: ESLint
|
|
1389
|
+
run: npm run lint
|
|
1390
|
+
|
|
1391
|
+
- name: Prettier
|
|
1392
|
+
run: npm run format:check
|
|
1393
|
+
|
|
1394
|
+
- name: TypeScript Check
|
|
1395
|
+
run: npm run typecheck
|
|
1396
|
+
|
|
1397
|
+
test:
|
|
1398
|
+
name: Test Suite
|
|
1399
|
+
runs-on: ubuntu-latest
|
|
1400
|
+
strategy:
|
|
1401
|
+
matrix:
|
|
1402
|
+
test-type: [unit, integration, e2e]
|
|
1403
|
+
steps:
|
|
1404
|
+
- uses: actions/checkout@v4
|
|
1405
|
+
|
|
1406
|
+
- name: Setup Node.js
|
|
1407
|
+
uses: actions/setup-node@v4
|
|
1408
|
+
with:
|
|
1409
|
+
node-version: ${{ env.NODE_VERSION }}
|
|
1410
|
+
cache: 'npm'
|
|
1411
|
+
|
|
1412
|
+
- name: Install dependencies
|
|
1413
|
+
run: npm ci
|
|
1414
|
+
|
|
1415
|
+
- name: Install Playwright (E2E only)
|
|
1416
|
+
if: matrix.test-type == 'e2e'
|
|
1417
|
+
run: npx playwright install
|
|
1418
|
+
|
|
1419
|
+
- name: Run tests
|
|
1420
|
+
run: npm run test:${{ matrix.test-type }}
|
|
1421
|
+
|
|
1422
|
+
- name: Upload coverage
|
|
1423
|
+
if: matrix.test-type == 'unit'
|
|
1424
|
+
uses: codecov/codecov-action@v3
|
|
1425
|
+
with:
|
|
1426
|
+
file: ./coverage/lcov.info
|
|
1427
|
+
|
|
1428
|
+
build:
|
|
1429
|
+
name: Build
|
|
1430
|
+
runs-on: ubuntu-latest
|
|
1431
|
+
needs: [lint, test]
|
|
1432
|
+
steps:
|
|
1433
|
+
- uses: actions/checkout@v4
|
|
1434
|
+
|
|
1435
|
+
- name: Setup Node.js
|
|
1436
|
+
uses: actions/setup-node@v4
|
|
1437
|
+
with:
|
|
1438
|
+
node-version: ${{ env.NODE_VERSION }}
|
|
1439
|
+
cache: 'npm'
|
|
1440
|
+
|
|
1441
|
+
- name: Install dependencies
|
|
1442
|
+
run: npm ci
|
|
1443
|
+
|
|
1444
|
+
- name: Build
|
|
1445
|
+
run: npm run build
|
|
1446
|
+
|
|
1447
|
+
- name: Size Check
|
|
1448
|
+
run: npm run size-check
|
|
1449
|
+
|
|
1450
|
+
- name: Upload build artifacts
|
|
1451
|
+
uses: actions/upload-artifact@v3
|
|
1452
|
+
with:
|
|
1453
|
+
name: dist
|
|
1454
|
+
path: dist/
|
|
1455
|
+
|
|
1456
|
+
performance:
|
|
1457
|
+
name: Performance Audit
|
|
1458
|
+
runs-on: ubuntu-latest
|
|
1459
|
+
needs: [build]
|
|
1460
|
+
steps:
|
|
1461
|
+
- uses: actions/checkout@v4
|
|
1462
|
+
|
|
1463
|
+
- name: Setup Node.js
|
|
1464
|
+
uses: actions/setup-node@v4
|
|
1465
|
+
with:
|
|
1466
|
+
node-version: ${{ env.NODE_VERSION }}
|
|
1467
|
+
cache: 'npm'
|
|
1468
|
+
|
|
1469
|
+
- name: Install dependencies
|
|
1470
|
+
run: npm ci
|
|
1471
|
+
|
|
1472
|
+
- name: Download build artifacts
|
|
1473
|
+
uses: actions/download-artifact@v3
|
|
1474
|
+
with:
|
|
1475
|
+
name: dist
|
|
1476
|
+
path: dist/
|
|
1477
|
+
|
|
1478
|
+
- name: Performance benchmarks
|
|
1479
|
+
run: npm run test:performance
|
|
1480
|
+
|
|
1481
|
+
- name: Lighthouse audit
|
|
1482
|
+
run: npm run perf-audit
|
|
1483
|
+
|
|
1484
|
+
release:
|
|
1485
|
+
name: Release
|
|
1486
|
+
runs-on: ubuntu-latest
|
|
1487
|
+
needs: [build, performance]
|
|
1488
|
+
if: github.ref == 'refs/heads/main'
|
|
1489
|
+
steps:
|
|
1490
|
+
- uses: actions/checkout@v4
|
|
1491
|
+
with:
|
|
1492
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
1493
|
+
|
|
1494
|
+
- name: Setup Node.js
|
|
1495
|
+
uses: actions/setup-node@v4
|
|
1496
|
+
with:
|
|
1497
|
+
node-version: ${{ env.NODE_VERSION }}
|
|
1498
|
+
cache: 'npm'
|
|
1499
|
+
registry-url: 'https://registry.npmjs.org'
|
|
1500
|
+
|
|
1501
|
+
- name: Install dependencies
|
|
1502
|
+
run: npm ci
|
|
1503
|
+
|
|
1504
|
+
- name: Download build artifacts
|
|
1505
|
+
uses: actions/download-artifact@v3
|
|
1506
|
+
with:
|
|
1507
|
+
name: dist
|
|
1508
|
+
path: dist/
|
|
1509
|
+
|
|
1510
|
+
- name: Semantic Release
|
|
1511
|
+
run: npx semantic-release
|
|
1512
|
+
env:
|
|
1513
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
1514
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
1515
|
+
```
|
|
1516
|
+
|
|
1517
|
+
---
|
|
1518
|
+
|
|
1519
|
+
## 🔍 Monitoring e Analytics
|
|
1520
|
+
|
|
1521
|
+
### 1. Performance Monitoring
|
|
1522
|
+
|
|
1523
|
+
```javascript
|
|
1524
|
+
// src/utils/performance.js
|
|
1525
|
+
export class PerformanceMonitor {
|
|
1526
|
+
constructor() {
|
|
1527
|
+
this.metrics = new Map();
|
|
1528
|
+
this.observers = new Map();
|
|
1529
|
+
this.setupObservers();
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
setupObservers() {
|
|
1533
|
+
// Performance Observer for paint metrics
|
|
1534
|
+
if ('PerformanceObserver' in window) {
|
|
1535
|
+
const paintObserver = new PerformanceObserver((list) => {
|
|
1536
|
+
list.getEntries().forEach((entry) => {
|
|
1537
|
+
this.recordMetric(`paint.${entry.name}`, entry.startTime);
|
|
1538
|
+
});
|
|
1539
|
+
});
|
|
1540
|
+
paintObserver.observe({ entryTypes: ['paint'] });
|
|
1541
|
+
this.observers.set('paint', paintObserver);
|
|
1542
|
+
|
|
1543
|
+
// Long task observer
|
|
1544
|
+
const longTaskObserver = new PerformanceObserver((list) => {
|
|
1545
|
+
list.getEntries().forEach((entry) => {
|
|
1546
|
+
this.recordMetric('long-task', entry.duration);
|
|
1547
|
+
});
|
|
1548
|
+
});
|
|
1549
|
+
longTaskObserver.observe({ entryTypes: ['longtask'] });
|
|
1550
|
+
this.observers.set('longtask', longTaskObserver);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
startMeasure(name) {
|
|
1555
|
+
performance.mark(`${name}-start`);
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
endMeasure(name) {
|
|
1559
|
+
performance.mark(`${name}-end`);
|
|
1560
|
+
performance.measure(name, `${name}-start`, `${name}-end`);
|
|
1561
|
+
|
|
1562
|
+
const measure = performance.getEntriesByName(name, 'measure')[0];
|
|
1563
|
+
this.recordMetric(name, measure.duration);
|
|
1564
|
+
|
|
1565
|
+
return measure.duration;
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
recordMetric(name, value) {
|
|
1569
|
+
if (!this.metrics.has(name)) {
|
|
1570
|
+
this.metrics.set(name, []);
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
this.metrics.get(name).push({
|
|
1574
|
+
value,
|
|
1575
|
+
timestamp: Date.now(),
|
|
1576
|
+
url: window.location.href
|
|
1577
|
+
});
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
getMetrics() {
|
|
1581
|
+
const summary = {};
|
|
1582
|
+
|
|
1583
|
+
for (const [name, values] of this.metrics) {
|
|
1584
|
+
const numbers = values.map(v => v.value);
|
|
1585
|
+
summary[name] = {
|
|
1586
|
+
count: numbers.length,
|
|
1587
|
+
mean: numbers.reduce((a, b) => a + b, 0) / numbers.length,
|
|
1588
|
+
min: Math.min(...numbers),
|
|
1589
|
+
max: Math.max(...numbers),
|
|
1590
|
+
p95: this.percentile(numbers, 95)
|
|
1591
|
+
};
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
return summary;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
```
|
|
1598
|
+
|
|
1599
|
+
### 2. Error Tracking
|
|
1600
|
+
|
|
1601
|
+
```javascript
|
|
1602
|
+
// src/utils/errorTracking.js
|
|
1603
|
+
export class ErrorTracker {
|
|
1604
|
+
constructor(config = {}) {
|
|
1605
|
+
this.config = {
|
|
1606
|
+
reportToConsole: true,
|
|
1607
|
+
reportToServer: false,
|
|
1608
|
+
serverEndpoint: null,
|
|
1609
|
+
...config
|
|
1610
|
+
};
|
|
1611
|
+
|
|
1612
|
+
this.setupErrorHandlers();
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
setupErrorHandlers() {
|
|
1616
|
+
// Global error handler
|
|
1617
|
+
window.addEventListener('error', (event) => {
|
|
1618
|
+
this.reportError({
|
|
1619
|
+
type: 'javascript',
|
|
1620
|
+
message: event.message,
|
|
1621
|
+
filename: event.filename,
|
|
1622
|
+
lineno: event.lineno,
|
|
1623
|
+
colno: event.colno,
|
|
1624
|
+
stack: event.error?.stack
|
|
1625
|
+
});
|
|
1626
|
+
});
|
|
1627
|
+
|
|
1628
|
+
// Unhandled promise rejection
|
|
1629
|
+
window.addEventListener('unhandledrejection', (event) => {
|
|
1630
|
+
this.reportError({
|
|
1631
|
+
type: 'promise',
|
|
1632
|
+
message: event.reason?.message || 'Unhandled promise rejection',
|
|
1633
|
+
stack: event.reason?.stack
|
|
1634
|
+
});
|
|
1635
|
+
});
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
reportError(error) {
|
|
1639
|
+
const errorReport = {
|
|
1640
|
+
...error,
|
|
1641
|
+
timestamp: new Date().toISOString(),
|
|
1642
|
+
url: window.location.href,
|
|
1643
|
+
userAgent: navigator.userAgent,
|
|
1644
|
+
component: 'chessboard.js'
|
|
1645
|
+
};
|
|
1646
|
+
|
|
1647
|
+
if (this.config.reportToConsole) {
|
|
1648
|
+
console.error('Chessboard.js Error:', errorReport);
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
if (this.config.reportToServer && this.config.serverEndpoint) {
|
|
1652
|
+
this.sendToServer(errorReport);
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
async sendToServer(errorReport) {
|
|
1657
|
+
try {
|
|
1658
|
+
await fetch(this.config.serverEndpoint, {
|
|
1659
|
+
method: 'POST',
|
|
1660
|
+
headers: {
|
|
1661
|
+
'Content-Type': 'application/json'
|
|
1662
|
+
},
|
|
1663
|
+
body: JSON.stringify(errorReport)
|
|
1664
|
+
});
|
|
1665
|
+
} catch (e) {
|
|
1666
|
+
console.warn('Failed to send error report to server:', e);
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
```
|
|
1671
|
+
|