@dianzhong/create-harness-app 0.1.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 (95) hide show
  1. package/dist/index.mjs +412 -0
  2. package/package.json +29 -0
  3. package/templates/axios/.env.example +2 -0
  4. package/templates/axios/src/api/auth.ts +19 -0
  5. package/templates/axios/src/api/request.ts +61 -0
  6. package/templates/axios/src/types/api.ts +26 -0
  7. package/templates/axios/src/utils/auth.ts +5 -0
  8. package/templates/axios/src/utils/storage.ts +17 -0
  9. package/templates/harness/full/.agents/skills/find-skills/SKILL.md +143 -0
  10. package/templates/harness/full/.agents/skills/vue-best-practices/LICENSE.md +21 -0
  11. package/templates/harness/full/.agents/skills/vue-best-practices/SKILL.md +155 -0
  12. package/templates/harness/full/.agents/skills/vue-best-practices/SYNC.md +5 -0
  13. package/templates/harness/full/.agents/skills/vue-best-practices/references/animation-class-based-technique.md +258 -0
  14. package/templates/harness/full/.agents/skills/vue-best-practices/references/animation-state-driven-technique.md +287 -0
  15. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-async.md +99 -0
  16. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-data-flow.md +313 -0
  17. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-fallthrough-attrs.md +179 -0
  18. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-keep-alive.md +139 -0
  19. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-slots.md +226 -0
  20. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-suspense.md +231 -0
  21. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-teleport.md +110 -0
  22. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-transition-group.md +131 -0
  23. package/templates/harness/full/.agents/skills/vue-best-practices/references/component-transition.md +135 -0
  24. package/templates/harness/full/.agents/skills/vue-best-practices/references/composables.md +303 -0
  25. package/templates/harness/full/.agents/skills/vue-best-practices/references/directives.md +168 -0
  26. package/templates/harness/full/.agents/skills/vue-best-practices/references/perf-avoid-component-abstraction-in-lists.md +177 -0
  27. package/templates/harness/full/.agents/skills/vue-best-practices/references/perf-v-once-v-memo-directives.md +185 -0
  28. package/templates/harness/full/.agents/skills/vue-best-practices/references/perf-virtualize-large-lists.md +182 -0
  29. package/templates/harness/full/.agents/skills/vue-best-practices/references/plugins.md +178 -0
  30. package/templates/harness/full/.agents/skills/vue-best-practices/references/reactivity.md +371 -0
  31. package/templates/harness/full/.agents/skills/vue-best-practices/references/render-functions.md +227 -0
  32. package/templates/harness/full/.agents/skills/vue-best-practices/references/sfc.md +355 -0
  33. package/templates/harness/full/.agents/skills/vue-best-practices/references/state-management.md +138 -0
  34. package/templates/harness/full/.agents/skills/vue-best-practices/references/updated-hook-performance.md +193 -0
  35. package/templates/harness/full/.claude/agents/code-reviewer.md +109 -0
  36. package/templates/harness/full/.claude/agents/harness-reviewer.md +51 -0
  37. package/templates/harness/full/.claude/hooks/guard-tool.cjs +234 -0
  38. package/templates/harness/full/.claude/hooks/notify.cjs +168 -0
  39. package/templates/harness/full/.claude/hooks/quality-gate.cjs +135 -0
  40. package/templates/harness/full/.claude/rules/delivery.md +66 -0
  41. package/templates/harness/full/.claude/rules/formatting.md +7 -0
  42. package/templates/harness/full/.claude/rules/git.md +8 -0
  43. package/templates/harness/full/.claude/rules/skills-mcp.md +13 -0
  44. package/templates/harness/full/.claude/rules/vue.md +227 -0
  45. package/templates/harness/full/.claude/settings.json +123 -0
  46. package/templates/harness/full/.claude/skills/find-skills/SKILL.md +143 -0
  47. package/templates/harness/full/.claude/skills/vue-best-practices/LICENSE.md +21 -0
  48. package/templates/harness/full/.claude/skills/vue-best-practices/SKILL.md +155 -0
  49. package/templates/harness/full/.claude/skills/vue-best-practices/SYNC.md +5 -0
  50. package/templates/harness/full/.claude/skills/vue-best-practices/references/animation-class-based-technique.md +258 -0
  51. package/templates/harness/full/.claude/skills/vue-best-practices/references/animation-state-driven-technique.md +287 -0
  52. package/templates/harness/full/.claude/skills/vue-best-practices/references/component-async.md +99 -0
  53. package/templates/harness/full/.claude/skills/vue-best-practices/references/component-data-flow.md +313 -0
  54. package/templates/harness/full/.claude/skills/vue-best-practices/references/component-fallthrough-attrs.md +179 -0
  55. package/templates/harness/full/.claude/skills/vue-best-practices/references/component-keep-alive.md +139 -0
  56. package/templates/harness/full/.claude/skills/vue-best-practices/references/component-slots.md +226 -0
  57. package/templates/harness/full/.claude/skills/vue-best-practices/references/component-suspense.md +231 -0
  58. package/templates/harness/full/.claude/skills/vue-best-practices/references/component-teleport.md +110 -0
  59. package/templates/harness/full/.claude/skills/vue-best-practices/references/component-transition-group.md +131 -0
  60. package/templates/harness/full/.claude/skills/vue-best-practices/references/component-transition.md +135 -0
  61. package/templates/harness/full/.claude/skills/vue-best-practices/references/composables.md +303 -0
  62. package/templates/harness/full/.claude/skills/vue-best-practices/references/directives.md +168 -0
  63. package/templates/harness/full/.claude/skills/vue-best-practices/references/perf-avoid-component-abstraction-in-lists.md +177 -0
  64. package/templates/harness/full/.claude/skills/vue-best-practices/references/perf-v-once-v-memo-directives.md +185 -0
  65. package/templates/harness/full/.claude/skills/vue-best-practices/references/perf-virtualize-large-lists.md +182 -0
  66. package/templates/harness/full/.claude/skills/vue-best-practices/references/plugins.md +178 -0
  67. package/templates/harness/full/.claude/skills/vue-best-practices/references/reactivity.md +371 -0
  68. package/templates/harness/full/.claude/skills/vue-best-practices/references/render-functions.md +227 -0
  69. package/templates/harness/full/.claude/skills/vue-best-practices/references/sfc.md +355 -0
  70. package/templates/harness/full/.claude/skills/vue-best-practices/references/state-management.md +138 -0
  71. package/templates/harness/full/.claude/skills/vue-best-practices/references/updated-hook-performance.md +193 -0
  72. package/templates/harness/full/.editorconfig +8 -0
  73. package/templates/harness/full/.husky/commit-msg +1 -0
  74. package/templates/harness/full/.husky/pre-commit +1 -0
  75. package/templates/harness/full/.lintstagedrc.json +4 -0
  76. package/templates/harness/full/.nvmrc +1 -0
  77. package/templates/harness/full/.oxlintrc.json +11 -0
  78. package/templates/harness/full/.prettierrc.json +6 -0
  79. package/templates/harness/full/AGENTS.md +3 -0
  80. package/templates/harness/full/CLAUDE.md +28 -0
  81. package/templates/harness/full/GEMINI.md +3 -0
  82. package/templates/harness/full/commitlint.config.ts +3 -0
  83. package/templates/harness/full/docs/ai-harness.md +77 -0
  84. package/templates/harness/full/docs/delivery-template.md +66 -0
  85. package/templates/harness/full/docs/git.md +24 -0
  86. package/templates/harness/full/docs/harness-quick-reference.md +89 -0
  87. package/templates/harness/full/docs/review-checklist.md +49 -0
  88. package/templates/harness/full/scripts/harness-hooks.test.mjs +218 -0
  89. package/templates/harness/full/scripts/verify-skills.mjs +248 -0
  90. package/templates/harness/full/scripts/verify-skills.test.mjs +72 -0
  91. package/templates/harness/full/skills-lock.json +50 -0
  92. package/templates/harness/minimal/.claude/hooks/guard-tool.cjs +234 -0
  93. package/templates/harness/minimal/.claude/hooks/quality-gate.cjs +135 -0
  94. package/templates/harness/minimal/.claude/settings.json +27 -0
  95. package/templates/harness/minimal/CLAUDE.md +12 -0
@@ -0,0 +1,303 @@
1
+ ---
2
+ title: Composable Organization Patterns
3
+ impact: MEDIUM
4
+ impactDescription: Well-structured composables improve maintainability, reusability, and update performance
5
+ type: best-practice
6
+ tags: [vue3, composables, composition-api, code-organization, api-design, readonly, utilities]
7
+ ---
8
+
9
+ # Composable Organization Patterns
10
+
11
+ **Impact: MEDIUM** - Treat composables as reusable, stateful building blocks and keep their code organized by feature concern. This keeps large components maintainable and prevents hard-to-debug mutation and API design issues.
12
+
13
+ ## Task List
14
+
15
+ - Compose complex behavior from small, focused composables
16
+ - Use options objects for composables with multiple optional parameters
17
+ - Return readonly state when updates must flow through explicit actions
18
+ - Keep pure utility functions as plain utilities, not composables
19
+ - Organize composable and component code by feature concern, and extract composables when components grow
20
+
21
+ ## Compose Composables from Smaller Primitives
22
+
23
+ **BAD:**
24
+
25
+ ```vue
26
+ <script setup>
27
+ import { computed, onMounted, onUnmounted, ref } from 'vue'
28
+
29
+ const x = ref(0)
30
+ const y = ref(0)
31
+ const inside = ref(false)
32
+ const el = ref(null)
33
+
34
+ function onMove(e) {
35
+ x.value = e.pageX
36
+ y.value = e.pageY
37
+ if (!el.value) return
38
+ const r = el.value.getBoundingClientRect()
39
+ inside.value = x.value >= r.left && x.value <= r.right && y.value >= r.top && y.value <= r.bottom
40
+ }
41
+
42
+ onMounted(() => window.addEventListener('mousemove', onMove))
43
+ onUnmounted(() => window.removeEventListener('mousemove', onMove))
44
+ </script>
45
+ ```
46
+
47
+ **GOOD:**
48
+
49
+ ```javascript
50
+ // composables/useEventListener.js
51
+ import { onMounted, onUnmounted, toValue } from 'vue'
52
+
53
+ export function useEventListener(target, event, callback) {
54
+ onMounted(() => toValue(target).addEventListener(event, callback))
55
+ onUnmounted(() => toValue(target).removeEventListener(event, callback))
56
+ }
57
+ ```
58
+
59
+ ```javascript
60
+ // composables/useMouse.js
61
+ import { ref } from 'vue'
62
+
63
+ import { useEventListener } from './useEventListener'
64
+
65
+ export function useMouse() {
66
+ const x = ref(0)
67
+ const y = ref(0)
68
+
69
+ useEventListener(window, 'mousemove', (e) => {
70
+ x.value = e.pageX
71
+ y.value = e.pageY
72
+ })
73
+
74
+ return { x, y }
75
+ }
76
+ ```
77
+
78
+ ```javascript
79
+ // composables/useMouseInElement.js
80
+ import { computed } from 'vue'
81
+
82
+ import { useMouse } from './useMouse'
83
+
84
+ export function useMouseInElement(elementRef) {
85
+ const { x, y } = useMouse()
86
+
87
+ const isOutside = computed(() => {
88
+ if (!elementRef.value) return true
89
+ const rect = elementRef.value.getBoundingClientRect()
90
+ return (
91
+ x.value < rect.left || x.value > rect.right || y.value < rect.top || y.value > rect.bottom
92
+ )
93
+ })
94
+
95
+ return { x, y, isOutside }
96
+ }
97
+ ```
98
+
99
+ ## Use Options Object Pattern for Composable Parameters
100
+
101
+ **BAD:**
102
+
103
+ ```javascript
104
+ export function useFetch(url, method, headers, timeout, retries, immediate) {
105
+ // hard to read and easy to misorder
106
+ }
107
+
108
+ useFetch('/api/users', 'GET', null, 5000, 3, true)
109
+ ```
110
+
111
+ **GOOD:**
112
+
113
+ ```javascript
114
+ export function useFetch(url, options = {}) {
115
+ const { method = 'GET', headers = {}, timeout = 30000, retries = 0, immediate = true } = options
116
+
117
+ // implementation
118
+ return { method, headers, timeout, retries, immediate }
119
+ }
120
+
121
+ useFetch('/api/users', {
122
+ method: 'POST',
123
+ timeout: 5000,
124
+ retries: 3,
125
+ })
126
+ ```
127
+
128
+ ```typescript
129
+ interface UseCounterOptions {
130
+ initial?: number
131
+ min?: number
132
+ max?: number
133
+ step?: number
134
+ }
135
+
136
+ export function useCounter(options: UseCounterOptions = {}) {
137
+ const { initial = 0, min = -Infinity, max = Infinity, step = 1 } = options
138
+ // implementation
139
+ }
140
+ ```
141
+
142
+ ## Return Readonly State with Explicit Actions
143
+
144
+ **BAD:**
145
+
146
+ ```javascript
147
+ export function useCart() {
148
+ const items = ref([])
149
+ const total = computed(() => items.value.reduce((sum, item) => sum + item.price, 0))
150
+ return { items, total } // any consumer can mutate directly
151
+ }
152
+
153
+ const { items } = useCart()
154
+ items.value.push({ id: 1, price: 10 })
155
+ ```
156
+
157
+ **GOOD:**
158
+
159
+ ```javascript
160
+ import { computed, readonly, ref } from 'vue'
161
+
162
+ export function useCart() {
163
+ const _items = ref([])
164
+
165
+ const total = computed(() =>
166
+ _items.value.reduce((sum, item) => sum + item.price * item.quantity, 0),
167
+ )
168
+
169
+ function addItem(product, quantity = 1) {
170
+ const existing = _items.value.find((item) => item.id === product.id)
171
+ if (existing) {
172
+ existing.quantity += quantity
173
+ return
174
+ }
175
+ _items.value.push({ ...product, quantity })
176
+ }
177
+
178
+ function removeItem(productId) {
179
+ _items.value = _items.value.filter((item) => item.id !== productId)
180
+ }
181
+
182
+ return {
183
+ items: readonly(_items),
184
+ total,
185
+ addItem,
186
+ removeItem,
187
+ }
188
+ }
189
+ ```
190
+
191
+ ## Keep Utilities as Utilities
192
+
193
+ **BAD:**
194
+
195
+ ```javascript
196
+ export function useFormatters() {
197
+ const formatDate = (date) => new Intl.DateTimeFormat('en-US').format(date)
198
+ const formatCurrency = (amount) =>
199
+ new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount)
200
+ return { formatDate, formatCurrency }
201
+ }
202
+
203
+ const { formatDate } = useFormatters()
204
+ ```
205
+
206
+ **GOOD:**
207
+
208
+ ```javascript
209
+ // utils/formatters.js
210
+ export function formatDate(date) {
211
+ return new Intl.DateTimeFormat('en-US').format(date)
212
+ }
213
+
214
+ export function formatCurrency(amount) {
215
+ return new Intl.NumberFormat('en-US', {
216
+ style: 'currency',
217
+ currency: 'USD',
218
+ }).format(amount)
219
+ }
220
+ ```
221
+
222
+ ```javascript
223
+ // composables/useInvoiceSummary.js
224
+ import { computed } from 'vue'
225
+
226
+ import { formatCurrency } from '@/utils/formatters'
227
+
228
+ export function useInvoiceSummary(invoiceRef) {
229
+ const totalLabel = computed(() => formatCurrency(invoiceRef.value.total))
230
+ return { totalLabel }
231
+ }
232
+ ```
233
+
234
+ ## Organize Composable and Component Code by Feature Concern
235
+
236
+ **BAD:**
237
+
238
+ ```vue
239
+ <script setup>
240
+ import { computed, onMounted, ref, watch } from 'vue'
241
+
242
+ const searchQuery = ref('')
243
+ const items = ref([])
244
+ const selected = ref(null)
245
+ const showModal = ref(false)
246
+ const sortBy = ref('name')
247
+ const filter = ref('all')
248
+ const loading = ref(false)
249
+
250
+ const filtered = computed(() => items.value.filter((i) => i.category === filter.value))
251
+ function openModal() {
252
+ showModal.value = true
253
+ }
254
+ const sorted = computed(() => [...filtered.value].sort(/* ... */))
255
+ watch(searchQuery, () => {
256
+ /* ... */
257
+ })
258
+ onMounted(() => {
259
+ /* ... */
260
+ })
261
+ </script>
262
+ ```
263
+
264
+ **GOOD:**
265
+
266
+ ```vue
267
+ <script setup>
268
+ import { useItems } from '@/composables/useItems'
269
+ import { useSearch } from '@/composables/useSearch'
270
+ import { useSelectionModal } from '@/composables/useSelectionModal'
271
+
272
+ // Data
273
+ const { items, loading, fetchItems } = useItems()
274
+
275
+ // Search/filter/sort
276
+ const { query, visibleItems } = useSearch(items)
277
+
278
+ // Selection + modal
279
+ const { selectedItem, isModalOpen, selectItem, closeModal } = useSelectionModal()
280
+ </script>
281
+ ```
282
+
283
+ ```javascript
284
+ // composables/useItems.js
285
+ import { onMounted, ref } from 'vue'
286
+
287
+ export function useItems() {
288
+ const items = ref([])
289
+ const loading = ref(false)
290
+
291
+ async function fetchItems() {
292
+ loading.value = true
293
+ try {
294
+ items.value = await api.getItems()
295
+ } finally {
296
+ loading.value = false
297
+ }
298
+ }
299
+
300
+ onMounted(fetchItems)
301
+ return { items, loading, fetchItems }
302
+ }
303
+ ```
@@ -0,0 +1,168 @@
1
+ ---
2
+ title: Directive Best Practices
3
+ impact: MEDIUM
4
+ impactDescription: Custom directives are powerful but easy to misuse; following patterns prevents leaks, invalid usage, and unclear abstractions
5
+ type: best-practice
6
+ tags: [vue3, directives, custom-directives, composition, typescript]
7
+ ---
8
+
9
+ # Directive Best Practices
10
+
11
+ **Impact: MEDIUM** - Directives are for low-level DOM access. Use them sparingly, keep them side-effect safe, and prefer components or composables when you need stateful or reusable UI behavior.
12
+
13
+ ## Task List
14
+
15
+ - Use directives only when you need direct DOM access
16
+ - Do not mutate directive arguments or binding objects
17
+ - Clean up timers, listeners, and observers in `unmounted`
18
+ - Register directives in `<script setup>` with the `v-` prefix
19
+ - In TypeScript projects, type directive values and augment template directive types
20
+ - Prefer components or composables for complex behavior
21
+
22
+ ## Treat Directive Arguments as Read-Only
23
+
24
+ Directive bindings are not reactive storage. Don’t write to them.
25
+
26
+ ```ts
27
+ const vFocus = {
28
+ mounted(el, binding) {
29
+ // binding.value is read-only
30
+ el.focus()
31
+ },
32
+ }
33
+ ```
34
+
35
+ ## Avoid Directives on Components
36
+
37
+ Directives apply to DOM elements. When used on components, they attach to the root element and can break if the root changes.
38
+
39
+ **BAD:**
40
+
41
+ ```vue
42
+ <MyInput v-focus />
43
+ ```
44
+
45
+ **GOOD:**
46
+
47
+ ```vue
48
+ <!-- MyInput.vue -->
49
+ <script setup>
50
+ const vFocus = (el) => el.focus()
51
+ </script>
52
+
53
+ <template>
54
+ <input v-focus />
55
+ </template>
56
+ ```
57
+
58
+ ## Clean Up Side Effects in `unmounted`
59
+
60
+ Any timers, listeners, or observers must be removed to avoid leaks.
61
+
62
+ ```ts
63
+ const vResize = {
64
+ mounted(el) {
65
+ const observer = new ResizeObserver(() => {})
66
+ observer.observe(el)
67
+ el._observer = observer
68
+ },
69
+ unmounted(el) {
70
+ el._observer?.disconnect()
71
+ },
72
+ }
73
+ ```
74
+
75
+ ## Prefer Function Shorthand for Single-Hook Directives
76
+
77
+ If you only need `mounted`/`updated`, use the function form.
78
+
79
+ ```ts
80
+ const vAutofocus = (el) => el.focus()
81
+ ```
82
+
83
+ ## Use the `v-` Prefix and Script Setup Registration
84
+
85
+ ```vue
86
+ <script setup>
87
+ const vFocus = (el) => el.focus()
88
+ </script>
89
+
90
+ <template>
91
+ <input v-focus />
92
+ </template>
93
+ ```
94
+
95
+ ## Type Custom Directives in TypeScript Projects
96
+
97
+ Use `Directive<Element, ValueType>` so `binding.value` is typed, and augment Vue's template types so directives are recognized in SFC templates.
98
+
99
+ **BAD:**
100
+
101
+ ```ts
102
+ // Untyped directive value and no template type augmentation
103
+ export const vHighlight = {
104
+ mounted(el, binding) {
105
+ el.style.backgroundColor = binding.value
106
+ },
107
+ }
108
+ ```
109
+
110
+ **GOOD:**
111
+
112
+ ```ts
113
+ import type { Directive } from 'vue'
114
+
115
+ type HighlightValue = string
116
+
117
+ export const vHighlight = {
118
+ mounted(el, binding) {
119
+ el.style.backgroundColor = binding.value
120
+ },
121
+ } satisfies Directive<HTMLElement, HighlightValue>
122
+
123
+ declare module 'vue' {
124
+ interface ComponentCustomProperties {
125
+ vHighlight: typeof vHighlight
126
+ }
127
+ }
128
+ ```
129
+
130
+ ## Handle SSR with `getSSRProps`
131
+
132
+ Directive hooks such as `mounted` and `updated` do not run during SSR. If a directive sets attributes/classes that affect rendered HTML, provide an SSR equivalent via `getSSRProps` to avoid hydration mismatches.
133
+
134
+ **BAD:**
135
+
136
+ ```ts
137
+ const vTooltip = {
138
+ mounted(el, binding) {
139
+ el.setAttribute('data-tooltip', binding.value)
140
+ el.classList.add('has-tooltip')
141
+ },
142
+ }
143
+ ```
144
+
145
+ **GOOD:**
146
+
147
+ ```ts
148
+ const vTooltip = {
149
+ mounted(el, binding) {
150
+ el.setAttribute('data-tooltip', binding.value)
151
+ el.classList.add('has-tooltip')
152
+ },
153
+ getSSRProps(binding) {
154
+ return {
155
+ 'data-tooltip': binding.value,
156
+ class: 'has-tooltip',
157
+ }
158
+ },
159
+ }
160
+ ```
161
+
162
+ ## Prefer Declarative Templates When Possible
163
+
164
+ If a standard attribute or binding works, use it instead of a directive.
165
+
166
+ ## Decide Between Directives and Components
167
+
168
+ Use a directive for DOM-level behavior. Use a component when behavior affects structure, state, or rendering.
@@ -0,0 +1,177 @@
1
+ ---
2
+ title: Avoid Excessive Component Abstraction in Large Lists
3
+ impact: MEDIUM
4
+ impactDescription: Each component instance has memory and render overhead - abstractions multiply this in lists
5
+ type: efficiency
6
+ tags: [vue3, performance, components, abstraction, lists, optimization]
7
+ ---
8
+
9
+ # Avoid Excessive Component Abstraction in Large Lists
10
+
11
+ **Impact: MEDIUM** - Component instances are more expensive than plain DOM nodes. While abstractions improve code organization, unnecessary nesting creates overhead. In large lists, this overhead multiplies - 100 items with 3 levels of abstraction means 300+ component instances instead of 100.
12
+
13
+ Don't avoid abstraction entirely, but be mindful of component depth in frequently-rendered elements like list items.
14
+
15
+ ## Task List
16
+
17
+ - Review list item components for unnecessary wrapper components
18
+ - Consider flattening component hierarchies in hot paths
19
+ - Use native elements when a component adds no value
20
+ - Profile component counts using Vue DevTools
21
+ - Focus optimization efforts on the most-rendered components
22
+
23
+ **BAD:**
24
+
25
+ ```vue
26
+ <!-- BAD: Deep abstraction in list items -->
27
+ <template>
28
+ <div class="user-list">
29
+ <!-- For 100 users: Creates 400 component instances -->
30
+ <UserCard v-for="user in users" :key="user.id" :user="user" />
31
+ </div>
32
+ </template>
33
+
34
+ <!-- UserCard.vue -->
35
+ <template>
36
+ <Card>
37
+ <!-- Wrapper component #1 -->
38
+ <CardHeader>
39
+ <!-- Wrapper component #2 -->
40
+ <UserAvatar :src="user.avatar" />
41
+ <!-- Wrapper component #3 -->
42
+ </CardHeader>
43
+ <CardBody>
44
+ <!-- Wrapper component #4 -->
45
+ <Text>{{ user.name }}</Text>
46
+ </CardBody>
47
+ </Card>
48
+ </template>
49
+
50
+ <!-- Each UserCard creates: Card + CardHeader + CardBody + UserAvatar + Text
51
+ 100 users = 500+ component instances -->
52
+ ```
53
+
54
+ **GOOD:**
55
+
56
+ ```vue
57
+ <!-- GOOD: Flattened structure in list items -->
58
+ <script setup>
59
+ defineProps({
60
+ user: Object,
61
+ })
62
+ </script>
63
+
64
+ <!-- UserCard.vue - Flattened, uses native elements -->
65
+ <template>
66
+ <div class="user-list">
67
+ <!-- For 100 users: Creates 100 component instances -->
68
+ <UserCard v-for="user in users" :key="user.id" :user="user" />
69
+ </div>
70
+ </template>
71
+
72
+ <template>
73
+ <div class="card">
74
+ <div class="card-header">
75
+ <img :src="user.avatar" :alt="user.name" class="avatar" />
76
+ </div>
77
+ <div class="card-body">
78
+ <span class="user-name">{{ user.name }}</span>
79
+ </div>
80
+ </div>
81
+ </template>
82
+
83
+ <style scoped>
84
+ /* Styles that would have been in Card, CardHeader, etc. */
85
+ .card {
86
+ /* ... */
87
+ }
88
+ .card-header {
89
+ /* ... */
90
+ }
91
+ .card-body {
92
+ /* ... */
93
+ }
94
+ .avatar {
95
+ /* ... */
96
+ }
97
+ </style>
98
+ ```
99
+
100
+ ## When Abstraction Is Still Worth It
101
+
102
+ ```vue
103
+ <!-- Component abstraction is valuable when: -->
104
+
105
+ <!-- 1. Complex behavior is encapsulated -->
106
+ <UserStatusIndicator :user="user" /> <!-- Has logic, tooltips, etc. -->
107
+
108
+ <!-- 2. Reused outside of the hot path -->
109
+ <Card> <!-- OK to use in one-off places, not in 100-item lists -->
110
+
111
+ <!-- 3. The list itself is small -->
112
+ <template v-if="items.length < 20">
113
+ <FancyItem v-for="item in items" :key="item.id" />
114
+ </template>
115
+
116
+ <!-- 4. Virtualization is used (only ~20 items rendered at once) -->
117
+ <RecycleScroller :items="items">
118
+ <template #default="{ item }">
119
+ <ComplexItem :item="item" /> <!-- OK - only 20 instances exist -->
120
+ </template>
121
+ </RecycleScroller>
122
+ ```
123
+
124
+ ## Measuring Component Overhead
125
+
126
+ ```javascript
127
+ // In development, profile component counts
128
+ import { getCurrentInstance, onMounted } from 'vue'
129
+
130
+ onMounted(() => {
131
+ const instance = getCurrentInstance()
132
+ let count = 0
133
+
134
+ function countComponents(vnode) {
135
+ if (vnode.component) count++
136
+ if (vnode.children) {
137
+ vnode.children.forEach((child) => {
138
+ if (child.component || child.children) countComponents(child)
139
+ })
140
+ }
141
+ }
142
+
143
+ // Use Vue DevTools instead for accurate counts
144
+ console.log('Check Vue DevTools Components tab for instance counts')
145
+ })
146
+ ```
147
+
148
+ ## Alternatives to Wrapper Components
149
+
150
+ ```vue
151
+ <!-- Instead of a <Button> component for styling: -->
152
+ <button class="btn btn-primary">
153
+ Click
154
+ </button>
155
+
156
+ <!-- Instead of a <Text> component: -->
157
+ <span class="text-body">
158
+ {{ content }}
159
+ </span>
160
+
161
+ <!-- Instead of layout wrapper components in lists: -->
162
+ <div class="flex items-center gap-2">
163
+ <!-- content -->
164
+ </div>
165
+
166
+ <!-- Use CSS classes or Tailwind instead of component abstractions for styling -->
167
+ ```
168
+
169
+ ## Impact Calculation
170
+
171
+ | List Size | Components per Item | Total Instances | Memory Impact |
172
+ | ---------- | ------------------- | --------------- | ------------- |
173
+ | 100 items | 1 (flat) | 100 | Baseline |
174
+ | 100 items | 3 (nested) | 300 | ~3x memory |
175
+ | 100 items | 5 (deeply nested) | 500 | ~5x memory |
176
+ | 1000 items | 1 (flat) | 1000 | High |
177
+ | 1000 items | 5 (deeply nested) | 5000 | Very High |