@pythoughts/vue-skills-mcp 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 (208) hide show
  1. package/README.md +63 -0
  2. package/index.mjs +139 -0
  3. package/package.json +34 -0
  4. package/skills/create-adaptable-composable/SKILL.md +76 -0
  5. package/skills/vue-best-practices/SKILL.md +154 -0
  6. package/skills/vue-best-practices/references/animation-class-based-technique.md +254 -0
  7. package/skills/vue-best-practices/references/animation-state-driven-technique.md +291 -0
  8. package/skills/vue-best-practices/references/component-async.md +97 -0
  9. package/skills/vue-best-practices/references/component-data-flow.md +350 -0
  10. package/skills/vue-best-practices/references/component-fallthrough-attrs.md +174 -0
  11. package/skills/vue-best-practices/references/component-keep-alive.md +137 -0
  12. package/skills/vue-best-practices/references/component-slots.md +216 -0
  13. package/skills/vue-best-practices/references/component-suspense.md +228 -0
  14. package/skills/vue-best-practices/references/component-teleport.md +108 -0
  15. package/skills/vue-best-practices/references/component-transition-group.md +128 -0
  16. package/skills/vue-best-practices/references/component-transition.md +125 -0
  17. package/skills/vue-best-practices/references/composables.md +290 -0
  18. package/skills/vue-best-practices/references/directives.md +162 -0
  19. package/skills/vue-best-practices/references/perf-avoid-component-abstraction-in-lists.md +159 -0
  20. package/skills/vue-best-practices/references/perf-v-once-v-memo-directives.md +182 -0
  21. package/skills/vue-best-practices/references/perf-virtualize-large-lists.md +187 -0
  22. package/skills/vue-best-practices/references/plugins.md +166 -0
  23. package/skills/vue-best-practices/references/reactivity.md +346 -0
  24. package/skills/vue-best-practices/references/render-functions.md +201 -0
  25. package/skills/vue-best-practices/references/sfc.md +310 -0
  26. package/skills/vue-best-practices/references/state-management.md +135 -0
  27. package/skills/vue-best-practices/references/updated-hook-performance.md +187 -0
  28. package/skills/vue-debug-guides/SKILL.md +205 -0
  29. package/skills/vue-debug-guides/reference/animation-key-for-rerender.md +160 -0
  30. package/skills/vue-debug-guides/reference/animation-transitiongroup-performance.md +241 -0
  31. package/skills/vue-debug-guides/reference/async-component-error-handling.md +115 -0
  32. package/skills/vue-debug-guides/reference/async-component-keepalive-ref-issue.md +112 -0
  33. package/skills/vue-debug-guides/reference/async-component-suspense-control.md +84 -0
  34. package/skills/vue-debug-guides/reference/async-component-vue-router.md +109 -0
  35. package/skills/vue-debug-guides/reference/attrs-event-listener-merging.md +205 -0
  36. package/skills/vue-debug-guides/reference/checkbox-true-false-value-form-submission.md +118 -0
  37. package/skills/vue-debug-guides/reference/cleanup-side-effects.md +172 -0
  38. package/skills/vue-debug-guides/reference/click-events-on-components.md +180 -0
  39. package/skills/vue-debug-guides/reference/component-naming-conflicts.md +159 -0
  40. package/skills/vue-debug-guides/reference/component-ref-requires-defineexpose.md +176 -0
  41. package/skills/vue-debug-guides/reference/composable-avoid-hidden-side-effects.md +208 -0
  42. package/skills/vue-debug-guides/reference/composable-call-location-restrictions.md +141 -0
  43. package/skills/vue-debug-guides/reference/composable-naming-return-pattern.md +139 -0
  44. package/skills/vue-debug-guides/reference/composable-tovalue-inside-watcheffect.md +182 -0
  45. package/skills/vue-debug-guides/reference/composition-api-not-functional-programming.md +120 -0
  46. package/skills/vue-debug-guides/reference/composition-api-script-setup-async-context.md +203 -0
  47. package/skills/vue-debug-guides/reference/composition-api-vs-react-hooks-differences.md +156 -0
  48. package/skills/vue-debug-guides/reference/computed-array-mutation.md +148 -0
  49. package/skills/vue-debug-guides/reference/computed-conditional-dependencies.md +147 -0
  50. package/skills/vue-debug-guides/reference/computed-no-parameters.md +159 -0
  51. package/skills/vue-debug-guides/reference/computed-no-side-effects.md +107 -0
  52. package/skills/vue-debug-guides/reference/computed-return-value-readonly.md +160 -0
  53. package/skills/vue-debug-guides/reference/configure-app-before-mount.md +89 -0
  54. package/skills/vue-debug-guides/reference/declare-emits-for-documentation.md +212 -0
  55. package/skills/vue-debug-guides/reference/define-expose-before-await.md +192 -0
  56. package/skills/vue-debug-guides/reference/define-model-default-value-sync.md +139 -0
  57. package/skills/vue-debug-guides/reference/defineEmits-must-be-top-level.md +164 -0
  58. package/skills/vue-debug-guides/reference/defineEmits-no-runtime-and-type-mixed.md +170 -0
  59. package/skills/vue-debug-guides/reference/definemodel-object-mutation-no-emit.md +148 -0
  60. package/skills/vue-debug-guides/reference/dom-update-timing-nexttick.md +90 -0
  61. package/skills/vue-debug-guides/reference/dynamic-argument-constraints.md +146 -0
  62. package/skills/vue-debug-guides/reference/dynamic-component-registration-vite.md +147 -0
  63. package/skills/vue-debug-guides/reference/event-modifier-order-matters.md +101 -0
  64. package/skills/vue-debug-guides/reference/exact-modifier-for-precise-shortcuts.md +155 -0
  65. package/skills/vue-debug-guides/reference/fallthrough-attrs-overwrite-vue3.md +159 -0
  66. package/skills/vue-debug-guides/reference/in-dom-template-parsing-caveats.md +149 -0
  67. package/skills/vue-debug-guides/reference/inheritattrs-false-for-wrapper-components.md +230 -0
  68. package/skills/vue-debug-guides/reference/keepalive-router-nested-double-mount.md +222 -0
  69. package/skills/vue-debug-guides/reference/keepalive-transition-memory-leak.md +144 -0
  70. package/skills/vue-debug-guides/reference/keyup-modifier-timing.md +137 -0
  71. package/skills/vue-debug-guides/reference/lifecycle-dom-access-timing.md +216 -0
  72. package/skills/vue-debug-guides/reference/lifecycle-hooks-synchronous-registration.md +156 -0
  73. package/skills/vue-debug-guides/reference/lifecycle-ssr-awareness.md +184 -0
  74. package/skills/vue-debug-guides/reference/local-components-not-in-descendants.md +151 -0
  75. package/skills/vue-debug-guides/reference/mount-return-value.md +88 -0
  76. package/skills/vue-debug-guides/reference/multi-root-component-class-attrs.md +93 -0
  77. package/skills/vue-debug-guides/reference/native-event-collision-with-emits.md +162 -0
  78. package/skills/vue-debug-guides/reference/no-passive-with-prevent.md +141 -0
  79. package/skills/vue-debug-guides/reference/no-v-if-with-v-for.md +136 -0
  80. package/skills/vue-debug-guides/reference/perf-computed-object-stability.md +157 -0
  81. package/skills/vue-debug-guides/reference/perf-props-stability-update-optimization.md +140 -0
  82. package/skills/vue-debug-guides/reference/plugin-global-properties-sparingly.md +109 -0
  83. package/skills/vue-debug-guides/reference/plugin-install-before-mount.md +124 -0
  84. package/skills/vue-debug-guides/reference/plugin-prefer-provide-inject-over-global-properties.md +120 -0
  85. package/skills/vue-debug-guides/reference/plugin-typescript-type-augmentation.md +157 -0
  86. package/skills/vue-debug-guides/reference/prop-defineprops-scope-limitation.md +161 -0
  87. package/skills/vue-debug-guides/reference/provide-inject-debugging-challenges.md +203 -0
  88. package/skills/vue-debug-guides/reference/provide-inject-default-value-factory.md +244 -0
  89. package/skills/vue-debug-guides/reference/provide-inject-reactivity-not-automatic.md +226 -0
  90. package/skills/vue-debug-guides/reference/provide-inject-synchronous-setup.md +235 -0
  91. package/skills/vue-debug-guides/reference/reactive-destructuring.md +89 -0
  92. package/skills/vue-debug-guides/reference/reactivity-debugging-hooks.md +132 -0
  93. package/skills/vue-debug-guides/reference/reactivity-markraw-for-non-reactive.md +149 -0
  94. package/skills/vue-debug-guides/reference/reactivity-proxy-identity-hazard.md +96 -0
  95. package/skills/vue-debug-guides/reference/reactivity-same-tick-batching.md +166 -0
  96. package/skills/vue-debug-guides/reference/ref-value-access.md +61 -0
  97. package/skills/vue-debug-guides/reference/refs-in-collections-need-value.md +81 -0
  98. package/skills/vue-debug-guides/reference/render-function-avoid-internal-vnode-properties.md +151 -0
  99. package/skills/vue-debug-guides/reference/render-function-vnodes-must-be-unique.md +133 -0
  100. package/skills/vue-debug-guides/reference/rendering-render-function-h-import-vue3.md +148 -0
  101. package/skills/vue-debug-guides/reference/rendering-render-function-return-from-setup.md +148 -0
  102. package/skills/vue-debug-guides/reference/rendering-render-function-slots-as-functions.md +168 -0
  103. package/skills/vue-debug-guides/reference/rendering-resolve-component-for-string-names.md +231 -0
  104. package/skills/vue-debug-guides/reference/select-initial-value-ios-bug.md +91 -0
  105. package/skills/vue-debug-guides/reference/self-referencing-component-name.md +157 -0
  106. package/skills/vue-debug-guides/reference/sfc-named-exports-forbidden.md +184 -0
  107. package/skills/vue-debug-guides/reference/sfc-scoped-css-child-component-styling.md +156 -0
  108. package/skills/vue-debug-guides/reference/sfc-scoped-css-dynamic-content.md +193 -0
  109. package/skills/vue-debug-guides/reference/sfc-scoped-css-slot-content.md +242 -0
  110. package/skills/vue-debug-guides/reference/sfc-script-setup-reactivity.md +195 -0
  111. package/skills/vue-debug-guides/reference/slot-forwarding-to-child-components.md +143 -0
  112. package/skills/vue-debug-guides/reference/slot-implicit-default-content.md +155 -0
  113. package/skills/vue-debug-guides/reference/slot-name-reserved-prop.md +109 -0
  114. package/skills/vue-debug-guides/reference/slot-named-scoped-explicit-default.md +95 -0
  115. package/skills/vue-debug-guides/reference/slot-render-scope-parent-only.md +135 -0
  116. package/skills/vue-debug-guides/reference/slot-v-slot-on-components-or-templates-only.md +122 -0
  117. package/skills/vue-debug-guides/reference/ssr-hydration-mismatch-causes.md +280 -0
  118. package/skills/vue-debug-guides/reference/ssr-platform-specific-apis.md +256 -0
  119. package/skills/vue-debug-guides/reference/state-ssr-cross-request-pollution.md +276 -0
  120. package/skills/vue-debug-guides/reference/suspense-no-builtin-error-handling.md +127 -0
  121. package/skills/vue-debug-guides/reference/suspense-ssr-hydration-issues.md +159 -0
  122. package/skills/vue-debug-guides/reference/tailwind-dynamic-class-generation.md +144 -0
  123. package/skills/vue-debug-guides/reference/teleport-scoped-styles-limitation.md +191 -0
  124. package/skills/vue-debug-guides/reference/teleport-ssr-hydration.md +152 -0
  125. package/skills/vue-debug-guides/reference/teleport-target-must-exist.md +113 -0
  126. package/skills/vue-debug-guides/reference/template-expressions-restrictions.md +114 -0
  127. package/skills/vue-debug-guides/reference/template-functions-no-side-effects.md +187 -0
  128. package/skills/vue-debug-guides/reference/template-ref-null-with-v-if.md +123 -0
  129. package/skills/vue-debug-guides/reference/template-ref-unwrapping-top-level.md +104 -0
  130. package/skills/vue-debug-guides/reference/template-ref-v-for-order.md +172 -0
  131. package/skills/vue-debug-guides/reference/textarea-no-interpolation.md +72 -0
  132. package/skills/vue-debug-guides/reference/transition-group-flip-inline-elements.md +152 -0
  133. package/skills/vue-debug-guides/reference/transition-group-move-animation-position-absolute.md +130 -0
  134. package/skills/vue-debug-guides/reference/transition-group-no-default-wrapper-vue3.md +152 -0
  135. package/skills/vue-debug-guides/reference/transition-js-hooks-done-callback.md +251 -0
  136. package/skills/vue-debug-guides/reference/transition-nested-duration.md +182 -0
  137. package/skills/vue-debug-guides/reference/transition-reusable-scoped-style.md +245 -0
  138. package/skills/vue-debug-guides/reference/transition-router-view-appear.md +193 -0
  139. package/skills/vue-debug-guides/reference/transition-type-when-mixed.md +172 -0
  140. package/skills/vue-debug-guides/reference/transition-unmount-hook-timing.md +149 -0
  141. package/skills/vue-debug-guides/reference/ts-defineprops-boolean-default-false.md +225 -0
  142. package/skills/vue-debug-guides/reference/ts-defineprops-imported-types-limitations.md +281 -0
  143. package/skills/vue-debug-guides/reference/ts-event-handler-explicit-typing.md +213 -0
  144. package/skills/vue-debug-guides/reference/ts-reactive-no-generic-argument.md +196 -0
  145. package/skills/vue-debug-guides/reference/ts-shallowref-for-dynamic-components.md +218 -0
  146. package/skills/vue-debug-guides/reference/ts-template-ref-null-handling.md +249 -0
  147. package/skills/vue-debug-guides/reference/ts-template-type-casting.md +214 -0
  148. package/skills/vue-debug-guides/reference/ts-withdefaults-mutable-factory-function.md +171 -0
  149. package/skills/vue-debug-guides/reference/undeclared-emits-double-firing.md +195 -0
  150. package/skills/vue-debug-guides/reference/use-template-ref-vue35.md +158 -0
  151. package/skills/vue-debug-guides/reference/v-else-must-follow-v-if.md +136 -0
  152. package/skills/vue-debug-guides/reference/v-for-component-props.md +95 -0
  153. package/skills/vue-debug-guides/reference/v-for-computed-reverse-sort.md +86 -0
  154. package/skills/vue-debug-guides/reference/v-for-key-attribute.md +90 -0
  155. package/skills/vue-debug-guides/reference/v-for-range-starts-at-one.md +66 -0
  156. package/skills/vue-debug-guides/reference/v-if-null-check-order.md +171 -0
  157. package/skills/vue-debug-guides/reference/v-model-ignores-html-attributes.md +83 -0
  158. package/skills/vue-debug-guides/reference/v-model-ime-composition.md +83 -0
  159. package/skills/vue-debug-guides/reference/v-model-number-modifier-behavior.md +124 -0
  160. package/skills/vue-debug-guides/reference/v-show-template-limitation.md +124 -0
  161. package/skills/vue-debug-guides/reference/watch-async-cleanup.md +180 -0
  162. package/skills/vue-debug-guides/reference/watch-async-creation-memory-leak.md +176 -0
  163. package/skills/vue-debug-guides/reference/watch-deep-same-object-reference.md +165 -0
  164. package/skills/vue-debug-guides/reference/watch-flush-timing.md +189 -0
  165. package/skills/vue-debug-guides/reference/watch-reactive-property-getter.md +108 -0
  166. package/skills/vue-debug-guides/reference/watcheffect-async-dependency-tracking.md +173 -0
  167. package/skills/vue-debug-guides/reference/watcheffect-flush-post-for-refs.md +176 -0
  168. package/skills/vue-jsx-best-practices/SKILL.md +12 -0
  169. package/skills/vue-jsx-best-practices/reference/render-function-jsx-vue-vs-react.md +141 -0
  170. package/skills/vue-options-api-best-practices/SKILL.md +23 -0
  171. package/skills/vue-options-api-best-practices/reference/no-arrow-functions-in-lifecycle-hooks.md +95 -0
  172. package/skills/vue-options-api-best-practices/reference/no-arrow-functions-in-methods.md +68 -0
  173. package/skills/vue-options-api-best-practices/reference/stateful-methods-lifecycle.md +61 -0
  174. package/skills/vue-options-api-best-practices/reference/ts-options-api-arrow-functions-validators.md +141 -0
  175. package/skills/vue-options-api-best-practices/reference/ts-options-api-computed-return-types.md +192 -0
  176. package/skills/vue-options-api-best-practices/reference/ts-options-api-proptype-complex-types.md +212 -0
  177. package/skills/vue-options-api-best-practices/reference/ts-options-api-provide-inject-limitations.md +135 -0
  178. package/skills/vue-options-api-best-practices/reference/ts-options-api-type-event-handlers.md +202 -0
  179. package/skills/vue-options-api-best-practices/reference/ts-options-api-use-definecomponent.md +172 -0
  180. package/skills/vue-options-api-best-practices/reference/ts-strict-mode-options-api.md +197 -0
  181. package/skills/vue-pinia-best-practices/SKILL.md +21 -0
  182. package/skills/vue-pinia-best-practices/reference/pinia-no-active-pinia-error.md +248 -0
  183. package/skills/vue-pinia-best-practices/reference/pinia-setup-store-return-all-state.md +227 -0
  184. package/skills/vue-pinia-best-practices/reference/pinia-store-destructuring-breaks-reactivity.md +193 -0
  185. package/skills/vue-pinia-best-practices/reference/state-url-for-ephemeral-filters.md +238 -0
  186. package/skills/vue-pinia-best-practices/reference/state-use-pinia-for-large-apps.md +262 -0
  187. package/skills/vue-pinia-best-practices/reference/store-method-binding-parentheses.md +191 -0
  188. package/skills/vue-router-best-practices/SKILL.md +23 -0
  189. package/skills/vue-router-best-practices/reference/router-beforeenter-no-param-trigger.md +167 -0
  190. package/skills/vue-router-best-practices/reference/router-beforerouteenter-no-this.md +176 -0
  191. package/skills/vue-router-best-practices/reference/router-guard-async-await-pattern.md +227 -0
  192. package/skills/vue-router-best-practices/reference/router-navigation-guard-infinite-loop.md +187 -0
  193. package/skills/vue-router-best-practices/reference/router-navigation-guard-next-deprecated.md +150 -0
  194. package/skills/vue-router-best-practices/reference/router-param-change-no-lifecycle.md +181 -0
  195. package/skills/vue-router-best-practices/reference/router-simple-routing-cleanup.md +209 -0
  196. package/skills/vue-router-best-practices/reference/router-use-vue-router-for-production.md +183 -0
  197. package/skills/vue-testing-best-practices/SKILL.md +29 -0
  198. package/skills/vue-testing-best-practices/reference/async-component-testing.md +163 -0
  199. package/skills/vue-testing-best-practices/reference/teleport-testing-complexity.md +158 -0
  200. package/skills/vue-testing-best-practices/reference/testing-async-await-flushpromises.md +175 -0
  201. package/skills/vue-testing-best-practices/reference/testing-browser-vs-node-runners.md +208 -0
  202. package/skills/vue-testing-best-practices/reference/testing-component-blackbox-approach.md +144 -0
  203. package/skills/vue-testing-best-practices/reference/testing-composables-helper-wrapper.md +238 -0
  204. package/skills/vue-testing-best-practices/reference/testing-e2e-playwright-recommended.md +242 -0
  205. package/skills/vue-testing-best-practices/reference/testing-no-snapshot-only.md +197 -0
  206. package/skills/vue-testing-best-practices/reference/testing-pinia-store-setup.md +228 -0
  207. package/skills/vue-testing-best-practices/reference/testing-suspense-async-components.md +229 -0
  208. package/skills/vue-testing-best-practices/reference/testing-vitest-recommended-for-vue.md +204 -0
@@ -0,0 +1,176 @@
1
+ ---
2
+ title: Component Refs Require defineExpose with Script Setup
3
+ impact: HIGH
4
+ impactDescription: Parent components cannot access child ref properties unless explicitly exposed
5
+ type: gotcha
6
+ tags: [vue3, template-refs, script-setup, defineExpose, component-communication]
7
+ ---
8
+
9
+ # Component Refs Require defineExpose with Script Setup
10
+
11
+ **Impact: HIGH** - Components using `<script setup>` are private by default. A parent component using a template ref to access a child will get an empty object unless the child explicitly exposes properties using `defineExpose()`. This is a fundamental change from Options API behavior.
12
+
13
+ This catches many developers off-guard when migrating from Options API, where `this.$refs.child` gave full access to the child instance.
14
+
15
+ ## Task Checklist
16
+
17
+ - [ ] Use `defineExpose()` to explicitly expose properties/methods to parent refs
18
+ - [ ] Only expose what's necessary - keep component internals private
19
+ - [ ] Document exposed APIs as they form your component's public interface
20
+ - [ ] Prefer props/emit for parent-child communication; use refs sparingly
21
+ - [ ] Call defineExpose before any await operation (see async caveat)
22
+
23
+ **Incorrect:**
24
+ ```vue
25
+ <!-- ChildComponent.vue -->
26
+ <script setup>
27
+ import { ref } from 'vue'
28
+
29
+ const count = ref(0)
30
+ const internalState = ref('private')
31
+
32
+ function increment() {
33
+ count.value++
34
+ }
35
+
36
+ function reset() {
37
+ count.value = 0
38
+ }
39
+
40
+ // WRONG: Nothing exposed - parent ref sees empty object
41
+ </script>
42
+
43
+ <template>
44
+ <div>{{ count }}</div>
45
+ </template>
46
+ ```
47
+
48
+ ```vue
49
+ <!-- ParentComponent.vue -->
50
+ <script setup>
51
+ import { ref, onMounted } from 'vue'
52
+ import ChildComponent from './ChildComponent.vue'
53
+
54
+ const childRef = ref(null)
55
+
56
+ onMounted(() => {
57
+ // WRONG: childRef.value is {} - empty object!
58
+ console.log(childRef.value.count) // undefined
59
+ childRef.value.increment() // TypeError: not a function
60
+ })
61
+ </script>
62
+
63
+ <template>
64
+ <ChildComponent ref="childRef" />
65
+ </template>
66
+ ```
67
+
68
+ **Correct:**
69
+ ```vue
70
+ <!-- ChildComponent.vue -->
71
+ <script setup>
72
+ import { ref } from 'vue'
73
+
74
+ const count = ref(0)
75
+ const internalState = ref('private') // Keep this private
76
+
77
+ function increment() {
78
+ count.value++
79
+ }
80
+
81
+ function reset() {
82
+ count.value = 0
83
+ }
84
+
85
+ // CORRECT: Explicitly expose public API
86
+ defineExpose({
87
+ count, // Expose the ref
88
+ increment, // Expose methods
89
+ reset
90
+ // internalState NOT exposed - stays private
91
+ })
92
+ </script>
93
+
94
+ <template>
95
+ <div>{{ count }}</div>
96
+ </template>
97
+ ```
98
+
99
+ ```vue
100
+ <!-- ParentComponent.vue -->
101
+ <script setup>
102
+ import { ref, onMounted } from 'vue'
103
+ import ChildComponent from './ChildComponent.vue'
104
+
105
+ const childRef = ref(null)
106
+
107
+ onMounted(() => {
108
+ // CORRECT: Can access exposed properties
109
+ console.log(childRef.value.count) // 0
110
+ childRef.value.increment() // Works!
111
+
112
+ // internalState is not accessible (private)
113
+ console.log(childRef.value.internalState) // undefined
114
+ })
115
+ </script>
116
+
117
+ <template>
118
+ <ChildComponent ref="childRef" />
119
+ </template>
120
+ ```
121
+
122
+ ```vue
123
+ <!-- Input wrapper example - exposing native element -->
124
+ <script setup>
125
+ import { ref } from 'vue'
126
+
127
+ const inputEl = ref(null)
128
+
129
+ // Expose the native input for parent to access (e.g., for focus)
130
+ defineExpose({
131
+ focus: () => inputEl.value?.focus(),
132
+ blur: () => inputEl.value?.blur(),
133
+ // Or expose the element directly
134
+ el: inputEl
135
+ })
136
+ </script>
137
+
138
+ <template>
139
+ <input ref="inputEl" v-bind="$attrs" />
140
+ </template>
141
+ ```
142
+
143
+ ```javascript
144
+ // Options API equivalent using expose option
145
+ export default {
146
+ expose: ['count', 'increment', 'reset'],
147
+ data() {
148
+ return {
149
+ count: 0,
150
+ internalState: 'private'
151
+ }
152
+ },
153
+ methods: {
154
+ increment() { this.count++ },
155
+ reset() { this.count = 0 }
156
+ }
157
+ }
158
+ ```
159
+
160
+ ## Best Practice Reminder
161
+
162
+ Component refs create tight coupling between parent and child. Prefer standard patterns:
163
+
164
+ ```vue
165
+ <!-- PREFERRED: Use props and emit for communication -->
166
+ <script setup>
167
+ const props = defineProps(['modelValue'])
168
+ const emit = defineEmits(['update:modelValue'])
169
+ </script>
170
+
171
+ <!-- Only use refs for imperative actions like focus(), scrollTo(), etc. -->
172
+ ```
173
+
174
+ ## Reference
175
+ - [Vue.js Component Refs](https://vuejs.org/guide/essentials/template-refs.html#ref-on-component)
176
+ - [Script Setup - defineExpose](https://vuejs.org/api/sfc-script-setup.html#defineexpose)
@@ -0,0 +1,208 @@
1
+ ---
2
+ title: Avoid Hidden Side Effects in Composables
3
+ impact: HIGH
4
+ impactDescription: Side effects hidden in composables make debugging difficult and create implicit coupling between components
5
+ type: best-practice
6
+ tags: [vue3, composables, composition-api, side-effects, provide-inject, global-state]
7
+ ---
8
+
9
+ # Avoid Hidden Side Effects in Composables
10
+
11
+ **Impact: HIGH** - Composables should encapsulate stateful logic, not hide side effects that affect things outside their scope. Hidden side effects like modifying global state, using provide/inject internally, or manipulating the DOM directly make composables unpredictable and hard to debug.
12
+
13
+ When a composable has unexpected side effects, consumers can't reason about what calling it will do. This leads to bugs that are difficult to trace and composables that can't be safely reused.
14
+
15
+ ## Task Checklist
16
+
17
+ - [ ] Avoid using provide/inject inside composables (make dependencies explicit)
18
+ - [ ] Don't modify Pinia/Vuex store state internally (accept store as parameter instead)
19
+ - [ ] Don't manipulate DOM directly (use template refs passed as arguments)
20
+ - [ ] Document any unavoidable side effects clearly
21
+ - [ ] Keep composables focused on returning reactive state and methods
22
+
23
+ **Incorrect:**
24
+ ```javascript
25
+ // WRONG: Hidden provide/inject dependency
26
+ export function useTheme() {
27
+ // Consumer has no idea this depends on a provided theme
28
+ const theme = inject('theme') // What if nothing provides this?
29
+
30
+ const isDark = computed(() => theme?.mode === 'dark')
31
+ return { isDark }
32
+ }
33
+
34
+ // WRONG: Modifying global store internally
35
+ import { useUserStore } from '@/stores/user'
36
+
37
+ export function useLogin() {
38
+ const userStore = useUserStore()
39
+
40
+ async function login(credentials) {
41
+ const user = await api.login(credentials)
42
+ // Hidden side effect: modifying global state
43
+ userStore.setUser(user)
44
+ userStore.setToken(user.token)
45
+ // Consumer doesn't know the store was modified!
46
+ }
47
+
48
+ return { login }
49
+ }
50
+
51
+ // WRONG: Hidden DOM manipulation
52
+ export function useFocusTrap() {
53
+ onMounted(() => {
54
+ // Which element? Consumer has no control
55
+ document.querySelector('.modal')?.focus()
56
+ })
57
+ }
58
+
59
+ // WRONG: Hidden provide that affects descendants
60
+ export function useFormContext() {
61
+ const form = reactive({ values: {}, errors: {} })
62
+ // Components calling this have no idea it provides something
63
+ provide('form-context', form)
64
+ return form
65
+ }
66
+ ```
67
+
68
+ **Correct:**
69
+ ```javascript
70
+ // CORRECT: Explicit dependency injection
71
+ export function useTheme(injectedTheme) {
72
+ // If no theme passed, consumer must handle it
73
+ const theme = injectedTheme ?? { mode: 'light' }
74
+
75
+ const isDark = computed(() => theme.mode === 'dark')
76
+ return { isDark }
77
+ }
78
+
79
+ // Usage - dependency is explicit
80
+ const theme = inject('theme', { mode: 'light' })
81
+ const { isDark } = useTheme(theme)
82
+
83
+ // CORRECT: Return actions, let consumer decide when to call them
84
+ export function useLogin() {
85
+ const user = ref(null)
86
+ const token = ref(null)
87
+ const isLoading = ref(false)
88
+ const error = ref(null)
89
+
90
+ async function login(credentials) {
91
+ isLoading.value = true
92
+ error.value = null
93
+ try {
94
+ const response = await api.login(credentials)
95
+ user.value = response.user
96
+ token.value = response.token
97
+ return response
98
+ } catch (e) {
99
+ error.value = e
100
+ throw e
101
+ } finally {
102
+ isLoading.value = false
103
+ }
104
+ }
105
+
106
+ return { user, token, isLoading, error, login }
107
+ }
108
+
109
+ // Consumer decides what to do with the result
110
+ const { user, token, login } = useLogin()
111
+ const userStore = useUserStore()
112
+
113
+ async function handleLogin(credentials) {
114
+ await login(credentials)
115
+ // Consumer explicitly updates the store
116
+ userStore.setUser(user.value)
117
+ userStore.setToken(token.value)
118
+ }
119
+
120
+ // CORRECT: Accept element as parameter
121
+ export function useFocusTrap(targetRef) {
122
+ onMounted(() => {
123
+ targetRef.value?.focus()
124
+ })
125
+
126
+ onUnmounted(() => {
127
+ // Cleanup focus trap
128
+ })
129
+ }
130
+
131
+ // Usage - consumer controls which element
132
+ const modalRef = ref(null)
133
+ useFocusTrap(modalRef)
134
+
135
+ // CORRECT: Separate composable from provider
136
+ export function useFormContext() {
137
+ const form = reactive({ values: {}, errors: {} })
138
+ return form
139
+ }
140
+
141
+ // In parent component - explicit provide
142
+ const form = useFormContext()
143
+ provide('form-context', form)
144
+ ```
145
+
146
+ ## Acceptable Side Effects (With Documentation)
147
+
148
+ Some side effects are acceptable when they're the core purpose of the composable:
149
+
150
+ ```javascript
151
+ /**
152
+ * Tracks mouse position globally.
153
+ *
154
+ * SIDE EFFECTS:
155
+ * - Adds 'mousemove' event listener to window (cleaned up on unmount)
156
+ *
157
+ * @returns {Object} Mouse coordinates { x, y }
158
+ */
159
+ export function useMouse() {
160
+ const x = ref(0)
161
+ const y = ref(0)
162
+
163
+ // This side effect is the whole point of the composable
164
+ // and is properly cleaned up
165
+ onMounted(() => window.addEventListener('mousemove', update))
166
+ onUnmounted(() => window.removeEventListener('mousemove', update))
167
+
168
+ function update(event) {
169
+ x.value = event.pageX
170
+ y.value = event.pageY
171
+ }
172
+
173
+ return { x, y }
174
+ }
175
+ ```
176
+
177
+ ## Pattern: Dependency Injection for Flexibility
178
+
179
+ ```javascript
180
+ // Composable accepts its dependencies
181
+ export function useDataFetcher(apiClient, cache = null) {
182
+ const data = ref(null)
183
+
184
+ async function fetch(url) {
185
+ if (cache) {
186
+ const cached = cache.get(url)
187
+ if (cached) {
188
+ data.value = cached
189
+ return
190
+ }
191
+ }
192
+
193
+ data.value = await apiClient.get(url)
194
+ cache?.set(url, data.value)
195
+ }
196
+
197
+ return { data, fetch }
198
+ }
199
+
200
+ // Usage - dependencies are explicit and testable
201
+ const apiClient = inject('apiClient')
202
+ const cache = inject('cache', null)
203
+ const { data, fetch } = useDataFetcher(apiClient, cache)
204
+ ```
205
+
206
+ ## Reference
207
+ - [Vue.js Composables](https://vuejs.org/guide/reusability/composables.html)
208
+ - [Common Mistakes Creating Composition Functions](https://www.telerik.com/blogs/common-mistakes-creating-composition-functions-vue)
@@ -0,0 +1,141 @@
1
+ ---
2
+ title: Call Composables Only in Setup Context Synchronously
3
+ impact: HIGH
4
+ impactDescription: Composables called outside setup context or asynchronously fail to register lifecycle hooks and may cause memory leaks
5
+ type: gotcha
6
+ tags: [vue3, composables, composition-api, setup, async, lifecycle]
7
+ ---
8
+
9
+ # Call Composables Only in Setup Context Synchronously
10
+
11
+ **Impact: HIGH** - Composables must be called synchronously within `<script setup>`, the `setup()` function, or lifecycle hooks. Calling composables asynchronously (after await), in callbacks, or outside component context prevents Vue from associating lifecycle hooks with the component instance, causing silent failures.
12
+
13
+ This is critical because composables often register `onMounted` and `onUnmounted` hooks internally. If called in the wrong context, these hooks are never registered, leading to uninitialized state or memory leaks.
14
+
15
+ ## Task Checklist
16
+
17
+ - [ ] Call all composables at the top level of `<script setup>` or `setup()`
18
+ - [ ] Never call composables inside async callbacks, setTimeout, or Promise.then
19
+ - [ ] Never call composables conditionally (if/else) - call unconditionally and handle the condition inside
20
+ - [ ] Never call composables inside loops - restructure to call once with array data
21
+ - [ ] Exception: Composables CAN be called in lifecycle hooks like `onMounted`
22
+
23
+ **Incorrect:**
24
+ ```vue
25
+ <script setup>
26
+ import { useFetch } from './composables/useFetch'
27
+ import { useAuth } from './composables/useAuth'
28
+
29
+ // WRONG: Composable called after await
30
+ const config = await loadConfig()
31
+ const { data } = useFetch(config.apiUrl) // Lifecycle hooks won't register!
32
+
33
+ // WRONG: Composable called conditionally
34
+ if (someCondition) {
35
+ const { user } = useAuth() // Inconsistent hook registration!
36
+ }
37
+
38
+ // WRONG: Composable called in callback
39
+ setTimeout(() => {
40
+ const { data } = useFetch('/api/delayed') // No component context!
41
+ }, 1000)
42
+
43
+ // WRONG: Composable called in loop
44
+ for (const url of urls) {
45
+ const { data } = useFetch(url) // Creates multiple instances incorrectly
46
+ }
47
+ </script>
48
+ ```
49
+
50
+ **Correct:**
51
+ ```vue
52
+ <script setup>
53
+ import { ref, onMounted } from 'vue'
54
+ import { useFetch } from './composables/useFetch'
55
+ import { useAuth } from './composables/useAuth'
56
+
57
+ // CORRECT: Call composables synchronously at top level
58
+ const { user, isAuthenticated } = useAuth()
59
+ const apiUrl = ref('/api/default')
60
+ const { data, execute } = useFetch(apiUrl)
61
+
62
+ // Handle async config loading differently
63
+ onMounted(async () => {
64
+ const config = await loadConfig()
65
+ apiUrl.value = config.apiUrl // Update the ref, composable reacts
66
+ })
67
+
68
+ // CORRECT: Handle condition inside, not outside
69
+ const showUserData = computed(() => isAuthenticated.value && someCondition)
70
+
71
+ // CORRECT: For multiple URLs, use a different pattern
72
+ const urls = ref(['/api/a', '/api/b', '/api/c'])
73
+ const results = ref([])
74
+
75
+ // Either fetch in onMounted or use a composable designed for arrays
76
+ onMounted(async () => {
77
+ results.value = await Promise.all(urls.value.map(url => fetch(url)))
78
+ })
79
+ </script>
80
+ ```
81
+
82
+ ## Exception: Calling in Lifecycle Hooks
83
+
84
+ Composables CAN be called inside lifecycle hooks because Vue maintains the component context:
85
+
86
+ ```vue
87
+ <script setup>
88
+ import { onMounted } from 'vue'
89
+ import { useEventListener } from '@vueuse/core'
90
+
91
+ // CORRECT: Called in lifecycle hook - component context is available
92
+ onMounted(() => {
93
+ // This works because we're still in the component's execution context
94
+ useEventListener(document, 'visibilitychange', handleVisibility)
95
+ })
96
+ </script>
97
+ ```
98
+
99
+ ## Special Case: Async Setup in `<script setup>`
100
+
101
+ Top-level await in `<script setup>` is special - Vue's compiler automatically preserves context:
102
+
103
+ ```vue
104
+ <script setup>
105
+ import { useFetch } from './composables/useFetch'
106
+
107
+ // CORRECT: Top-level await in <script setup> preserves context
108
+ // Vue compiler handles this specially
109
+ const config = await loadConfig()
110
+ const { data } = useFetch(config.apiUrl) // This works!
111
+
112
+ // But nested awaits still break context:
113
+ async function initLater() {
114
+ await delay(1000)
115
+ const { data } = useFetch('/api/late') // WRONG: This won't work!
116
+ }
117
+ </script>
118
+ ```
119
+
120
+ ## Why This Matters
121
+
122
+ When you call a composable, Vue needs to know which component instance to associate it with. This association happens through an internal "current instance" that's only set during synchronous setup execution.
123
+
124
+ ```javascript
125
+ // Inside a composable
126
+ export function useFetch(url) {
127
+ const data = ref(null)
128
+
129
+ // These need the current component instance!
130
+ onMounted(() => { /* ... */ })
131
+ onUnmounted(() => { /* cleanup */ })
132
+
133
+ // If called outside setup context, Vue can't find the instance
134
+ // and these hooks are silently ignored
135
+ return { data }
136
+ }
137
+ ```
138
+
139
+ ## Reference
140
+ - [Vue.js Composables - Usage Restrictions](https://vuejs.org/guide/reusability/composables.html#usage-restrictions)
141
+ - [Vue.js Composition API - Setup Context](https://vuejs.org/api/composition-api-setup.html)
@@ -0,0 +1,139 @@
1
+ ---
2
+ title: Follow Composable Naming Convention and Return Pattern
3
+ impact: MEDIUM
4
+ impactDescription: Inconsistent composable patterns lead to confusing APIs and reactivity issues when destructuring
5
+ type: best-practice
6
+ tags: [vue3, composables, composition-api, naming, conventions, refs]
7
+ ---
8
+
9
+ # Follow Composable Naming Convention and Return Pattern
10
+
11
+ **Impact: MEDIUM** - Vue composables should follow established conventions: prefix names with "use" and return plain objects containing refs (not reactive objects). Returning reactive objects causes reactivity loss when destructuring, while inconsistent naming makes code harder to understand.
12
+
13
+ ## Task Checklist
14
+
15
+ - [ ] Name composables with "use" prefix (e.g., `useMouse`, `useFetch`, `useAuth`)
16
+ - [ ] Return a plain object containing refs, not a reactive object
17
+ - [ ] Allow both destructuring and object-style access
18
+ - [ ] Document the returned refs for consumers
19
+
20
+ **Incorrect:**
21
+ ```javascript
22
+ // WRONG: No "use" prefix - unclear it's a composable
23
+ export function mousePosition() {
24
+ const x = ref(0)
25
+ const y = ref(0)
26
+ return { x, y }
27
+ }
28
+
29
+ // WRONG: Returning reactive object - destructuring loses reactivity
30
+ export function useMouse() {
31
+ const state = reactive({
32
+ x: 0,
33
+ y: 0
34
+ })
35
+ // When consumer destructures: const { x, y } = useMouse()
36
+ // x and y become plain values, not reactive!
37
+ return state
38
+ }
39
+
40
+ // WRONG: Returning single ref directly - inconsistent API
41
+ export function useCounter() {
42
+ const count = ref(0)
43
+ return count // Consumer must use .value everywhere
44
+ }
45
+ ```
46
+
47
+ **Correct:**
48
+ ```javascript
49
+ // CORRECT: "use" prefix and returns plain object with refs
50
+ export function useMouse() {
51
+ const x = ref(0)
52
+ const y = ref(0)
53
+
54
+ function update(event) {
55
+ x.value = event.pageX
56
+ y.value = event.pageY
57
+ }
58
+
59
+ onMounted(() => window.addEventListener('mousemove', update))
60
+ onUnmounted(() => window.removeEventListener('mousemove', update))
61
+
62
+ // Return plain object containing refs
63
+ return { x, y }
64
+ }
65
+
66
+ // Consumer can destructure and keep reactivity
67
+ const { x, y } = useMouse()
68
+ watch(x, (newX) => console.log('x changed:', newX)) // Works!
69
+
70
+ // Or use as object if preferred
71
+ const mouse = useMouse()
72
+ console.log(mouse.x.value)
73
+ ```
74
+
75
+ ## Using reactive() Wrapper for Auto-Unwrapping
76
+
77
+ If consumers prefer auto-unwrapping (no `.value`), they can wrap the result:
78
+
79
+ ```javascript
80
+ import { reactive } from 'vue'
81
+ import { useMouse } from './composables/useMouse'
82
+
83
+ // Wrapping in reactive() links the refs
84
+ const mouse = reactive(useMouse())
85
+
86
+ // Now access without .value
87
+ console.log(mouse.x) // Auto-unwrapped, still reactive
88
+
89
+ // But DON'T destructure from this!
90
+ const { x } = reactive(useMouse()) // WRONG: loses reactivity again
91
+ ```
92
+
93
+ ## Pattern: Returning Both State and Actions
94
+
95
+ ```javascript
96
+ // Composable with state AND methods
97
+ export function useCounter(initialValue = 0) {
98
+ const count = ref(initialValue)
99
+ const doubleCount = computed(() => count.value * 2)
100
+
101
+ function increment() {
102
+ count.value++
103
+ }
104
+
105
+ function decrement() {
106
+ count.value--
107
+ }
108
+
109
+ function reset() {
110
+ count.value = initialValue
111
+ }
112
+
113
+ // Return all refs and functions in plain object
114
+ return {
115
+ count,
116
+ doubleCount,
117
+ increment,
118
+ decrement,
119
+ reset
120
+ }
121
+ }
122
+
123
+ // Usage
124
+ const { count, doubleCount, increment, reset } = useCounter(10)
125
+ ```
126
+
127
+ ## Naming Convention Examples
128
+
129
+ | Good Name | Bad Name | Reason |
130
+ |-----------|----------|--------|
131
+ | `useFetch` | `fetch` | Conflicts with native fetch |
132
+ | `useAuth` | `authStore` | "Store" implies Pinia/Vuex |
133
+ | `useLocalStorage` | `localStorage` | Conflicts with native API |
134
+ | `useFormValidation` | `validateForm` | Sounds like a one-shot function |
135
+ | `useWindowSize` | `getWindowSize` | "get" implies synchronous getter |
136
+
137
+ ## Reference
138
+ - [Vue.js Composables - Conventions and Best Practices](https://vuejs.org/guide/reusability/composables.html#conventions-and-best-practices)
139
+ - [Vue.js Composables - Return Values](https://vuejs.org/guide/reusability/composables.html#return-values)