diff --git a/.changeset/proud-cycles-hunt.md b/.changeset/proud-cycles-hunt.md
deleted file mode 100644
index 5f0f88cb69..0000000000
--- a/.changeset/proud-cycles-hunt.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: align `disclose-version` exports specification
diff --git a/.changeset/six-teachers-divide.md b/.changeset/six-teachers-divide.md
deleted file mode 100644
index a11f257fef..0000000000
--- a/.changeset/six-teachers-divide.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'svelte': patch
----
-
-fix: check srcset when hydrating to prevent needless requests
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c92e8a5355..b8e948b3a4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -7,7 +7,7 @@ permissions:
contents: read # to fetch code (actions/checkout)
env:
- # We only install Chromium manually
+ # We only install Chromium manually
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
jobs:
diff --git a/documentation/blog/2020-07-17-svelte-and-typescript.md b/documentation/blog/2020-07-17-svelte-and-typescript.md
index bcca836d4e..a6e07242ec 100644
--- a/documentation/blog/2020-07-17-svelte-and-typescript.md
+++ b/documentation/blog/2020-07-17-svelte-and-typescript.md
@@ -16,13 +16,9 @@ We think it'll give you a much nicer development experience — one that also sc
## Try it now
-You can start a new Svelte TypeScript project using the [normal template](https://github.com/sveltejs/template) and by running `node scripts/setupTypeScript.js` before you do anything else:
+You can start a new Svelte TypeScript project using Svelte's official scaffolding CLI by running `npm create svelte@latest` and following the prompts. This sets up a new SvelteKit project for you.
-```bash
-npx degit sveltejs/template svelte-typescript-app
-cd svelte-typescript-app
-node scripts/setupTypeScript.js
-```
+Alternatively you can run `npm create vite@latest myapp -- --template svelte-ts` to scaffold a Vite project using Svelte and TypeScript.
If you're a VS Code user, make sure you're using the (new) [official extension](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode), which replaces the popular extension by James Birtles.
Later in this blog post, we'll detail the individual steps involved in using TypeScript in an existing Svelte project.
diff --git a/documentation/docs/05-misc/04-v4-migration-guide.md b/documentation/docs/05-misc/04-v4-migration-guide.md
index 0f660b68a1..4b634a978e 100644
--- a/documentation/docs/05-misc/04-v4-migration-guide.md
+++ b/documentation/docs/05-misc/04-v4-migration-guide.md
@@ -121,7 +121,18 @@ The migration script will do both automatically for you. ([#8512](https://github
## Transitions are local by default
-Transitions are now local by default to prevent confusion around page navigations. To make them global, add the `|global` modifier. The migration script will do this automatically for you. ([#6686](https://github.com/sveltejs/svelte/issues/6686))
+Transitions are now local by default to prevent confusion around page navigations. "local" means that a transition will not play if it's within a nested control flow block (`each/if/await/key`) and not the direct parent block but a block above it is created/destroyed. In the following example, the `slide` intro animation will only play when `success` goes from `false` to `true`, but it will _not_ play when `show` goes from `false` to `true`:
+
+```svelte
+{#if show}
+ ...
+ {#if success}
+
Success
+ {/each}
+{/if}
+```
+
+To make transitions global, add the `|global` modifier - then they will play when _any_ control flow block above is created/destroyed. The migration script will do this automatically for you. ([#6686](https://github.com/sveltejs/svelte/issues/6686))
## Default slot bindings
@@ -148,6 +159,10 @@ This makes slot bindings more consistent as the behavior is undefined when for e
The order in which preprocessors are applied has changed. Now, preprocessors are executed in order, and within one group, the order is markup, script, style. Each preprocessor must also have a name. ([#8618](https://github.com/sveltejs/svelte/issues/8618))
+## New eslint package
+
+`eslint-plugin-svelte3` is deprecated. It may still work with Svelte 4 but we make no guarantees about that. We recommend switching to our new package [eslint-plugin-svelte](https://github.com/sveltejs/eslint-plugin-svelte). See [this Github post](https://github.com/sveltejs/kit/issues/10242#issuecomment-1610798405) for an instruction how to migrate. Alternatively, you can create a new project using `npm create svelte@latest`, select the eslint (and possibly TypeScript) option and then copy over the related files into your existing project.
+
## Other breaking changes
- the `inert` attribute is now applied to outroing elements to make them invisible to assistive technology and prevent interaction. ([#8628](https://github.com/sveltejs/svelte/pull/8628))
diff --git a/documentation/tutorial/02-reactivity/01-reactive-assignments/app-a/App.svelte b/documentation/tutorial/02-reactivity/01-reactive-assignments/app-a/App.svelte
index 836e573dc6..8b1569bffb 100644
--- a/documentation/tutorial/02-reactivity/01-reactive-assignments/app-a/App.svelte
+++ b/documentation/tutorial/02-reactivity/01-reactive-assignments/app-a/App.svelte
@@ -10,3 +10,9 @@
Clicked {count}
{count === 1 ? 'time' : 'times'}
+
+
diff --git a/documentation/tutorial/02-reactivity/01-reactive-assignments/app-b/App.svelte b/documentation/tutorial/02-reactivity/01-reactive-assignments/app-b/App.svelte
index 382b2ee9e0..d51ca2e068 100644
--- a/documentation/tutorial/02-reactivity/01-reactive-assignments/app-b/App.svelte
+++ b/documentation/tutorial/02-reactivity/01-reactive-assignments/app-b/App.svelte
@@ -10,3 +10,9 @@
Clicked {count}
{count === 1 ? 'time' : 'times'}
+
+
diff --git a/documentation/tutorial/05-events/02-inline-handlers/text.md b/documentation/tutorial/05-events/02-inline-handlers/text.md
index 2fe1c48e2c..99ae43c429 100644
--- a/documentation/tutorial/05-events/02-inline-handlers/text.md
+++ b/documentation/tutorial/05-events/02-inline-handlers/text.md
@@ -10,6 +10,4 @@ You can also declare event handlers inline:
```
-The quote marks are optional, but they're helpful for syntax highlighting in some environments.
-
> In some frameworks you may see recommendations to avoid inline event handlers for performance reasons, particularly inside loops. That advice doesn't apply to Svelte — the compiler will always do the right thing, whichever form you choose.
diff --git a/documentation/tutorial/10-transitions/09-key-blocks/text.md b/documentation/tutorial/10-transitions/09-key-blocks/text.md
index 7a2e6eaca7..d0d9163d0d 100644
--- a/documentation/tutorial/10-transitions/09-key-blocks/text.md
+++ b/documentation/tutorial/10-transitions/09-key-blocks/text.md
@@ -5,8 +5,10 @@ title: Key blocks
Key blocks destroy and recreate their contents when the value of an expression changes.
```svelte
-{#key value}
-
{value}
+{#key number}
+
+ {number}
+
{/key}
```
diff --git a/package.json b/package.json
index 47afc17e58..e0f1bdcfd9 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,7 @@
"build": "pnpm -r --filter=./packages/* build",
"build:sites": "pnpm -r --filter=./sites/* build",
"check": "cd packages/svelte && pnpm build && cd ../../ && pnpm -r check",
- "lint": "pnpm -r lint",
+ "lint": "cd packages/svelte && pnpm build && cd ../../ && pnpm -r lint",
"format": "pnpm -r format",
"changeset:version": "changeset version && pnpm -r generate:version && git add --all",
"changeset:publish": "changeset publish"
diff --git a/packages/svelte/.prettierignore b/packages/svelte/.prettierignore
index 3f6fbefc04..fef52b1e8f 100644
--- a/packages/svelte/.prettierignore
+++ b/packages/svelte/.prettierignore
@@ -16,6 +16,7 @@ sites/svelte.dev/.vercel
/test/**/expected*
/test/**/_output
/test/**/shards/*.test.js
+/test/hydration/samples/raw-repair/_after.html
/types
!rollup.config.js
!vitest.config.js
diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md
index ffa517208d..94dd154174 100644
--- a/packages/svelte/CHANGELOG.md
+++ b/packages/svelte/CHANGELOG.md
@@ -1,5 +1,33 @@
# svelte
+## 4.0.4
+
+### Patch Changes
+
+- fix: claim svg tags in raw mustache tags correctly ([#8910](https://github.com/sveltejs/svelte/pull/8910))
+
+- fix: repair invalid raw html content during hydration ([#8912](https://github.com/sveltejs/svelte/pull/8912))
+
+## 4.0.3
+
+### Patch Changes
+
+- fix: handle falsy srcset values ([#8901](https://github.com/sveltejs/svelte/pull/8901))
+
+## 4.0.2
+
+### Patch Changes
+
+- fix: reflect all custom element prop updates back to attribute ([#8898](https://github.com/sveltejs/svelte/pull/8898))
+
+- fix: shrink custom element baseline a bit ([#8858](https://github.com/sveltejs/svelte/pull/8858))
+
+- fix: use non-destructive hydration for all `@html` tags ([#8880](https://github.com/sveltejs/svelte/pull/8880))
+
+- fix: align `disclose-version` exports specification ([#8874](https://github.com/sveltejs/svelte/pull/8874))
+
+- fix: check srcset when hydrating to prevent needless requests ([#8868](https://github.com/sveltejs/svelte/pull/8868))
+
## 4.0.1
### Patch Changes
diff --git a/packages/svelte/package.json b/packages/svelte/package.json
index 0b554d0efa..d65541b130 100644
--- a/packages/svelte/package.json
+++ b/packages/svelte/package.json
@@ -1,6 +1,6 @@
{
"name": "svelte",
- "version": "4.0.1",
+ "version": "4.0.4",
"description": "Cybernetically enhanced web apps",
"type": "module",
"module": "src/runtime/index.js",
@@ -74,7 +74,7 @@
"types": "types/index.d.ts",
"scripts": {
"format": "prettier . --cache --plugin-search-dir=. --write",
- "check": "prettier . --cache --plugin-search-dir=. --check",
+ "check": "tsc --noEmit",
"test": "vitest run && echo \"manually check that there are no type errors in test/types by opening the files in there\"",
"build": "rollup -c && pnpm types",
"generate:version": "node ./scripts/generate-version.js",
@@ -82,7 +82,7 @@
"posttest": "agadoo src/internal/index.js",
"prepublishOnly": "pnpm build",
"types": "node ./scripts/generate-dts.js",
- "lint": "eslint \"{src,test}/**/*.{ts,js}\" --cache"
+ "lint": "prettier . --cache --plugin-search-dir=. --check && eslint \"{src,test}/**/*.{ts,js}\" --cache"
},
"repository": {
"type": "git",
diff --git a/packages/svelte/src/compiler/compile/render_dom/wrappers/RawMustacheTag.js b/packages/svelte/src/compiler/compile/render_dom/wrappers/RawMustacheTag.js
index 11a838ab8e..cc382a9b0b 100644
--- a/packages/svelte/src/compiler/compile/render_dom/wrappers/RawMustacheTag.js
+++ b/packages/svelte/src/compiler/compile/render_dom/wrappers/RawMustacheTag.js
@@ -26,7 +26,7 @@ export default class RawMustacheTagWrapper extends Tag {
render(block, parent_node, _parent_nodes) {
const in_head = is_head(parent_node);
const can_use_innerhtml = !in_head && parent_node && !this.prev && !this.next;
- if (can_use_innerhtml) {
+ if (can_use_innerhtml && !this.renderer.options.hydratable) {
/** @param {import('estree').Node} content */
const insert = (content) => b`${parent_node}.innerHTML = ${content};`[0];
const { init } = this.rename_this_method(block, (content) => insert(content));
diff --git a/packages/svelte/src/runtime/internal/Component.js b/packages/svelte/src/runtime/internal/Component.js
index a1da213da3..4733b1d745 100644
--- a/packages/svelte/src/runtime/internal/Component.js
+++ b/packages/svelte/src/runtime/internal/Component.js
@@ -13,7 +13,9 @@ import {
start_hydrating,
end_hydrating,
get_custom_elements_slots,
- insert
+ insert,
+ element,
+ attr
} from './dom.js';
import { transition_in } from './transitions.js';
@@ -157,23 +159,29 @@ export let SvelteElement;
if (typeof HTMLElement === 'function') {
SvelteElement = class extends HTMLElement {
- $$componentCtor;
- $$slots;
- $$component;
- $$connected = false;
- $$data = {};
- $$reflecting = false;
- /** @type {Record} */
- $$props_definition = {};
- /** @type {Record} */
- $$listeners = {};
- /** @type {Map} */
- $$listener_unsubscribe_fns = new Map();
+ /** The Svelte component constructor */
+ $$ctor;
+ /** Slots */
+ $$s;
+ /** The Svelte component instance */
+ $$c;
+ /** Whether or not the custom element is connected */
+ $$cn = false;
+ /** Component props data */
+ $$d = {};
+ /** `true` if currently in the process of reflecting component props back to attributes */
+ $$r = false;
+ /** @type {Record} Props definition (name, reflected, type etc) */
+ $$p_d = {};
+ /** @type {Record} Event listeners */
+ $$l = {};
+ /** @type {Map} Event listener unsubscribe functions */
+ $$l_u = new Map();
constructor($$componentCtor, $$slots, use_shadow_dom) {
super();
- this.$$componentCtor = $$componentCtor;
- this.$$slots = $$slots;
+ this.$$ctor = $$componentCtor;
+ this.$$s = $$slots;
if (use_shadow_dom) {
this.attachShadow({ mode: 'open' });
}
@@ -183,32 +191,32 @@ if (typeof HTMLElement === 'function') {
// We can't determine upfront if the event is a custom event or not, so we have to
// listen to both. If someone uses a custom event with the same name as a regular
// browser event, this fires twice - we can't avoid that.
- this.$$listeners[type] = this.$$listeners[type] || [];
- this.$$listeners[type].push(listener);
- if (this.$$component) {
- const unsub = this.$$component.$on(type, listener);
- this.$$listener_unsubscribe_fns.set(listener, unsub);
+ this.$$l[type] = this.$$l[type] || [];
+ this.$$l[type].push(listener);
+ if (this.$$c) {
+ const unsub = this.$$c.$on(type, listener);
+ this.$$l_u.set(listener, unsub);
}
super.addEventListener(type, listener, options);
}
removeEventListener(type, listener, options) {
super.removeEventListener(type, listener, options);
- if (this.$$component) {
- const unsub = this.$$listener_unsubscribe_fns.get(listener);
+ if (this.$$c) {
+ const unsub = this.$$l_u.get(listener);
if (unsub) {
unsub();
- this.$$listener_unsubscribe_fns.delete(listener);
+ this.$$l_u.delete(listener);
}
}
}
async connectedCallback() {
- this.$$connected = true;
- if (!this.$$component) {
+ this.$$cn = true;
+ if (!this.$$c) {
// We wait one tick to let possible child slot elements be created/mounted
await Promise.resolve();
- if (!this.$$connected) {
+ if (!this.$$cn) {
return;
}
function create_slot(name) {
@@ -216,9 +224,9 @@ if (typeof HTMLElement === 'function') {
let node;
const obj = {
c: function create() {
- node = document.createElement('slot');
+ node = element('slot');
if (name !== 'default') {
- node.setAttribute('name', name);
+ attr(node, 'name', name);
}
},
/**
@@ -239,74 +247,89 @@ if (typeof HTMLElement === 'function') {
}
const $$slots = {};
const existing_slots = get_custom_elements_slots(this);
- for (const name of this.$$slots) {
+ for (const name of this.$$s) {
if (name in existing_slots) {
$$slots[name] = [create_slot(name)];
}
}
for (const attribute of this.attributes) {
// this.$$data takes precedence over this.attributes
- const name = this.$$get_prop_name(attribute.name);
- if (!(name in this.$$data)) {
- this.$$data[name] = get_custom_element_value(
- name,
- attribute.value,
- this.$$props_definition,
- 'toProp'
- );
+ const name = this.$$g_p(attribute.name);
+ if (!(name in this.$$d)) {
+ this.$$d[name] = get_custom_element_value(name, attribute.value, this.$$p_d, 'toProp');
}
}
- this.$$component = new this.$$componentCtor({
+ this.$$c = new this.$$ctor({
target: this.shadowRoot || this,
props: {
- ...this.$$data,
+ ...this.$$d,
$$slots,
$$scope: {
ctx: []
}
}
});
- for (const type in this.$$listeners) {
- for (const listener of this.$$listeners[type]) {
- const unsub = this.$$component.$on(type, listener);
- this.$$listener_unsubscribe_fns.set(listener, unsub);
+
+ // Reflect component props as attributes
+ const reflect_attributes = () => {
+ this.$$r = true;
+ for (const key in this.$$p_d) {
+ this.$$d[key] = this.$$c.$$.ctx[this.$$c.$$.props[key]];
+ if (this.$$p_d[key].reflect) {
+ const attribute_value = get_custom_element_value(
+ key,
+ this.$$d[key],
+ this.$$p_d,
+ 'toAttribute'
+ );
+ if (attribute_value == null) {
+ this.removeAttribute(key);
+ } else {
+ this.setAttribute(this.$$p_d[key].attribute || key, attribute_value);
+ }
+ }
+ }
+ this.$$r = false;
+ };
+ this.$$c.$$.after_update.push(reflect_attributes);
+ reflect_attributes(); // once initially because after_update is added too late for first render
+
+ for (const type in this.$$l) {
+ for (const listener of this.$$l[type]) {
+ const unsub = this.$$c.$on(type, listener);
+ this.$$l_u.set(listener, unsub);
}
}
- this.$$listeners = {};
+ this.$$l = {};
}
}
// We don't need this when working within Svelte code, but for compatibility of people using this outside of Svelte
// and setting attributes through setAttribute etc, this is helpful
attributeChangedCallback(attr, _oldValue, newValue) {
- if (this.$$reflecting) return;
- attr = this.$$get_prop_name(attr);
- this.$$data[attr] = get_custom_element_value(
- attr,
- newValue,
- this.$$props_definition,
- 'toProp'
- );
- this.$$component?.$set({ [attr]: this.$$data[attr] });
+ if (this.$$r) return;
+ attr = this.$$g_p(attr);
+ this.$$d[attr] = get_custom_element_value(attr, newValue, this.$$p_d, 'toProp');
+ this.$$c?.$set({ [attr]: this.$$d[attr] });
}
disconnectedCallback() {
- this.$$connected = false;
+ this.$$cn = false;
// In a microtask, because this could be a move within the DOM
Promise.resolve().then(() => {
- if (!this.$$connected) {
- this.$$component.$destroy();
- this.$$component = undefined;
+ if (!this.$$cn) {
+ this.$$c.$destroy();
+ this.$$c = undefined;
}
});
}
- $$get_prop_name(attribute_name) {
+ $$g_p(attribute_name) {
return (
- Object.keys(this.$$props_definition).find(
+ Object.keys(this.$$p_d).find(
(key) =>
- this.$$props_definition[key].attribute === attribute_name ||
- (!this.$$props_definition[key].attribute && key.toLowerCase() === attribute_name)
+ this.$$p_d[key].attribute === attribute_name ||
+ (!this.$$p_d[key].attribute && key.toLowerCase() === attribute_name)
) || attribute_name
);
}
@@ -371,7 +394,7 @@ export function create_custom_element(
const Class = class extends SvelteElement {
constructor() {
super(Component, slots, use_shadow_dom);
- this.$$props_definition = props_definition;
+ this.$$p_d = props_definition;
}
static get observedAttributes() {
return Object.keys(props_definition).map((key) =>
@@ -382,36 +405,19 @@ export function create_custom_element(
Object.keys(props_definition).forEach((prop) => {
Object.defineProperty(Class.prototype, prop, {
get() {
- return this.$$component && prop in this.$$component
- ? this.$$component[prop]
- : this.$$data[prop];
+ return this.$$c && prop in this.$$c ? this.$$c[prop] : this.$$d[prop];
},
set(value) {
value = get_custom_element_value(prop, value, props_definition);
- this.$$data[prop] = value;
- this.$$component?.$set({ [prop]: value });
- if (props_definition[prop].reflect) {
- this.$$reflecting = true;
- const attribute_value = get_custom_element_value(
- prop,
- value,
- props_definition,
- 'toAttribute'
- );
- if (attribute_value == null) {
- this.removeAttribute(prop);
- } else {
- this.setAttribute(props_definition[prop].attribute || prop, attribute_value);
- }
- this.$$reflecting = false;
- }
+ this.$$d[prop] = value;
+ this.$$c?.$set({ [prop]: value });
}
});
});
accessors.forEach((accessor) => {
Object.defineProperty(Class.prototype, accessor, {
get() {
- return this.$$component?.[accessor];
+ return this.$$c?.[accessor];
}
});
});
diff --git a/packages/svelte/src/runtime/internal/dom.js b/packages/svelte/src/runtime/internal/dom.js
index e1969c5514..72cb8ab75b 100644
--- a/packages/svelte/src/runtime/internal/dom.js
+++ b/packages/svelte/src/runtime/internal/dom.js
@@ -777,14 +777,14 @@ export function claim_comment(nodes, data) {
);
}
-function find_comment(nodes, text, start) {
+function get_comment_idx(nodes, text, start) {
for (let i = start; i < nodes.length; i += 1) {
const node = nodes[i];
if (node.nodeType === 8 /* comment node */ && node.textContent.trim() === text) {
return i;
}
}
- return nodes.length;
+ return -1;
}
/**
@@ -793,11 +793,12 @@ function find_comment(nodes, text, start) {
*/
export function claim_html_tag(nodes, is_svg) {
// find html opening tag
- const start_index = find_comment(nodes, 'HTML_TAG_START', 0);
- const end_index = find_comment(nodes, 'HTML_TAG_END', start_index);
- if (start_index === end_index) {
- return new HtmlTagHydration(undefined, is_svg);
+ const start_index = get_comment_idx(nodes, 'HTML_TAG_START', 0);
+ const end_index = get_comment_idx(nodes, 'HTML_TAG_END', start_index + 1);
+ if (start_index === -1 || end_index === -1) {
+ return new HtmlTagHydration(is_svg);
}
+
init_claim_info(nodes);
const html_tag_nodes = nodes.splice(start_index, end_index - start_index + 1);
detach(html_tag_nodes[0]);
@@ -807,7 +808,7 @@ export function claim_html_tag(nodes, is_svg) {
n.claim_order = nodes.claim_info.total_claimed;
nodes.claim_info.total_claimed += 1;
}
- return new HtmlTagHydration(claimed_nodes, is_svg);
+ return new HtmlTagHydration(is_svg, claimed_nodes);
}
/**
@@ -1048,17 +1049,13 @@ export class HtmlTag {
* @default false
*/
is_svg = false;
- // parent for creating node
- /** */
+ /** parent for creating node */
e = undefined;
- // html tag nodes
- /** */
+ /** html tag nodes */
n = undefined;
- // target
- /** */
+ /** target */
t = undefined;
- // anchor
- /** */
+ /** anchor */
a = undefined;
constructor(is_svg = false) {
this.is_svg = is_svg;
@@ -1134,13 +1131,11 @@ export class HtmlTag {
}
}
-/**
- * @extends HtmlTag */
export class HtmlTagHydration extends HtmlTag {
- // hydration claimed nodes
- /** */
+ /** @type {Element[]} hydration claimed nodes */
l = undefined;
- constructor(claimed_nodes, is_svg = false) {
+
+ constructor(is_svg = false, claimed_nodes) {
super(is_svg);
this.e = this.n = null;
this.l = claimed_nodes;
diff --git a/packages/svelte/src/runtime/internal/utils.js b/packages/svelte/src/runtime/internal/utils.js
index 190c4534c6..ec39c0d9e4 100644
--- a/packages/svelte/src/runtime/internal/utils.js
+++ b/packages/svelte/src/runtime/internal/utils.js
@@ -90,12 +90,12 @@ function split_srcset(srcset) {
/**
* @param {HTMLSourceElement | HTMLImageElement} element_srcset
- * @param {string} srcset
+ * @param {string | undefined | null} srcset
* @returns {boolean}
*/
export function srcset_url_equal(element_srcset, srcset) {
const element_urls = split_srcset(element_srcset.srcset);
- const urls = split_srcset(srcset);
+ const urls = split_srcset(srcset || '');
return (
urls.length === element_urls.length &&
diff --git a/packages/svelte/src/shared/version.js b/packages/svelte/src/shared/version.js
index 835c2fda5b..e286ec644f 100644
--- a/packages/svelte/src/shared/version.js
+++ b/packages/svelte/src/shared/version.js
@@ -6,5 +6,5 @@
* https://svelte.dev/docs/svelte-compiler#svelte-version
* @type {string}
*/
-export const VERSION = '4.0.1';
+export const VERSION = '4.0.4';
export const PUBLIC_VERSION = '4';
diff --git a/packages/svelte/svelte-4.0.0.tgz b/packages/svelte/svelte-4.0.0.tgz
deleted file mode 100644
index 8c527a3fd8..0000000000
Binary files a/packages/svelte/svelte-4.0.0.tgz and /dev/null differ
diff --git a/packages/svelte/test/hydration/samples/raw-repair/_after.html b/packages/svelte/test/hydration/samples/raw-repair/_after.html
new file mode 100644
index 0000000000..31cdc12015
--- /dev/null
+++ b/packages/svelte/test/hydration/samples/raw-repair/_after.html
@@ -0,0 +1,2 @@
+
invalid
+
invalid
\ No newline at end of file
diff --git a/packages/svelte/test/hydration/samples/raw-repair/_before.html b/packages/svelte/test/hydration/samples/raw-repair/_before.html
new file mode 100644
index 0000000000..fe958bece5
--- /dev/null
+++ b/packages/svelte/test/hydration/samples/raw-repair/_before.html
@@ -0,0 +1,8 @@
+
+