Skip to content

Commit 8740110

Browse files
committed
WIP: attempt to add render title attributes
SSR is pretty straight-forward. The client side is more difficult since the TitleElement doesn't have a node_id.
1 parent 5b73606 commit 8740110

File tree

9 files changed

+91
-14
lines changed

9 files changed

+91
-14
lines changed

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

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
/** @import { AST } from '#compiler' */
22
/** @import { ComponentContext } from '../types' */
33
import * as b from '#compiler/builders';
4-
import { build_template_chunk } from './shared/utils.js';
4+
import {build_template_chunk, get_expression_id} from './shared/utils.js';
5+
import { is_event_attribute, is_text_attribute } from '../../../../utils/ast.js';
6+
import {build_attribute_value} from "./shared/element.js";
7+
import { visit_event_attribute } from './shared/events.js';
8+
import {normalize_attribute} from "../../../../../utils.js";
9+
import {is_ignored} from "../../../../state.js";
510

611
/**
712
* @param {AST.TitleElement} node
@@ -15,10 +20,46 @@ export function TitleElement(node, context) {
1520
);
1621

1722
const statement = b.stmt(b.assignment('=', b.id('$.document.title'), value));
18-
1923
if (has_state) {
2024
context.state.update.push(statement);
2125
} else {
2226
context.state.init.push(statement);
2327
}
28+
29+
// TODO: is this the right approach?
30+
31+
/** @type {Array<AST.Attribute | AST.SpreadAttribute>} */
32+
const attributes = [];
33+
for (const attribute of node.attributes) {
34+
switch (attribute.type) {
35+
case 'Attribute':
36+
attributes.push(attribute);
37+
break;
38+
}
39+
}
40+
41+
const node_id = {"type": "Identifier", "name": "title"}
42+
43+
for (const attribute of /** @type {AST.Attribute[]} */ (attributes)) {
44+
if (is_event_attribute(attribute)) {
45+
visit_event_attribute(attribute, context);
46+
continue;
47+
}
48+
49+
const name = normalize_attribute(attribute.name);
50+
const { value, has_state } = build_attribute_value(
51+
attribute.value,
52+
context,
53+
(value, metadata) => (metadata.has_call ? get_expression_id(context.state, value) : value)
54+
);
55+
56+
const update = b.call(
57+
'$.set_attribute',
58+
false,
59+
b.literal(name),
60+
value,
61+
is_ignored(node, 'hydration_attribute_changed') && b.true
62+
);
63+
(has_state ? context.state.update : context.state.init).push(b.stmt(update));
64+
}
2465
}

packages/svelte/src/compiler/phases/3-transform/server/visitors/TitleElement.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@
22
/** @import { ComponentContext } from '../types.js' */
33
import * as b from '#compiler/builders';
44
import { process_children, build_template } from './shared/utils.js';
5+
import { build_element_attributes } from "./shared/element.js";
56

67
/**
78
* @param {AST.TitleElement} node
89
* @param {ComponentContext} context
910
*/
1011
export function TitleElement(node, context) {
1112
// title is guaranteed to contain only text/expression tag children
12-
const template = [b.literal('<title>')];
13+
const template = [b.literal('<title')];
14+
build_element_attributes(node, { ...context, state: { ...context.state, template } });
15+
template.push(b.literal('>'));
1316
process_children(node.fragment.nodes, { ...context, state: { ...context.state, template } });
1417
template.push(b.literal('</title>'));
1518

packages/svelte/src/compiler/phases/3-transform/server/visitors/shared/element.js

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ import { escape_html } from '../../../../../../escaping.js';
2525
const WHITESPACE_INSENSITIVE_ATTRIBUTES = ['class', 'style'];
2626

2727
/**
28-
* Writes the output to the template output. Some elements may have attributes on them that require the
28+
* Writes the output to the template output. Some elements may have attributes on them that require
2929
* their output to be the child content instead. In this case, an object is returned.
30-
* @param {AST.RegularElement | AST.SvelteElement} node
30+
* @param {AST.RegularElement | AST.SvelteElement | AST.TitleElement} node
3131
* @param {import('zimmerframe').Context<AST.SvelteNode, ComponentServerTransformState>} context
3232
*/
3333
export function build_element_attributes(node, context) {
@@ -203,7 +203,7 @@ export function build_element_attributes(node, context) {
203203
if (has_spread) {
204204
build_element_spread_attributes(node, attributes, style_directives, class_directives, context);
205205
} else {
206-
const css_hash = node.metadata.scoped ? context.state.analysis.css.hash : null;
206+
const css_hash = node.type !== 'TitleElement' && node.metadata.scoped ? context.state.analysis.css.hash : null;
207207

208208
for (const attribute of /** @type {AST.Attribute[]} */ (attributes)) {
209209
const name = get_attribute_name(node, attribute);
@@ -273,12 +273,12 @@ export function build_element_attributes(node, context) {
273273
}
274274

275275
/**
276-
* @param {AST.RegularElement | AST.SvelteElement} element
276+
* @param {AST.RegularElement | AST.SvelteElement | AST.TitleElement} element
277277
* @param {AST.Attribute} attribute
278278
*/
279279
function get_attribute_name(element, attribute) {
280280
let name = attribute.name;
281-
if (!element.metadata.svg && !element.metadata.mathml) {
281+
if (element.type !== 'TitleElement' && !element.metadata.svg && !element.metadata.mathml) {
282282
name = name.toLowerCase();
283283
// don't lookup boolean aliases here, the server runtime function does only
284284
// check for the lowercase variants of boolean attributes
@@ -288,7 +288,7 @@ function get_attribute_name(element, attribute) {
288288

289289
/**
290290
*
291-
* @param {AST.RegularElement | AST.SvelteElement} element
291+
* @param {AST.RegularElement | AST.SvelteElement | AST.TitleElement} element
292292
* @param {Array<AST.Attribute | AST.SpreadAttribute>} attributes
293293
* @param {AST.StyleDirective[]} style_directives
294294
* @param {AST.ClassDirective[]} class_directives
@@ -330,10 +330,12 @@ function build_element_spread_attributes(
330330
styles = b.object(properties);
331331
}
332332

333-
if (element.metadata.svg || element.metadata.mathml) {
334-
flags |= ELEMENT_IS_NAMESPACED | ELEMENT_PRESERVE_ATTRIBUTE_CASE;
335-
} else if (is_custom_element_node(element)) {
336-
flags |= ELEMENT_PRESERVE_ATTRIBUTE_CASE;
333+
if (element.type !== 'TitleElement') {
334+
if (element.metadata.svg || element.metadata.mathml) {
335+
flags |= ELEMENT_IS_NAMESPACED | ELEMENT_PRESERVE_ATTRIBUTE_CASE;
336+
} else if (is_custom_element_node(element)) {
337+
flags |= ELEMENT_PRESERVE_ATTRIBUTE_CASE;
338+
}
337339
}
338340

339341
const object = b.object(
@@ -353,7 +355,7 @@ function build_element_spread_attributes(
353355
);
354356

355357
const css_hash =
356-
element.metadata.scoped && context.state.analysis.css.hash
358+
element.type !== 'TitleElement' && element.metadata.scoped && context.state.analysis.css.hash
357359
? b.literal(context.state.analysis.css.hash)
358360
: b.null;
359361

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { test } from '../../test';
2+
3+
export default test({
4+
test({ assert, component, window }) {
5+
assert.equal(window.document.title, 'Foo');
6+
7+
const elems = window.document.getElementsByTagName('title');
8+
assert.equal(elems.length, 1);
9+
const attrValue = elems[0].getAttribute('aria-live');
10+
assert.equal(attrValue, 'assertive');
11+
}
12+
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<svelte:head>
2+
<title aria-live="assertive">Foo</title>
3+
</svelte:head>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<title aria-live="assertive">Foo</title>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<svelte:head>
2+
<title aria-live="assertive">Foo</title>
3+
</svelte:head>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<title value="bar" form="qux" list="quu">Foo</title>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<script>
2+
let props = {
3+
value: 'bar',
4+
form: 'qux',
5+
list: 'quu',
6+
};
7+
</script>
8+
9+
<svelte:head>
10+
<title {...props}>Foo</title>
11+
</svelte:head>

0 commit comments

Comments
 (0)