Skip to content

Commit 18cef80

Browse files
committed
DRY
1 parent 0d38498 commit 18cef80

File tree

3 files changed

+90
-170
lines changed

3 files changed

+90
-170
lines changed

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

Lines changed: 3 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ import {
2424
get_attribute_name,
2525
build_attribute_value,
2626
build_class_directives,
27-
build_style_directives
27+
build_style_directives,
28+
build_set_attributes
2829
} from './shared/element.js';
2930
import { process_children } from './shared/fragment.js';
3031
import {
@@ -208,7 +209,7 @@ export function RegularElement(node, context) {
208209
if (has_spread) {
209210
const attributes_id = b.id(context.state.scope.generate('attributes'));
210211

211-
build_element_spread_attributes(
212+
build_set_attributes(
212213
attributes,
213214
context,
214215
node,
@@ -480,87 +481,6 @@ function setup_select_synchronization(value_binding, context) {
480481
);
481482
}
482483

483-
/**
484-
* @param {Array<AST.Attribute | AST.SpreadAttribute>} attributes
485-
* @param {ComponentContext} context
486-
* @param {AST.RegularElement} element
487-
* @param {Identifier} element_id
488-
* @param {Identifier} attributes_id
489-
* @param {false | Expression} preserve_attribute_case
490-
* @param {false | Expression} is_custom_element
491-
*/
492-
function build_element_spread_attributes(
493-
attributes,
494-
context,
495-
element,
496-
element_id,
497-
attributes_id,
498-
preserve_attribute_case,
499-
is_custom_element
500-
) {
501-
let needs_isolation = false;
502-
let is_reactive = false;
503-
504-
/** @type {ObjectExpression['properties']} */
505-
const values = [];
506-
507-
for (const attribute of attributes) {
508-
if (attribute.type === 'Attribute') {
509-
const { value } = build_attribute_value(attribute.value, context);
510-
511-
if (
512-
is_event_attribute(attribute) &&
513-
(get_attribute_expression(attribute).type === 'ArrowFunctionExpression' ||
514-
get_attribute_expression(attribute).type === 'FunctionExpression')
515-
) {
516-
// Give the event handler a stable ID so it isn't removed and readded on every update
517-
const id = context.state.scope.generate('event_handler');
518-
context.state.init.push(b.var(id, value));
519-
values.push(b.init(attribute.name, b.id(id)));
520-
} else {
521-
values.push(b.init(attribute.name, value));
522-
}
523-
} else {
524-
values.push(b.spread(/** @type {Expression} */ (context.visit(attribute))));
525-
}
526-
527-
is_reactive ||=
528-
attribute.metadata.expression.has_state ||
529-
// objects could contain reactive getters -> play it safe and always assume spread attributes are reactive
530-
attribute.type === 'SpreadAttribute';
531-
needs_isolation ||=
532-
attribute.type === 'SpreadAttribute' && attribute.metadata.expression.has_call;
533-
}
534-
535-
const call = b.call(
536-
'$.set_attributes',
537-
element_id,
538-
is_reactive ? attributes_id : b.literal(null),
539-
b.object(values),
540-
context.state.analysis.css.hash !== '' && b.literal(context.state.analysis.css.hash),
541-
preserve_attribute_case,
542-
is_custom_element,
543-
is_ignored(element, 'hydration_attribute_changed') && b.true
544-
);
545-
546-
if (is_reactive) {
547-
context.state.init.push(b.let(attributes_id));
548-
549-
const update = b.stmt(b.assignment('=', attributes_id, call));
550-
551-
if (needs_isolation) {
552-
context.state.init.push(build_update(update));
553-
return false;
554-
}
555-
556-
context.state.update.push(update);
557-
return true;
558-
}
559-
560-
context.state.init.push(b.stmt(call));
561-
return false;
562-
}
563-
564484
/**
565485
* Serializes an assignment to an element property by adding relevant statements to either only
566486
* the init or the the init and update arrays, depending on whether or not the value is dynamic.

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

Lines changed: 3 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { determine_namespace_for_children } from '../../utils.js';
1212
import {
1313
build_attribute_value,
1414
build_class_directives,
15+
build_set_attributes,
1516
build_style_directives
1617
} from './shared/element.js';
1718
import { build_render_statement, build_update } from './shared/utils.js';
@@ -94,10 +95,10 @@ export function SvelteElement(node, context) {
9495

9596
// Always use spread because we don't know whether the element is a custom element or not,
9697
// therefore we need to do the "how to set an attribute" logic at runtime.
97-
is_attributes_reactive = build_dynamic_element_attributes(
98-
node,
98+
is_attributes_reactive = build_set_attributes(
9999
attributes,
100100
inner_context,
101+
node,
101102
element_id,
102103
attributes_id,
103104
b.binary('!==', b.member(element_id, 'namespaceURI'), b.id('$.NAMESPACE_SVG')),
@@ -152,87 +153,3 @@ export function SvelteElement(node, context) {
152153
)
153154
);
154155
}
155-
156-
/**
157-
* Serializes dynamic element attribute assignments.
158-
* Returns the `true` if spread is deemed reactive.
159-
* @param {AST.SvelteElement} element
160-
* @param {Array<AST.Attribute | AST.SpreadAttribute>} attributes
161-
* @param {ComponentContext} context
162-
* @param {Identifier} element_id
163-
* @param {Identifier} attributes_id
164-
* @param {false | Expression} preserve_attribute_case
165-
* @param {false | Expression} is_custom_element
166-
* @returns {boolean}
167-
*/
168-
function build_dynamic_element_attributes(
169-
element,
170-
attributes,
171-
context,
172-
element_id,
173-
attributes_id,
174-
preserve_attribute_case,
175-
is_custom_element
176-
) {
177-
let needs_isolation = false;
178-
let is_reactive = false;
179-
180-
/** @type {ObjectExpression['properties']} */
181-
const values = [];
182-
183-
for (const attribute of attributes) {
184-
if (attribute.type === 'Attribute') {
185-
const { value } = build_attribute_value(attribute.value, context);
186-
187-
if (
188-
is_event_attribute(attribute) &&
189-
(get_attribute_expression(attribute).type === 'ArrowFunctionExpression' ||
190-
get_attribute_expression(attribute).type === 'FunctionExpression')
191-
) {
192-
// Give the event handler a stable ID so it isn't removed and readded on every update
193-
const id = context.state.scope.generate('event_handler');
194-
context.state.init.push(b.var(id, value));
195-
values.push(b.init(attribute.name, b.id(id)));
196-
} else {
197-
values.push(b.init(attribute.name, value));
198-
}
199-
} else {
200-
values.push(b.spread(/** @type {Expression} */ (context.visit(attribute))));
201-
}
202-
203-
is_reactive ||=
204-
attribute.metadata.expression.has_state ||
205-
// objects could contain reactive getters -> play it safe and always assume spread attributes are reactive
206-
attribute.type === 'SpreadAttribute';
207-
needs_isolation ||=
208-
attribute.type === 'SpreadAttribute' && attribute.metadata.expression.has_call;
209-
}
210-
211-
const call = b.call(
212-
'$.set_attributes',
213-
element_id,
214-
is_reactive ? attributes_id : b.literal(null),
215-
b.object(values),
216-
context.state.analysis.css.hash !== '' && b.literal(context.state.analysis.css.hash),
217-
preserve_attribute_case,
218-
is_custom_element,
219-
is_ignored(element, 'hydration_attribute_changed') && b.true
220-
);
221-
222-
if (is_reactive) {
223-
context.state.init.push(b.let(attributes_id));
224-
225-
const update = b.stmt(b.assignment('=', attributes_id, call));
226-
227-
if (needs_isolation) {
228-
context.state.init.push(build_update(update));
229-
return false;
230-
}
231-
232-
context.state.update.push(update);
233-
return true;
234-
}
235-
236-
context.state.init.push(b.stmt(call));
237-
return false;
238-
}

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

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,94 @@
1-
/** @import { Expression, Identifier } from 'estree' */
1+
/** @import { Expression, Identifier, ObjectExpression } from 'estree' */
22
/** @import { AST, Namespace } from '#compiler' */
33
/** @import { ComponentContext } from '../../types' */
44
import { normalize_attribute } from '../../../../../../utils.js';
5+
import { is_ignored } from '../../../../../state.js';
6+
import { get_attribute_expression, is_event_attribute } from '../../../../../utils/ast.js';
57
import * as b from '../../../../../utils/builders.js';
68
import { build_getter, create_derived } from '../../utils.js';
79
import { build_template_literal, build_update } from './utils.js';
810

11+
/**
12+
* @param {Array<AST.Attribute | AST.SpreadAttribute>} attributes
13+
* @param {ComponentContext} context
14+
* @param {AST.RegularElement | AST.SvelteElement} element
15+
* @param {Identifier} element_id
16+
* @param {Identifier} attributes_id
17+
* @param {false | Expression} preserve_attribute_case
18+
* @param {false | Expression} is_custom_element
19+
*/
20+
export function build_set_attributes(
21+
attributes,
22+
context,
23+
element,
24+
element_id,
25+
attributes_id,
26+
preserve_attribute_case,
27+
is_custom_element
28+
) {
29+
let needs_isolation = false;
30+
let is_reactive = false;
31+
32+
/** @type {ObjectExpression['properties']} */
33+
const values = [];
34+
35+
for (const attribute of attributes) {
36+
if (attribute.type === 'Attribute') {
37+
const { value } = build_attribute_value(attribute.value, context);
38+
39+
if (
40+
is_event_attribute(attribute) &&
41+
(get_attribute_expression(attribute).type === 'ArrowFunctionExpression' ||
42+
get_attribute_expression(attribute).type === 'FunctionExpression')
43+
) {
44+
// Give the event handler a stable ID so it isn't removed and readded on every update
45+
const id = context.state.scope.generate('event_handler');
46+
context.state.init.push(b.var(id, value));
47+
values.push(b.init(attribute.name, b.id(id)));
48+
} else {
49+
values.push(b.init(attribute.name, value));
50+
}
51+
} else {
52+
values.push(b.spread(/** @type {Expression} */ (context.visit(attribute))));
53+
}
54+
55+
is_reactive ||=
56+
attribute.metadata.expression.has_state ||
57+
// objects could contain reactive getters -> play it safe and always assume spread attributes are reactive
58+
attribute.type === 'SpreadAttribute';
59+
needs_isolation ||=
60+
attribute.type === 'SpreadAttribute' && attribute.metadata.expression.has_call;
61+
}
62+
63+
const call = b.call(
64+
'$.set_attributes',
65+
element_id,
66+
is_reactive ? attributes_id : b.literal(null),
67+
b.object(values),
68+
context.state.analysis.css.hash !== '' && b.literal(context.state.analysis.css.hash),
69+
preserve_attribute_case,
70+
is_custom_element,
71+
is_ignored(element, 'hydration_attribute_changed') && b.true
72+
);
73+
74+
if (is_reactive) {
75+
context.state.init.push(b.let(attributes_id));
76+
77+
const update = b.stmt(b.assignment('=', attributes_id, call));
78+
79+
if (needs_isolation) {
80+
context.state.init.push(build_update(update));
81+
return false;
82+
}
83+
84+
context.state.update.push(update);
85+
return true;
86+
}
87+
88+
context.state.init.push(b.stmt(call));
89+
return false;
90+
}
91+
992
/**
1093
* Serializes each style directive into something like `$.set_style(element, style_property, value)`
1194
* and adds it either to init or update, depending on whether or not the value or the attributes are dynamic.

0 commit comments

Comments
 (0)