Skip to content

Commit 72a309b

Browse files
committed
Merge branch 'main' into new-spring
2 parents 1e457a8 + 3d36e15 commit 72a309b

File tree

33 files changed

+321
-48
lines changed

33 files changed

+321
-48
lines changed

.changeset/wild-islands-help.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

documentation/docs/03-template-syntax/06-snippet.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,23 @@ We can tighten things up further by declaring a generic, so that `data` and `row
246246
</script>
247247
```
248248

249+
## Exporting snippets
250+
251+
Snippets declared at the top level of a `.svelte` file can be exported from a `<script module>` for use in other components, provided they don't reference any declarations in a non-module `<script>` (whether directly or indirectly, via other snippets) ([demo](/playground/untitled#H4sIAAAAAAAAE3WPwY7CMAxEf8UyB1hRgdhjl13Bga8gHFJipEqtGyUGFUX5dxJUtEB3b9bYM_MckHVLWOKut50TMuC5tpbEY4GnuiGP5T6gXG0-ykLSB8vW2oW_UCNZq7Snv_Rjx0Kc4kpc-6OrrfwoVlK3uQ4CaGMgwsl1LUwXy0f54J9-KV4vf20cNo7YkMu22aqAz4-oOLUI9YKluDPF4h_at-hX5PFyzA1tZ84N3fGpf8YfUU6GvDumLqDKmEqCjjCHUEX4hqDTWCU5PJ6Or38c4g1cPu9tnAEAAA==)):
252+
253+
```svelte
254+
<script module>
255+
export { add };
256+
</script>
257+
258+
{#snippet add(a, b)}
259+
{a} + {b} = {a + b}
260+
{/snippet}
261+
```
262+
263+
> [!NOTE]
264+
> This requires Svelte 5.5.0 or newer
265+
249266
## Programmatic snippets
250267

251268
Snippets can be created programmatically with the [`createRawSnippet`](svelte#createRawSnippet) API. This is intended for advanced use cases.

documentation/docs/07-misc/03-typescript.md

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,26 +40,26 @@ If you want to use one of these features, you need to setup up a `script` prepro
4040

4141
To use non-type-only TypeScript features within Svelte components, you need to add a preprocessor that will turn TypeScript into JavaScript.
4242

43-
### Using SvelteKit or Vite
44-
45-
The easiest way to get started is scaffolding a new SvelteKit project by typing `npx sv create`, following the prompts and choosing the TypeScript option.
46-
4743
```ts
4844
/// file: svelte.config.js
4945
// @noErrors
50-
import { vitePreprocess } from '@sveltejs/kit/vite';
46+
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
5147

5248
const config = {
53-
preprocess: vitePreprocess()
49+
// Note the additional `{ script: true }`
50+
preprocess: vitePreprocess({ script: true })
5451
};
5552

5653
export default config;
5754
```
5855

59-
If you don't need or want all the features SvelteKit has to offer, you can scaffold a Svelte-flavoured Vite project instead by typing `npm create vite@latest` and selecting the `svelte-ts` option.
56+
### Using SvelteKit or Vite
57+
58+
The easiest way to get started is scaffolding a new SvelteKit project by typing `npx sv create`, following the prompts and choosing the TypeScript option.
6059

6160
```ts
6261
/// file: svelte.config.js
62+
// @noErrors
6363
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
6464

6565
const config = {
@@ -69,6 +69,8 @@ const config = {
6969
export default config;
7070
```
7171

72+
If you don't need or want all the features SvelteKit has to offer, you can scaffold a Svelte-flavoured Vite project instead by typing `npm create vite@latest` and selecting the `svelte-ts` option.
73+
7274
In both cases, a `svelte.config.js` with `vitePreprocess` will be added. Vite/SvelteKit will read from this config file.
7375

7476
### Other build tools
@@ -77,6 +79,14 @@ If you're using tools like Rollup or Webpack instead, install their respective S
7779

7880
> [!NOTE] If you're starting a new project, we recommend using SvelteKit or Vite instead
7981
82+
## tsconfig.json settings
83+
84+
When using TypeScript, make sure your `tsconfig.json` is setup correctly.
85+
86+
- Use a [`target`](https://www.typescriptlang.org/tsconfig/#target) of at least `ES2022`, or a `target` of at least `ES2015` alongside [`useDefineForClassFields`](https://www.typescriptlang.org/tsconfig/#useDefineForClassFields). This ensures that rune declarations on class fields are not messed with, which would break the Svelte compiler
87+
- Set [`verbatimModuleSyntax`](https://www.typescriptlang.org/tsconfig/#verbatimModuleSyntax) to `true` so that imports are left as-is
88+
- Set [`isolatedModules`](https://www.typescriptlang.org/tsconfig/#isolatedModules) to `true` so that each file is looked at in isolation. TypeScript has a few features which require cross-file analysis and compilation, which the Svelte compiler and tooling like Vite don't do.
89+
8090
## Typing `$props`
8191

8292
Type `$props` just like a regular object with certain properties.

documentation/docs/98-reference/.generated/compile-errors.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,12 @@ Expected token %token%
400400
Expected whitespace
401401
```
402402

403+
### export_undefined
404+
405+
```
406+
`%name%` is not defined
407+
```
408+
403409
### global_reference_invalid
404410

405411
```
@@ -694,6 +700,30 @@ Cannot use `<slot>` syntax and `{@render ...}` tags in the same component. Migra
694700
Cannot use explicit children snippet at the same time as implicit children content. Remove either the non-whitespace content or the children snippet block
695701
```
696702

703+
### snippet_invalid_export
704+
705+
```
706+
An exported snippet can only reference things declared in a `<script module>`, or other exportable snippets
707+
```
708+
709+
It's possible to export a snippet from a `<script module>` block, but only if it doesn't reference anything defined inside a non-module-level `<script>`. For example you can't do this...
710+
711+
```svelte
712+
<script module>
713+
export { greeting };
714+
</script>
715+
716+
<script>
717+
let message = 'hello';
718+
</script>
719+
720+
{#snippet greeting(name)}
721+
<p>{message} {name}!</p>
722+
{/snippet}
723+
```
724+
725+
...because `greeting` references `message`, which is defined in the second `<script>`.
726+
697727
### snippet_invalid_rest_parameter
698728

699729
```

packages/svelte/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# svelte
22

3+
## 5.5.0
4+
5+
### Minor Changes
6+
7+
- feat: allow snippets to be exported from module scripts ([#14315](https://github.com/sveltejs/svelte/pull/14315))
8+
9+
### Patch Changes
10+
11+
- fix: ignore TypeScript generics on variables ([#14509](https://github.com/sveltejs/svelte/pull/14509))
12+
313
## 5.4.0
414

515
### Minor Changes

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@
3838

3939
> `$effect()` can only be used as an expression statement
4040
41+
## export_undefined
42+
43+
> `%name%` is not defined
44+
4145
## global_reference_invalid
4246

4347
> `%name%` is an illegal variable name. To reference a global variable called `%name%`, use `globalThis.%name%`
@@ -134,6 +138,28 @@
134138

135139
> %name% cannot be used in runes mode
136140
141+
## snippet_invalid_export
142+
143+
> An exported snippet can only reference things declared in a `<script module>`, or other exportable snippets
144+
145+
It's possible to export a snippet from a `<script module>` block, but only if it doesn't reference anything defined inside a non-module-level `<script>`. For example you can't do this...
146+
147+
```svelte
148+
<script module>
149+
export { greeting };
150+
</script>
151+
152+
<script>
153+
let message = 'hello';
154+
</script>
155+
156+
{#snippet greeting(name)}
157+
<p>{message} {name}!</p>
158+
{/snippet}
159+
```
160+
161+
...because `greeting` references `message`, which is defined in the second `<script>`.
162+
137163
## snippet_parameter_assignment
138164

139165
> Cannot reassign or bind to snippet parameter

packages/svelte/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "svelte",
33
"description": "Cybernetically enhanced web apps",
44
"license": "MIT",
5-
"version": "5.4.0",
5+
"version": "5.5.0",
66
"type": "module",
77
"types": "./types/index.d.ts",
88
"engines": {

packages/svelte/src/compiler/errors.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,16 @@ export function effect_invalid_placement(node) {
168168
e(node, "effect_invalid_placement", "`$effect()` can only be used as an expression statement");
169169
}
170170

171+
/**
172+
* `%name%` is not defined
173+
* @param {null | number | NodeLike} node
174+
* @param {string} name
175+
* @returns {never}
176+
*/
177+
export function export_undefined(node, name) {
178+
e(node, "export_undefined", `\`${name}\` is not defined`);
179+
}
180+
171181
/**
172182
* `%name%` is an illegal variable name. To reference a global variable called `%name%`, use `globalThis.%name%`
173183
* @param {null | number | NodeLike} node
@@ -395,6 +405,15 @@ export function runes_mode_invalid_import(node, name) {
395405
e(node, "runes_mode_invalid_import", `${name} cannot be used in runes mode`);
396406
}
397407

408+
/**
409+
* An exported snippet can only reference things declared in a `<script module>`, or other exportable snippets
410+
* @param {null | number | NodeLike} node
411+
* @returns {never}
412+
*/
413+
export function snippet_invalid_export(node) {
414+
e(node, "snippet_invalid_export", "An exported snippet can only reference things declared in a `<script module>`, or other exportable snippets");
415+
}
416+
398417
/**
399418
* Cannot reassign or bind to snippet parameter
400419
* @param {null | number | NodeLike} node

packages/svelte/src/compiler/phases/1-parse/acorn.js

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,43 @@ const ParserWithTS = acorn.Parser.extend(tsPlugin({ allowSatisfies: true }));
1010
/**
1111
* @param {string} source
1212
* @param {boolean} typescript
13+
* @param {boolean} [is_script]
1314
*/
14-
export function parse(source, typescript) {
15+
export function parse(source, typescript, is_script) {
1516
const parser = typescript ? ParserWithTS : acorn.Parser;
1617
const { onComment, add_comments } = get_comment_handlers(source);
17-
18-
const ast = parser.parse(source, {
19-
onComment,
20-
sourceType: 'module',
21-
ecmaVersion: 13,
22-
locations: true
23-
});
18+
// @ts-ignore
19+
const parse_statement = parser.prototype.parseStatement;
20+
21+
// If we're dealing with a <script> then it might contain an export
22+
// for something that doesn't exist directly inside but is inside the
23+
// component instead, so we need to ensure that Acorn doesn't throw
24+
// an error in these cases
25+
if (is_script) {
26+
// @ts-ignore
27+
parser.prototype.parseStatement = function (...args) {
28+
const v = parse_statement.call(this, ...args);
29+
// @ts-ignore
30+
this.undefinedExports = {};
31+
return v;
32+
};
33+
}
34+
35+
let ast;
36+
37+
try {
38+
ast = parser.parse(source, {
39+
onComment,
40+
sourceType: 'module',
41+
ecmaVersion: 13,
42+
locations: true
43+
});
44+
} finally {
45+
if (is_script) {
46+
// @ts-ignore
47+
parser.prototype.parseStatement = parse_statement;
48+
}
49+
}
2450

2551
if (typescript) amend(source, ast);
2652
add_comments(ast);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export function read_script(parser, start, attributes) {
3434
let ast;
3535

3636
try {
37-
ast = acorn.parse(source, parser.ts);
37+
ast = acorn.parse(source, parser.ts, true);
3838
} catch (err) {
3939
parser.acorn_error(err);
4040
}

packages/svelte/src/compiler/phases/1-parse/state/tag.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ function open(parser) {
325325
parameters: function_expression.params,
326326
body: create_fragment(),
327327
metadata: {
328+
can_hoist: false,
328329
sites: new Set()
329330
}
330331
});

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,6 @@ export function analyze_component(root, source, options) {
429429
reactive_statements: new Map(),
430430
binding_groups: new Map(),
431431
slot_names: new Map(),
432-
top_level_snippets: [],
433432
css: {
434433
ast: root.css,
435434
hash: root.css
@@ -443,6 +442,7 @@ export function analyze_component(root, source, options) {
443442
keyframes: []
444443
},
445444
source,
445+
undefined_exports: new Map(),
446446
snippet_renderers: new Map(),
447447
snippets: new Set()
448448
};
@@ -697,6 +697,17 @@ export function analyze_component(root, source, options) {
697697
analysis.reactive_statements = order_reactive_statements(analysis.reactive_statements);
698698
}
699699

700+
for (const node of analysis.module.ast.body) {
701+
if (node.type === 'ExportNamedDeclaration' && node.specifiers !== null) {
702+
for (const specifier of node.specifiers) {
703+
if (specifier.local.type !== 'Identifier') continue;
704+
705+
const binding = analysis.module.scope.get(specifier.local.name);
706+
if (!binding) e.export_undefined(specifier, specifier.local.name);
707+
}
708+
}
709+
}
710+
700711
if (analysis.event_directive_node && analysis.uses_event_attributes) {
701712
e.mixed_event_handler_syntaxes(
702713
analysis.event_directive_node,

0 commit comments

Comments
 (0)