Skip to content

Commit e304445

Browse files
committed
fix: fall back to component namespace when not statically determinable
In #10006 we added more elaborate mechanisms to determine which namespace a given element is in. For `<svelte:element>` we added a "can't know at compile time" case and introduced a limited heuristic into the runtime. This doesn't work for a few reasons: - we're checking the parent's namespace to determine the current namespace, but the element itself could be the one that _changes_ the namespace - as mentioned in the previous comment already, on the first render we can't do any parent analysis - it does not take into account the static component namespace The last point is the crucial one: In Svelte 4, we're falling back to the component namespace if we can't know statically - e.g. if someone added `<svelte:options namespace="svg">` then `<svelte:element>` should fall back to that namespace instead. We were not doing that up until now, which introduced a regression. Fixing this also means getting rid of the (flawed) "can't know statically" heuristic. Fixes #10858, though for a complete solution we likely need some way to tell `<svelte:element>` the namespace at runtime through a special attribute. Maybe we can use `xmlns` for that like we do in the static case
1 parent 4b59ef3 commit e304445

File tree

8 files changed

+33
-20
lines changed

8 files changed

+33
-20
lines changed

packages/svelte/src/compiler/phases/2-analyze/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1341,7 +1341,8 @@ const common_visitors = {
13411341
ancestor.type === 'SvelteFragment' ||
13421342
ancestor.type === 'SnippetBlock'
13431343
) {
1344-
// Inside a slot or a snippet -> this resets the namespace, so we can't determine it
1344+
// Inside a slot or a snippet -> this resets the namespace, so assume the component namespace
1345+
node.metadata.svg = context.state.options.namespace === 'svg';
13451346
return;
13461347
}
13471348
if (ancestor.type === 'SvelteElement' || ancestor.type === 'RegularElement') {

packages/svelte/src/compiler/phases/3-transform/client/transform-client.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { global_visitors } from './visitors/global.js';
77
import { javascript_visitors } from './visitors/javascript.js';
88
import { javascript_visitors_runes } from './visitors/javascript-runes.js';
99
import { javascript_visitors_legacy } from './visitors/javascript-legacy.js';
10-
import { is_state_source, serialize_get_binding } from './utils.js';
10+
import { serialize_get_binding } from './utils.js';
1111
import { render_stylesheet } from '../css/index.js';
1212

1313
/**

packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2096,11 +2096,7 @@ export const template_visitors = {
20962096
'$.element',
20972097
context.state.node,
20982098
get_tag,
2099-
node.metadata.svg === true
2100-
? b.true
2101-
: node.metadata.svg === false
2102-
? b.false
2103-
: b.literal(null),
2099+
node.metadata.svg ? b.true : b.false,
21042100
inner.length === 0
21052101
? /** @type {any} */ (undefined)
21062102
: b.arrow([element_id, b.id('$$anchor')], b.block(inner))

packages/svelte/src/compiler/types/template.d.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -316,10 +316,10 @@ export interface SvelteElement extends BaseElement {
316316
tag: Expression;
317317
metadata: {
318318
/**
319-
* `true`/`false` if this is definitely (not) an svg element.
320-
* `null` means we can't know statically.
319+
* `true` if this is an svg element. The boolean may not be accurate because
320+
* the tag is dynamic, but we do our best to infer it from the template.
321321
*/
322-
svg: boolean | null;
322+
svg: boolean;
323323
scoped: boolean;
324324
};
325325
}

packages/svelte/src/internal/client/dom/blocks/svelte-element.js

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ function swap_block_dom(effect, from, to) {
3939
/**
4040
* @param {Comment} anchor
4141
* @param {() => string} get_tag
42-
* @param {boolean | null} is_svg `null` == not statically known
42+
* @param {boolean} is_svg
4343
* @param {undefined | ((element: Element, anchor: Node) => void)} render_fn
4444
* @returns {void}
4545
*/
@@ -74,15 +74,10 @@ export function element(anchor, get_tag, is_svg, render_fn) {
7474
var previous_each_item = current_each_item;
7575
set_current_each_item(each_item_block);
7676

77-
// We try our best infering the namespace in case it's not possible to determine statically,
78-
// but on the first render on the client (without hydration) the parent will be undefined,
79-
// since the anchor is not attached to its parent / the dom yet.
80-
const ns =
81-
is_svg || next_tag === 'svg'
82-
? namespace_svg
83-
: is_svg === false || anchor.parentElement?.tagName === 'foreignObject'
84-
? null
85-
: anchor.parentElement?.namespaceURI ?? null;
77+
// The namespace may not be statically known but we can't really infer it either,
78+
// because on the first render on the client (without hydration) the parent will be undefined,
79+
// and the element itself could be a tag that changes the namespace.
80+
const ns = is_svg || next_tag === 'svg' ? namespace_svg : null;
8681

8782
if (effect) {
8883
if (next_tag === null) {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
test({ assert, target }) {
5+
const path = target.querySelector('path');
6+
7+
assert.equal(path?.namespaceURI, 'http://www.w3.org/2000/svg');
8+
}
9+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<svelte:options namespace="svg" />
2+
3+
<script>
4+
import Svg from "./svg.svelte";
5+
6+
let tag = "path";
7+
</script>
8+
9+
<Svg>
10+
<svelte:element this="{tag}" d="M21 12a9 9 0 1 1-6.219-8.56"/>
11+
</Svg>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<svg><slot></slot></svg>

0 commit comments

Comments
 (0)