@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,149 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: In-DOM Template Parsing Caveats
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Browser HTML parsing before Vue compilation causes case sensitivity, self-closing tag, and element nesting issues
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, templates, in-dom, html-parsing, kebab-case, self-closing-tags]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# In-DOM Template Parsing Caveats
|
|
10
|
+
|
|
11
|
+
**Impact: HIGH** - When writing Vue templates directly in the DOM (not in `.vue` files), the browser's native HTML parser processes the template BEFORE Vue sees it. This causes three critical issues: case sensitivity problems, self-closing tag failures, and element placement restrictions.
|
|
12
|
+
|
|
13
|
+
These issues do NOT apply to Single-File Components (SFCs) or string templates where Vue's compiler handles parsing directly.
|
|
14
|
+
|
|
15
|
+
## Task Checklist
|
|
16
|
+
|
|
17
|
+
- [ ] Use kebab-case for component names in in-DOM templates
|
|
18
|
+
- [ ] Use kebab-case for prop names in in-DOM templates
|
|
19
|
+
- [ ] Use explicit closing tags (not self-closing) in in-DOM templates
|
|
20
|
+
- [ ] Use `is="vue:component-name"` for components inside restricted elements
|
|
21
|
+
- [ ] Prefer SFCs to avoid all in-DOM parsing issues
|
|
22
|
+
|
|
23
|
+
## Issue 1: Case Insensitivity
|
|
24
|
+
|
|
25
|
+
HTML is case-insensitive. The browser lowercases everything before Vue sees it.
|
|
26
|
+
|
|
27
|
+
**Incorrect (in-DOM template):**
|
|
28
|
+
```html
|
|
29
|
+
<!-- Browser converts to: <blogpost posttitle="hello"> -->
|
|
30
|
+
<BlogPost postTitle="hello" @updatePost="onUpdate"></BlogPost>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Correct (in-DOM template):**
|
|
34
|
+
```html
|
|
35
|
+
<!-- Use kebab-case for everything -->
|
|
36
|
+
<blog-post post-title="hello" @update-post="onUpdate"></blog-post>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**In SFCs, PascalCase works fine:**
|
|
40
|
+
```vue
|
|
41
|
+
<!-- BlogPost.vue - PascalCase recommended -->
|
|
42
|
+
<template>
|
|
43
|
+
<BlogPost postTitle="hello" @updatePost="onUpdate" />
|
|
44
|
+
</template>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Issue 2: Self-Closing Tags Fail
|
|
48
|
+
|
|
49
|
+
HTML only allows self-closing syntax for void elements (`<input>`, `<img>`, etc.). For all others, the browser expects closing tags.
|
|
50
|
+
|
|
51
|
+
**Incorrect (in-DOM template):**
|
|
52
|
+
```html
|
|
53
|
+
<!-- Browser thinks the tag never closed, breaks nesting -->
|
|
54
|
+
<my-component />
|
|
55
|
+
<another-component />
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Correct (in-DOM template):**
|
|
59
|
+
```html
|
|
60
|
+
<!-- Explicit closing tags required -->
|
|
61
|
+
<my-component></my-component>
|
|
62
|
+
<another-component></another-component>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**In SFCs, self-closing works fine:**
|
|
66
|
+
```vue
|
|
67
|
+
<template>
|
|
68
|
+
<MyComponent />
|
|
69
|
+
<AnotherComponent />
|
|
70
|
+
</template>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Issue 3: Element Placement Restrictions
|
|
74
|
+
|
|
75
|
+
Some HTML elements have strict rules about valid children. Invalid elements are hoisted out by the browser before Vue sees the template.
|
|
76
|
+
|
|
77
|
+
**Restricted parent elements:**
|
|
78
|
+
- `<ul>`, `<ol>` - only allow `<li>`
|
|
79
|
+
- `<table>` - only allows `<thead>`, `<tbody>`, `<tfoot>`, `<tr>`, `<caption>`, `<colgroup>`
|
|
80
|
+
- `<tr>` - only allows `<td>`, `<th>`
|
|
81
|
+
- `<select>` - only allows `<option>`, `<optgroup>`
|
|
82
|
+
|
|
83
|
+
**Incorrect (in-DOM template):**
|
|
84
|
+
```html
|
|
85
|
+
<!-- Browser hoists blog-post-row outside the table -->
|
|
86
|
+
<table>
|
|
87
|
+
<blog-post-row v-for="post in posts" :post="post"></blog-post-row>
|
|
88
|
+
</table>
|
|
89
|
+
|
|
90
|
+
<!-- Renders as: -->
|
|
91
|
+
<blog-post-row></blog-post-row>
|
|
92
|
+
<blog-post-row></blog-post-row>
|
|
93
|
+
<table></table>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Correct (in-DOM template):**
|
|
97
|
+
```html
|
|
98
|
+
<!-- Use is="vue:component-name" on a valid native element -->
|
|
99
|
+
<table>
|
|
100
|
+
<tr is="vue:blog-post-row" v-for="post in posts" :key="post.id" :post="post"></tr>
|
|
101
|
+
</table>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
```html
|
|
105
|
+
<ul>
|
|
106
|
+
<li is="vue:todo-item" v-for="todo in todos" :key="todo.id" :todo="todo"></li>
|
|
107
|
+
</ul>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Important:** The `vue:` prefix is required! Without it, `is` is treated as a native customized built-in element attribute.
|
|
111
|
+
|
|
112
|
+
```html
|
|
113
|
+
<!-- WRONG: Missing vue: prefix -->
|
|
114
|
+
<tr is="blog-post-row"></tr>
|
|
115
|
+
|
|
116
|
+
<!-- CORRECT: With vue: prefix -->
|
|
117
|
+
<tr is="vue:blog-post-row"></tr>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## When Do These Apply?
|
|
121
|
+
|
|
122
|
+
| Template Type | Affected? | Example |
|
|
123
|
+
|---------------|-----------|---------|
|
|
124
|
+
| Single-File Component (`.vue`) | No | `<template>` section |
|
|
125
|
+
| String template | No | `template: '<div>...</div>'` |
|
|
126
|
+
| In-DOM template | **Yes** | `<div id="app">...</div>` |
|
|
127
|
+
| `<script type="text/x-template">` | **Yes** | Browser parses the script content |
|
|
128
|
+
|
|
129
|
+
## Best Practice: Use SFCs
|
|
130
|
+
|
|
131
|
+
The simplest solution is to use Single-File Components (`.vue` files) which completely avoid in-DOM parsing issues:
|
|
132
|
+
|
|
133
|
+
```vue
|
|
134
|
+
<!-- MyComponent.vue - All issues avoided -->
|
|
135
|
+
<script setup>
|
|
136
|
+
import BlogPost from './BlogPost.vue'
|
|
137
|
+
</script>
|
|
138
|
+
|
|
139
|
+
<template>
|
|
140
|
+
<BlogPost postTitle="hello" @updatePost="onUpdate" />
|
|
141
|
+
|
|
142
|
+
<table>
|
|
143
|
+
<BlogPostRow v-for="post in posts" :key="post.id" :post="post" />
|
|
144
|
+
</table>
|
|
145
|
+
</template>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Reference
|
|
149
|
+
- [Vue.js - In-DOM Template Parsing Caveats](https://vuejs.org/guide/essentials/component-basics.html#in-dom-template-parsing-caveats)
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# Use inheritAttrs: false for Wrapper Components
|
|
2
|
+
|
|
3
|
+
## Rule
|
|
4
|
+
|
|
5
|
+
When building wrapper components where attributes should be applied to an inner element instead of the root element, always set `inheritAttrs: false` and explicitly bind `$attrs` to the target element.
|
|
6
|
+
|
|
7
|
+
## Why This Matters
|
|
8
|
+
|
|
9
|
+
- By default, Vue applies all non-prop attributes to the root element
|
|
10
|
+
- Wrapper components often have a non-semantic root (div wrapper, label wrapper)
|
|
11
|
+
- Attributes like `id`, `aria-*`, `data-*`, and event listeners should target the functional element
|
|
12
|
+
- Without `inheritAttrs: false`, accessibility and functionality can break
|
|
13
|
+
|
|
14
|
+
## Bad Code
|
|
15
|
+
|
|
16
|
+
```vue
|
|
17
|
+
<!-- BaseInput.vue - WRONG: attrs go to wrapper div, not input -->
|
|
18
|
+
<template>
|
|
19
|
+
<div class="input-wrapper">
|
|
20
|
+
<label>{{ label }}</label>
|
|
21
|
+
<input type="text" />
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<script setup>
|
|
26
|
+
defineProps(['label'])
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<!-- Parent usage -->
|
|
30
|
+
<BaseInput
|
|
31
|
+
id="email"
|
|
32
|
+
placeholder="Enter email"
|
|
33
|
+
aria-describedby="email-help"
|
|
34
|
+
@focus="handleFocus"
|
|
35
|
+
/>
|
|
36
|
+
|
|
37
|
+
<!--
|
|
38
|
+
RESULT: All attrs go to the wrapper div!
|
|
39
|
+
<div class="input-wrapper" id="email" placeholder="Enter email" ...>
|
|
40
|
+
<label>...</label>
|
|
41
|
+
<input type="text" /> <!-- No id, placeholder, or aria! -->
|
|
42
|
+
</div>
|
|
43
|
+
-->
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Good Code
|
|
47
|
+
|
|
48
|
+
```vue
|
|
49
|
+
<!-- BaseInput.vue - CORRECT: attrs bound to input element -->
|
|
50
|
+
<script setup>
|
|
51
|
+
defineProps(['label'])
|
|
52
|
+
|
|
53
|
+
defineOptions({
|
|
54
|
+
inheritAttrs: false
|
|
55
|
+
})
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<template>
|
|
59
|
+
<div class="input-wrapper">
|
|
60
|
+
<label>{{ label }}</label>
|
|
61
|
+
<input type="text" v-bind="$attrs" />
|
|
62
|
+
</div>
|
|
63
|
+
</template>
|
|
64
|
+
|
|
65
|
+
<!-- Parent usage -->
|
|
66
|
+
<BaseInput
|
|
67
|
+
id="email"
|
|
68
|
+
placeholder="Enter email"
|
|
69
|
+
aria-describedby="email-help"
|
|
70
|
+
@focus="handleFocus"
|
|
71
|
+
/>
|
|
72
|
+
|
|
73
|
+
<!--
|
|
74
|
+
RESULT: Attrs correctly applied to input
|
|
75
|
+
<div class="input-wrapper">
|
|
76
|
+
<label>...</label>
|
|
77
|
+
<input type="text" id="email" placeholder="Enter email"
|
|
78
|
+
aria-describedby="email-help" />
|
|
79
|
+
</div>
|
|
80
|
+
-->
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Setting inheritAttrs in Different Syntaxes
|
|
84
|
+
|
|
85
|
+
### Script Setup (Vue 3.3+)
|
|
86
|
+
|
|
87
|
+
```vue
|
|
88
|
+
<script setup>
|
|
89
|
+
defineOptions({
|
|
90
|
+
inheritAttrs: false
|
|
91
|
+
})
|
|
92
|
+
</script>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Script Setup (Before Vue 3.3)
|
|
96
|
+
|
|
97
|
+
```vue
|
|
98
|
+
<script>
|
|
99
|
+
export default {
|
|
100
|
+
inheritAttrs: false
|
|
101
|
+
}
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
<script setup>
|
|
105
|
+
// Your setup code here
|
|
106
|
+
</script>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Options API
|
|
110
|
+
|
|
111
|
+
```vue
|
|
112
|
+
<script>
|
|
113
|
+
export default {
|
|
114
|
+
inheritAttrs: false,
|
|
115
|
+
// other options...
|
|
116
|
+
}
|
|
117
|
+
</script>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Common Wrapper Component Patterns
|
|
121
|
+
|
|
122
|
+
### Form Input Wrapper
|
|
123
|
+
|
|
124
|
+
```vue
|
|
125
|
+
<script setup>
|
|
126
|
+
import { useAttrs, computed } from 'vue'
|
|
127
|
+
|
|
128
|
+
defineProps({
|
|
129
|
+
label: String,
|
|
130
|
+
error: String
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
defineOptions({
|
|
134
|
+
inheritAttrs: false
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
const attrs = useAttrs()
|
|
138
|
+
|
|
139
|
+
// Separate class/style for wrapper vs input
|
|
140
|
+
const inputAttrs = computed(() => {
|
|
141
|
+
const { class: _, style: __, ...rest } = attrs
|
|
142
|
+
return rest
|
|
143
|
+
})
|
|
144
|
+
</script>
|
|
145
|
+
|
|
146
|
+
<template>
|
|
147
|
+
<div class="form-field" :class="{ 'has-error': error }">
|
|
148
|
+
<label v-if="label">{{ label }}</label>
|
|
149
|
+
<input v-bind="inputAttrs" />
|
|
150
|
+
<span v-if="error" class="error">{{ error }}</span>
|
|
151
|
+
</div>
|
|
152
|
+
</template>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Button with Icon Wrapper
|
|
156
|
+
|
|
157
|
+
```vue
|
|
158
|
+
<script setup>
|
|
159
|
+
defineProps({
|
|
160
|
+
icon: String,
|
|
161
|
+
iconPosition: {
|
|
162
|
+
type: String,
|
|
163
|
+
default: 'left'
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
defineOptions({
|
|
168
|
+
inheritAttrs: false
|
|
169
|
+
})
|
|
170
|
+
</script>
|
|
171
|
+
|
|
172
|
+
<template>
|
|
173
|
+
<button class="icon-button" v-bind="$attrs">
|
|
174
|
+
<span v-if="icon && iconPosition === 'left'" class="icon">{{ icon }}</span>
|
|
175
|
+
<slot />
|
|
176
|
+
<span v-if="icon && iconPosition === 'right'" class="icon">{{ icon }}</span>
|
|
177
|
+
</button>
|
|
178
|
+
</template>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Link Wrapper Component
|
|
182
|
+
|
|
183
|
+
```vue
|
|
184
|
+
<script setup>
|
|
185
|
+
defineProps({
|
|
186
|
+
to: String,
|
|
187
|
+
external: Boolean
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
defineOptions({
|
|
191
|
+
inheritAttrs: false
|
|
192
|
+
})
|
|
193
|
+
</script>
|
|
194
|
+
|
|
195
|
+
<template>
|
|
196
|
+
<a
|
|
197
|
+
v-if="external"
|
|
198
|
+
:href="to"
|
|
199
|
+
target="_blank"
|
|
200
|
+
rel="noopener noreferrer"
|
|
201
|
+
v-bind="$attrs"
|
|
202
|
+
>
|
|
203
|
+
<slot />
|
|
204
|
+
</a>
|
|
205
|
+
<router-link v-else :to="to" v-bind="$attrs">
|
|
206
|
+
<slot />
|
|
207
|
+
</router-link>
|
|
208
|
+
</template>
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## When NOT to Use inheritAttrs: false
|
|
212
|
+
|
|
213
|
+
- Simple components with a single semantic root element
|
|
214
|
+
- Components where the root element should receive all attributes
|
|
215
|
+
- Components that don't wrap other functional elements
|
|
216
|
+
|
|
217
|
+
```vue
|
|
218
|
+
<!-- SimpleCard.vue - No need for inheritAttrs: false -->
|
|
219
|
+
<template>
|
|
220
|
+
<article class="card">
|
|
221
|
+
<slot />
|
|
222
|
+
</article>
|
|
223
|
+
</template>
|
|
224
|
+
<!-- Passing class, id, or data-* to the root article is fine -->
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## References
|
|
228
|
+
|
|
229
|
+
- [Fallthrough Attributes - Disabling Attribute Inheritance](https://vuejs.org/guide/components/attrs.html#disabling-attribute-inheritance)
|
|
230
|
+
- [Build Advanced Components in Vue 3 using $attrs](https://www.thisdot.co/blog/build-advanced-components-in-vue-3-using-usdattrs)
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: KeepAlive with Nested Routes Double Mount Issue
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Using KeepAlive with nested Vue Router routes can cause child components to mount twice
|
|
5
|
+
type: gotcha
|
|
6
|
+
tags: [vue3, keepalive, vue-router, nested-routes, double-mount, bug]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# KeepAlive with Nested Routes Double Mount Issue
|
|
10
|
+
|
|
11
|
+
**Impact: HIGH** - When using `<KeepAlive>` with nested Vue Router routes, child route components may mount twice. This is a known issue that can cause duplicate API calls, broken state, and confusing behavior.
|
|
12
|
+
|
|
13
|
+
## Task Checklist
|
|
14
|
+
|
|
15
|
+
- [ ] Test nested routes thoroughly when using KeepAlive
|
|
16
|
+
- [ ] Avoid mixing KeepAlive with deeply nested route structures
|
|
17
|
+
- [ ] Use workarounds if double mount is observed
|
|
18
|
+
- [ ] Consider alternative caching strategies for nested routes
|
|
19
|
+
|
|
20
|
+
## The Problem
|
|
21
|
+
|
|
22
|
+
```vue
|
|
23
|
+
<!-- App.vue -->
|
|
24
|
+
<template>
|
|
25
|
+
<router-view v-slot="{ Component }">
|
|
26
|
+
<KeepAlive>
|
|
27
|
+
<component :is="Component" />
|
|
28
|
+
</KeepAlive>
|
|
29
|
+
</router-view>
|
|
30
|
+
</template>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
// router.js
|
|
35
|
+
const routes = [
|
|
36
|
+
{
|
|
37
|
+
path: '/parent',
|
|
38
|
+
component: Parent,
|
|
39
|
+
children: [
|
|
40
|
+
{
|
|
41
|
+
path: 'child',
|
|
42
|
+
component: Child // This may mount TWICE!
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
]
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Symptoms:**
|
|
50
|
+
- `onMounted` called twice in child component
|
|
51
|
+
- Duplicate API requests
|
|
52
|
+
- State initialization runs twice
|
|
53
|
+
- Console logs appear doubled
|
|
54
|
+
|
|
55
|
+
## Diagnosis
|
|
56
|
+
|
|
57
|
+
Add logging to confirm the issue:
|
|
58
|
+
|
|
59
|
+
```vue
|
|
60
|
+
<!-- Child.vue -->
|
|
61
|
+
<script setup>
|
|
62
|
+
import { onMounted, onActivated } from 'vue'
|
|
63
|
+
|
|
64
|
+
let mountCount = 0
|
|
65
|
+
|
|
66
|
+
onMounted(() => {
|
|
67
|
+
mountCount++
|
|
68
|
+
console.log('Child mounted - count:', mountCount)
|
|
69
|
+
// If you see "count: 2", you have the double mount issue
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
onActivated(() => {
|
|
73
|
+
console.log('Child activated')
|
|
74
|
+
})
|
|
75
|
+
</script>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Workarounds
|
|
79
|
+
|
|
80
|
+
### Option 1: Use `useActivatedRoute` Pattern
|
|
81
|
+
|
|
82
|
+
Don't use `useRoute()` directly with KeepAlive:
|
|
83
|
+
|
|
84
|
+
```vue
|
|
85
|
+
<script setup>
|
|
86
|
+
import { ref, onActivated } from 'vue'
|
|
87
|
+
import { useRoute } from 'vue-router'
|
|
88
|
+
|
|
89
|
+
// Problem: useRoute() can cause issues with KeepAlive
|
|
90
|
+
// const route = useRoute()
|
|
91
|
+
|
|
92
|
+
// Solution: Get route info in onActivated
|
|
93
|
+
const routeParams = ref({})
|
|
94
|
+
|
|
95
|
+
onActivated(() => {
|
|
96
|
+
const route = useRoute()
|
|
97
|
+
routeParams.value = { ...route.params }
|
|
98
|
+
})
|
|
99
|
+
</script>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Option 2: Avoid KeepAlive for Nested Route Parents
|
|
103
|
+
|
|
104
|
+
Only cache leaf routes, not parent layouts:
|
|
105
|
+
|
|
106
|
+
```vue
|
|
107
|
+
<script setup>
|
|
108
|
+
import { computed } from 'vue'
|
|
109
|
+
import { useRoute } from 'vue-router'
|
|
110
|
+
|
|
111
|
+
const route = useRoute()
|
|
112
|
+
|
|
113
|
+
// Only cache specific leaf routes
|
|
114
|
+
const cachedRoutes = computed(() => {
|
|
115
|
+
// Don't cache parent routes that have children
|
|
116
|
+
return ['UserProfile', 'UserSettings'] // Only leaf components
|
|
117
|
+
})
|
|
118
|
+
</script>
|
|
119
|
+
|
|
120
|
+
<template>
|
|
121
|
+
<router-view v-slot="{ Component, route: currentRoute }">
|
|
122
|
+
<KeepAlive :include="cachedRoutes">
|
|
123
|
+
<component :is="Component" :key="currentRoute.fullPath" />
|
|
124
|
+
</KeepAlive>
|
|
125
|
+
</router-view>
|
|
126
|
+
</template>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Option 3: Guard Against Double Initialization
|
|
130
|
+
|
|
131
|
+
Protect your component from double mount effects:
|
|
132
|
+
|
|
133
|
+
```vue
|
|
134
|
+
<script setup>
|
|
135
|
+
import { ref, onMounted } from 'vue'
|
|
136
|
+
|
|
137
|
+
const isInitialized = ref(false)
|
|
138
|
+
|
|
139
|
+
onMounted(() => {
|
|
140
|
+
if (isInitialized.value) {
|
|
141
|
+
console.warn('Double mount detected, skipping initialization')
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
isInitialized.value = true
|
|
145
|
+
|
|
146
|
+
// Safe to initialize
|
|
147
|
+
fetchData()
|
|
148
|
+
setupEventListeners()
|
|
149
|
+
})
|
|
150
|
+
</script>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Option 4: Use Route-Level Cache Control
|
|
154
|
+
|
|
155
|
+
```vue
|
|
156
|
+
<!-- App.vue -->
|
|
157
|
+
<script setup>
|
|
158
|
+
import { computed } from 'vue'
|
|
159
|
+
import { useRoute } from 'vue-router'
|
|
160
|
+
|
|
161
|
+
const route = useRoute()
|
|
162
|
+
|
|
163
|
+
// Define which routes should be cached in route meta
|
|
164
|
+
const shouldCache = computed(() => {
|
|
165
|
+
return route.meta.keepAlive !== false
|
|
166
|
+
})
|
|
167
|
+
</script>
|
|
168
|
+
|
|
169
|
+
<template>
|
|
170
|
+
<router-view v-slot="{ Component }">
|
|
171
|
+
<KeepAlive v-if="shouldCache">
|
|
172
|
+
<component :is="Component" />
|
|
173
|
+
</KeepAlive>
|
|
174
|
+
<component v-else :is="Component" />
|
|
175
|
+
</router-view>
|
|
176
|
+
</template>
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
```javascript
|
|
180
|
+
// router.js
|
|
181
|
+
const routes = [
|
|
182
|
+
{
|
|
183
|
+
path: '/parent',
|
|
184
|
+
component: Parent,
|
|
185
|
+
meta: { keepAlive: false }, // Don't cache parent routes
|
|
186
|
+
children: [
|
|
187
|
+
{
|
|
188
|
+
path: 'child',
|
|
189
|
+
component: Child,
|
|
190
|
+
meta: { keepAlive: true } // Cache leaf routes
|
|
191
|
+
}
|
|
192
|
+
]
|
|
193
|
+
}
|
|
194
|
+
]
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Option 5: Flatten Route Structure
|
|
198
|
+
|
|
199
|
+
Avoid nesting if possible:
|
|
200
|
+
|
|
201
|
+
```javascript
|
|
202
|
+
// Instead of nested routes
|
|
203
|
+
const routes = [
|
|
204
|
+
// Flat structure avoids the issue
|
|
205
|
+
{ path: '/users', component: UserList },
|
|
206
|
+
{ path: '/users/:id', component: UserDetail },
|
|
207
|
+
{ path: '/users/:id/settings', component: UserSettings }
|
|
208
|
+
]
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Key Points
|
|
212
|
+
|
|
213
|
+
1. **Known Vue Router issue** - Double mount with KeepAlive + nested routes
|
|
214
|
+
2. **Watch for symptoms** - Duplicate API calls, doubled logs
|
|
215
|
+
3. **Avoid caching parent routes** - Only cache leaf components
|
|
216
|
+
4. **Add initialization guards** - Protect against double execution
|
|
217
|
+
5. **Test thoroughly** - This issue may not appear immediately
|
|
218
|
+
|
|
219
|
+
## Reference
|
|
220
|
+
- [Vue Router Issue #626: keep-alive in nested route mounted twice](https://github.com/vuejs/router/issues/626)
|
|
221
|
+
- [GitHub: vue3-keep-alive-component workaround](https://github.com/emiyalee1005/vue3-keep-alive-component)
|
|
222
|
+
- [Vue.js KeepAlive Documentation](https://vuejs.org/guide/built-ins/keep-alive.html)
|