Skip to content

Commit 39acd8d

Browse files
committed
merge main
2 parents d859f0a + d421838 commit 39acd8d

File tree

26 files changed

+256
-177
lines changed

26 files changed

+256
-177
lines changed

.changeset/eleven-teachers-drive.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: treat module-level imports as non-reactive in legacy mode

.changeset/hot-tips-appear.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
feat: more efficient text-only fragments

.changeset/six-vans-add.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: stricter `crossorigin` and `wrap` attributes types

CONTRIBUTING.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,10 @@ When adding a new breaking change, follow this template in your pull request:
160160
- **Severity (number of people affected x effort)**:
161161
```
162162

163+
### Reviewing pull requests
164+
165+
If you'd like to manually test a pull request in another pnpm project, you can do so by running `pnpm add -D "github:sveltejs/svelte#path:packages/svelte&branch-name"` in that project.
166+
163167
## License
164168

165169
By contributing to Svelte, you agree that your contributions will be licensed under its [MIT license](https://github.com/sveltejs/svelte/blob/master/LICENSE.md).

packages/svelte/elements.d.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1091,7 +1091,7 @@ export interface HTMLLiAttributes extends HTMLAttributes<HTMLLIElement> {
10911091

10921092
export interface HTMLLinkAttributes extends HTMLAttributes<HTMLLinkElement> {
10931093
as?: string | undefined | null;
1094-
crossorigin?: string | undefined | null;
1094+
crossorigin?: 'anonymous' | 'use-credentials' | '' | undefined | null;
10951095
href?: string | undefined | null;
10961096
hreflang?: string | undefined | null;
10971097
integrity?: string | undefined | null;
@@ -1125,7 +1125,7 @@ export interface HTMLMediaAttributes<T extends HTMLMediaElement> extends HTMLAtt
11251125
| (string & {})
11261126
| undefined
11271127
| null;
1128-
crossorigin?: string | undefined | null;
1128+
crossorigin?: 'anonymous' | 'use-credentials' | '' | undefined | null;
11291129
currenttime?: number | undefined | null;
11301130
defaultmuted?: boolean | undefined | null;
11311131
defaultplaybackrate?: number | undefined | null;
@@ -1236,7 +1236,7 @@ export interface HTMLScriptAttributes extends HTMLAttributes<HTMLScriptElement>
12361236
async?: boolean | undefined | null;
12371237
/** @deprecated */
12381238
charset?: string | undefined | null;
1239-
crossorigin?: string | undefined | null;
1239+
crossorigin?: 'anonymous' | 'use-credentials' | '' | undefined | null;
12401240
defer?: boolean | undefined | null;
12411241
fetchpriority?: 'auto' | 'high' | 'low' | undefined | null;
12421242
integrity?: string | undefined | null;
@@ -1306,7 +1306,7 @@ export interface HTMLTextareaAttributes extends HTMLAttributes<HTMLTextAreaEleme
13061306
required?: boolean | undefined | null;
13071307
rows?: number | undefined | null;
13081308
value?: string | string[] | number | undefined | null;
1309-
wrap?: string | undefined | null;
1309+
wrap?: 'hard' | 'soft' | undefined | null;
13101310

13111311
'on:change'?: ChangeEventHandler<HTMLTextAreaElement> | undefined | null;
13121312
onchange?: ChangeEventHandler<HTMLTextAreaElement> | undefined | null;

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
@@ -197,7 +197,7 @@ export function client_component(analysis, options) {
197197
)
198198
);
199199

200-
instance.body.unshift(...state.legacy_reactive_imports);
200+
module.body.unshift(...state.legacy_reactive_imports);
201201

202202
/** @type {ESTree.Statement[]} */
203203
const store_setup = [];

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,14 @@ export function Fragment(node, context) {
128128
} else if (is_single_child_not_needing_template) {
129129
context.visit(trimmed[0], state);
130130
body.push(...state.before_init, ...state.init);
131+
} else if (trimmed.length === 1 && trimmed[0].type === 'Text') {
132+
const id = b.id(context.state.scope.generate('text'));
133+
body.push(
134+
b.var(id, b.call('$.text', b.literal(trimmed[0].data))),
135+
...state.before_init,
136+
...state.init
137+
);
138+
close = b.stmt(b.call('$.append', b.id('$$anchor'), id));
131139
} else if (trimmed.length > 0) {
132140
const id = b.id(context.state.scope.generate('fragment'));
133141

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

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/** @import { Expression, MemberExpression, Program } from 'estree' */
1+
/** @import { Expression, ImportDeclaration, MemberExpression, Program } from 'estree' */
22
/** @import { ComponentContext } from '../types' */
33
import { build_getter, is_prop_source } from '../utils.js';
44
import * as b from '../../../../utils/builders.js';
@@ -16,16 +16,26 @@ export function Program(_, context) {
1616

1717
for (const [name, binding] of context.state.scope.declarations) {
1818
if (binding.declaration_kind === 'import' && binding.mutated) {
19-
const id = b.id('$$_import_' + name);
19+
// the declaration itself is hoisted to the module scope, so we need
20+
// to resort to cruder measures to differentiate instance/module imports
21+
const { start, end } = context.state.analysis.instance.ast;
22+
const node = /** @type {ImportDeclaration} */ (binding.initial);
23+
const is_instance_import =
24+
/** @type {number} */ (node.start) > /** @type {number} */ (start) &&
25+
/** @type {number} */ (node.end) < /** @type {number} */ (end);
2026

21-
context.state.transform[name] = {
22-
read: (_) => b.call(id),
23-
mutate: (_, mutation) => b.call(id, mutation)
24-
};
27+
if (is_instance_import) {
28+
const id = b.id('$$_import_' + name);
29+
30+
context.state.transform[name] = {
31+
read: (_) => b.call(id),
32+
mutate: (_, mutation) => b.call(id, mutation)
33+
};
2534

26-
context.state.legacy_reactive_imports.push(
27-
b.var(id, b.call('$.reactive_import', b.thunk(b.id(name))))
28-
);
35+
context.state.legacy_reactive_imports.push(
36+
b.var(id, b.call('$.reactive_import', b.thunk(b.id(name))))
37+
);
38+
}
2939
}
3040
}
3141
}

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

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@ import {
2727
build_style_directives
2828
} from './shared/element.js';
2929
import { process_children } from './shared/fragment.js';
30-
import { build_render_statement, build_update, build_update_assignment } from './shared/utils.js';
30+
import {
31+
build_render_statement,
32+
build_template_literal,
33+
build_update,
34+
build_update_assignment
35+
} from './shared/utils.js';
3136
import { visit_event_attribute } from './shared/events.js';
3237

3338
/**
@@ -320,28 +325,43 @@ export function RegularElement(node, context) {
320325
context.visit(node, child_state);
321326
}
322327

323-
/** @type {Expression} */
324-
let arg = context.state.node;
325-
326-
// If `hydrate_node` is set inside the element, we need to reset it
327-
// after the element has been hydrated
328-
let needs_reset = trimmed.some((node) => node.type !== 'Text');
328+
// special case — if an element that only contains text, we don't need
329+
// to descend into it if the text is non-reactive
330+
const text_content =
331+
trimmed.every((node) => node.type === 'Text' || node.type === 'ExpressionTag') &&
332+
trimmed.some((node) => node.type === 'ExpressionTag') &&
333+
build_template_literal(trimmed, context.visit, child_state);
329334

330-
// The same applies if it's a `<template>` element, since we need to
331-
// set the value of `hydrate_node` to `node.content`
332-
if (node.name === 'template') {
333-
needs_reset = true;
334-
child_state.init.push(b.stmt(b.call('$.hydrate_template', arg)));
335-
arg = b.member(arg, b.id('content'));
336-
}
335+
if (text_content && !text_content.has_state) {
336+
child_state.init.push(
337+
b.stmt(
338+
b.assignment('=', b.member(context.state.node, b.id('textContent')), text_content.value)
339+
)
340+
);
341+
} else {
342+
/** @type {Expression} */
343+
let arg = context.state.node;
344+
345+
// If `hydrate_node` is set inside the element, we need to reset it
346+
// after the element has been hydrated
347+
let needs_reset = trimmed.some((node) => node.type !== 'Text');
348+
349+
// The same applies if it's a `<template>` element, since we need to
350+
// set the value of `hydrate_node` to `node.content`
351+
if (node.name === 'template') {
352+
needs_reset = true;
353+
child_state.init.push(b.stmt(b.call('$.hydrate_template', arg)));
354+
arg = b.member(arg, b.id('content'));
355+
}
337356

338-
process_children(trimmed, () => b.call('$.child', arg), true, {
339-
...context,
340-
state: child_state
341-
});
357+
process_children(trimmed, () => b.call('$.child', arg), true, {
358+
...context,
359+
state: child_state
360+
});
342361

343-
if (needs_reset) {
344-
child_state.init.push(b.stmt(b.call('$.reset', context.state.node)));
362+
if (needs_reset) {
363+
child_state.init.push(b.stmt(b.call('$.reset', context.state.node)));
364+
}
345365
}
346366

347367
if (has_declaration) {

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

Lines changed: 13 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -34,53 +34,26 @@ export function process_children(nodes, expression, is_element, { visit, state }
3434
state.template.push(node.raw);
3535
return;
3636
}
37+
}
3738

38-
state.template.push(' ');
39+
const text_id = get_node_id(expression(true), state, 'text');
3940

40-
const text_id = get_node_id(expression(true), state, 'text');
41+
state.template.push(' ');
4142

42-
const update = b.stmt(
43-
b.call('$.set_text', text_id, /** @type {Expression} */ (visit(node.expression, state)))
44-
);
43+
const { has_state, has_call, value } = build_template_literal(sequence, visit, state);
4544

46-
if (node.metadata.expression.has_call && !within_bound_contenteditable) {
47-
state.init.push(build_update(update));
48-
} else if (node.metadata.expression.has_state && !within_bound_contenteditable) {
49-
state.update.push(update);
50-
} else {
51-
state.init.push(
52-
b.stmt(
53-
b.assignment(
54-
'=',
55-
b.member(text_id, b.id('nodeValue')),
56-
/** @type {Expression} */ (visit(node.expression))
57-
)
58-
)
59-
);
60-
}
45+
const update = b.stmt(b.call('$.set_text', text_id, value));
6146

62-
expression = (is_text) =>
63-
is_text ? b.call('$.sibling', text_id, b.true) : b.call('$.sibling', text_id);
47+
if (has_call && !within_bound_contenteditable) {
48+
state.init.push(build_update(update));
49+
} else if (has_state && !within_bound_contenteditable) {
50+
state.update.push(update);
6451
} else {
65-
const text_id = get_node_id(expression(true), state, 'text');
66-
67-
state.template.push(' ');
68-
69-
const { has_state, has_call, value } = build_template_literal(sequence, visit, state);
70-
71-
const update = b.stmt(b.call('$.set_text', text_id, value));
72-
73-
if (has_call && !within_bound_contenteditable) {
74-
state.init.push(build_update(update));
75-
} else if (has_state && !within_bound_contenteditable) {
76-
state.update.push(update);
77-
} else {
78-
state.init.push(b.stmt(b.assignment('=', b.member(text_id, b.id('nodeValue')), value)));
79-
}
80-
81-
expression = (is_text) =>
82-
is_text ? b.call('$.sibling', text_id, b.true) : b.call('$.sibling', text_id);
52+
state.init.push(b.stmt(b.assignment('=', b.member(text_id, b.id('nodeValue')), value)));
8353
}
54+
55+
expression = (is_text) =>
56+
is_text ? b.call('$.sibling', text_id, b.true) : b.call('$.sibling', text_id);
8457
}
8558

8659
for (let i = 0; i < nodes.length; i += 1) {

0 commit comments

Comments
 (0)