- Debian-based api image (bookworm-slim), docker/debian mirrors, prisma binaryTargets for openssl 3.0 - nginx: admin SPA under /admin, TLS via acme.sh (ZeroSSL) + auto-renewal cron, http->https redirect - prisma: add origin_goods.delisted migration, sync missing schema (good_image/tag_font_color/good_tags), fix users.createdAt Timestamptz - api: CORS wildcard reflection, helmet CORP cross-origin, price backfill in persistProductDetail, categoryIcon ancestor fallback, mediaByColor per-color gallery in public goods detail - admin: /admin base path (vite + router) - import-data.mjs: udt_name casting, serial sequence advance fix
4.6 KiB
title, impact, impactDescription, type, tags
| title | impact | impactDescription | type | tags | ||||||
|---|---|---|---|---|---|---|---|---|---|---|
| In-DOM Template Parsing Caveats | HIGH | Browser HTML parsing before Vue compilation causes case sensitivity, self-closing tag, and element nesting issues | gotcha |
|
In-DOM Template Parsing Caveats
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.
These issues do NOT apply to Single-File Components (SFCs) or string templates where Vue's compiler handles parsing directly.
Task Checklist
- Use kebab-case for component names in in-DOM templates
- Use kebab-case for prop names in in-DOM templates
- Use explicit closing tags (not self-closing) in in-DOM templates
- Use
is="vue:component-name"for components inside restricted elements - Prefer SFCs to avoid all in-DOM parsing issues
Issue 1: Case Insensitivity
HTML is case-insensitive. The browser lowercases everything before Vue sees it.
Incorrect (in-DOM template):
<!-- Browser converts to: <blogpost posttitle="hello"> -->
<BlogPost postTitle="hello" @updatePost="onUpdate"></BlogPost>
Correct (in-DOM template):
<!-- Use kebab-case for everything -->
<blog-post post-title="hello" @update-post="onUpdate"></blog-post>
In SFCs, PascalCase works fine:
<!-- BlogPost.vue - PascalCase recommended -->
<template>
<BlogPost postTitle="hello" @updatePost="onUpdate" />
</template>
Issue 2: Self-Closing Tags Fail
HTML only allows self-closing syntax for void elements (<input>, <img>, etc.). For all others, the browser expects closing tags.
Incorrect (in-DOM template):
<!-- Browser thinks the tag never closed, breaks nesting -->
<my-component />
<another-component />
Correct (in-DOM template):
<!-- Explicit closing tags required -->
<my-component></my-component>
<another-component></another-component>
In SFCs, self-closing works fine:
<template>
<MyComponent />
<AnotherComponent />
</template>
Issue 3: Element Placement Restrictions
Some HTML elements have strict rules about valid children. Invalid elements are hoisted out by the browser before Vue sees the template.
Restricted parent elements:
<ul>,<ol>- only allow<li><table>- only allows<thead>,<tbody>,<tfoot>,<tr>,<caption>,<colgroup><tr>- only allows<td>,<th><select>- only allows<option>,<optgroup>
Incorrect (in-DOM template):
<!-- Browser hoists blog-post-row outside the table -->
<table>
<blog-post-row v-for="post in posts" :post="post"></blog-post-row>
</table>
<!-- Renders as: -->
<blog-post-row></blog-post-row>
<blog-post-row></blog-post-row>
<table></table>
Correct (in-DOM template):
<!-- Use is="vue:component-name" on a valid native element -->
<table>
<tr is="vue:blog-post-row" v-for="post in posts" :key="post.id" :post="post"></tr>
</table>
<ul>
<li is="vue:todo-item" v-for="todo in todos" :key="todo.id" :todo="todo"></li>
</ul>
Important: The vue: prefix is required! Without it, is is treated as a native customized built-in element attribute.
<!-- WRONG: Missing vue: prefix -->
<tr is="blog-post-row"></tr>
<!-- CORRECT: With vue: prefix -->
<tr is="vue:blog-post-row"></tr>
When Do These Apply?
| Template Type | Affected? | Example |
|---|---|---|
Single-File Component (.vue) |
No | <template> section |
| String template | No | template: '<div>...</div>' |
| In-DOM template | Yes | <div id="app">...</div> |
<script type="text/x-template"> |
Yes | Browser parses the script content |
Best Practice: Use SFCs
The simplest solution is to use Single-File Components (.vue files) which completely avoid in-DOM parsing issues:
<!-- MyComponent.vue - All issues avoided -->
<script setup>
import BlogPost from './BlogPost.vue'
</script>
<template>
<BlogPost postTitle="hello" @updatePost="onUpdate" />
<table>
<BlogPostRow v-for="post in posts" :key="post.id" :post="post" />
</table>
</template>