Skip to content

Commit e7510a0

Browse files
committed
breaking: deprecate context="module" in favor of module
Also reserve a few attributes, which we may or may not use in the future closes #12637
1 parent af35fb7 commit e7510a0

File tree

65 files changed

+235
-127
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+235
-127
lines changed

.changeset/quick-eagles-sit.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+
breaking: deprecate `context="module"` in favor of `module`

documentation/docs/02-template-syntax/01-component-fundamentals.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,16 +161,16 @@ If you'd like to react to changes to a prop, use the `$derived` or `$effect` run
161161

162162
For more information on reactivity, read the documentation around runes.
163163

164-
## <script context="module">
164+
## <script module>
165165

166-
A `<script>` tag with a `context="module"` attribute runs once when the module first evaluates, rather than for each component instance. Values declared in this block are accessible from a regular `<script>` (and the component markup) but not vice versa.
166+
A `<script>` tag with a `module` attribute runs once when the module first evaluates, rather than for each component instance. Values declared in this block are accessible from a regular `<script>` (and the component markup) but not vice versa.
167167

168168
You can `export` bindings from this block, and they will become exports of the compiled module.
169169

170170
You cannot `export default`, since the default export is the component itself.
171171

172172
```svelte
173-
<script context="module">
173+
<script module>
174174
let totalComponents = 0;
175175
176176
// the export keyword allows this function to imported with e.g.

packages/svelte/messages/compile-errors/script.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
1717
## declaration_duplicate_module_import
1818

19-
> Cannot declare same variable name which is imported inside `<script context="module">`
19+
> Cannot declare same variable name which is imported inside `<script module>`
2020
2121
## derived_invalid_export
2222

@@ -152,7 +152,7 @@
152152
153153
## store_invalid_subscription
154154

155-
> Cannot reference store value inside `<script context="module">`
155+
> Cannot reference store value inside `<script module>`
156156
157157
## store_invalid_subscription_module
158158

packages/svelte/messages/compile-errors/template.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,12 +216,20 @@ HTML restricts where certain elements can appear. In case of a violation the bro
216216
217217
## script_duplicate
218218

219-
> A component can have a single top-level `<script>` element and/or a single top-level `<script context="module">` element
219+
> A component can have a single top-level `<script>` element and/or a single top-level `<script module>` element
220+
221+
## script_invalid_attribute_value
222+
223+
> If the %name% attribute is supplied, it must be a boolean attribute
220224
221225
## script_invalid_context
222226

223227
> If the context attribute is supplied, its value must be "module"
224228
229+
## script_reserved_attribute
230+
231+
> The %name% attribute is reserved and cannot be used
232+
225233
## slot_attribute_duplicate
226234

227235
> Duplicate slot name '%name%' in <%component%>

packages/svelte/messages/compile-warnings/template.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ HTML restricts where certain elements can appear. In case of a violation the bro
5050

5151
This code will work when the component is rendered on the client (which is why this is a warning rather than an error), but if you use server rendering it will cause hydration to fail.
5252

53+
## script_context_deprecated
54+
55+
> `context="module"` is deprecated, use the `module` attribute instead
56+
5357
## slot_element_deprecated
5458

5559
> Using `<slot>` to render parent content is deprecated. Use `{@render ...}` tags instead

packages/svelte/src/compiler/errors.js

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,12 @@ export function declaration_duplicate(node, name) {
9999
}
100100

101101
/**
102-
* Cannot declare same variable name which is imported inside `<script context="module">`
102+
* Cannot declare same variable name which is imported inside `<script module>`
103103
* @param {null | number | NodeLike} node
104104
* @returns {never}
105105
*/
106106
export function declaration_duplicate_module_import(node) {
107-
e(node, "declaration_duplicate_module_import", "Cannot declare same variable name which is imported inside `<script context=\"module\">`");
107+
e(node, "declaration_duplicate_module_import", "Cannot declare same variable name which is imported inside `<script module>`");
108108
}
109109

110110
/**
@@ -417,12 +417,12 @@ export function store_invalid_scoped_subscription(node) {
417417
}
418418

419419
/**
420-
* Cannot reference store value inside `<script context="module">`
420+
* Cannot reference store value inside `<script module>`
421421
* @param {null | number | NodeLike} node
422422
* @returns {never}
423423
*/
424424
export function store_invalid_subscription(node) {
425-
e(node, "store_invalid_subscription", "Cannot reference store value inside `<script context=\"module\">`");
425+
e(node, "store_invalid_subscription", "Cannot reference store value inside `<script module>`");
426426
}
427427

428428
/**
@@ -1044,12 +1044,22 @@ export function render_tag_invalid_spread_argument(node) {
10441044
}
10451045

10461046
/**
1047-
* A component can have a single top-level `<script>` element and/or a single top-level `<script context="module">` element
1047+
* A component can have a single top-level `<script>` element and/or a single top-level `<script module>` element
10481048
* @param {null | number | NodeLike} node
10491049
* @returns {never}
10501050
*/
10511051
export function script_duplicate(node) {
1052-
e(node, "script_duplicate", "A component can have a single top-level `<script>` element and/or a single top-level `<script context=\"module\">` element");
1052+
e(node, "script_duplicate", "A component can have a single top-level `<script>` element and/or a single top-level `<script module>` element");
1053+
}
1054+
1055+
/**
1056+
* If the %name% attribute is supplied, it must be a boolean attribute
1057+
* @param {null | number | NodeLike} node
1058+
* @param {string} name
1059+
* @returns {never}
1060+
*/
1061+
export function script_invalid_attribute_value(node, name) {
1062+
e(node, "script_invalid_attribute_value", `If the ${name} attribute is supplied, it must be a boolean attribute`);
10531063
}
10541064

10551065
/**
@@ -1061,6 +1071,16 @@ export function script_invalid_context(node) {
10611071
e(node, "script_invalid_context", "If the context attribute is supplied, its value must be \"module\"");
10621072
}
10631073

1074+
/**
1075+
* The %name% attribute is reserved and cannot be used
1076+
* @param {null | number | NodeLike} node
1077+
* @param {string} name
1078+
* @returns {never}
1079+
*/
1080+
export function script_reserved_attribute(node, name) {
1081+
e(node, "script_reserved_attribute", `The ${name} attribute is reserved and cannot be used`);
1082+
}
1083+
10641084
/**
10651085
* Duplicate slot name '%name%' in <%component%>
10661086
* @param {null | number | NodeLike} node

packages/svelte/src/compiler/migrate/index.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
/** @import { VariableDeclarator, Node, Identifier } from 'estree' */
2-
/** @import { SvelteNode } from '../types/template.js' */
32
/** @import { Visitors } from 'zimmerframe' */
43
/** @import { ComponentAnalysis } from '../phases/types.js' */
54
/** @import { Scope } from '../phases/scope.js' */
@@ -58,6 +57,13 @@ export function migrate(source) {
5857
needs_run: false
5958
};
6059

60+
if (parsed.module) {
61+
const context = parsed.module.attributes.find((attr) => attr.name === 'context');
62+
if (context) {
63+
state.str.update(context.start, context.end, 'module');
64+
}
65+
}
66+
6167
if (parsed.instance) {
6268
walk(parsed.instance.content, state, instance_script);
6369
}
@@ -223,7 +229,7 @@ export function migrate(source) {
223229
* }} State
224230
*/
225231

226-
/** @type {Visitors<SvelteNode, State>} */
232+
/** @type {Visitors<Compiler.SvelteNode, State>} */
227233
const instance_script = {
228234
_(node, { state, next }) {
229235
// @ts-expect-error
@@ -472,7 +478,7 @@ const instance_script = {
472478
}
473479
};
474480

475-
/** @type {Visitors<SvelteNode, State>} */
481+
/** @type {Visitors<Compiler.SvelteNode, State>} */
476482
const template = {
477483
Identifier(node, { state, path }) {
478484
handle_identifier(node, state, path);
@@ -590,7 +596,7 @@ const template = {
590596
/**
591597
* @param {VariableDeclarator} declarator
592598
* @param {MagicString} str
593-
* @param {Compiler.SvelteNode[]} path
599+
* @param {Array<Compiler.SvelteNode>} path
594600
*/
595601
function extract_type_and_comment(declarator, str, path) {
596602
const parent = path.at(-1);

packages/svelte/src/compiler/phases/1-parse/read/script.js

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import * as acorn from '../acorn.js';
55
import { regex_not_newline_characters } from '../../patterns.js';
66
import * as e from '../../../errors.js';
7+
import * as w from '../../../warnings.js';
78

89
const regex_closing_script_tag = /<\/script\s*>/;
910
const regex_starts_with_closing_script_tag = /^<\/script\s*>/;
@@ -13,22 +14,42 @@ const regex_starts_with_closing_script_tag = /^<\/script\s*>/;
1314
* @returns {string}
1415
*/
1516
function get_context(attributes) {
16-
const context = attributes.find(
17-
/** @param {any} attribute */ (attribute) => attribute.name === 'context'
18-
);
19-
if (!context) return 'default';
17+
for (const attribute of attributes) {
18+
switch (attribute.name) {
19+
case 'context': {
20+
if (attribute.value.length !== 1 || attribute.value[0].type !== 'Text') {
21+
e.script_invalid_context(attribute.start);
22+
}
2023

21-
if (context.value.length !== 1 || context.value[0].type !== 'Text') {
22-
e.script_invalid_context(context.start);
23-
}
24+
const value = attribute.value[0].data;
25+
26+
if (value !== 'module') {
27+
e.script_invalid_context(attribute.start);
28+
}
29+
30+
w.script_context_deprecated(attribute);
2431

25-
const value = context.value[0].data;
32+
return value;
33+
}
34+
case 'module': {
35+
if (attribute.value !== true) {
36+
// Deliberately a generic code to future-proof for potential other attributes
37+
e.script_invalid_attribute_value(attribute.start, attribute.name);
38+
}
2639

27-
if (value !== 'module') {
28-
e.script_invalid_context(context.start);
40+
return 'module';
41+
}
42+
case 'server':
43+
case 'client':
44+
case 'worker':
45+
case 'test':
46+
case 'default': {
47+
e.script_reserved_attribute(attribute.start, attribute.name);
48+
}
49+
}
2950
}
3051

31-
return value;
52+
return 'default';
3253
}
3354

3455
/**

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ export function analyze_component(root, source, options) {
330330

331331
if (module.ast) {
332332
for (const { node, path } of references) {
333-
// if the reference is inside context="module", error. this is a bit hacky but it works
333+
// if the reference is inside module, error. this is a bit hacky but it works
334334
if (
335335
/** @type {number} */ (node.start) > /** @type {number} */ (module.ast.start) &&
336336
/** @type {number} */ (node.end) < /** @type {number} */ (module.ast.end) &&

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export interface Root extends BaseNode {
6161
css: Css.StyleSheet | null;
6262
/** The parsed `<script>` element, if exists */
6363
instance: Script | null;
64-
/** The parsed `<script context="module">` element, if exists */
64+
/** The parsed `<script module>` element, if exists */
6565
module: Script | null;
6666
metadata: {
6767
/** Whether the component was parsed with typescript */

packages/svelte/src/compiler/warnings.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ export const codes = [
117117
"element_invalid_self_closing_tag",
118118
"event_directive_deprecated",
119119
"node_invalid_placement_ssr",
120+
"script_context_deprecated",
120121
"slot_element_deprecated",
121122
"svelte_component_deprecated",
122123
"svelte_element_invalid_this"
@@ -769,6 +770,14 @@ export function node_invalid_placement_ssr(node, thing, parent) {
769770
w(node, "node_invalid_placement_ssr", `${thing} is invalid inside \`<${parent}>\`. When rendering this component on the server, the resulting HTML will be modified by the browser, likely resulting in a \`hydration_mismatch\` warning`);
770771
}
771772

773+
/**
774+
* `context="module"` is deprecated, use the `module` attribute instead
775+
* @param {null | NodeLike} node
776+
*/
777+
export function script_context_deprecated(node) {
778+
w(node, "script_context_deprecated", "`context=\"module\"` is deprecated, use the `module` attribute instead");
779+
}
780+
772781
/**
773782
* Using `<slot>` to render parent content is deprecated. Use `{@render ...}` tags instead
774783
* @param {null | NodeLike} node

packages/svelte/tests/compiler-errors/samples/export-state-module/main.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<script context="module">
1+
<script module>
22
export const object = $state({
33
ok: true
44
});

packages/svelte/tests/compiler-errors/samples/runes-export-let/main.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<script context="module">
1+
<script module>
22
export let bar = ''; // check that it doesn't error here already
33
</script>
44

packages/svelte/tests/compiler-errors/samples/store-autosub-context-module/_config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { test } from '../../test';
33
export default test({
44
error: {
55
code: 'store_invalid_subscription',
6-
message: 'Cannot reference store value inside `<script context="module">`',
6+
message: 'Cannot reference store value inside `<script module>`',
77
position: [164, 168]
88
}
99
});

packages/svelte/tests/compiler-errors/samples/store-autosub-context-module/main.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<script context="module">
1+
<script module>
22
// this should be fine (state rune is not treated as a store)
33
const state = $state(0);
44
// this is not
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<script context="module">
2+
let foo = true;
3+
</script>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<script module>
2+
let foo = true;
3+
</script>

packages/svelte/tests/parser-modern/samples/options/input.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<svelte:options customElement="my-custom-element" runes={true} />
22

3-
<script context="module" lang="ts">
3+
<script module lang="ts">
44
</script>
55

66
<script lang="ts" generics="T extends { foo: number }">
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
<script context="module">
2-
import Tooltip from './Tooltip.svelte';
1+
<script module>
2+
import Tooltip from './Tooltip.svelte';
33
4-
export const Widget = { Tooltip };
5-
</script>
4+
export const Widget = { Tooltip };
5+
</script>
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
<script context="module">
2-
import Foo from './Foo.svelte';
1+
<script module>
2+
import Foo from './Foo.svelte';
33
4-
export const Components = { Foo };
5-
</script>
4+
export const Components = { Foo };
5+
</script>

0 commit comments

Comments
 (0)