Skip to content

Commit 732dbf7

Browse files
breaking: deprecate context="module" in favor of module (#12948)
* 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 * fix tests * one more * add svelte package to the root so eslint and prettier can use it * tweak messages * warn on unknown attributes * regenerate --------- Co-authored-by: Rich Harris <[email protected]>
1 parent 3b3ed77 commit 732dbf7

File tree

74 files changed

+339
-258
lines changed

Some content is hidden

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

74 files changed

+339
-258
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-
## &lt;script context="module"&gt;
164+
## &lt;script module&gt;
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.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"playwright": "^1.41.1",
4141
"prettier": "^3.2.4",
4242
"prettier-plugin-svelte": "^3.1.2",
43+
"svelte": "workspace:^",
4344
"typescript": "^5.5.2",
4445
"typescript-eslint": "^8.0.0-alpha.34",
4546
"v8-natives": "^1.2.5",

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 a variable with the same name as an import 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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ 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+
57+
## script_unknown_attribute
58+
59+
> Unrecognized attribute — should be one of `generics`, `lang` or `module`. If this exists for a preprocessor, ensure that the preprocessor removes it
60+
5361
## slot_element_deprecated
5462

5563
> 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 a variable with the same name as an import 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 a variable with the same name as an import 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: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,14 @@
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';
8+
import { is_text_attribute } from '../../../utils/ast.js';
79

810
const regex_closing_script_tag = /<\/script\s*>/;
911
const regex_starts_with_closing_script_tag = /^<\/script\s*>/;
1012

11-
/**
12-
* @param {any[]} attributes
13-
* @returns {string}
14-
*/
15-
function get_context(attributes) {
16-
const context = attributes.find(
17-
/** @param {any} attribute */ (attribute) => attribute.name === 'context'
18-
);
19-
if (!context) return 'default';
20-
21-
if (context.value.length !== 1 || context.value[0].type !== 'Text') {
22-
e.script_invalid_context(context.start);
23-
}
24-
25-
const value = context.value[0].data;
26-
27-
if (value !== 'module') {
28-
e.script_invalid_context(context.start);
29-
}
30-
31-
return value;
32-
}
13+
const RESERVED_ATTRIBUTES = ['server', 'client', 'worker', 'test', 'default'];
14+
const ALLOWED_ATTRIBUTES = ['context', 'generics', 'lang', 'module'];
3315

3416
/**
3517
* @param {Parser} parser
@@ -60,14 +42,56 @@ export function read_script(parser, start, attributes) {
6042
// TODO is this necessary?
6143
ast.start = script_start;
6244

45+
/** @type {'default' | 'module'} */
46+
let context = 'default';
47+
48+
for (const attribute of /** @type {Attribute[]} */ (attributes)) {
49+
if (RESERVED_ATTRIBUTES.includes(attribute.name)) {
50+
e.script_reserved_attribute(attribute, attribute.name);
51+
}
52+
53+
if (!ALLOWED_ATTRIBUTES.includes(attribute.name)) {
54+
w.script_unknown_attribute(attribute);
55+
}
56+
57+
if (attribute.name === 'module') {
58+
if (attribute.value !== true) {
59+
// Deliberately a generic code to future-proof for potential other attributes
60+
e.script_invalid_attribute_value(attribute, attribute.name);
61+
}
62+
63+
context = 'module';
64+
}
65+
66+
if (attribute.name === 'context') {
67+
if (attribute.value === true || !is_text_attribute(attribute)) {
68+
throw new Error('TODO');
69+
}
70+
71+
if (attribute.value.length !== 1 || attribute.value[0].type !== 'Text') {
72+
e.script_invalid_context(attribute);
73+
}
74+
75+
const value = attribute.value[0].data;
76+
77+
if (value !== 'module') {
78+
e.script_invalid_context(attribute);
79+
}
80+
81+
w.script_context_deprecated(attribute);
82+
83+
context = 'module';
84+
}
85+
}
86+
6387
return {
6488
type: 'Script',
6589
start,
6690
end: parser.index,
67-
context: get_context(attributes),
91+
context,
6892
content: ast,
6993
parent: null,
7094
// @ts-ignore
71-
attributes: attributes
95+
attributes
7296
};
7397
}

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) &&

0 commit comments

Comments
 (0)