cp-toolkit 2.0.0

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.
Files changed (196) hide show
  1. package/README.md +130 -0
  2. package/bin/cp-kit.js +72 -0
  3. package/package.json +46 -0
  4. package/src/commands/add.js +212 -0
  5. package/src/commands/doctor.js +149 -0
  6. package/src/commands/init.js +662 -0
  7. package/src/commands/list.js +128 -0
  8. package/src/index.js +13 -0
  9. package/templates/agents/backend-specialist.md +263 -0
  10. package/templates/agents/code-archaeologist.md +106 -0
  11. package/templates/agents/database-architect.md +226 -0
  12. package/templates/agents/debugger.md +225 -0
  13. package/templates/agents/devops-engineer.md +242 -0
  14. package/templates/agents/documentation-writer.md +104 -0
  15. package/templates/agents/explorer-agent.md +73 -0
  16. package/templates/agents/frontend-specialist.md +556 -0
  17. package/templates/agents/game-developer.md +162 -0
  18. package/templates/agents/mobile-developer.md +377 -0
  19. package/templates/agents/orchestrator.md +416 -0
  20. package/templates/agents/penetration-tester.md +188 -0
  21. package/templates/agents/performance-optimizer.md +187 -0
  22. package/templates/agents/product-manager.md +112 -0
  23. package/templates/agents/product-owner.md +95 -0
  24. package/templates/agents/project-planner.md +406 -0
  25. package/templates/agents/qa-automation-engineer.md +103 -0
  26. package/templates/agents/security-auditor.md +170 -0
  27. package/templates/agents/seo-specialist.md +111 -0
  28. package/templates/agents/test-engineer.md +158 -0
  29. package/templates/github/agents/backend-specialist.md +67 -0
  30. package/templates/github/agents/code-archaeologist.md +61 -0
  31. package/templates/github/agents/database-architect.md +73 -0
  32. package/templates/github/agents/debugger.md +71 -0
  33. package/templates/github/agents/devops-engineer.md +85 -0
  34. package/templates/github/agents/documentation-writer.md +107 -0
  35. package/templates/github/agents/explorer-agent.md +87 -0
  36. package/templates/github/agents/frontend-specialist.md +54 -0
  37. package/templates/github/agents/game-developer.md +94 -0
  38. package/templates/github/agents/mobile-developer.md +75 -0
  39. package/templates/github/agents/orchestrator.md +48 -0
  40. package/templates/github/agents/penetration-tester.md +87 -0
  41. package/templates/github/agents/performance-optimizer.md +70 -0
  42. package/templates/github/agents/product-manager.md +85 -0
  43. package/templates/github/agents/product-owner.md +77 -0
  44. package/templates/github/agents/project-planner.md +83 -0
  45. package/templates/github/agents/qa-automation-engineer.md +95 -0
  46. package/templates/github/agents/security-auditor.md +72 -0
  47. package/templates/github/agents/seo-specialist.md +78 -0
  48. package/templates/github/agents/test-engineer.md +79 -0
  49. package/templates/github/instructions/database.instructions.md +74 -0
  50. package/templates/github/instructions/python.instructions.md +76 -0
  51. package/templates/github/instructions/security.instructions.md +73 -0
  52. package/templates/github/instructions/typescript.instructions.md +50 -0
  53. package/templates/rules/GEMINI.md +273 -0
  54. package/templates/scripts/mcp-server.js +704 -0
  55. package/templates/skills/core/behavioral-modes/SKILL.md +242 -0
  56. package/templates/skills/core/brainstorming/SKILL.md +163 -0
  57. package/templates/skills/core/brainstorming/dynamic-questioning.md +350 -0
  58. package/templates/skills/core/clean-code/SKILL.md +201 -0
  59. package/templates/skills/core/intelligent-routing/SKILL.md +335 -0
  60. package/templates/skills/core/mcp-builder/SKILL.md +176 -0
  61. package/templates/skills/core/parallel-agents/SKILL.md +175 -0
  62. package/templates/skills/core/plan-writing/SKILL.md +152 -0
  63. package/templates/skills/optional/api-patterns/SKILL.md +81 -0
  64. package/templates/skills/optional/api-patterns/api-style.md +42 -0
  65. package/templates/skills/optional/api-patterns/auth.md +24 -0
  66. package/templates/skills/optional/api-patterns/documentation.md +26 -0
  67. package/templates/skills/optional/api-patterns/graphql.md +41 -0
  68. package/templates/skills/optional/api-patterns/rate-limiting.md +31 -0
  69. package/templates/skills/optional/api-patterns/response.md +37 -0
  70. package/templates/skills/optional/api-patterns/rest.md +40 -0
  71. package/templates/skills/optional/api-patterns/scripts/api_validator.py +211 -0
  72. package/templates/skills/optional/api-patterns/security-testing.md +122 -0
  73. package/templates/skills/optional/api-patterns/trpc.md +41 -0
  74. package/templates/skills/optional/api-patterns/versioning.md +22 -0
  75. package/templates/skills/optional/app-builder/SKILL.md +75 -0
  76. package/templates/skills/optional/app-builder/agent-coordination.md +71 -0
  77. package/templates/skills/optional/app-builder/feature-building.md +53 -0
  78. package/templates/skills/optional/app-builder/project-detection.md +34 -0
  79. package/templates/skills/optional/app-builder/scaffolding.md +118 -0
  80. package/templates/skills/optional/app-builder/tech-stack.md +40 -0
  81. package/templates/skills/optional/app-builder/templates/SKILL.md +39 -0
  82. package/templates/skills/optional/app-builder/templates/astro-static/TEMPLATE.md +76 -0
  83. package/templates/skills/optional/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
  84. package/templates/skills/optional/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
  85. package/templates/skills/optional/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
  86. package/templates/skills/optional/app-builder/templates/express-api/TEMPLATE.md +83 -0
  87. package/templates/skills/optional/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
  88. package/templates/skills/optional/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
  89. package/templates/skills/optional/app-builder/templates/nextjs-fullstack/TEMPLATE.md +82 -0
  90. package/templates/skills/optional/app-builder/templates/nextjs-saas/TEMPLATE.md +100 -0
  91. package/templates/skills/optional/app-builder/templates/nextjs-static/TEMPLATE.md +106 -0
  92. package/templates/skills/optional/app-builder/templates/nuxt-app/TEMPLATE.md +101 -0
  93. package/templates/skills/optional/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
  94. package/templates/skills/optional/app-builder/templates/react-native-app/TEMPLATE.md +93 -0
  95. package/templates/skills/optional/architecture/SKILL.md +55 -0
  96. package/templates/skills/optional/architecture/context-discovery.md +43 -0
  97. package/templates/skills/optional/architecture/examples.md +94 -0
  98. package/templates/skills/optional/architecture/pattern-selection.md +68 -0
  99. package/templates/skills/optional/architecture/patterns-reference.md +50 -0
  100. package/templates/skills/optional/architecture/trade-off-analysis.md +77 -0
  101. package/templates/skills/optional/bash-linux/SKILL.md +199 -0
  102. package/templates/skills/optional/code-review-checklist/SKILL.md +109 -0
  103. package/templates/skills/optional/database-design/SKILL.md +52 -0
  104. package/templates/skills/optional/database-design/database-selection.md +43 -0
  105. package/templates/skills/optional/database-design/indexing.md +39 -0
  106. package/templates/skills/optional/database-design/migrations.md +48 -0
  107. package/templates/skills/optional/database-design/optimization.md +36 -0
  108. package/templates/skills/optional/database-design/orm-selection.md +30 -0
  109. package/templates/skills/optional/database-design/schema-design.md +56 -0
  110. package/templates/skills/optional/database-design/scripts/schema_validator.py +172 -0
  111. package/templates/skills/optional/deployment-procedures/SKILL.md +241 -0
  112. package/templates/skills/optional/documentation-templates/SKILL.md +194 -0
  113. package/templates/skills/optional/frontend-design/SKILL.md +418 -0
  114. package/templates/skills/optional/frontend-design/animation-guide.md +331 -0
  115. package/templates/skills/optional/frontend-design/color-system.md +311 -0
  116. package/templates/skills/optional/frontend-design/decision-trees.md +418 -0
  117. package/templates/skills/optional/frontend-design/motion-graphics.md +306 -0
  118. package/templates/skills/optional/frontend-design/scripts/accessibility_checker.py +183 -0
  119. package/templates/skills/optional/frontend-design/scripts/ux_audit.py +722 -0
  120. package/templates/skills/optional/frontend-design/typography-system.md +345 -0
  121. package/templates/skills/optional/frontend-design/ux-psychology.md +541 -0
  122. package/templates/skills/optional/frontend-design/visual-effects.md +383 -0
  123. package/templates/skills/optional/game-development/2d-games/SKILL.md +119 -0
  124. package/templates/skills/optional/game-development/3d-games/SKILL.md +135 -0
  125. package/templates/skills/optional/game-development/SKILL.md +167 -0
  126. package/templates/skills/optional/game-development/game-art/SKILL.md +185 -0
  127. package/templates/skills/optional/game-development/game-audio/SKILL.md +190 -0
  128. package/templates/skills/optional/game-development/game-design/SKILL.md +129 -0
  129. package/templates/skills/optional/game-development/mobile-games/SKILL.md +108 -0
  130. package/templates/skills/optional/game-development/multiplayer/SKILL.md +132 -0
  131. package/templates/skills/optional/game-development/pc-games/SKILL.md +144 -0
  132. package/templates/skills/optional/game-development/vr-ar/SKILL.md +123 -0
  133. package/templates/skills/optional/game-development/web-games/SKILL.md +150 -0
  134. package/templates/skills/optional/geo-fundamentals/SKILL.md +156 -0
  135. package/templates/skills/optional/geo-fundamentals/scripts/geo_checker.py +289 -0
  136. package/templates/skills/optional/i18n-localization/SKILL.md +154 -0
  137. package/templates/skills/optional/i18n-localization/scripts/i18n_checker.py +241 -0
  138. package/templates/skills/optional/lint-and-validate/SKILL.md +45 -0
  139. package/templates/skills/optional/lint-and-validate/scripts/lint_runner.py +172 -0
  140. package/templates/skills/optional/lint-and-validate/scripts/type_coverage.py +173 -0
  141. package/templates/skills/optional/mobile-design/SKILL.md +394 -0
  142. package/templates/skills/optional/mobile-design/decision-trees.md +516 -0
  143. package/templates/skills/optional/mobile-design/mobile-backend.md +491 -0
  144. package/templates/skills/optional/mobile-design/mobile-color-system.md +420 -0
  145. package/templates/skills/optional/mobile-design/mobile-debugging.md +122 -0
  146. package/templates/skills/optional/mobile-design/mobile-design-thinking.md +357 -0
  147. package/templates/skills/optional/mobile-design/mobile-navigation.md +458 -0
  148. package/templates/skills/optional/mobile-design/mobile-performance.md +767 -0
  149. package/templates/skills/optional/mobile-design/mobile-testing.md +356 -0
  150. package/templates/skills/optional/mobile-design/mobile-typography.md +433 -0
  151. package/templates/skills/optional/mobile-design/platform-android.md +666 -0
  152. package/templates/skills/optional/mobile-design/platform-ios.md +561 -0
  153. package/templates/skills/optional/mobile-design/scripts/mobile_audit.py +670 -0
  154. package/templates/skills/optional/mobile-design/touch-psychology.md +537 -0
  155. package/templates/skills/optional/nextjs-react-expert/1-async-eliminating-waterfalls.md +312 -0
  156. package/templates/skills/optional/nextjs-react-expert/2-bundle-bundle-size-optimization.md +240 -0
  157. package/templates/skills/optional/nextjs-react-expert/3-server-server-side-performance.md +490 -0
  158. package/templates/skills/optional/nextjs-react-expert/4-client-client-side-data-fetching.md +264 -0
  159. package/templates/skills/optional/nextjs-react-expert/5-rerender-re-render-optimization.md +581 -0
  160. package/templates/skills/optional/nextjs-react-expert/6-rendering-rendering-performance.md +432 -0
  161. package/templates/skills/optional/nextjs-react-expert/7-js-javascript-performance.md +684 -0
  162. package/templates/skills/optional/nextjs-react-expert/8-advanced-advanced-patterns.md +150 -0
  163. package/templates/skills/optional/nextjs-react-expert/SKILL.md +267 -0
  164. package/templates/skills/optional/nextjs-react-expert/scripts/convert_rules.py +222 -0
  165. package/templates/skills/optional/nextjs-react-expert/scripts/react_performance_checker.py +252 -0
  166. package/templates/skills/optional/nodejs-best-practices/SKILL.md +333 -0
  167. package/templates/skills/optional/performance-profiling/SKILL.md +143 -0
  168. package/templates/skills/optional/performance-profiling/scripts/lighthouse_audit.py +76 -0
  169. package/templates/skills/optional/powershell-windows/SKILL.md +167 -0
  170. package/templates/skills/optional/python-patterns/SKILL.md +441 -0
  171. package/templates/skills/optional/red-team-tactics/SKILL.md +199 -0
  172. package/templates/skills/optional/seo-fundamentals/SKILL.md +129 -0
  173. package/templates/skills/optional/seo-fundamentals/scripts/seo_checker.py +219 -0
  174. package/templates/skills/optional/server-management/SKILL.md +161 -0
  175. package/templates/skills/optional/systematic-debugging/SKILL.md +109 -0
  176. package/templates/skills/optional/tailwind-patterns/SKILL.md +269 -0
  177. package/templates/skills/optional/tdd-workflow/SKILL.md +149 -0
  178. package/templates/skills/optional/testing-patterns/SKILL.md +178 -0
  179. package/templates/skills/optional/testing-patterns/scripts/test_runner.py +219 -0
  180. package/templates/skills/optional/vulnerability-scanner/SKILL.md +276 -0
  181. package/templates/skills/optional/vulnerability-scanner/checklists.md +121 -0
  182. package/templates/skills/optional/vulnerability-scanner/scripts/security_scan.py +458 -0
  183. package/templates/skills/optional/web-design-guidelines/SKILL.md +57 -0
  184. package/templates/skills/optional/webapp-testing/SKILL.md +187 -0
  185. package/templates/skills/optional/webapp-testing/scripts/playwright_runner.py +173 -0
  186. package/templates/workflows/brainstorm.md +113 -0
  187. package/templates/workflows/create.md +59 -0
  188. package/templates/workflows/debug.md +103 -0
  189. package/templates/workflows/deploy.md +176 -0
  190. package/templates/workflows/enhance.md +63 -0
  191. package/templates/workflows/orchestrate.md +237 -0
  192. package/templates/workflows/plan.md +89 -0
  193. package/templates/workflows/preview.md +81 -0
  194. package/templates/workflows/status.md +86 -0
  195. package/templates/workflows/test.md +144 -0
  196. package/templates/workflows/ui-ux-pro-max.md +296 -0
@@ -0,0 +1,684 @@
1
+ # 7. JavaScript Performance
2
+
3
+ > **Impact:** LOW-MEDIUM
4
+ > **Focus:** Micro-optimizations for hot paths can add up to meaningful improvements.
5
+
6
+ ---
7
+
8
+ ## Overview
9
+
10
+ This section contains **12 rules** focused on javascript performance.
11
+
12
+ ---
13
+
14
+ ## Rule 7.1: Avoid Layout Thrashing
15
+
16
+ **Impact:** MEDIUM
17
+ **Tags:** javascript, dom, css, performance, reflow, layout-thrashing
18
+
19
+ ## Avoid Layout Thrashing
20
+
21
+ Avoid interleaving style writes with layout reads. When you read a layout property (like `offsetWidth`, `getBoundingClientRect()`, or `getComputedStyle()`) between style changes, the browser is forced to trigger a synchronous reflow.
22
+
23
+ **This is OK (browser batches style changes):**
24
+ ```typescript
25
+ function updateElementStyles(element: HTMLElement) {
26
+ // Each line invalidates style, but browser batches the recalculation
27
+ element.style.width = '100px'
28
+ element.style.height = '200px'
29
+ element.style.backgroundColor = 'blue'
30
+ element.style.border = '1px solid black'
31
+ }
32
+ ```
33
+
34
+ **Incorrect (interleaved reads and writes force reflows):**
35
+ ```typescript
36
+ function layoutThrashing(element: HTMLElement) {
37
+ element.style.width = '100px'
38
+ const width = element.offsetWidth // Forces reflow
39
+ element.style.height = '200px'
40
+ const height = element.offsetHeight // Forces another reflow
41
+ }
42
+ ```
43
+
44
+ **Correct (batch writes, then read once):**
45
+ ```typescript
46
+ function updateElementStyles(element: HTMLElement) {
47
+ // Batch all writes together
48
+ element.style.width = '100px'
49
+ element.style.height = '200px'
50
+ element.style.backgroundColor = 'blue'
51
+ element.style.border = '1px solid black'
52
+
53
+ // Read after all writes are done (single reflow)
54
+ const { width, height } = element.getBoundingClientRect()
55
+ }
56
+ ```
57
+
58
+ **Correct (batch reads, then writes):**
59
+ ```typescript
60
+ function avoidThrashing(element: HTMLElement) {
61
+ // Read phase - all layout queries first
62
+ const rect1 = element.getBoundingClientRect()
63
+ const offsetWidth = element.offsetWidth
64
+ const offsetHeight = element.offsetHeight
65
+
66
+ // Write phase - all style changes after
67
+ element.style.width = '100px'
68
+ element.style.height = '200px'
69
+ }
70
+ ```
71
+
72
+ **Better: use CSS classes**
73
+ ```css
74
+ .highlighted-box {
75
+ width: 100px;
76
+ height: 200px;
77
+ background-color: blue;
78
+ border: 1px solid black;
79
+ }
80
+ ```
81
+ ```typescript
82
+ function updateElementStyles(element: HTMLElement) {
83
+ element.classList.add('highlighted-box')
84
+
85
+ const { width, height } = element.getBoundingClientRect()
86
+ }
87
+ ```
88
+
89
+ **React example:**
90
+ ```tsx
91
+ // Incorrect: interleaving style changes with layout queries
92
+ function Box({ isHighlighted }: { isHighlighted: boolean }) {
93
+ const ref = useRef<HTMLDivElement>(null)
94
+
95
+ useEffect(() => {
96
+ if (ref.current && isHighlighted) {
97
+ ref.current.style.width = '100px'
98
+ const width = ref.current.offsetWidth // Forces layout
99
+ ref.current.style.height = '200px'
100
+ }
101
+ }, [isHighlighted])
102
+
103
+ return <div ref={ref}>Content</div>
104
+ }
105
+
106
+ // Correct: toggle class
107
+ function Box({ isHighlighted }: { isHighlighted: boolean }) {
108
+ return (
109
+ <div className={isHighlighted ? 'highlighted-box' : ''}>
110
+ Content
111
+ </div>
112
+ )
113
+ }
114
+ ```
115
+
116
+ Prefer CSS classes over inline styles when possible. CSS files are cached by the browser, and classes provide better separation of concerns and are easier to maintain.
117
+
118
+ See [this gist](https://gist.github.com/paulirish/5d52fb081b3570c81e3a) and [CSS Triggers](https://csstriggers.com/) for more information on layout-forcing operations.
119
+
120
+ ---
121
+
122
+ ## Rule 7.2: Build Index Maps for Repeated Lookups
123
+
124
+ **Impact:** LOW-MEDIUM
125
+ **Tags:** javascript, map, indexing, optimization, performance
126
+
127
+ ## Build Index Maps for Repeated Lookups
128
+
129
+ Multiple `.find()` calls by the same key should use a Map.
130
+
131
+ **Incorrect (O(n) per lookup):**
132
+
133
+ ```typescript
134
+ function processOrders(orders: Order[], users: User[]) {
135
+ return orders.map(order => ({
136
+ ...order,
137
+ user: users.find(u => u.id === order.userId)
138
+ }))
139
+ }
140
+ ```
141
+
142
+ **Correct (O(1) per lookup):**
143
+
144
+ ```typescript
145
+ function processOrders(orders: Order[], users: User[]) {
146
+ const userById = new Map(users.map(u => [u.id, u]))
147
+
148
+ return orders.map(order => ({
149
+ ...order,
150
+ user: userById.get(order.userId)
151
+ }))
152
+ }
153
+ ```
154
+
155
+ Build map once (O(n)), then all lookups are O(1).
156
+ For 1000 orders × 1000 users: 1M ops → 2K ops.
157
+
158
+ ---
159
+
160
+ ## Rule 7.3: Cache Property Access in Loops
161
+
162
+ **Impact:** LOW-MEDIUM
163
+ **Tags:** javascript, loops, optimization, caching
164
+
165
+ ## Cache Property Access in Loops
166
+
167
+ Cache object property lookups in hot paths.
168
+
169
+ **Incorrect (3 lookups × N iterations):**
170
+
171
+ ```typescript
172
+ for (let i = 0; i < arr.length; i++) {
173
+ process(obj.config.settings.value)
174
+ }
175
+ ```
176
+
177
+ **Correct (1 lookup total):**
178
+
179
+ ```typescript
180
+ const value = obj.config.settings.value
181
+ const len = arr.length
182
+ for (let i = 0; i < len; i++) {
183
+ process(value)
184
+ }
185
+ ```
186
+
187
+ ---
188
+
189
+ ## Rule 7.4: Cache Repeated Function Calls
190
+
191
+ **Impact:** MEDIUM
192
+ **Tags:** javascript, cache, memoization, performance
193
+
194
+ ## Cache Repeated Function Calls
195
+
196
+ Use a module-level Map to cache function results when the same function is called repeatedly with the same inputs during render.
197
+
198
+ **Incorrect (redundant computation):**
199
+
200
+ ```typescript
201
+ function ProjectList({ projects }: { projects: Project[] }) {
202
+ return (
203
+ <div>
204
+ {projects.map(project => {
205
+ // slugify() called 100+ times for same project names
206
+ const slug = slugify(project.name)
207
+
208
+ return <ProjectCard key={project.id} slug={slug} />
209
+ })}
210
+ </div>
211
+ )
212
+ }
213
+ ```
214
+
215
+ **Correct (cached results):**
216
+
217
+ ```typescript
218
+ // Module-level cache
219
+ const slugifyCache = new Map<string, string>()
220
+
221
+ function cachedSlugify(text: string): string {
222
+ if (slugifyCache.has(text)) {
223
+ return slugifyCache.get(text)!
224
+ }
225
+ const result = slugify(text)
226
+ slugifyCache.set(text, result)
227
+ return result
228
+ }
229
+
230
+ function ProjectList({ projects }: { projects: Project[] }) {
231
+ return (
232
+ <div>
233
+ {projects.map(project => {
234
+ // Computed only once per unique project name
235
+ const slug = cachedSlugify(project.name)
236
+
237
+ return <ProjectCard key={project.id} slug={slug} />
238
+ })}
239
+ </div>
240
+ )
241
+ }
242
+ ```
243
+
244
+ **Simpler pattern for single-value functions:**
245
+
246
+ ```typescript
247
+ let isLoggedInCache: boolean | null = null
248
+
249
+ function isLoggedIn(): boolean {
250
+ if (isLoggedInCache !== null) {
251
+ return isLoggedInCache
252
+ }
253
+
254
+ isLoggedInCache = document.cookie.includes('auth=')
255
+ return isLoggedInCache
256
+ }
257
+
258
+ // Clear cache when auth changes
259
+ function onAuthChange() {
260
+ isLoggedInCache = null
261
+ }
262
+ ```
263
+
264
+ Use a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components.
265
+
266
+ Reference: [How we made the Vercel Dashboard twice as fast](https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast)
267
+
268
+ ---
269
+
270
+ ## Rule 7.5: Cache Storage API Calls
271
+
272
+ **Impact:** LOW-MEDIUM
273
+ **Tags:** javascript, localStorage, storage, caching, performance
274
+
275
+ ## Cache Storage API Calls
276
+
277
+ `localStorage`, `sessionStorage`, and `document.cookie` are synchronous and expensive. Cache reads in memory.
278
+
279
+ **Incorrect (reads storage on every call):**
280
+
281
+ ```typescript
282
+ function getTheme() {
283
+ return localStorage.getItem('theme') ?? 'light'
284
+ }
285
+ // Called 10 times = 10 storage reads
286
+ ```
287
+
288
+ **Correct (Map cache):**
289
+
290
+ ```typescript
291
+ const storageCache = new Map<string, string | null>()
292
+
293
+ function getLocalStorage(key: string) {
294
+ if (!storageCache.has(key)) {
295
+ storageCache.set(key, localStorage.getItem(key))
296
+ }
297
+ return storageCache.get(key)
298
+ }
299
+
300
+ function setLocalStorage(key: string, value: string) {
301
+ localStorage.setItem(key, value)
302
+ storageCache.set(key, value) // keep cache in sync
303
+ }
304
+ ```
305
+
306
+ Use a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components.
307
+
308
+ **Cookie caching:**
309
+
310
+ ```typescript
311
+ let cookieCache: Record<string, string> | null = null
312
+
313
+ function getCookie(name: string) {
314
+ if (!cookieCache) {
315
+ cookieCache = Object.fromEntries(
316
+ document.cookie.split('; ').map(c => c.split('='))
317
+ )
318
+ }
319
+ return cookieCache[name]
320
+ }
321
+ ```
322
+
323
+ **Important (invalidate on external changes):**
324
+
325
+ If storage can change externally (another tab, server-set cookies), invalidate cache:
326
+
327
+ ```typescript
328
+ window.addEventListener('storage', (e) => {
329
+ if (e.key) storageCache.delete(e.key)
330
+ })
331
+
332
+ document.addEventListener('visibilitychange', () => {
333
+ if (document.visibilityState === 'visible') {
334
+ storageCache.clear()
335
+ }
336
+ })
337
+ ```
338
+
339
+ ---
340
+
341
+ ## Rule 7.6: Combine Multiple Array Iterations
342
+
343
+ **Impact:** LOW-MEDIUM
344
+ **Tags:** javascript, arrays, loops, performance
345
+
346
+ ## Combine Multiple Array Iterations
347
+
348
+ Multiple `.filter()` or `.map()` calls iterate the array multiple times. Combine into one loop.
349
+
350
+ **Incorrect (3 iterations):**
351
+
352
+ ```typescript
353
+ const admins = users.filter(u => u.isAdmin)
354
+ const testers = users.filter(u => u.isTester)
355
+ const inactive = users.filter(u => !u.isActive)
356
+ ```
357
+
358
+ **Correct (1 iteration):**
359
+
360
+ ```typescript
361
+ const admins: User[] = []
362
+ const testers: User[] = []
363
+ const inactive: User[] = []
364
+
365
+ for (const user of users) {
366
+ if (user.isAdmin) admins.push(user)
367
+ if (user.isTester) testers.push(user)
368
+ if (!user.isActive) inactive.push(user)
369
+ }
370
+ ```
371
+
372
+ ---
373
+
374
+ ## Rule 7.7: Early Length Check for Array Comparisons
375
+
376
+ **Impact:** MEDIUM-HIGH
377
+ **Tags:** javascript, arrays, performance, optimization, comparison
378
+
379
+ ## Early Length Check for Array Comparisons
380
+
381
+ When comparing arrays with expensive operations (sorting, deep equality, serialization), check lengths first. If lengths differ, the arrays cannot be equal.
382
+
383
+ In real-world applications, this optimization is especially valuable when the comparison runs in hot paths (event handlers, render loops).
384
+
385
+ **Incorrect (always runs expensive comparison):**
386
+
387
+ ```typescript
388
+ function hasChanges(current: string[], original: string[]) {
389
+ // Always sorts and joins, even when lengths differ
390
+ return current.sort().join() !== original.sort().join()
391
+ }
392
+ ```
393
+
394
+ Two O(n log n) sorts run even when `current.length` is 5 and `original.length` is 100. There is also overhead of joining the arrays and comparing the strings.
395
+
396
+ **Correct (O(1) length check first):**
397
+
398
+ ```typescript
399
+ function hasChanges(current: string[], original: string[]) {
400
+ // Early return if lengths differ
401
+ if (current.length !== original.length) {
402
+ return true
403
+ }
404
+ // Only sort when lengths match
405
+ const currentSorted = current.toSorted()
406
+ const originalSorted = original.toSorted()
407
+ for (let i = 0; i < currentSorted.length; i++) {
408
+ if (currentSorted[i] !== originalSorted[i]) {
409
+ return true
410
+ }
411
+ }
412
+ return false
413
+ }
414
+ ```
415
+
416
+ This new approach is more efficient because:
417
+ - It avoids the overhead of sorting and joining the arrays when lengths differ
418
+ - It avoids consuming memory for the joined strings (especially important for large arrays)
419
+ - It avoids mutating the original arrays
420
+ - It returns early when a difference is found
421
+
422
+ ---
423
+
424
+ ## Rule 7.8: Early Return from Functions
425
+
426
+ **Impact:** LOW-MEDIUM
427
+ **Tags:** javascript, functions, optimization, early-return
428
+
429
+ ## Early Return from Functions
430
+
431
+ Return early when result is determined to skip unnecessary processing.
432
+
433
+ **Incorrect (processes all items even after finding answer):**
434
+
435
+ ```typescript
436
+ function validateUsers(users: User[]) {
437
+ let hasError = false
438
+ let errorMessage = ''
439
+
440
+ for (const user of users) {
441
+ if (!user.email) {
442
+ hasError = true
443
+ errorMessage = 'Email required'
444
+ }
445
+ if (!user.name) {
446
+ hasError = true
447
+ errorMessage = 'Name required'
448
+ }
449
+ // Continues checking all users even after error found
450
+ }
451
+
452
+ return hasError ? { valid: false, error: errorMessage } : { valid: true }
453
+ }
454
+ ```
455
+
456
+ **Correct (returns immediately on first error):**
457
+
458
+ ```typescript
459
+ function validateUsers(users: User[]) {
460
+ for (const user of users) {
461
+ if (!user.email) {
462
+ return { valid: false, error: 'Email required' }
463
+ }
464
+ if (!user.name) {
465
+ return { valid: false, error: 'Name required' }
466
+ }
467
+ }
468
+
469
+ return { valid: true }
470
+ }
471
+ ```
472
+
473
+ ---
474
+
475
+ ## Rule 7.9: Hoist RegExp Creation
476
+
477
+ **Impact:** LOW-MEDIUM
478
+ **Tags:** javascript, regexp, optimization, memoization
479
+
480
+ ## Hoist RegExp Creation
481
+
482
+ Don't create RegExp inside render. Hoist to module scope or memoize with `useMemo()`.
483
+
484
+ **Incorrect (new RegExp every render):**
485
+
486
+ ```tsx
487
+ function Highlighter({ text, query }: Props) {
488
+ const regex = new RegExp(`(${query})`, 'gi')
489
+ const parts = text.split(regex)
490
+ return <>{parts.map((part, i) => ...)}</>
491
+ }
492
+ ```
493
+
494
+ **Correct (memoize or hoist):**
495
+
496
+ ```tsx
497
+ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
498
+
499
+ function Highlighter({ text, query }: Props) {
500
+ const regex = useMemo(
501
+ () => new RegExp(`(${escapeRegex(query)})`, 'gi'),
502
+ [query]
503
+ )
504
+ const parts = text.split(regex)
505
+ return <>{parts.map((part, i) => ...)}</>
506
+ }
507
+ ```
508
+
509
+ **Warning (global regex has mutable state):**
510
+
511
+ Global regex (`/g`) has mutable `lastIndex` state:
512
+
513
+ ```typescript
514
+ const regex = /foo/g
515
+ regex.test('foo') // true, lastIndex = 3
516
+ regex.test('foo') // false, lastIndex = 0
517
+ ```
518
+
519
+ ---
520
+
521
+ ## Rule 7.10: Use Loop for Min/Max Instead of Sort
522
+
523
+ **Impact:** LOW
524
+ **Tags:** javascript, arrays, performance, sorting, algorithms
525
+
526
+ ## Use Loop for Min/Max Instead of Sort
527
+
528
+ Finding the smallest or largest element only requires a single pass through the array. Sorting is wasteful and slower.
529
+
530
+ **Incorrect (O(n log n) - sort to find latest):**
531
+
532
+ ```typescript
533
+ interface Project {
534
+ id: string
535
+ name: string
536
+ updatedAt: number
537
+ }
538
+
539
+ function getLatestProject(projects: Project[]) {
540
+ const sorted = [...projects].sort((a, b) => b.updatedAt - a.updatedAt)
541
+ return sorted[0]
542
+ }
543
+ ```
544
+
545
+ Sorts the entire array just to find the maximum value.
546
+
547
+ **Incorrect (O(n log n) - sort for oldest and newest):**
548
+
549
+ ```typescript
550
+ function getOldestAndNewest(projects: Project[]) {
551
+ const sorted = [...projects].sort((a, b) => a.updatedAt - b.updatedAt)
552
+ return { oldest: sorted[0], newest: sorted[sorted.length - 1] }
553
+ }
554
+ ```
555
+
556
+ Still sorts unnecessarily when only min/max are needed.
557
+
558
+ **Correct (O(n) - single loop):**
559
+
560
+ ```typescript
561
+ function getLatestProject(projects: Project[]) {
562
+ if (projects.length === 0) return null
563
+
564
+ let latest = projects[0]
565
+
566
+ for (let i = 1; i < projects.length; i++) {
567
+ if (projects[i].updatedAt > latest.updatedAt) {
568
+ latest = projects[i]
569
+ }
570
+ }
571
+
572
+ return latest
573
+ }
574
+
575
+ function getOldestAndNewest(projects: Project[]) {
576
+ if (projects.length === 0) return { oldest: null, newest: null }
577
+
578
+ let oldest = projects[0]
579
+ let newest = projects[0]
580
+
581
+ for (let i = 1; i < projects.length; i++) {
582
+ if (projects[i].updatedAt < oldest.updatedAt) oldest = projects[i]
583
+ if (projects[i].updatedAt > newest.updatedAt) newest = projects[i]
584
+ }
585
+
586
+ return { oldest, newest }
587
+ }
588
+ ```
589
+
590
+ Single pass through the array, no copying, no sorting.
591
+
592
+ **Alternative (Math.min/Math.max for small arrays):**
593
+
594
+ ```typescript
595
+ const numbers = [5, 2, 8, 1, 9]
596
+ const min = Math.min(...numbers)
597
+ const max = Math.max(...numbers)
598
+ ```
599
+
600
+ This works for small arrays, but can be slower or just throw an error for very large arrays due to spread operator limitations. Maximal array length is approximately 124000 in Chrome 143 and 638000 in Safari 18; exact numbers may vary - see [the fiddle](https://jsfiddle.net/qw1jabsx/4/). Use the loop approach for reliability.
601
+
602
+ ---
603
+
604
+ ## Rule 7.11: Use Set/Map for O(1) Lookups
605
+
606
+ **Impact:** LOW-MEDIUM
607
+ **Tags:** javascript, set, map, data-structures, performance
608
+
609
+ ## Use Set/Map for O(1) Lookups
610
+
611
+ Convert arrays to Set/Map for repeated membership checks.
612
+
613
+ **Incorrect (O(n) per check):**
614
+
615
+ ```typescript
616
+ const allowedIds = ['a', 'b', 'c', ...]
617
+ items.filter(item => allowedIds.includes(item.id))
618
+ ```
619
+
620
+ **Correct (O(1) per check):**
621
+
622
+ ```typescript
623
+ const allowedIds = new Set(['a', 'b', 'c', ...])
624
+ items.filter(item => allowedIds.has(item.id))
625
+ ```
626
+
627
+ ---
628
+
629
+ ## Rule 7.12: Use toSorted() Instead of sort() for Immutability
630
+
631
+ **Impact:** MEDIUM-HIGH
632
+ **Tags:** javascript, arrays, immutability, react, state, mutation
633
+
634
+ ## Use toSorted() Instead of sort() for Immutability
635
+
636
+ `.sort()` mutates the array in place, which can cause bugs with React state and props. Use `.toSorted()` to create a new sorted array without mutation.
637
+
638
+ **Incorrect (mutates original array):**
639
+
640
+ ```typescript
641
+ function UserList({ users }: { users: User[] }) {
642
+ // Mutates the users prop array!
643
+ const sorted = useMemo(
644
+ () => users.sort((a, b) => a.name.localeCompare(b.name)),
645
+ [users]
646
+ )
647
+ return <div>{sorted.map(renderUser)}</div>
648
+ }
649
+ ```
650
+
651
+ **Correct (creates new array):**
652
+
653
+ ```typescript
654
+ function UserList({ users }: { users: User[] }) {
655
+ // Creates new sorted array, original unchanged
656
+ const sorted = useMemo(
657
+ () => users.toSorted((a, b) => a.name.localeCompare(b.name)),
658
+ [users]
659
+ )
660
+ return <div>{sorted.map(renderUser)}</div>
661
+ }
662
+ ```
663
+
664
+ **Why this matters in React:**
665
+
666
+ 1. Props/state mutations break React's immutability model - React expects props and state to be treated as read-only
667
+ 2. Causes stale closure bugs - Mutating arrays inside closures (callbacks, effects) can lead to unexpected behavior
668
+
669
+ **Browser support (fallback for older browsers):**
670
+
671
+ `.toSorted()` is available in all modern browsers (Chrome 110+, Safari 16+, Firefox 115+, Node.js 20+). For older environments, use spread operator:
672
+
673
+ ```typescript
674
+ // Fallback for older browsers
675
+ const sorted = [...items].sort((a, b) => a.value - b.value)
676
+ ```
677
+
678
+ **Other immutable array methods:**
679
+
680
+ - `.toSorted()` - immutable sort
681
+ - `.toReversed()` - immutable reverse
682
+ - `.toSpliced()` - immutable splice
683
+ - `.with()` - immutable element replacement
684
+