Skip to content

breaking: remove foreign namespace #12869

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/gorgeous-coats-jog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

breaking: remove foreign namespace
12 changes: 2 additions & 10 deletions packages/svelte/src/compiler/phases/1-parse/read/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,18 +157,10 @@ export default function read_options(node) {
component_options.namespace = 'svg';
} else if (value === NAMESPACE_MATHML) {
component_options.namespace = 'mathml';
} else if (
value === 'html' ||
value === 'mathml' ||
value === 'svg' ||
value === 'foreign'
) {
} else if (value === 'html' || value === 'mathml' || value === 'svg') {
component_options.namespace = value;
} else {
e.svelte_options_invalid_attribute_value(
attribute,
`"html", "mathml", "svg" or "foreign"`
);
e.svelte_options_invalid_attribute_value(attribute, `"html", "mathml" or "svg"`);
}

break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,6 @@ export function BindDirective(node, context) {
parent?.type === 'SvelteDocument' ||
parent?.type === 'SvelteBody'
) {
if (context.state.options.namespace === 'foreign' && node.name !== 'this') {
e.bind_invalid_name(node, node.name, 'Foreign elements only support `bind:this`');
}

if (node.name in binding_properties) {
const property = binding_properties[node.name];
if (property.valid_elements && !property.valid_elements.includes(parent.name)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,7 @@ export function RegularElement(node, context) {
context.state.analysis.elements.push(node);

// Special case: Move the children of <textarea> into a value attribute if they are dynamic
if (
context.state.options.namespace !== 'foreign' &&
node.name === 'textarea' &&
node.fragment.nodes.length > 0
) {
if (node.name === 'textarea' && node.fragment.nodes.length > 0) {
for (const attribute of node.attributes) {
if (attribute.type === 'Attribute' && attribute.name === 'value') {
e.textarea_invalid_content(node);
Expand Down Expand Up @@ -65,7 +61,6 @@ export function RegularElement(node, context) {
// Special case: single expression tag child of option element -> add "fake" attribute
// to ensure that value types are the same (else for example numbers would be strings)
if (
context.state.options.namespace !== 'foreign' &&
node.name === 'option' &&
node.fragment.nodes?.length === 1 &&
node.fragment.nodes[0].type === 'ExpressionTag' &&
Expand All @@ -90,10 +85,8 @@ export function RegularElement(node, context) {
(attribute) => attribute.type === 'SpreadAttribute'
);

if (context.state.options.namespace !== 'foreign') {
node.metadata.svg = is_svg(node.name);
node.metadata.mathml = is_mathml(node.name);
}
node.metadata.svg = is_svg(node.name);
node.metadata.mathml = is_mathml(node.name);

if (context.state.parent_element) {
let past_parent = false;
Expand Down Expand Up @@ -156,7 +149,6 @@ export function RegularElement(node, context) {

if (
context.state.analysis.source[node.end - 2] === '/' &&
context.state.options.namespace !== 'foreign' &&
!is_void(node_name) &&
!is_svg(node_name)
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -687,9 +687,6 @@ function get_static_text_value(attribute) {
* @param {AnalysisState} state
*/
export function check_element(node, state) {
// foreign namespace means elements can have completely different meanings, therefore we don't check them
if (state.options.namespace === 'foreign') return;

/** @type {Map<string, Attribute>} */
const attribute_map = new Map();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export function validate_element(node, context) {
validate_slot_attribute(context, attribute);
}

if (attribute.name === 'is' && context.state.options.namespace !== 'foreign') {
if (attribute.name === 'is') {
w.attribute_avoid_is(attribute);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,14 +178,6 @@ export function RegularElement(node, context) {
}
}

if (child_metadata.namespace === 'foreign') {
// input/select etc could mean something completely different in foreign namespace, so don't special-case them
needs_content_reset = false;
needs_input_reset = false;
needs_special_value_handling = false;
value_binding = null;
}

if (is_content_editable && has_content_editable_binding) {
child_metadata.bound_contenteditable = true;
}
Expand Down Expand Up @@ -219,7 +211,7 @@ export function RegularElement(node, context) {
node,
node_id,
// If value binding exists, that one takes care of calling $.init_select
value_binding === null && node.name === 'select' && child_metadata.namespace !== 'foreign'
value_binding === null && node.name === 'select'
);
is_attributes_reactive = true;
} else {
Expand Down Expand Up @@ -249,8 +241,6 @@ export function RegularElement(node, context) {
const value = is_text_attribute(attribute) ? attribute.value[0].data : true;

if (name !== 'class' || value) {
// TODO namespace=foreign probably doesn't want to do template stuff at all and instead use programmatic methods
// to create the elements it needs.
context.state.template.push(
` ${attribute.name}${
is_boolean_attribute(name) && value === true
Expand All @@ -262,10 +252,9 @@ export function RegularElement(node, context) {
}
}

const is =
is_custom_element && child_metadata.namespace !== 'foreign'
? build_custom_element_attribute_update_assignment(node_id, attribute, context)
: build_element_attribute_update_assignment(node, node_id, attribute, context);
const is = is_custom_element
? build_custom_element_attribute_update_assignment(node_id, attribute, context)
: build_element_attribute_update_assignment(node, node_id, attribute, context);
if (is) is_attributes_reactive = true;
}
}
Expand Down Expand Up @@ -301,8 +290,7 @@ export function RegularElement(node, context) {
locations: child_locations,
scope: /** @type {Scope} */ (context.state.scopes.get(node.fragment)),
preserve_whitespace:
context.state.preserve_whitespace ||
((node.name === 'pre' || node.name === 'textarea') && child_metadata.namespace !== 'foreign')
context.state.preserve_whitespace || node.name === 'pre' || node.name === 'textarea'
};

const { hoisted, trimmed } = clean_nodes(
Expand Down Expand Up @@ -587,28 +575,6 @@ function build_element_attribute_update_assignment(element, node_id, attribute,
const is_mathml = context.state.metadata.namespace === 'mathml';
let { has_call, value } = build_attribute_value(attribute.value, context);

// The foreign namespace doesn't have any special handling, everything goes through the attr function
if (context.state.metadata.namespace === 'foreign') {
const statement = b.stmt(
b.call(
'$.set_attribute',
node_id,
b.literal(name),
value,
is_ignored(element, 'hydration_attribute_changed') && b.true
)
);

if (attribute.metadata.expression.has_state) {
const id = state.scope.generate(`${node_id.name}_${name}`);
build_update_assignment(state, id, undefined, value, statement);
return true;
} else {
state.init.push(statement);
return false;
}
}

if (name === 'autofocus') {
state.init.push(b.stmt(b.call('$.autofocus', node_id, value)));
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export function SvelteElement(node, context) {

const get_tag = b.thunk(/** @type {Expression} */ (context.visit(node.tag)));

if (dev && context.state.metadata.namespace !== 'foreign') {
if (dev) {
if (node.fragment.nodes.length > 0) {
context.state.init.push(b.stmt(b.call('$.validate_void_dynamic_element', get_tag)));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,7 @@ export function build_attribute_value(value, context) {
* @param {{ state: { metadata: { namespace: Namespace }}}} context
*/
export function get_attribute_name(element, attribute, context) {
if (
!element.metadata.svg &&
!element.metadata.mathml &&
context.state.metadata.namespace !== 'foreign'
) {
if (!element.metadata.svg && !element.metadata.mathml) {
return normalize_attribute(attribute.name);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ export function RegularElement(node, context) {
...context.state,
namespace,
preserve_whitespace:
context.state.preserve_whitespace ||
((node.name === 'pre' || node.name === 'textarea') && namespace !== 'foreign')
context.state.preserve_whitespace || node.name === 'pre' || node.name === 'textarea'
};

context.state.template.push(b.literal(`<${node.name}`));
Expand Down Expand Up @@ -95,7 +94,7 @@ export function RegularElement(node, context) {
);
}

if (!is_void(node.name) || namespace === 'foreign') {
if (!is_void(node.name)) {
state.template.push(b.literal(`</${node.name}>`));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ export function build_element_attributes(node, context) {
*/
function get_attribute_name(element, attribute, context) {
let name = attribute.name;
if (!element.metadata.svg && !element.metadata.mathml && context.state.namespace !== 'foreign') {
if (!element.metadata.svg && !element.metadata.mathml) {
name = name.toLowerCase();
// don't lookup boolean aliases here, the server runtime function does only
// check for the lowercase variants of boolean attributes
Expand Down
48 changes: 21 additions & 27 deletions packages/svelte/src/compiler/phases/3-transform/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -306,32 +306,30 @@ export function clean_nodes(
* @param {Compiler.SvelteNode[]} nodes
*/
export function infer_namespace(namespace, parent, nodes) {
if (namespace !== 'foreign') {
if (parent.type === 'RegularElement' && parent.name === 'foreignObject') {
return 'html';
}
if (parent.type === 'RegularElement' && parent.name === 'foreignObject') {
return 'html';
}

if (parent.type === 'RegularElement' || parent.type === 'SvelteElement') {
if (parent.metadata.svg) {
return 'svg';
}
return parent.metadata.mathml ? 'mathml' : 'html';
if (parent.type === 'RegularElement' || parent.type === 'SvelteElement') {
if (parent.metadata.svg) {
return 'svg';
}
return parent.metadata.mathml ? 'mathml' : 'html';
}

// Re-evaluate the namespace inside slot nodes that reset the namespace
if (
parent.type === 'Fragment' ||
parent.type === 'Root' ||
parent.type === 'Component' ||
parent.type === 'SvelteComponent' ||
parent.type === 'SvelteFragment' ||
parent.type === 'SnippetBlock' ||
parent.type === 'SlotElement'
) {
const new_namespace = check_nodes_for_namespace(nodes, 'keep');
if (new_namespace !== 'keep' && new_namespace !== 'maybe_html') {
return new_namespace;
}
// Re-evaluate the namespace inside slot nodes that reset the namespace
if (
parent.type === 'Fragment' ||
parent.type === 'Root' ||
parent.type === 'Component' ||
parent.type === 'SvelteComponent' ||
parent.type === 'SvelteFragment' ||
parent.type === 'SnippetBlock' ||
parent.type === 'SlotElement'
) {
const new_namespace = check_nodes_for_namespace(nodes, 'keep');
if (new_namespace !== 'keep' && new_namespace !== 'maybe_html') {
return new_namespace;
}
}

Expand Down Expand Up @@ -401,10 +399,6 @@ function check_nodes_for_namespace(nodes, namespace) {
* @returns {Compiler.Namespace}
*/
export function determine_namespace_for_children(node, namespace) {
if (namespace === 'foreign') {
return namespace;
}

if (node.name === 'foreignObject') {
return 'html';
}
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/src/compiler/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export interface CompileOptions extends ModuleCompileOptions {
*/
accessors?: boolean;
/**
* The namespace of the element; e.g., `"html"`, `"svg"`, `"foreign"`.
* The namespace of the element; e.g., `"html"`, `"svg"`, `"mathml"`.
*
* @default 'html'
*/
Expand Down
5 changes: 1 addition & 4 deletions packages/svelte/src/compiler/types/template.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,8 @@ export interface Fragment {
* - `html` — the default, for e.g. `<div>` or `<span>`
* - `svg` — for e.g. `<svg>` or `<g>`
* - `mathml` — for e.g. `<math>` or `<mrow>`
* - `foreign` — for other compilation targets than the web, e.g. Svelte Native.
* Disallows bindings other than bind:this, disables a11y checks, disables any special attribute handling
* (also see https://github.com/sveltejs/svelte/pull/5652)
*/
export type Namespace = 'html' | 'svg' | 'mathml' | 'foreign';
export type Namespace = 'html' | 'svg' | 'mathml';

export interface Root extends BaseNode {
type: 'Root';
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/src/compiler/validate-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export const validate_component_options =

name: string(undefined),

namespace: list(['html', 'svg', 'foreign']),
namespace: list(['html', 'mathml', 'svg']),

modernAst: boolean(false),

Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Loading
Loading