bug: setTagTransformer transforms every tag rendered by h(), including native HTML tags
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 45/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- javascript, typescript, web-dev
Research direction
Start in the runtime's h() function, where transformTag is applied to every string node name, and compare it with how cmpMeta.$tagName$ identifies component tags. Also read setTagTransformer and its JSDoc example. Done means either the transformer no longer receives native HTML tags such as div, span and slot, or the docs state clearly that it receives every rendered tag. The maintainers still need to choose between these options, so confirm that before writing code.
Written by the indexing model from the issue text.
Description
Stencil Version
4.45.2
Current Behavior
When a transformer is registered via setTagTransformer(fn), the runtime applies fn to every string tag rendered through h() — not only component tags. Native HTML tags used inside a component's shadow DOM (div, span, label, slot, input, …) are renamed as well.
With a natural consumer callback like (tag) => \${tag}-sample`, the entire internal structure of every component becomes unknown elements (, ): slotted content no longer projects, ` stops being an input, and components render broken with no error or warning.
The mechanism sits in the runtime's h():
var h = (nodeName, vnodeData, ...children) => {
if (typeof nodeName === "string") {
nodeName = transformTag(nodeName); // every string tag
}
...
}
The docs/JSDoc example only accidentally protects users:
setTagTransformer((tag) => {
if (tag.startsWith('my-')) return `new-${tag}`;
return tag;
});
It never states that the callback receives every rendered tag, including native HTML.
Minimal reproduction
Self-contained page (importmap + a built dist-custom-elements component), verified against 4.45.2:
<script type="importmap">
{"imports": {"@stencil/core/internal/client": "./vendor/stencil-core/internal/client/index.js"}}
</script>
<script type="module">
import { setTagTransformer, transformTag } from '@stencil/core/internal/client';
import { defineCustomElement } from './components/kol-button.js';
setTagTransformer((tag) => `${tag}-sample`);
defineCustomElement();
const host = document.createElement(transformTag('kol-button'));
host.setAttribute('_label', 'Label');
document.body.append(host);
</script>
Observed shadow DOM (host correctly upgrades as kol-button-sample, internals do not):
<div-sample class="kol-button kol-button--standalone kol-button--normal">
<button-sample class="kol-button__interactive-element" type="button">
<span-sample class="kol-span kol-button__text">
<span-sample class="kol-span__label">Label</span-sample>
<slot-sample name="expert" slot="expert"></slot-sample>
Expected Behavior
The runtime already knows which tags are component tags (cmpMeta.$tagName$). The transformer should receive only tags it can meaningfully rename — ideally component tags by default — or the docs/JSDoc must state explicitly that the callback receives every rendered tag including native HTML, and that the callback therefore has to be selective. Currently a one-line consumer callback silently destroys every component's internal structure.
System Info
macOS 15 (arm64) and Linux (CI, Firefox), Stencil 4.45.2, Node 24. Reproduced with dist-custom-elements output and with the lazy loader.
Additional context
We hit this while migrating a component library's tag-rename feature from a tag-constant mutation approach to the native API. Our visual regression suite (which renders the whole sample app with the transformer enabled) caught it; a naive one-line callback destroyed the internal structure of every component while unit tests stayed green, because the components themselves hydrate fine — only the shadow structure is renamed.
- Dominant language
- TypeScript
- Stars
- 13.2k
- Forks
- 865
- Avg merge
- 2d 16h
- Merged PRs (30d)
- 29
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from stenciljs/core
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
bug(mock-doc): `Element.toggleAttribute()` is missing, so components that use it fail during SSROpentriage
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
Stencil FOUC styles should use @layer to avoid cascade conflicts with React 19Possibly taken @johnjenkins claimed this 230 days ago. OpenStencil v5
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 1 day
-
Difficulty 4/5 3-5 days Newbie friendliness 22/100
Maintainers usually reply within 1 day
-
Difficulty 3/5 1-2 days Newbie friendliness 35/100
Maintainers usually reply within 1 day
Similar issues
-
[BUG]: Duplicate preposition in heading and unformatted feedback text on Strengthen Argument pageOpenbug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
AOSSIE-Org/DebateAI#801 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 1 day
-
agents area: runtime automation chore cli consistency dependencies developer-experience difficulty: easy documentation enhancement good first issue help wanted hygiene json-output priority: P3 starters tests
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
-
Add: BOM CineOpenapproved check:passed streams:add
Difficulty 1/5 Under an hour Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
area:claude-code-web bug has repro platform:linux platform:web
Difficulty 1/5 Under an hour Newbie friendliness 66/100
anthropics/claude-code#110168 ·