@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.
- package/README.md +63 -0
- package/index.mjs +139 -0
- package/package.json +34 -0
- package/skills/create-adaptable-composable/SKILL.md +76 -0
- package/skills/vue-best-practices/SKILL.md +154 -0
- package/skills/vue-best-practices/references/animation-class-based-technique.md +254 -0
- package/skills/vue-best-practices/references/animation-state-driven-technique.md +291 -0
- package/skills/vue-best-practices/references/component-async.md +97 -0
- package/skills/vue-best-practices/references/component-data-flow.md +350 -0
- package/skills/vue-best-practices/references/component-fallthrough-attrs.md +174 -0
- package/skills/vue-best-practices/references/component-keep-alive.md +137 -0
- package/skills/vue-best-practices/references/component-slots.md +216 -0
- package/skills/vue-best-practices/references/component-suspense.md +228 -0
- package/skills/vue-best-practices/references/component-teleport.md +108 -0
- package/skills/vue-best-practices/references/component-transition-group.md +128 -0
- package/skills/vue-best-practices/references/component-transition.md +125 -0
- package/skills/vue-best-practices/references/composables.md +290 -0
- package/skills/vue-best-practices/references/directives.md +162 -0
- package/skills/vue-best-practices/references/perf-avoid-component-abstraction-in-lists.md +159 -0
- package/skills/vue-best-practices/references/perf-v-once-v-memo-directives.md +182 -0
- package/skills/vue-best-practices/references/perf-virtualize-large-lists.md +187 -0
- package/skills/vue-best-practices/references/plugins.md +166 -0
- package/skills/vue-best-practices/references/reactivity.md +346 -0
- package/skills/vue-best-practices/references/render-functions.md +201 -0
- package/skills/vue-best-practices/references/sfc.md +310 -0
- package/skills/vue-best-practices/references/state-management.md +135 -0
- package/skills/vue-best-practices/references/updated-hook-performance.md +187 -0
- package/skills/vue-debug-guides/SKILL.md +205 -0
- package/skills/vue-debug-guides/reference/animation-key-for-rerender.md +160 -0
- package/skills/vue-debug-guides/reference/animation-transitiongroup-performance.md +241 -0
- package/skills/vue-debug-guides/reference/async-component-error-handling.md +115 -0
- package/skills/vue-debug-guides/reference/async-component-keepalive-ref-issue.md +112 -0
- package/skills/vue-debug-guides/reference/async-component-suspense-control.md +84 -0
- package/skills/vue-debug-guides/reference/async-component-vue-router.md +109 -0
- package/skills/vue-debug-guides/reference/attrs-event-listener-merging.md +205 -0
- package/skills/vue-debug-guides/reference/checkbox-true-false-value-form-submission.md +118 -0
- package/skills/vue-debug-guides/reference/cleanup-side-effects.md +172 -0
- package/skills/vue-debug-guides/reference/click-events-on-components.md +180 -0
- package/skills/vue-debug-guides/reference/component-naming-conflicts.md +159 -0
- package/skills/vue-debug-guides/reference/component-ref-requires-defineexpose.md +176 -0
- package/skills/vue-debug-guides/reference/composable-avoid-hidden-side-effects.md +208 -0
- package/skills/vue-debug-guides/reference/composable-call-location-restrictions.md +141 -0
- package/skills/vue-debug-guides/reference/composable-naming-return-pattern.md +139 -0
- package/skills/vue-debug-guides/reference/composable-tovalue-inside-watcheffect.md +182 -0
- package/skills/vue-debug-guides/reference/composition-api-not-functional-programming.md +120 -0
- package/skills/vue-debug-guides/reference/composition-api-script-setup-async-context.md +203 -0
- package/skills/vue-debug-guides/reference/composition-api-vs-react-hooks-differences.md +156 -0
- package/skills/vue-debug-guides/reference/computed-array-mutation.md +148 -0
- package/skills/vue-debug-guides/reference/computed-conditional-dependencies.md +147 -0
- package/skills/vue-debug-guides/reference/computed-no-parameters.md +159 -0
- package/skills/vue-debug-guides/reference/computed-no-side-effects.md +107 -0
- package/skills/vue-debug-guides/reference/computed-return-value-readonly.md +160 -0
- package/skills/vue-debug-guides/reference/configure-app-before-mount.md +89 -0
- package/skills/vue-debug-guides/reference/declare-emits-for-documentation.md +212 -0
- package/skills/vue-debug-guides/reference/define-expose-before-await.md +192 -0
- package/skills/vue-debug-guides/reference/define-model-default-value-sync.md +139 -0
- package/skills/vue-debug-guides/reference/defineEmits-must-be-top-level.md +164 -0
- package/skills/vue-debug-guides/reference/defineEmits-no-runtime-and-type-mixed.md +170 -0
- package/skills/vue-debug-guides/reference/definemodel-object-mutation-no-emit.md +148 -0
- package/skills/vue-debug-guides/reference/dom-update-timing-nexttick.md +90 -0
- package/skills/vue-debug-guides/reference/dynamic-argument-constraints.md +146 -0
- package/skills/vue-debug-guides/reference/dynamic-component-registration-vite.md +147 -0
- package/skills/vue-debug-guides/reference/event-modifier-order-matters.md +101 -0
- package/skills/vue-debug-guides/reference/exact-modifier-for-precise-shortcuts.md +155 -0
- package/skills/vue-debug-guides/reference/fallthrough-attrs-overwrite-vue3.md +159 -0
- package/skills/vue-debug-guides/reference/in-dom-template-parsing-caveats.md +149 -0
- package/skills/vue-debug-guides/reference/inheritattrs-false-for-wrapper-components.md +230 -0
- package/skills/vue-debug-guides/reference/keepalive-router-nested-double-mount.md +222 -0
- package/skills/vue-debug-guides/reference/keepalive-transition-memory-leak.md +144 -0
- package/skills/vue-debug-guides/reference/keyup-modifier-timing.md +137 -0
- package/skills/vue-debug-guides/reference/lifecycle-dom-access-timing.md +216 -0
- package/skills/vue-debug-guides/reference/lifecycle-hooks-synchronous-registration.md +156 -0
- package/skills/vue-debug-guides/reference/lifecycle-ssr-awareness.md +184 -0
- package/skills/vue-debug-guides/reference/local-components-not-in-descendants.md +151 -0
- package/skills/vue-debug-guides/reference/mount-return-value.md +88 -0
- package/skills/vue-debug-guides/reference/multi-root-component-class-attrs.md +93 -0
- package/skills/vue-debug-guides/reference/native-event-collision-with-emits.md +162 -0
- package/skills/vue-debug-guides/reference/no-passive-with-prevent.md +141 -0
- package/skills/vue-debug-guides/reference/no-v-if-with-v-for.md +136 -0
- package/skills/vue-debug-guides/reference/perf-computed-object-stability.md +157 -0
- package/skills/vue-debug-guides/reference/perf-props-stability-update-optimization.md +140 -0
- package/skills/vue-debug-guides/reference/plugin-global-properties-sparingly.md +109 -0
- package/skills/vue-debug-guides/reference/plugin-install-before-mount.md +124 -0
- package/skills/vue-debug-guides/reference/plugin-prefer-provide-inject-over-global-properties.md +120 -0
- package/skills/vue-debug-guides/reference/plugin-typescript-type-augmentation.md +157 -0
- package/skills/vue-debug-guides/reference/prop-defineprops-scope-limitation.md +161 -0
- package/skills/vue-debug-guides/reference/provide-inject-debugging-challenges.md +203 -0
- package/skills/vue-debug-guides/reference/provide-inject-default-value-factory.md +244 -0
- package/skills/vue-debug-guides/reference/provide-inject-reactivity-not-automatic.md +226 -0
- package/skills/vue-debug-guides/reference/provide-inject-synchronous-setup.md +235 -0
- package/skills/vue-debug-guides/reference/reactive-destructuring.md +89 -0
- package/skills/vue-debug-guides/reference/reactivity-debugging-hooks.md +132 -0
- package/skills/vue-debug-guides/reference/reactivity-markraw-for-non-reactive.md +149 -0
- package/skills/vue-debug-guides/reference/reactivity-proxy-identity-hazard.md +96 -0
- package/skills/vue-debug-guides/reference/reactivity-same-tick-batching.md +166 -0
- package/skills/vue-debug-guides/reference/ref-value-access.md +61 -0
- package/skills/vue-debug-guides/reference/refs-in-collections-need-value.md +81 -0
- package/skills/vue-debug-guides/reference/render-function-avoid-internal-vnode-properties.md +151 -0
- package/skills/vue-debug-guides/reference/render-function-vnodes-must-be-unique.md +133 -0
- package/skills/vue-debug-guides/reference/rendering-render-function-h-import-vue3.md +148 -0
- package/skills/vue-debug-guides/reference/rendering-render-function-return-from-setup.md +148 -0
- package/skills/vue-debug-guides/reference/rendering-render-function-slots-as-functions.md +168 -0
- package/skills/vue-debug-guides/reference/rendering-resolve-component-for-string-names.md +231 -0
- package/skills/vue-debug-guides/reference/select-initial-value-ios-bug.md +91 -0
- package/skills/vue-debug-guides/reference/self-referencing-component-name.md +157 -0
- package/skills/vue-debug-guides/reference/sfc-named-exports-forbidden.md +184 -0
- package/skills/vue-debug-guides/reference/sfc-scoped-css-child-component-styling.md +156 -0
- package/skills/vue-debug-guides/reference/sfc-scoped-css-dynamic-content.md +193 -0
- package/skills/vue-debug-guides/reference/sfc-scoped-css-slot-content.md +242 -0
- package/skills/vue-debug-guides/reference/sfc-script-setup-reactivity.md +195 -0
- package/skills/vue-debug-guides/reference/slot-forwarding-to-child-components.md +143 -0
- package/skills/vue-debug-guides/reference/slot-implicit-default-content.md +155 -0
- package/skills/vue-debug-guides/reference/slot-name-reserved-prop.md +109 -0
- package/skills/vue-debug-guides/reference/slot-named-scoped-explicit-default.md +95 -0
- package/skills/vue-debug-guides/reference/slot-render-scope-parent-only.md +135 -0
- package/skills/vue-debug-guides/reference/slot-v-slot-on-components-or-templates-only.md +122 -0
- package/skills/vue-debug-guides/reference/ssr-hydration-mismatch-causes.md +280 -0
- package/skills/vue-debug-guides/reference/ssr-platform-specific-apis.md +256 -0
- package/skills/vue-debug-guides/reference/state-ssr-cross-request-pollution.md +276 -0
- package/skills/vue-debug-guides/reference/suspense-no-builtin-error-handling.md +127 -0
- package/skills/vue-debug-guides/reference/suspense-ssr-hydration-issues.md +159 -0
- package/skills/vue-debug-guides/reference/tailwind-dynamic-class-generation.md +144 -0
- package/skills/vue-debug-guides/reference/teleport-scoped-styles-limitation.md +191 -0
- package/skills/vue-debug-guides/reference/teleport-ssr-hydration.md +152 -0
- package/skills/vue-debug-guides/reference/teleport-target-must-exist.md +113 -0
- package/skills/vue-debug-guides/reference/template-expressions-restrictions.md +114 -0
- package/skills/vue-debug-guides/reference/template-functions-no-side-effects.md +187 -0
- package/skills/vue-debug-guides/reference/template-ref-null-with-v-if.md +123 -0
- package/skills/vue-debug-guides/reference/template-ref-unwrapping-top-level.md +104 -0
- package/skills/vue-debug-guides/reference/template-ref-v-for-order.md +172 -0
- package/skills/vue-debug-guides/reference/textarea-no-interpolation.md +72 -0
- package/skills/vue-debug-guides/reference/transition-group-flip-inline-elements.md +152 -0
- package/skills/vue-debug-guides/reference/transition-group-move-animation-position-absolute.md +130 -0
- package/skills/vue-debug-guides/reference/transition-group-no-default-wrapper-vue3.md +152 -0
- package/skills/vue-debug-guides/reference/transition-js-hooks-done-callback.md +251 -0
- package/skills/vue-debug-guides/reference/transition-nested-duration.md +182 -0
- package/skills/vue-debug-guides/reference/transition-reusable-scoped-style.md +245 -0
- package/skills/vue-debug-guides/reference/transition-router-view-appear.md +193 -0
- package/skills/vue-debug-guides/reference/transition-type-when-mixed.md +172 -0
- package/skills/vue-debug-guides/reference/transition-unmount-hook-timing.md +149 -0
- package/skills/vue-debug-guides/reference/ts-defineprops-boolean-default-false.md +225 -0
- package/skills/vue-debug-guides/reference/ts-defineprops-imported-types-limitations.md +281 -0
- package/skills/vue-debug-guides/reference/ts-event-handler-explicit-typing.md +213 -0
- package/skills/vue-debug-guides/reference/ts-reactive-no-generic-argument.md +196 -0
- package/skills/vue-debug-guides/reference/ts-shallowref-for-dynamic-components.md +218 -0
- package/skills/vue-debug-guides/reference/ts-template-ref-null-handling.md +249 -0
- package/skills/vue-debug-guides/reference/ts-template-type-casting.md +214 -0
- package/skills/vue-debug-guides/reference/ts-withdefaults-mutable-factory-function.md +171 -0
- package/skills/vue-debug-guides/reference/undeclared-emits-double-firing.md +195 -0
- package/skills/vue-debug-guides/reference/use-template-ref-vue35.md +158 -0
- package/skills/vue-debug-guides/reference/v-else-must-follow-v-if.md +136 -0
- package/skills/vue-debug-guides/reference/v-for-component-props.md +95 -0
- package/skills/vue-debug-guides/reference/v-for-computed-reverse-sort.md +86 -0
- package/skills/vue-debug-guides/reference/v-for-key-attribute.md +90 -0
- package/skills/vue-debug-guides/reference/v-for-range-starts-at-one.md +66 -0
- package/skills/vue-debug-guides/reference/v-if-null-check-order.md +171 -0
- package/skills/vue-debug-guides/reference/v-model-ignores-html-attributes.md +83 -0
- package/skills/vue-debug-guides/reference/v-model-ime-composition.md +83 -0
- package/skills/vue-debug-guides/reference/v-model-number-modifier-behavior.md +124 -0
- package/skills/vue-debug-guides/reference/v-show-template-limitation.md +124 -0
- package/skills/vue-debug-guides/reference/watch-async-cleanup.md +180 -0
- package/skills/vue-debug-guides/reference/watch-async-creation-memory-leak.md +176 -0
- package/skills/vue-debug-guides/reference/watch-deep-same-object-reference.md +165 -0
- package/skills/vue-debug-guides/reference/watch-flush-timing.md +189 -0
- package/skills/vue-debug-guides/reference/watch-reactive-property-getter.md +108 -0
- package/skills/vue-debug-guides/reference/watcheffect-async-dependency-tracking.md +173 -0
- package/skills/vue-debug-guides/reference/watcheffect-flush-post-for-refs.md +176 -0
- package/skills/vue-jsx-best-practices/SKILL.md +12 -0
- package/skills/vue-jsx-best-practices/reference/render-function-jsx-vue-vs-react.md +141 -0
- package/skills/vue-options-api-best-practices/SKILL.md +23 -0
- package/skills/vue-options-api-best-practices/reference/no-arrow-functions-in-lifecycle-hooks.md +95 -0
- package/skills/vue-options-api-best-practices/reference/no-arrow-functions-in-methods.md +68 -0
- package/skills/vue-options-api-best-practices/reference/stateful-methods-lifecycle.md +61 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-arrow-functions-validators.md +141 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-computed-return-types.md +192 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-proptype-complex-types.md +212 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-provide-inject-limitations.md +135 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-type-event-handlers.md +202 -0
- package/skills/vue-options-api-best-practices/reference/ts-options-api-use-definecomponent.md +172 -0
- package/skills/vue-options-api-best-practices/reference/ts-strict-mode-options-api.md +197 -0
- package/skills/vue-pinia-best-practices/SKILL.md +21 -0
- package/skills/vue-pinia-best-practices/reference/pinia-no-active-pinia-error.md +248 -0
- package/skills/vue-pinia-best-practices/reference/pinia-setup-store-return-all-state.md +227 -0
- package/skills/vue-pinia-best-practices/reference/pinia-store-destructuring-breaks-reactivity.md +193 -0
- package/skills/vue-pinia-best-practices/reference/state-url-for-ephemeral-filters.md +238 -0
- package/skills/vue-pinia-best-practices/reference/state-use-pinia-for-large-apps.md +262 -0
- package/skills/vue-pinia-best-practices/reference/store-method-binding-parentheses.md +191 -0
- package/skills/vue-router-best-practices/SKILL.md +23 -0
- package/skills/vue-router-best-practices/reference/router-beforeenter-no-param-trigger.md +167 -0
- package/skills/vue-router-best-practices/reference/router-beforerouteenter-no-this.md +176 -0
- package/skills/vue-router-best-practices/reference/router-guard-async-await-pattern.md +227 -0
- package/skills/vue-router-best-practices/reference/router-navigation-guard-infinite-loop.md +187 -0
- package/skills/vue-router-best-practices/reference/router-navigation-guard-next-deprecated.md +150 -0
- package/skills/vue-router-best-practices/reference/router-param-change-no-lifecycle.md +181 -0
- package/skills/vue-router-best-practices/reference/router-simple-routing-cleanup.md +209 -0
- package/skills/vue-router-best-practices/reference/router-use-vue-router-for-production.md +183 -0
- package/skills/vue-testing-best-practices/SKILL.md +29 -0
- package/skills/vue-testing-best-practices/reference/async-component-testing.md +163 -0
- package/skills/vue-testing-best-practices/reference/teleport-testing-complexity.md +158 -0
- package/skills/vue-testing-best-practices/reference/testing-async-await-flushpromises.md +175 -0
- package/skills/vue-testing-best-practices/reference/testing-browser-vs-node-runners.md +208 -0
- package/skills/vue-testing-best-practices/reference/testing-component-blackbox-approach.md +144 -0
- package/skills/vue-testing-best-practices/reference/testing-composables-helper-wrapper.md +238 -0
- package/skills/vue-testing-best-practices/reference/testing-e2e-playwright-recommended.md +242 -0
- package/skills/vue-testing-best-practices/reference/testing-no-snapshot-only.md +197 -0
- package/skills/vue-testing-best-practices/reference/testing-pinia-store-setup.md +228 -0
- package/skills/vue-testing-best-practices/reference/testing-suspense-async-components.md +229 -0
- package/skills/vue-testing-best-practices/reference/testing-vitest-recommended-for-vue.md +204 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Dynamic Directive Arguments Have Syntax Constraints
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Invalid dynamic arguments cause silent failures or browser compatibility issues
|
|
5
|
+
type: capability
|
|
6
|
+
tags: [vue3, template, directives, v-bind, v-on, dynamic-arguments]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Dynamic Directive Arguments Have Syntax Constraints
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - Dynamic directive arguments (e.g., `:[attributeName]`, `@[eventName]`) have value and syntax constraints that can cause silent failures. In-DOM templates also have case sensitivity issues with browsers lowercasing attribute names.
|
|
12
|
+
|
|
13
|
+
Dynamic arguments allow runtime determination of which attribute or event to bind, but they have restrictions that differ from static arguments.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Ensure dynamic arguments evaluate to strings or `null`
|
|
18
|
+
- [ ] Avoid spaces and quotes inside dynamic argument brackets
|
|
19
|
+
- [ ] Use computed properties for complex dynamic argument expressions
|
|
20
|
+
- [ ] In in-DOM templates, use lowercase attribute names or switch to SFCs
|
|
21
|
+
- [ ] Use `null` to explicitly remove a binding
|
|
22
|
+
|
|
23
|
+
**Incorrect:**
|
|
24
|
+
```vue
|
|
25
|
+
<template>
|
|
26
|
+
<!-- ERROR: Spaces and quotes not allowed in dynamic arguments -->
|
|
27
|
+
<a :[' foo' + bar]="value">Link</a>
|
|
28
|
+
<a :["data-" + name]="value">Link</a>
|
|
29
|
+
|
|
30
|
+
<!-- WARNING: Non-string values (except null) trigger warnings -->
|
|
31
|
+
<a :[123]="value">Link</a>
|
|
32
|
+
<a :[someObject]="value">Link</a>
|
|
33
|
+
|
|
34
|
+
<!-- BUG in in-DOM templates: Browsers lowercase attribute names -->
|
|
35
|
+
<!-- This becomes :[someattr] which won't match someAttr -->
|
|
36
|
+
<a :[someAttr]="url">Link</a>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<script setup>
|
|
40
|
+
// If component expects someAttr but browser lowercases to someattr
|
|
41
|
+
// the binding silently fails
|
|
42
|
+
const someAttr = 'href'
|
|
43
|
+
</script>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Correct:**
|
|
47
|
+
```vue
|
|
48
|
+
<template>
|
|
49
|
+
<!-- OK: Simple variable reference -->
|
|
50
|
+
<a :[attributeName]="url">Link</a>
|
|
51
|
+
|
|
52
|
+
<!-- OK: Use computed property for complex expressions -->
|
|
53
|
+
<a :[dynamicAttr]="value">Link</a>
|
|
54
|
+
|
|
55
|
+
<!-- OK: null removes the binding -->
|
|
56
|
+
<button :[disabledAttr]="isDisabled">Submit</button>
|
|
57
|
+
|
|
58
|
+
<!-- OK: Dynamic event names -->
|
|
59
|
+
<button @[eventName]="handler">Click</button>
|
|
60
|
+
|
|
61
|
+
<!-- OK: In SFCs, case is preserved -->
|
|
62
|
+
<a :[someAttr]="url">Link</a>
|
|
63
|
+
</template>
|
|
64
|
+
|
|
65
|
+
<script setup>
|
|
66
|
+
import { ref, computed } from 'vue'
|
|
67
|
+
|
|
68
|
+
// Simple string variable
|
|
69
|
+
const attributeName = ref('href')
|
|
70
|
+
const url = ref('https://vuejs.org')
|
|
71
|
+
|
|
72
|
+
// Complex expression via computed property
|
|
73
|
+
const prefix = ref('data')
|
|
74
|
+
const name = ref('id')
|
|
75
|
+
const dynamicAttr = computed(() => `${prefix.value}-${name.value}`)
|
|
76
|
+
|
|
77
|
+
// Conditional binding with null
|
|
78
|
+
const isDisabled = ref(false)
|
|
79
|
+
const disabledAttr = computed(() => isDisabled.value ? 'disabled' : null)
|
|
80
|
+
|
|
81
|
+
// Dynamic events
|
|
82
|
+
const useTouch = ref(false)
|
|
83
|
+
const eventName = computed(() => useTouch.value ? 'touchstart' : 'click')
|
|
84
|
+
|
|
85
|
+
function handler() {
|
|
86
|
+
console.log('Event triggered')
|
|
87
|
+
}
|
|
88
|
+
</script>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## In-DOM Template Workaround
|
|
92
|
+
|
|
93
|
+
When writing templates directly in HTML (not SFCs), use lowercase:
|
|
94
|
+
|
|
95
|
+
```html
|
|
96
|
+
<!-- In-DOM template (index.html) -->
|
|
97
|
+
<div id="app">
|
|
98
|
+
<!-- Use lowercase to avoid browser issues -->
|
|
99
|
+
<a :[attrname]="url">Link</a>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<script type="module">
|
|
103
|
+
import { createApp, ref } from 'vue'
|
|
104
|
+
|
|
105
|
+
createApp({
|
|
106
|
+
setup() {
|
|
107
|
+
// Match the lowercase used in template
|
|
108
|
+
const attrname = ref('href')
|
|
109
|
+
const url = ref('https://vuejs.org')
|
|
110
|
+
return { attrname, url }
|
|
111
|
+
}
|
|
112
|
+
}).mount('#app')
|
|
113
|
+
</script>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## SFC vs In-DOM Templates
|
|
117
|
+
|
|
118
|
+
| Feature | SFC (.vue files) | In-DOM (HTML) |
|
|
119
|
+
|---------|------------------|---------------|
|
|
120
|
+
| Case sensitivity | Preserved | Lowercased by browser |
|
|
121
|
+
| Dynamic arguments | Full support | Lowercase only |
|
|
122
|
+
| Recommendation | Preferred | Use for progressive enhancement |
|
|
123
|
+
|
|
124
|
+
## Valid Dynamic Argument Values
|
|
125
|
+
|
|
126
|
+
```vue
|
|
127
|
+
<script setup>
|
|
128
|
+
// String values - OK
|
|
129
|
+
const attr1 = 'href'
|
|
130
|
+
const attr2 = 'data-custom'
|
|
131
|
+
|
|
132
|
+
// null - OK (removes binding)
|
|
133
|
+
const attr3 = null
|
|
134
|
+
|
|
135
|
+
// undefined - OK (removes binding)
|
|
136
|
+
const attr4 = undefined
|
|
137
|
+
|
|
138
|
+
// Numbers, objects, arrays - WARNING
|
|
139
|
+
const attr5 = 123 // Warning: should be string
|
|
140
|
+
const attr6 = { foo: 1 } // Warning: should be string
|
|
141
|
+
</script>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Reference
|
|
145
|
+
- [Vue.js Template Syntax - Dynamic Arguments](https://vuejs.org/guide/essentials/template-syntax.html#dynamic-arguments)
|
|
146
|
+
- [Vue.js Template Syntax - Dynamic Argument Value Constraints](https://vuejs.org/guide/essentials/template-syntax.html#dynamic-argument-value-constraints)
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use import.meta.glob for Dynamic Component Registration in Vite
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: require.context from Webpack doesn't work in Vite projects
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, component-registration, vite, dynamic-import, migration, webpack]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Use import.meta.glob for Dynamic Component Registration in Vite
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - When migrating from Webpack to Vite or starting a new Vite project, the `require.context` pattern for dynamically registering components won't work. Vite uses `import.meta.glob` instead. Using the wrong approach will cause build errors or runtime failures.
|
|
12
|
+
|
|
13
|
+
## Task Checklist
|
|
14
|
+
|
|
15
|
+
- [ ] Replace `require.context` with `import.meta.glob` in Vite projects
|
|
16
|
+
- [ ] Update component registration patterns when migrating from Vue CLI to Vite
|
|
17
|
+
- [ ] Use `{ eager: true }` for synchronous loading when needed
|
|
18
|
+
- [ ] Handle async components appropriately with `defineAsyncComponent`
|
|
19
|
+
|
|
20
|
+
**Incorrect (Webpack pattern - doesn't work in Vite):**
|
|
21
|
+
```javascript
|
|
22
|
+
// main.js - WRONG for Vite
|
|
23
|
+
import { createApp } from 'vue'
|
|
24
|
+
import App from './App.vue'
|
|
25
|
+
|
|
26
|
+
const app = createApp(App)
|
|
27
|
+
|
|
28
|
+
// This Webpack-specific API doesn't exist in Vite
|
|
29
|
+
const requireComponent = require.context(
|
|
30
|
+
'./components/base',
|
|
31
|
+
false,
|
|
32
|
+
/Base[A-Z]\w+\.vue$/
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
requireComponent.keys().forEach(fileName => {
|
|
36
|
+
const componentConfig = requireComponent(fileName)
|
|
37
|
+
const componentName = fileName
|
|
38
|
+
.split('/')
|
|
39
|
+
.pop()
|
|
40
|
+
.replace(/\.\w+$/, '')
|
|
41
|
+
|
|
42
|
+
app.component(componentName, componentConfig.default || componentConfig)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
app.mount('#app')
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Correct (Vite pattern):**
|
|
49
|
+
```javascript
|
|
50
|
+
// main.js - Correct for Vite
|
|
51
|
+
import { createApp } from 'vue'
|
|
52
|
+
import App from './App.vue'
|
|
53
|
+
|
|
54
|
+
const app = createApp(App)
|
|
55
|
+
|
|
56
|
+
// Vite's glob import - eager loading for synchronous registration
|
|
57
|
+
const modules = import.meta.glob('./components/base/Base*.vue', { eager: true })
|
|
58
|
+
|
|
59
|
+
for (const path in modules) {
|
|
60
|
+
// Extract component name from path: './components/base/BaseButton.vue' -> 'BaseButton'
|
|
61
|
+
const componentName = path.split('/').pop().replace('.vue', '')
|
|
62
|
+
app.component(componentName, modules[path].default)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
app.mount('#app')
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Lazy Loading with Async Components
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
// main.js - Lazy loading variant
|
|
72
|
+
import { createApp, defineAsyncComponent } from 'vue'
|
|
73
|
+
import App from './App.vue'
|
|
74
|
+
|
|
75
|
+
const app = createApp(App)
|
|
76
|
+
|
|
77
|
+
// Without { eager: true }, returns functions that return Promises
|
|
78
|
+
const modules = import.meta.glob('./components/base/Base*.vue')
|
|
79
|
+
|
|
80
|
+
for (const path in modules) {
|
|
81
|
+
const componentName = path.split('/').pop().replace('.vue', '')
|
|
82
|
+
// Wrap in defineAsyncComponent for lazy loading
|
|
83
|
+
app.component(componentName, defineAsyncComponent(modules[path]))
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
app.mount('#app')
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Glob Pattern Examples
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
// All .vue files in a directory (not recursive)
|
|
93
|
+
import.meta.glob('./components/*.vue', { eager: true })
|
|
94
|
+
|
|
95
|
+
// All .vue files recursively
|
|
96
|
+
import.meta.glob('./components/**/*.vue', { eager: true })
|
|
97
|
+
|
|
98
|
+
// Specific naming pattern
|
|
99
|
+
import.meta.glob('./components/Base*.vue', { eager: true })
|
|
100
|
+
|
|
101
|
+
// Multiple patterns
|
|
102
|
+
import.meta.glob([
|
|
103
|
+
'./components/Base*.vue',
|
|
104
|
+
'./components/App*.vue'
|
|
105
|
+
], { eager: true })
|
|
106
|
+
|
|
107
|
+
// Exclude patterns
|
|
108
|
+
import.meta.glob('./components/**/*.vue', {
|
|
109
|
+
eager: true,
|
|
110
|
+
ignore: ['**/*.test.vue', '**/*.spec.vue']
|
|
111
|
+
})
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## TypeScript Support
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
// main.ts - with proper typing
|
|
118
|
+
import { createApp, Component } from 'vue'
|
|
119
|
+
import App from './App.vue'
|
|
120
|
+
|
|
121
|
+
const app = createApp(App)
|
|
122
|
+
|
|
123
|
+
const modules = import.meta.glob<{ default: Component }>(
|
|
124
|
+
'./components/base/Base*.vue',
|
|
125
|
+
{ eager: true }
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
for (const path in modules) {
|
|
129
|
+
const componentName = path.split('/').pop()!.replace('.vue', '')
|
|
130
|
+
app.component(componentName, modules[path].default)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
app.mount('#app')
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Migration Checklist (Webpack to Vite)
|
|
137
|
+
|
|
138
|
+
| Webpack | Vite |
|
|
139
|
+
|---------|------|
|
|
140
|
+
| `require.context(dir, recursive, regex)` | `import.meta.glob(pattern, options)` |
|
|
141
|
+
| Synchronous by default | Use `{ eager: true }` for sync |
|
|
142
|
+
| `.keys()` returns array | Returns object with paths as keys |
|
|
143
|
+
| Returns module directly | Access via `.default` for ES modules |
|
|
144
|
+
|
|
145
|
+
## Reference
|
|
146
|
+
- [Vite - Glob Import](https://vitejs.dev/guide/features.html#glob-import)
|
|
147
|
+
- [Vue.js Component Registration](https://vuejs.org/guide/components/registration.html)
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Event Modifier Order Matters
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Modifier order affects event handling behavior - wrong order causes unexpected propagation or prevention
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, events, modifiers, v-on, click, form]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Event Modifier Order Matters
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - When chaining event modifiers, the order determines behavior because Vue generates code in the same sequence. Using `.prevent.self` vs `.self.prevent` produces different results that can cause subtle bugs in event handling.
|
|
12
|
+
|
|
13
|
+
## Task Checklist
|
|
14
|
+
|
|
15
|
+
- [ ] Always consider modifier order when chaining multiple modifiers
|
|
16
|
+
- [ ] Use `.prevent.self` to prevent default on element AND children
|
|
17
|
+
- [ ] Use `.self.prevent` to prevent default ONLY on the element itself
|
|
18
|
+
- [ ] Test event behavior on both the element and its children
|
|
19
|
+
|
|
20
|
+
**Incorrect:**
|
|
21
|
+
```html
|
|
22
|
+
<!-- WRONG: Unintended behavior - prevents clicks on children too -->
|
|
23
|
+
<template>
|
|
24
|
+
<div @click.prevent.self="handleClick">
|
|
25
|
+
<button>Child Button</button> <!-- Default also prevented here! -->
|
|
26
|
+
</div>
|
|
27
|
+
</template>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```html
|
|
31
|
+
<!-- WRONG: Assuming order doesn't matter -->
|
|
32
|
+
<template>
|
|
33
|
+
<!-- Developer expects only self clicks to be handled -->
|
|
34
|
+
<!-- But .prevent runs first, affecting all clicks -->
|
|
35
|
+
<a href="/page" @click.prevent.self="navigate">
|
|
36
|
+
<span>Click me</span>
|
|
37
|
+
</a>
|
|
38
|
+
</template>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Correct:**
|
|
42
|
+
```html
|
|
43
|
+
<!-- CORRECT: Only prevent default on the element itself -->
|
|
44
|
+
<template>
|
|
45
|
+
<div @click.self.prevent="handleClick">
|
|
46
|
+
<button>Child Button</button> <!-- Default NOT prevented here -->
|
|
47
|
+
</div>
|
|
48
|
+
</template>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```html
|
|
52
|
+
<!-- CORRECT: Prevent default on element and children -->
|
|
53
|
+
<template>
|
|
54
|
+
<form @submit.prevent.self="onSubmit">
|
|
55
|
+
<button type="submit">Submit</button>
|
|
56
|
+
</form>
|
|
57
|
+
</template>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```html
|
|
61
|
+
<!-- CORRECT: Explicit intent with separate handlers when needed -->
|
|
62
|
+
<template>
|
|
63
|
+
<div @click.self="handleSelfClick">
|
|
64
|
+
<button @click.prevent="handleChildClick">
|
|
65
|
+
Child with prevented default
|
|
66
|
+
</button>
|
|
67
|
+
</div>
|
|
68
|
+
</template>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## How Modifier Order Works
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
// Vue compiles modifiers in order, so:
|
|
75
|
+
|
|
76
|
+
// @click.prevent.self compiles to:
|
|
77
|
+
// event.preventDefault()
|
|
78
|
+
// if (event.target !== event.currentTarget) return
|
|
79
|
+
// handler()
|
|
80
|
+
|
|
81
|
+
// @click.self.prevent compiles to:
|
|
82
|
+
// if (event.target !== event.currentTarget) return
|
|
83
|
+
// event.preventDefault()
|
|
84
|
+
// handler()
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Common Modifier Combinations
|
|
88
|
+
|
|
89
|
+
```html
|
|
90
|
+
<!-- Stop propagation AND prevent default -->
|
|
91
|
+
<a @click.stop.prevent="handleClick">Link</a>
|
|
92
|
+
|
|
93
|
+
<!-- Capture mode with once -->
|
|
94
|
+
<div @click.capture.once="handleOnce">...</div>
|
|
95
|
+
|
|
96
|
+
<!-- Only exact modifier key combination -->
|
|
97
|
+
<button @click.ctrl.exact="onCtrlClick">Ctrl+Click Only</button>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Reference
|
|
101
|
+
- [Vue.js Event Handling - Event Modifiers](https://vuejs.org/guide/essentials/event-handling.html#event-modifiers)
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use .exact Modifier for Precise Keyboard/Mouse Shortcuts
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Without .exact, shortcuts fire even when additional modifier keys are pressed, causing unintended behavior
|
|
5
|
+
type: best-practice
|
|
6
|
+
tags: [vue3, events, keyboard, modifiers, shortcuts, accessibility]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Use .exact Modifier for Precise Keyboard/Mouse Shortcuts
|
|
10
|
+
|
|
11
|
+
**Impact: MEDIUM** - By default, Vue's modifier key handlers (`.ctrl`, `.alt`, `.shift`, `.meta`) fire even when other modifier keys are also pressed. Use `.exact` to require that ONLY the specified modifiers are pressed, preventing accidental triggering of shortcuts.
|
|
12
|
+
|
|
13
|
+
## Task Checklist
|
|
14
|
+
|
|
15
|
+
- [ ] Use `.exact` when you need precise modifier combinations
|
|
16
|
+
- [ ] Without `.exact`: `@click.ctrl` fires for Ctrl+Click AND Ctrl+Shift+Click
|
|
17
|
+
- [ ] With `.exact`: `@click.ctrl.exact` fires ONLY for Ctrl+Click
|
|
18
|
+
- [ ] Use `@click.exact` for plain clicks with no modifiers
|
|
19
|
+
|
|
20
|
+
**Incorrect:**
|
|
21
|
+
```html
|
|
22
|
+
<!-- WRONG: Fires even with additional modifiers -->
|
|
23
|
+
<template>
|
|
24
|
+
<button @click.ctrl="copyItem">Copy</button>
|
|
25
|
+
<!-- Also fires on Ctrl+Shift+Click, Ctrl+Alt+Click, etc. -->
|
|
26
|
+
|
|
27
|
+
<button @click.ctrl.shift="copyAll">Copy All</button>
|
|
28
|
+
<!-- User expects Ctrl+Shift, but also fires on Ctrl+Shift+Alt -->
|
|
29
|
+
</template>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<!-- WRONG: Conflicting shortcuts without .exact -->
|
|
34
|
+
<template>
|
|
35
|
+
<div>
|
|
36
|
+
<button @click.ctrl="copy">Copy (Ctrl+Click)</button>
|
|
37
|
+
<button @click.ctrl.shift="copyAll">Copy All (Ctrl+Shift+Click)</button>
|
|
38
|
+
<!-- Both fire when user does Ctrl+Shift+Click! -->
|
|
39
|
+
</div>
|
|
40
|
+
</template>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Correct:**
|
|
44
|
+
```html
|
|
45
|
+
<!-- CORRECT: Precise modifier matching with .exact -->
|
|
46
|
+
<template>
|
|
47
|
+
<button @click.ctrl.exact="copyItem">Copy (Ctrl only)</button>
|
|
48
|
+
<!-- Only fires on Ctrl+Click, not Ctrl+Shift+Click -->
|
|
49
|
+
|
|
50
|
+
<button @click.ctrl.shift.exact="copyAll">Copy All (Ctrl+Shift only)</button>
|
|
51
|
+
<!-- Only fires on Ctrl+Shift+Click, not Ctrl+Shift+Alt+Click -->
|
|
52
|
+
</template>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```html
|
|
56
|
+
<!-- CORRECT: Plain click without any modifiers -->
|
|
57
|
+
<template>
|
|
58
|
+
<button @click.exact="selectItem">Select</button>
|
|
59
|
+
<!-- Only fires when NO modifier keys are pressed -->
|
|
60
|
+
<!-- Ctrl+Click, Shift+Click, etc. will NOT trigger this -->
|
|
61
|
+
</template>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
```html
|
|
65
|
+
<!-- CORRECT: Non-conflicting shortcuts -->
|
|
66
|
+
<template>
|
|
67
|
+
<div class="editor">
|
|
68
|
+
<div
|
|
69
|
+
@click.exact="selectItem"
|
|
70
|
+
@click.ctrl.exact="addToSelection"
|
|
71
|
+
@click.shift.exact="extendSelection"
|
|
72
|
+
@click.ctrl.shift.exact="selectRange"
|
|
73
|
+
>
|
|
74
|
+
Click, Ctrl+Click, Shift+Click, or Ctrl+Shift+Click
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</template>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Behavior Comparison
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
// WITHOUT .exact
|
|
84
|
+
@click.ctrl="handler"
|
|
85
|
+
// Fires when: Ctrl+Click, Ctrl+Shift+Click, Ctrl+Alt+Click, Ctrl+Shift+Alt+Click
|
|
86
|
+
// Does NOT fire: Click (without Ctrl)
|
|
87
|
+
|
|
88
|
+
// WITH .exact
|
|
89
|
+
@click.ctrl.exact="handler"
|
|
90
|
+
// Fires when: ONLY Ctrl+Click
|
|
91
|
+
// Does NOT fire: Ctrl+Shift+Click, Ctrl+Alt+Click, Click
|
|
92
|
+
|
|
93
|
+
// ONLY .exact (no other modifiers)
|
|
94
|
+
@click.exact="handler"
|
|
95
|
+
// Fires when: Plain click with NO modifiers
|
|
96
|
+
// Does NOT fire: Ctrl+Click, Shift+Click, Alt+Click
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Practical Example: File Browser Selection
|
|
100
|
+
|
|
101
|
+
```vue
|
|
102
|
+
<template>
|
|
103
|
+
<ul class="file-list">
|
|
104
|
+
<li
|
|
105
|
+
v-for="file in files"
|
|
106
|
+
:key="file.id"
|
|
107
|
+
@click.exact="selectSingle(file)"
|
|
108
|
+
@click.ctrl.exact="toggleSelection(file)"
|
|
109
|
+
@click.shift.exact="selectRange(file)"
|
|
110
|
+
@click.ctrl.shift.exact="addRangeToSelection(file)"
|
|
111
|
+
:class="{ selected: isSelected(file) }"
|
|
112
|
+
>
|
|
113
|
+
{{ file.name }}
|
|
114
|
+
</li>
|
|
115
|
+
</ul>
|
|
116
|
+
</template>
|
|
117
|
+
|
|
118
|
+
<script setup>
|
|
119
|
+
// Each click type has distinct, non-overlapping behavior
|
|
120
|
+
function selectSingle(file) {
|
|
121
|
+
// Clear selection and select only this file
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function toggleSelection(file) {
|
|
125
|
+
// Add or remove this file from current selection
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function selectRange(file) {
|
|
129
|
+
// Select all files from last selected to this one
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function addRangeToSelection(file) {
|
|
133
|
+
// Add range to existing selection
|
|
134
|
+
}
|
|
135
|
+
</script>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Keyboard Shortcuts with .exact
|
|
139
|
+
|
|
140
|
+
```html
|
|
141
|
+
<template>
|
|
142
|
+
<div
|
|
143
|
+
tabindex="0"
|
|
144
|
+
@keydown.ctrl.s.exact.prevent="save"
|
|
145
|
+
@keydown.ctrl.shift.s.exact.prevent="saveAs"
|
|
146
|
+
@keydown.ctrl.z.exact.prevent="undo"
|
|
147
|
+
@keydown.ctrl.shift.z.exact.prevent="redo"
|
|
148
|
+
>
|
|
149
|
+
<!-- Each shortcut is precisely defined -->
|
|
150
|
+
</div>
|
|
151
|
+
</template>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Reference
|
|
155
|
+
- [Vue.js Event Handling - .exact Modifier](https://vuejs.org/guide/essentials/event-handling.html#exact-modifier)
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Fallthrough Attributes Overwrite Explicit Attributes in Vue 3
|
|
2
|
+
|
|
3
|
+
## Rule
|
|
4
|
+
|
|
5
|
+
In Vue 3, fallthrough attributes overwrite explicitly set attributes on the root element (except `class` and `style` which are merged). This is a breaking change from Vue 2. To preserve explicit attribute values, use `inheritAttrs: false` and manually bind `$attrs` before the explicit attribute.
|
|
6
|
+
|
|
7
|
+
## Why This Matters
|
|
8
|
+
|
|
9
|
+
- Silent behavior change from Vue 2 to Vue 3
|
|
10
|
+
- Can cause unexpected attribute values in migrated codebases
|
|
11
|
+
- Only `class` and `style` merge intelligently; other attributes are overwritten
|
|
12
|
+
- Affects component composition patterns and wrapper components
|
|
13
|
+
|
|
14
|
+
## Bad Code
|
|
15
|
+
|
|
16
|
+
```vue
|
|
17
|
+
<!-- Parent.vue -->
|
|
18
|
+
<template>
|
|
19
|
+
<Child msg="Passed from Parent" />
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<!-- Child.vue - UNEXPECTED BEHAVIOR -->
|
|
23
|
+
<template>
|
|
24
|
+
<GrandChild msg="Set in Child" />
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<!--
|
|
28
|
+
Vue 3 Result: GrandChild receives msg="Passed from Parent"
|
|
29
|
+
The fallthrough attribute OVERWRITES the explicit one!
|
|
30
|
+
|
|
31
|
+
Vue 2 Result: GrandChild receives msg="Set in Child"
|
|
32
|
+
The explicit attribute took precedence
|
|
33
|
+
-->
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Another common case with data attributes
|
|
37
|
+
|
|
38
|
+
```vue
|
|
39
|
+
<!-- Parent.vue -->
|
|
40
|
+
<template>
|
|
41
|
+
<Button data-testid="parent-button" />
|
|
42
|
+
</template>
|
|
43
|
+
|
|
44
|
+
<!-- Button.vue - WRONG: explicit data-testid is overwritten -->
|
|
45
|
+
<template>
|
|
46
|
+
<button data-testid="submit-btn">Submit</button>
|
|
47
|
+
</template>
|
|
48
|
+
|
|
49
|
+
<!-- Result: <button data-testid="parent-button">Submit</button> -->
|
|
50
|
+
<!-- The component's intended test ID is lost! -->
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Good Code
|
|
54
|
+
|
|
55
|
+
### Option 1: Control attribute order with inheritAttrs: false
|
|
56
|
+
|
|
57
|
+
```vue
|
|
58
|
+
<!-- Child.vue - CORRECT: Control attribute precedence -->
|
|
59
|
+
<script setup>
|
|
60
|
+
defineOptions({
|
|
61
|
+
inheritAttrs: false
|
|
62
|
+
})
|
|
63
|
+
</script>
|
|
64
|
+
|
|
65
|
+
<template>
|
|
66
|
+
<!-- v-bind="$attrs" FIRST, then explicit attribute -->
|
|
67
|
+
<GrandChild v-bind="$attrs" msg="Set in Child" />
|
|
68
|
+
</template>
|
|
69
|
+
|
|
70
|
+
<!--
|
|
71
|
+
Result: GrandChild receives msg="Set in Child"
|
|
72
|
+
Explicit attribute overrides fallthrough because it comes last
|
|
73
|
+
-->
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Option 2: Exclude specific attrs from fallthrough
|
|
77
|
+
|
|
78
|
+
```vue
|
|
79
|
+
<script setup>
|
|
80
|
+
import { computed, useAttrs } from 'vue'
|
|
81
|
+
|
|
82
|
+
defineOptions({
|
|
83
|
+
inheritAttrs: false
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
const attrs = useAttrs()
|
|
87
|
+
|
|
88
|
+
// Filter out attributes you want to control explicitly
|
|
89
|
+
const filteredAttrs = computed(() => {
|
|
90
|
+
const { msg, ...rest } = attrs
|
|
91
|
+
return rest
|
|
92
|
+
})
|
|
93
|
+
</script>
|
|
94
|
+
|
|
95
|
+
<template>
|
|
96
|
+
<GrandChild v-bind="filteredAttrs" msg="Set in Child" />
|
|
97
|
+
</template>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Option 3: For wrapper components, declare as prop
|
|
101
|
+
|
|
102
|
+
```vue
|
|
103
|
+
<!-- Button.vue - BEST: Declare attributes you need to control -->
|
|
104
|
+
<script setup>
|
|
105
|
+
const props = defineProps({
|
|
106
|
+
dataTestid: {
|
|
107
|
+
type: String,
|
|
108
|
+
default: 'submit-btn'
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
defineOptions({
|
|
113
|
+
inheritAttrs: false
|
|
114
|
+
})
|
|
115
|
+
</script>
|
|
116
|
+
|
|
117
|
+
<template>
|
|
118
|
+
<button :data-testid="dataTestid" v-bind="$attrs">
|
|
119
|
+
<slot />
|
|
120
|
+
</button>
|
|
121
|
+
</template>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Class and Style Are Special
|
|
125
|
+
|
|
126
|
+
Unlike other attributes, `class` and `style` merge rather than overwrite:
|
|
127
|
+
|
|
128
|
+
```vue
|
|
129
|
+
<!-- Parent.vue -->
|
|
130
|
+
<template>
|
|
131
|
+
<Button class="large" style="color: red" />
|
|
132
|
+
</template>
|
|
133
|
+
|
|
134
|
+
<!-- Button.vue -->
|
|
135
|
+
<template>
|
|
136
|
+
<button class="btn" style="padding: 10px">Submit</button>
|
|
137
|
+
</template>
|
|
138
|
+
|
|
139
|
+
<!--
|
|
140
|
+
Result: <button class="btn large" style="padding: 10px; color: red">
|
|
141
|
+
Both classes and styles are MERGED, not overwritten
|
|
142
|
+
-->
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Vue 2 to Vue 3 Migration Checklist
|
|
146
|
+
|
|
147
|
+
When migrating components that rely on attribute precedence:
|
|
148
|
+
|
|
149
|
+
1. Identify components that set explicit attributes on root elements
|
|
150
|
+
2. Check if parent components pass the same attributes
|
|
151
|
+
3. If explicit values should take precedence:
|
|
152
|
+
- Add `inheritAttrs: false`
|
|
153
|
+
- Use `v-bind="$attrs"` before explicit attributes
|
|
154
|
+
|
|
155
|
+
## References
|
|
156
|
+
|
|
157
|
+
- [Fallthrough Attributes](https://vuejs.org/guide/components/attrs.html)
|
|
158
|
+
- [Vue 3 Migration Guide - Attribute Coercion Behavior](https://v3-migration.vuejs.org/breaking-changes/)
|
|
159
|
+
- [Vue Fallthrough Attributes behaviour changes from Vue 2 to Vue 3](https://lukes.tips/posts/vue-3-fallthough-attributes-changes/)
|