Repository: sveltejs/kit
Pull request #6479 (2022-08-31 16:48:33)

[site] move Hero and its image into routes directory
Corresponding PR in the `sites` repo: https://github.com/sveltejs/sites/pull/375

Two changes:
- Moves `Hero.svelte` from `sites` repo and removes an unused prop
- Moves hero image into `src/routes` and removes `$img` alias (thank you directory routing!)

changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: f751771ee32bc768ce2fa0aeb398fba7c9dbc448

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/master/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/benmccann/kit/new/hero?filename=.changeset/small-dingos-type.md&value=---%0A%22kit.svelte.dev%22%3A%20patch%0A---%0A%0A%5Bsite%5D%20move%20Hero%20and%20its%20image%20into%20routes%20directory%0A)



Patch:
diff --git a/.changeset/cold-cycles-report.md b/.changeset/cold-cycles-report.md
new file mode 100644
index 000000000..d6acd2c45
--- /dev/null
+++ b/.changeset/cold-cycles-report.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Prevent validation_errors from being serialized twice
diff --git a/.changeset/three-garlics-brush.md b/.changeset/three-garlics-brush.md
new file mode 100644
index 000000000..fe4139bfc
--- /dev/null
+++ b/.changeset/three-garlics-brush.md
@@ -0,0 +1,5 @@
+---
+'create-svelte': patch
+---
+
+reorder create-svelte templates such that library is last
diff --git a/packages/create-svelte/index.js b/packages/create-svelte/index.js
index b002120a8..737318815 100755
--- a/packages/create-svelte/index.js
+++ b/packages/create-svelte/index.js
@@ -83,7 +83,7 @@ function write_common_files(cwd, options, name) {
  * @returns {boolean}
  */
 function matches_condition(condition, options) {
-	if (condition === 'default' || condition === 'skeleton' || condition === 'libskeleton') {
+	if (condition === 'default' || condition === 'skeleton' || condition === 'skeletonlib') {
 		return options.template === condition;
 	}
 	if (condition === 'typescript' || condition === 'checkjs') {
diff --git a/packages/create-svelte/templates/libskeleton/.gitignore b/packages/create-svelte/templates/skeletonlib/.gitignore
similarity index 100%
rename from packages/create-svelte/templates/libskeleton/.gitignore
rename to packages/create-svelte/templates/skeletonlib/.gitignore
diff --git a/packages/create-svelte/templates/libskeleton/.ignore b/packages/create-svelte/templates/skeletonlib/.ignore
similarity index 100%
rename from packages/create-svelte/templates/libskeleton/.ignore
rename to packages/create-svelte/templates/skeletonlib/.ignore
diff --git a/packages/create-svelte/templates/libskeleton/.meta.json b/packages/create-svelte/templates/skeletonlib/.meta.json
similarity index 100%
rename from packages/create-svelte/templates/libskeleton/.meta.json
rename to packages/create-svelte/templates/skeletonlib/.meta.json
diff --git a/packages/create-svelte/templates/libskeleton/.npmrc b/packages/create-svelte/templates/skeletonlib/.npmrc
similarity index 100%
rename from packages/create-svelte/templates/libskeleton/.npmrc
rename to packages/create-svelte/templates/skeletonlib/.npmrc
diff --git a/packages/create-svelte/templates/libskeleton/package.template.json b/packages/create-svelte/templates/skeletonlib/package.template.json
similarity index 100%
rename from packages/create-svelte/templates/libskeleton/package.template.json
rename to packages/create-svelte/templates/skeletonlib/package.template.json
diff --git a/packages/create-svelte/templates/libskeleton/src/app.d.ts b/packages/create-svelte/templates/skeletonlib/src/app.d.ts
similarity index 100%
rename from packages/create-svelte/templates/libskeleton/src/app.d.ts
rename to packages/create-svelte/templates/skeletonlib/src/app.d.ts
diff --git a/packages/create-svelte/templates/libskeleton/src/app.html b/packages/create-svelte/templates/skeletonlib/src/app.html
similarity index 100%
rename from packages/create-svelte/templates/libskeleton/src/app.html
rename to packages/create-svelte/templates/skeletonlib/src/app.html
diff --git a/packages/create-svelte/templates/libskeleton/src/lib/index.js b/packages/create-svelte/templates/skeletonlib/src/lib/index.js
similarity index 100%
rename from packages/create-svelte/templates/libskeleton/src/lib/index.js
rename to packages/create-svelte/templates/skeletonlib/src/lib/index.js
diff --git a/packages/create-svelte/templates/libskeleton/src/routes/+page.svelte b/packages/create-svelte/templates/skeletonlib/src/routes/+page.svelte
similarity index 100%
rename from packages/create-svelte/templates/libskeleton/src/routes/+page.svelte
rename to packages/create-svelte/templates/skeletonlib/src/routes/+page.svelte
diff --git a/packages/create-svelte/templates/libskeleton/static/favicon.png b/packages/create-svelte/templates/skeletonlib/static/favicon.png
similarity index 100%
rename from packages/create-svelte/templates/libskeleton/static/favicon.png
rename to packages/create-svelte/templates/skeletonlib/static/favicon.png
diff --git a/packages/create-svelte/templates/libskeleton/svelte.config.js b/packages/create-svelte/templates/skeletonlib/svelte.config.js
similarity index 100%
rename from packages/create-svelte/templates/libskeleton/svelte.config.js
rename to packages/create-svelte/templates/skeletonlib/svelte.config.js
diff --git a/packages/create-svelte/templates/libskeleton/vite.config.js b/packages/create-svelte/templates/skeletonlib/vite.config.js
similarity index 100%
rename from packages/create-svelte/templates/libskeleton/vite.config.js
rename to packages/create-svelte/templates/skeletonlib/vite.config.js
diff --git a/packages/create-svelte/types/internal.d.ts b/packages/create-svelte/types/internal.d.ts
index ad1fa0b2d..857180765 100644
--- a/packages/create-svelte/types/internal.d.ts
+++ b/packages/create-svelte/types/internal.d.ts
@@ -1,6 +1,6 @@
 export type Options = {
 	name: string;
-	template: 'default' | 'skeleton' | 'libskeleton';
+	template: 'default' | 'skeleton' | 'skeletonlib';
 	types: 'typescript' | 'checkjs' | null;
 	prettier: boolean;
 	eslint: boolean;
@@ -20,7 +20,7 @@ export type Condition =
 	| 'playwright'
 	| 'skeleton'
 	| 'default'
-	| 'libskeleton';
+	| 'skeletonlib';
 
 export type Common = {
 	files: Array<{
diff --git a/packages/kit/src/runtime/client/fetcher.js b/packages/kit/src/runtime/client/fetcher.js
index e806f9c0a..97ee337dc 100644
--- a/packages/kit/src/runtime/client/fetcher.js
+++ b/packages/kit/src/runtime/client/fetcher.js
@@ -44,10 +44,10 @@ if (import.meta.env.DEV) {
 export function initial_fetch(resource, opts) {
 	const url = JSON.stringify(typeof resource === 'string' ? resource : resource.url);
 
-	let selector = `script[sveltekit\\:data-type="data"][sveltekit\\:data-url=${url}]`;
+	let selector = `script[data-sveltekit-fetched][data-url=${url}]`;
 
 	if (opts && typeof opts.body === 'string') {
-		selector += `[sveltekit\\:data-body="${hash(opts.body)}"]`;
+		selector += `[data-hash="${hash(opts.body)}"]`;
 	}
 
 	const script = document.querySelector(selector);
diff --git a/packages/kit/src/runtime/server/page/fetch.js b/packages/kit/src/runtime/server/page/fetch.js
index efef5f7b3..adde5f994 100644
--- a/packages/kit/src/runtime/server/page/fetch.js
+++ b/packages/kit/src/runtime/server/page/fetch.js
@@ -192,6 +192,7 @@ export function create_fetch({ event, options, state, route, prerender_default }
 				async function text() {
 					const body = await response.text();
 
+					// TODO just pass `response.headers`, for processing inside `serialize_data`
 					/** @type {import('types').ResponseHeaders} */
 					const headers = {};
 					for (const [key, value] of response.headers) {
diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js
index 4ca30dc50..9e69c3dcd 100644
--- a/packages/kit/src/runtime/server/page/render.js
+++ b/packages/kit/src/runtime/server/page/render.js
@@ -2,7 +2,7 @@ import { devalue } from 'devalue';
 import { readable, writable } from 'svelte/store';
 import * as cookie from 'cookie';
 import { hash } from '../../hash.js';
-import { render_json_payload_script } from '../../../utils/escape.js';
+import { serialize_data } from './serialize_data.js';
 import { s } from '../../../utils/misc.js';
 import { Csp } from './csp.js';
 import { serialize_error } from '../utils.js';
@@ -284,25 +284,7 @@ export async function render_response({
 	}
 
 	if (page_config.ssr && page_config.csr) {
-		/** @type {string[]} */
-		const serialized_data = [];
-
-		for (const { url, body, response } of fetched) {
-			serialized_data.push(
-				render_json_payload_script(
-					{ type: 'data', url, body: typeof body === 'string' ? hash(body) : undefined },
-					response
-				)
-			);
-		}
-
-		if (validation_errors) {
-			serialized_data.push(
-				render_json_payload_script({ type: 'validation_errors' }, validation_errors)
-			);
-		}
-
-		body += `\n\t${serialized_data.join('\n\t')}`;
+		body += `\n\t${fetched.map(serialize_data).join('\n\t')}`;
 	}
 
 	if (options.service_worker) {
diff --git a/packages/kit/src/runtime/server/page/serialize_data.js b/packages/kit/src/runtime/server/page/serialize_data.js
new file mode 100644
index 000000000..f32922bb5
--- /dev/null
+++ b/packages/kit/src/runtime/server/page/serialize_data.js
@@ -0,0 +1,58 @@
+import { escape_html_attr } from '../../../utils/escape.js';
+import { hash } from '../../hash.js';
+
+/**
+ * Inside a script element, only `</script` and `<!--` hold special meaning to the HTML parser.
+ *
+ * The first closes the script element, so everything after is treated as raw HTML.
+ * The second disables further parsing until `-->`, so the script element might be unexpectedly
+ * kept open until until an unrelated HTML comment in the page.
+ *
+ * U+2028 LINE SEPARATOR and U+2029 PARAGRAPH SEPARATOR are escaped for the sake of pre-2018
+ * browsers.
+ *
+ * @see tests for unsafe parsing examples.
+ * @see https://html.spec.whatwg.org/multipage/scripting.html#restrictions-for-contents-of-script-elements
+ * @see https://html.spec.whatwg.org/multipage/syntax.html#cdata-rcdata-restrictions
+ * @see https://html.spec.whatwg.org/multipage/parsing.html#script-data-state
+ * @see https://html.spec.whatwg.org/multipage/parsing.html#script-data-double-escaped-state
+ * @see https://github.com/tc39/proposal-json-superset
+ * @type {Record<string, string>}
+ */
+const replacements = {
+	'<': '\\u003C',
+	'\u2028': '\\u2028',
+	'\u2029': '\\u2029'
+};
+
+const pattern = new RegExp(`[${Object.keys(replacements).join('')}]`, 'g');
+
+/**
+ * Generates a raw HTML string containing a safe script element carrying data and associated attributes.
+ *
+ * It escapes all the special characters needed to guarantee the element is unbroken, but care must
+ * be taken to ensure it is inserted in the document at an acceptable position for a script element,
+ * and that the resulting string isn't further modified.
+ *
+ * @param {import('./types.js').Fetched} fetched
+ * @returns {string} The raw HTML of a script element carrying the JSON payload.
+ * @example const html = serialize_data('/data.json', null, { foo: 'bar' });
+ */
+export function serialize_data(fetched) {
+	const safe_payload = JSON.stringify(fetched.response).replace(
+		pattern,
+		(match) => replacements[match]
+	);
+
+	const attrs = [
+		'type="application/json"',
+		'data-sveltekit-fetched',
+		`data-url=${escape_html_attr(fetched.url)}`
+	];
+
+	if (fetched.body) {
+		attrs.push(`data-hash=${escape_html_attr(hash(fetched.body))}`);
+	}
+
+	return `<script ${attrs.join(' ')}>${safe_payload}</script>`;
+}
diff --git a/packages/kit/src/runtime/server/page/serialize_data.spec.js b/packages/kit/src/runtime/server/page/serialize_data.spec.js
new file mode 100644
index 000000000..9906ba429
--- /dev/null
+++ b/packages/kit/src/runtime/server/page/serialize_data.spec.js
@@ -0,0 +1,59 @@
+import { test } from 'uvu';
+import * as assert from 'uvu/assert';
+import { serialize_data } from './serialize_data.js';
+
+test('escapes slashes', () => {
+	assert.equal(
+		serialize_data({
+			url: 'foo',
+			body: null,
+			response: {
+				status: 200,
+				statusText: 'OK',
+				headers: {},
+				body: '</script><script>alert("xss")'
+			}
+		}),
+		'<script type="application/json" data-sveltekit-fetched data-url="foo">' +
+			'{"status":200,"statusText":"OK","headers":{},"body":"\\u003C/script>\\u003Cscript>alert(\\"xss\\")"}' +
+			'</script>'
+	);
+});
+
+test('escapes exclamation marks', () => {
+	assert.equal(
+		serialize_data({
+			url: 'foo',
+			body: null,
+			response: {
+				status: 200,
+				statusText: 'OK',
+				headers: {},
+				body: '<!--</script>...-->alert("xss")'
+			}
+		}),
+		'<script type="application/json" data-sveltekit-fetched data-url="foo">' +
+			'{"status":200,"statusText":"OK","headers":{},"body":"\\u003C!--\\u003C/script>...-->alert(\\"xss\\")"}' +
+			'</script>'
+	);
+});
+
+test('escapes the attribute values', () => {
+	const raw = 'an "attr" & a \ud800';
+	const escaped = 'an &quot;attr&quot; &amp; a &#55296;';
+	assert.equal(
+		serialize_data({
+			url: raw,
+			body: null,
+			response: {
+				status: 200,
+				statusText: 'OK',
+				headers: {},
+				body: ''
+			}
+		}),
+		`<script type="application/json" data-sveltekit-fetched data-url="${escaped}">{"status":200,"statusText":"OK","headers":{},"body":\"\"}</script>`
+	);
+});
+
+test.run();
diff --git a/packages/kit/src/utils/escape.js b/packages/kit/src/utils/escape.js
index 959a16067..543e1a13c 100644
--- a/packages/kit/src/utils/escape.js
+++ b/packages/kit/src/utils/escape.js
@@ -1,61 +1,3 @@
-/**
- * Inside a script element, only `</script` and `<!--` hold special meaning to the HTML parser.
- *
- * The first closes the script element, so everything after is treated as raw HTML.
- * The second disables further parsing until `-->`, so the script element might be unexpectedly
- * kept open until until an unrelated HTML comment in the page.
- *
- * U+2028 LINE SEPARATOR and U+2029 PARAGRAPH SEPARATOR are escaped for the sake of pre-2018
- * browsers.
- *
- * @see tests for unsafe parsing examples.
- * @see https://html.spec.whatwg.org/multipage/scripting.html#restrictions-for-contents-of-script-elements
- * @see https://html.spec.whatwg.org/multipage/syntax.html#cdata-rcdata-restrictions
- * @see https://html.spec.whatwg.org/multipage/parsing.html#script-data-state
- * @see https://html.spec.whatwg.org/multipage/parsing.html#script-data-double-escaped-state
- * @see https://github.com/tc39/proposal-json-superset
- * @type {Record<string, string>}
- */
-const render_json_payload_script_dict = {
-	'<': '\\u003C',
-	'\u2028': '\\u2028',
-	'\u2029': '\\u2029'
-};
-
-const render_json_payload_script_regex = new RegExp(
-	`[${Object.keys(render_json_payload_script_dict).join('')}]`,
-	'g'
-);
-
-/**
- * Generates a raw HTML string containing a safe script element carrying JSON data and associated attributes.
- *
- * It escapes all the special characters needed to guarantee the element is unbroken, but care must
- * be taken to ensure it is inserted in the document at an acceptable position for a script element,
- * and that the resulting string isn't further modified.
- *
- * Attribute names must be type-checked so we don't need to escape them.
- *
- * @param {import('types').PayloadScriptAttributes} attrs A list of attributes to be added to the element.
- * @param {any} payload The data to be carried by the element. Must be serializable to JSON.
- * @returns {string} The raw HTML of a script element carrying the JSON payload.
- * @example const html = render_json_payload_script({ type: 'data', url: '/data.json' }, { foo: 'bar' });
- */
-export function render_json_payload_script(attrs, payload) {
-	const safe_payload = JSON.stringify(payload).replace(
-		render_json_payload_script_regex,
-		(match) => render_json_payload_script_dict[match]
-	);
-
-	let safe_attrs = '';
-	for (const [key, value] of Object.entries(attrs)) {
-		if (value === undefined) continue;
-		safe_attrs += ` sveltekit:data-${key}=${escape_html_attr(value)}`;
-	}
-
-	return `<script type="application/json"${safe_attrs}>${safe_payload}</script>`;
-}
-
 /**
  * When inside a double-quoted attribute value, only `&` and `"` hold special meaning.
  * @see https://html.spec.whatwg.org/multipage/parsing.html#attribute-value-(double-quoted)-state
diff --git a/packages/kit/src/utils/escape.spec.js b/packages/kit/src/utils/escape.spec.js
index 57eef069c..4c6585280 100644
--- a/packages/kit/src/utils/escape.spec.js
+++ b/packages/kit/src/utils/escape.spec.js
@@ -1,52 +1,15 @@
-import { suite } from 'uvu';
+import { test } from 'uvu';
 import * as assert from 'uvu/assert';
-import { render_json_payload_script, escape_html_attr } from './escape.js';
+import { escape_html_attr } from './escape.js';
 
-const json = suite('render_json_payload_script');
-
-json('escapes slashes', () => {
-	// The type here doesn't really matter for the purposes of escaping,
-	// but we want to avoid upsetting TypeScript.
-	assert.equal(
-		render_json_payload_script({ type: 'validation_errors' }, [
-			{ unsafe: '</script><script>alert("xss")' }
-		]),
-		'<script type="application/json" sveltekit:data-type="validation_errors">' +
-			'[{"unsafe":"\\u003C/script>\\u003Cscript>alert(\\"xss\\")"}]' +
-			'</script>'
-	);
-});
-
-json('escapes exclamation marks', () => {
-	assert.equal(
-		render_json_payload_script({ type: 'validation_errors' }, [
-			{ '<!--</script>...-->alert("xss")': 'unsafe' }
-		]),
-		'<script type="application/json" sveltekit:data-type="validation_errors">' +
-			'[{"\\u003C!--\\u003C/script>...-->alert(\\"xss\\")":"unsafe"}]' +
-			'</script>'
-	);
-});
-
-json('escapes the attribute values', () => {
-	const raw = 'an "attr" & a \ud800';
-	const escaped = 'an &quot;attr&quot; &amp; a &#55296;';
-	assert.equal(
-		render_json_payload_script({ type: 'data', url: raw }, {}),
-		`<script type="application/json" sveltekit:data-type="data" sveltekit:data-url="${escaped}">{}</script>`
-	);
-});
-
-const attr = suite('escape_html_attr');
-
-attr('escapes special attribute characters', () => {
+test('escape_html_attr escapes special attribute characters', () => {
 	assert.equal(
 		escape_html_attr('some "values" are &special here, <others> aren\'t.'),
 		'"some &quot;values&quot; are &amp;special here, <others> aren\'t."'
 	);
 });
 
-attr('escapes invalid surrogates', () => {
+test('escape_html_attr escapes invalid surrogates', () => {
 	assert.equal(escape_html_attr('\ud800\udc00'), '"\ud800\udc00"');
 	assert.equal(escape_html_attr('\ud800'), '"&#55296;"');
 	assert.equal(escape_html_attr('\udc00'), '"&#56320;"');
@@ -56,5 +19,4 @@ attr('escapes invalid surrogates', () => {
 	assert.equal(escape_html_attr('\ud800\ud800\udc00\udc00'), '"&#55296;\ud800\udc00&#56320;"');
 });
 
-json.run();
-attr.run();
+test.run();
diff --git a/packages/kit/types/internal.d.ts b/packages/kit/types/internal.d.ts
index c5669e786..234af54f8 100644
--- a/packages/kit/types/internal.d.ts
+++ b/packages/kit/types/internal.d.ts
@@ -135,10 +135,6 @@ export interface PageNode {
 	child_pages?: PageNode[];
 }
 
-export type PayloadScriptAttributes =
-	| { type: 'data'; url: string; body?: string }
-	| { type: 'validation_errors' };
-
 export interface PrerenderDependency {
 	response: Response;
 	body: null | string | Uint8Array;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index aca679ea9..6d729cd68 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -566,7 +566,7 @@ importers:
       '@sveltejs/adapter-static': workspace:*
       '@sveltejs/amp': workspace:*
       '@sveltejs/kit': workspace:*
-      '@sveltejs/site-kit': ^2.1.0
+      '@sveltejs/site-kit': ^2.1.2
       '@types/node': ^16.11.36
       flexsearch: ^0.7.21
       marked: ^4.0.16
@@ -582,7 +582,7 @@ importers:
       '@sveltejs/adapter-static': link:../../packages/adapter-static
       '@sveltejs/amp': link:../../packages/amp
       '@sveltejs/kit': link:../../packages/kit
-      '@sveltejs/site-kit': 2.1.0
+      '@sveltejs/site-kit': 2.1.2
       '@types/node': 16.11.42
       flexsearch: 0.7.21
       marked: 4.0.17
@@ -1016,8 +1016,8 @@ packages:
       estree-walker: 2.0.2
       picomatch: 2.3.1
 
-  /@sveltejs/site-kit/2.1.0:
-    resolution: {integrity: sha512-nJddyYdBHXmiyhIAq0DQZpEjJ0B2Up4NCg08gVZuUxuOsshjUK8wZ5kuszP3Tl/q176MxFbceJo2Me4fj9bu9w==}
+  /@sveltejs/site-kit/2.1.2:
+    resolution: {integrity: sha512-VB5JhdvO1299lgR0ZU1MUdKNPb0TZMq4ck8cxg7p/o3TXFTLwbgwF/aV4ei9I/p11bpWFh9FKGzGUUGxWgJxSg==}
     dependencies:
       golden-fleece: 1.0.9
     dev: true
diff --git a/sites/kit.svelte.dev/package.json b/sites/kit.svelte.dev/package.json
index 2138e12bf..21511e975 100644
--- a/sites/kit.svelte.dev/package.json
+++ b/sites/kit.svelte.dev/package.json
@@ -12,7 +12,7 @@
 		"@sveltejs/adapter-static": "workspace:*",
 		"@sveltejs/amp": "workspace:*",
 		"@sveltejs/kit": "workspace:*",
-		"@sveltejs/site-kit": "^2.1.0",
+		"@sveltejs/site-kit": "^2.1.2",
 		"@types/node": "^16.11.36",
 		"flexsearch": "^0.7.21",
 		"marked": "^4.0.16",
diff --git a/sites/kit.svelte.dev/src/routes/+page.svelte b/sites/kit.svelte.dev/src/routes/+page.svelte
index a18aa397b..fa91ff421 100644
--- a/sites/kit.svelte.dev/src/routes/+page.svelte
+++ b/sites/kit.svelte.dev/src/routes/+page.svelte
@@ -1,6 +1,7 @@
 <script>
-	import Machine from '$img/svelte-kit-machine.webp?w=1440;800&format=avif;webp;png&meta';
-	import { Hero, Blurb } from '@sveltejs/site-kit';
+	import { Blurb } from '@sveltejs/site-kit';
+	import Hero from './Hero.svelte';
+	import Machine from './svelte-kit-machine.webp?w=1440;800&format=avif;webp;png&picture';
 </script>
 
 <svelte:head>
diff --git a/sites/kit.svelte.dev/src/routes/Hero.svelte b/sites/kit.svelte.dev/src/routes/Hero.svelte
new file mode 100644
index 000000000..542890836
--- /dev/null
+++ b/sites/kit.svelte.dev/src/routes/Hero.svelte
@@ -0,0 +1,138 @@
+<script>
+	export let alt;
+	export let title;
+	export let tagline;
+	export let logotype;
+	export let background;
+</script>
+
+<section class="hero-banner">
+	<div class="hero-container">
+		<div class="hero-text">
+			<img alt="{title}" width="400" height="50" class="logotype" src={logotype} />
+			<div class="tagline">{tagline}</div>
+		</div>
+
+		<div class="hero-image">
+			<picture>
+				{#each Object.entries(background.sources) as [format, images]}
+					<source srcset={images.map((i) => `${i.src} ${i.width}w`).join(', ')} type={'image/' + format} /> 
+				{/each}
+				<img src={background.fallback} {alt} />
+			</picture>
+		</div>
+	</div>
+</section>
+
+<style>
+	.hero-banner {
+		max-width: 100vw;
+		background: rgb(211, 214, 217);
+		background: radial-gradient(34.14% 72.25% at 47.58% 31.75%, rgba(232, 244, 255, 0.52) 0%, rgba(255, 255, 255, 0) 100%),
+linear-gradient(92.4deg, #D1D4D7 14.67%, rgba(238, 247, 255, 0.48) 54.37%, rgba(206, 216, 224, 0.62) 92.49%),
+linear-gradient(0deg, #DBE7EF, #DBE7EF);
+		position: relative;
+		padding: 8rem var(--side-nav) 0;
+		display: flex;
+		justify-content: flex-start;
+		align-items: center;
+		overflow: hidden;
+	}
+
+	.hero-container {
+		width: 100%;
+		max-width: 90rem;
+		margin: 0 auto;
+		/* display: flex; */
+		justify-content: center;
+	}
+
+	.hero-text {
+		display: flex;
+		flex-direction: column;
+		justify-content: center;
+		align-items: center;
+		mix-blend-mode: multiply;
+	}
+
+	.hero-banner .tagline {
+		margin-top: 0;
+		position: relative;
+		font-size: 2rem;
+		font-weight: 200;
+		line-height: 1.2;
+		letter-spacing: 0.1em;
+		text-align: center;
+		text-transform: uppercase;
+		color: var(--text);
+		max-width: 12em;
+		font-family: var(--font);
+	}
+
+	.logotype {
+		position: relative;
+		width: 100%;
+		max-width: 400px;
+		margin: 0 0 2rem 0;
+	}
+
+	.hero-image {
+		display: flex;
+		flex: 1;
+		justify-content: center;
+		align-items: center;
+	}
+
+	.hero-image img {
+		width: 600px;
+		height: 450px;
+		object-fit: cover;
+		transform: translate(-2%,-10%);
+	}
+
+	@media (min-width: 480px) {
+		.hero-banner .tagline {
+			max-width: auto;
+		}
+
+		.hero-image img {
+			width: 800px;
+			height: 600px;
+		}
+	}
+
+	@media (min-width: 1024px) {
+		.hero-banner {
+			padding: 0 var(--side-nav);
+		}
+
+		.hero-text {
+			margin-top: -10rem;
+			align-items: flex-end;
+			flex: 1;
+		}
+
+		.hero-image {
+			flex: 1.2;
+		}
+
+		.hero-image img {
+			width: 480px;
+			object-fit: contain;
+			transform: scale(1.8);
+		}
+
+		.hero-banner .tagline {
+			text-align: right;
+			max-width: 12em;
+		}
+
+		.hero-container {
+			height: 70vh;
+			display: flex;
+			justify-content: flex-start;
+			margin: 0 auto;
+			padding: 0;
+		}
+	}
+</style>
diff --git a/sites/kit.svelte.dev/src/images/svelte-kit-machine.webp b/sites/kit.svelte.dev/src/routes/svelte-kit-machine.webp
similarity index 100%
rename from sites/kit.svelte.dev/src/images/svelte-kit-machine.webp
rename to sites/kit.svelte.dev/src/routes/svelte-kit-machine.webp
diff --git a/sites/kit.svelte.dev/vite.config.js b/sites/kit.svelte.dev/vite.config.js
index 7c1529e4a..3e299b48d 100644
--- a/sites/kit.svelte.dev/vite.config.js
+++ b/sites/kit.svelte.dev/vite.config.js
@@ -8,12 +8,6 @@ const config = {
 
 	plugins: [imagetools(), sveltekit()],
 
-	resolve: {
-		alias: {
-			$img: path.resolve('src/images')
-		}
-	},
-
 	server: {
 		fs: {
 			strict: false


Test patch:
diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js
index be319a1f9..6e9eb5652 100644
--- a/packages/kit/test/apps/basics/test/test.js
+++ b/packages/kit/test/apps/basics/test/test.js
@@ -663,7 +663,7 @@ test.describe('Load', () => {
 
 		if (!javaScriptEnabled) {
 			// by the time JS has run, hydration will have nuked these scripts
-			const script_contents = await page.innerHTML('script[sveltekit\\:data-type="data"]');
+			const script_contents = await page.innerHTML('script[data-sveltekit-fetched]');
 
 			const payload =
 				'{"status":200,"statusText":"","headers":{"content-type":"application/json"},"body":"{\\"answer\\":42}"}';
@@ -693,11 +693,11 @@ test.describe('Load', () => {
 		if (!javaScriptEnabled) {
 			// by the time JS has run, hydration will have nuked these scripts
 			const script_contents_a = await page.innerHTML(
-				'script[sveltekit\\:data-type="data"][sveltekit\\:data-url="/load/serialization-post.json"][sveltekit\\:data-body="3t25"]'
+				'script[data-sveltekit-fetched][data-url="/load/serialization-post.json"][data-hash="3t25"]'
 			);
 
 			const script_contents_b = await page.innerHTML(
-				'script[sveltekit\\:data-type="data"][sveltekit\\:data-url="/load/serialization-post.json"][sveltekit\\:data-body="3t24"]'
+				'script[data-sveltekit-fetched][data-url="/load/serialization-post.json"][data-hash="3t24"]'
 			);
 
 			expect(script_contents_a).toBe(payload_a);