Repository: sveltejs/kit
Pull request #8402 (2023-01-09 03:20:07)

Update pnpm to v7.23.0
[![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [pnpm](https://pnpm.io) ([source](https://togithub.com/pnpm/pnpm)) | [`7.22.0` -> `7.23.0`](https://renovatebot.com/diffs/npm/pnpm/7.22.0/7.23.0) | [![age](https://badges.renovateapi.com/packages/npm/pnpm/7.23.0/age-slim)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://badges.renovateapi.com/packages/npm/pnpm/7.23.0/adoption-slim)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://badges.renovateapi.com/packages/npm/pnpm/7.23.0/compatibility-slim/7.22.0)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://badges.renovateapi.com/packages/npm/pnpm/7.23.0/confidence-slim/7.22.0)](https://docs.renovatebot.com/merge-confidence/) |

---

### Release Notes

<details>
<summary>pnpm/pnpm</summary>

### [`v7.23.0`](https://togithub.com/pnpm/pnpm/releases/tag/v7.23.0)

[Compare Source](https://togithub.com/pnpm/pnpm/compare/v7.22.0...v7.23.0)

#### Minor Changes

-   When the `resolve-peers-from-workspace-root` setting is set to `true`, pnpm will use dependencies installed in the root of the workspace to resolve peer dependencies in any of the workspace's projects [#&#8203;5882](https://togithub.com/pnpm/pnpm/pull/5882).

#### Patch Changes

-   The help of the run command should list the `--resume-from` option.
-   Should display `--include-workspace-root` option on recursive command's help info.

#### Our Gold Sponsors

<table>
  <tbody>
    <tr>
      <td align="center" valign="middle">
        <a href="https://bit.dev/?utm_source=pnpm&utm_medium=release_notes" target="_blank"><img src="https://pnpm.io/img/users/bit.svg" width="80"></a>
      </td>
      <td align="center" valign="middle">
        <a href="https://nhost.io/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/nhost.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/nhost_light.svg" />
            <img src="https://pnpm.io/img/users/nhost.svg" width="180" />
          </picture>
        </a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://novu.co/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/novu.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/novu_light.svg" />
            <img src="https://pnpm.io/img/users/novu.svg" width="180" />
          </picture>
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://prisma.io/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/prisma.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/prisma_light.svg" />
            <img src="https://pnpm.io/img/users/prisma.svg" width="180" />
          </picture>
        </a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://www.flightcontrol.dev/?ref=pnpm" target="_blank"><img src="https://pnpm.io/img/users/flightcontrol.png" width="240"></a>
      </td>
    </tr>
  </tbody>
</table>

#### Our Silver Sponsors

<table>
  <tbody>
    <tr>
      <td align="center" valign="middle">
        <a href="https://leniolabs.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
          <img src="https://pnpm.io/img/users/leniolabs.jpg" width="80">
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://vercel.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/vercel.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/vercel_light.svg" />
            <img src="https://pnpm.io/img/users/vercel.svg" width="180" />
          </picture>
        </a>
      </td>
    </tr>
    <tr>
      <td align="center" valign="middle">
        <a href="https://www.takeshape.io/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
          <img src="https://pnpm.io/img/users/takeshape.svg" width="280">
        </a>
      </td>
      <td align="center" valign="middle">
        <a href="https://doppler.com/?utm_source=pnpm&utm_medium=release_notes" target="_blank">
          <picture>
            <source media="(prefers-color-scheme: light)" srcset="https://pnpm.io/img/users/doppler.svg" />
            <source media="(prefers-color-scheme: dark)" srcset="https://pnpm.io/img/users/doppler_light.svg" />
            <img src="https://pnpm.io/img/users/doppler.svg" width="280" />
          </picture>
        </a>
      </td>
    </tr>
  </tbody>
</table>

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://app.renovatebot.com/dashboard#github/sveltejs/kit).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNC44NC4yIiwidXBkYXRlZEluVmVyIjoiMzQuODQuMiJ9-->


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

Latest commit: e17482e366946cd13528fae5353e5b2650e2be8a

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/sveltejs/kit/new/renovate/all-minor-patch?filename=.changeset/funny-experts-sin.md&value=---%0A%0A---%0A%0AUpdate%20pnpm%20to%20v7.23.0%0A)



Patch:
diff --git a/.changeset/big-cobras-sip.md b/.changeset/big-cobras-sip.md
new file mode 100644
index 000000000..4436c6b4e
--- /dev/null
+++ b/.changeset/big-cobras-sip.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Disable illegal import detection when running unit tests
diff --git a/.changeset/breezy-walls-provide.md b/.changeset/breezy-walls-provide.md
new file mode 100644
index 000000000..28b778bfa
--- /dev/null
+++ b/.changeset/breezy-walls-provide.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+only prerender a given dependency once
diff --git a/.changeset/dry-poets-crash.md b/.changeset/dry-poets-crash.md
new file mode 100644
index 000000000..58eeacb84
--- /dev/null
+++ b/.changeset/dry-poets-crash.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+`updated.check()` type changed to Promise<boolean>
diff --git a/.changeset/stupid-actors-look.md b/.changeset/stupid-actors-look.md
new file mode 100644
index 000000000..b31e235a4
--- /dev/null
+++ b/.changeset/stupid-actors-look.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+[fix] quote 'script' in CSP directives
diff --git a/.changeset/sweet-hairs-roll.md b/.changeset/sweet-hairs-roll.md
new file mode 100644
index 000000000..fb969c331
--- /dev/null
+++ b/.changeset/sweet-hairs-roll.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+fix route id comparison for load change detection
diff --git a/.changeset/wet-feet-lie.md b/.changeset/wet-feet-lie.md
new file mode 100644
index 000000000..6cee5b7d2
--- /dev/null
+++ b/.changeset/wet-feet-lie.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+[fix] don't add nonce attribute to `<link>` elements
diff --git a/documentation/docs/20-core-concepts/20-load.md b/documentation/docs/20-core-concepts/20-load.md
index a4f8d9561..834f4101c 100644
--- a/documentation/docs/20-core-concepts/20-load.md
+++ b/documentation/docs/20-core-concepts/20-load.md
@@ -521,7 +521,7 @@ export async function load({ fetch, depends }) {
 ```svelte
 /// file: src/routes/random-number/+page.svelte
 <script>
-	import { invalidateAll } from '$app/navigation';
+	import { invalidate, invalidateAll } from '$app/navigation';
 
 	/** @type {import('./$types').PageData} */
 	export let data;
diff --git a/documentation/docs/30-advanced/50-server-only-modules.md b/documentation/docs/30-advanced/50-server-only-modules.md
index c14a4f21d..e6c424003 100644
--- a/documentation/docs/30-advanced/50-server-only-modules.md
+++ b/documentation/docs/30-advanced/50-server-only-modules.md
@@ -51,4 +51,6 @@ Cannot import $lib/server/secrets.js into public-facing code:
 
 Even though the public-facing code — `src/routes/+page.svelte` — only uses the `add` export and not the secret `atlantisCoordinates` export, the secret code could end up in JavaScript that the browser downloads, and so the import chain is considered unsafe.
 
-This feature also works with dynamic imports, even interpolated ones like ``await import(`./${foo}.js`)``, with one small caveat: during development, if there are two or more dynamic imports between the public-facing code and the server-only module, the illegal import will not be detected the first time the code is loaded.
\ No newline at end of file
+This feature also works with dynamic imports, even interpolated ones like ``await import(`./${foo}.js`)``, with one small caveat: during development, if there are two or more dynamic imports between the public-facing code and the server-only module, the illegal import will not be detected the first time the code is loaded.
+
+> Unit testing frameworks like Vitest do not distinguish between server-only and public-facing code. For this reason, illegal import detection is disabled when running tests, as determined by `process.env.TEST === 'true'`.
\ No newline at end of file
diff --git a/package.json b/package.json
index 955239522..1b4e7d3ac 100644
--- a/package.json
+++ b/package.json
@@ -36,7 +36,7 @@
 		"turbo": "^1.6.3",
 		"typescript": "^4.9.4"
 	},
-	"packageManager": "pnpm@7.22.0",
+	"packageManager": "pnpm@7.23.0",
 	"engines": {
 		"pnpm": "^7.0.0"
 	},
diff --git a/packages/kit/src/core/prerender/prerender.js b/packages/kit/src/core/prerender/prerender.js
index aa3e732c6..0e6f86306 100644
--- a/packages/kit/src/core/prerender/prerender.js
+++ b/packages/kit/src/core/prerender/prerender.js
@@ -102,10 +102,20 @@ export async function prerender() {
 	/** @type {import('types').SSRManifest} */
 	const manifest = (await import(pathToFileURL(manifest_path).href)).manifest;
 
+	/** @type {Map<string, string>} */
+	const saved = new Map();
+
 	override({
 		building: true,
 		paths: config.paths,
-		read: (file) => readFileSync(join(config.files.assets, file))
+		read: (file) => {
+			// stuff we just wrote
+			const filepath = saved.get(file);
+			if (filepath) return readFileSync(filepath);
+
+			// stuff in `static`
+			return readFileSync(join(config.files.assets, file));
+		}
 	});
 
 	const server = new Server(manifest);
@@ -222,6 +232,7 @@ export async function prerender() {
 			}
 
 			const body = result.body ?? new Uint8Array(await result.response.arrayBuffer());
+
 			save(
 				'dependencies',
 				result.response,
@@ -351,6 +362,9 @@ export async function prerender() {
 		} else if (response_type !== OK) {
 			handle_http_error({ status: response.status, path: decoded, referrer, referenceType });
 		}
+
+		manifest.assets.add(file);
+		saved.set(file, dest);
 	}
 
 	for (const route of manifest._.routes) {
diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js
index 0acd4dabd..03956dd3b 100644
--- a/packages/kit/src/exports/vite/index.js
+++ b/packages/kit/src/exports/vite/index.js
@@ -301,7 +301,7 @@ function kit({ svelte_config }) {
 		},
 
 		async load(id, options) {
-			if (options?.ssr === false) {
+			if (options?.ssr === false && process.env.TEST !== 'true') {
 				const normalized_cwd = vite.normalizePath(cwd);
 				const normalized_lib = vite.normalizePath(svelte_config.kit.files.lib);
 				if (
diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js
index 6ef4cc0ed..69fb5e4f1 100644
--- a/packages/kit/src/runtime/client/client.js
+++ b/packages/kit/src/runtime/client/client.js
@@ -709,7 +709,7 @@ export function create_client({ target, base }) {
 		let server_data = null;
 
 		const url_changed = current.url ? id !== current.url.pathname + current.url.search : false;
-		const route_changed = current.route ? id !== current.route.id : false;
+		const route_changed = current.route ? route.id !== current.route.id : false;
 
 		const invalid_server_nodes = loaders.reduce((acc, loader, i) => {
 			const previous = current.branch[i];
diff --git a/packages/kit/src/runtime/client/utils.js b/packages/kit/src/runtime/client/utils.js
index 8e1a04764..aa14bcce7 100644
--- a/packages/kit/src/runtime/client/utils.js
+++ b/packages/kit/src/runtime/client/utils.js
@@ -211,6 +211,7 @@ export function create_updated_store() {
 	/** @type {NodeJS.Timeout} */
 	let timeout;
 
+	/** @type {() => Promise<boolean>} */
 	async function check() {
 		if (DEV || !BROWSER) return false;
 
diff --git a/packages/kit/src/runtime/server/page/csp.js b/packages/kit/src/runtime/server/page/csp.js
index 282369140..f7b8eedd4 100644
--- a/packages/kit/src/runtime/server/page/csp.js
+++ b/packages/kit/src/runtime/server/page/csp.js
@@ -16,7 +16,8 @@ const quoted = new Set([
 	'none',
 	'strict-dynamic',
 	'report-sample',
-	'wasm-unsafe-eval'
+	'wasm-unsafe-eval',
+	'script'
 ]);
 
 const crypto_pattern = /^(nonce|sha\d\d\d)-/;
diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js
index 91337cab0..030b43561 100644
--- a/packages/kit/src/runtime/server/page/render.js
+++ b/packages/kit/src/runtime/server/page/render.js
@@ -211,22 +211,17 @@ export async function render_response({
 		const path = prefixed(dep);
 
 		if (resolve_opts.preload({ type: 'css', path })) {
-			const attributes = [];
-
-			if (csp.style_needs_nonce) {
-				attributes.push(`nonce="${csp.nonce}"`);
-			}
+			const attributes = ['rel="stylesheet"'];
 
 			if (inline_styles.has(dep)) {
 				// don't load stylesheets that are already inlined
 				// include them in disabled state so that Vite can detect them and doesn't try to add them
 				attributes.push('disabled', 'media="(max-width: 0)"');
 			} else {
-				const preload_atts = ['rel="preload"', 'as="style"'].concat(attributes);
+				const preload_atts = ['rel="preload"', 'as="style"'];
 				link_header_preloads.add(`<${encodeURI(path)}>; ${preload_atts.join(';')}; nopush`);
 			}
 
-			attributes.unshift('rel="stylesheet"');
 			head += `\n\t\t<link href="${path}" ${attributes.join(' ')}>`;
 		}
 	}
diff --git a/packages/kit/types/ambient.d.ts b/packages/kit/types/ambient.d.ts
index efcc1904d..a8bd4cf9f 100644
--- a/packages/kit/types/ambient.d.ts
+++ b/packages/kit/types/ambient.d.ts
@@ -320,7 +320,7 @@ declare module '$app/stores' {
 	/**
 	 *  A readable store whose initial value is `false`. If [`version.pollInterval`](https://kit.svelte.dev/docs/configuration#version) is a non-zero value, SvelteKit will poll for new versions of the app and update the store value to `true` when it detects one. `updated.check()` will force an immediate check, regardless of polling.
 	 */
-	export const updated: Readable<boolean> & { check(): boolean };
+	export const updated: Readable<boolean> & { check(): Promise<boolean> };
 
 	/**
 	 * A function that returns all of the contextual stores. On the server, this must be called during component initialization.


Test patch:
diff --git a/packages/kit/test/apps/basics/src/routes/load/invalidation/route/shared/+layout.js b/packages/kit/test/apps/basics/src/routes/load/invalidation/route/shared/+layout.js
index b37c81283..a144dc362 100644
--- a/packages/kit/test/apps/basics/src/routes/load/invalidation/route/shared/+layout.js
+++ b/packages/kit/test/apps/basics/src/routes/load/invalidation/route/shared/+layout.js
@@ -1,4 +1,4 @@
 /** @type {import('./$types').LayoutLoad} */
 export function load({ route }) {
-	return { route };
+	return { route, random: Math.random() };
 }
diff --git a/packages/kit/test/apps/basics/src/routes/load/invalidation/route/shared/+layout.svelte b/packages/kit/test/apps/basics/src/routes/load/invalidation/route/shared/+layout.svelte
index 2f2c178ac..3346ea43f 100644
--- a/packages/kit/test/apps/basics/src/routes/load/invalidation/route/shared/+layout.svelte
+++ b/packages/kit/test/apps/basics/src/routes/load/invalidation/route/shared/+layout.svelte
@@ -4,7 +4,11 @@
 </script>
 
 <h1>route.id: {data.route.id}</h1>
+<h2>random id: {data.random}</h2>
 <slot />
 
 <a href="/load/invalidation/route/shared/a">/load/invalidation/route/shared/a</a>
 <a href="/load/invalidation/route/shared/b">/load/invalidation/route/shared/b</a>
+
+<a href="/load/invalidation/route/shared/unchanged-x">/load/invalidation/route/shared/unchanged-x</a>
+<a href="/load/invalidation/route/shared/unchanged-y">/load/invalidation/route/shared/unchanged-y</a>
diff --git a/packages/kit/test/apps/basics/src/routes/load/invalidation/route/shared/[x]/+page.svelte b/packages/kit/test/apps/basics/src/routes/load/invalidation/route/shared/[x]/+page.svelte
new file mode 100644
index 000000000..ae59943f0
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/load/invalidation/route/shared/[x]/+page.svelte
@@ -0,0 +1,1 @@
+<p>[x]</p>
diff --git a/packages/kit/test/apps/basics/test/client.test.js b/packages/kit/test/apps/basics/test/client.test.js
index d6bf2f9b1..0a8cc4c66 100644
--- a/packages/kit/test/apps/basics/test/client.test.js
+++ b/packages/kit/test/apps/basics/test/client.test.js
@@ -1069,6 +1069,16 @@ test.describe('Invalidation', () => {
 		expect(await page.textContent('h1')).toBe('route.id: /load/invalidation/route/shared/b');
 	});
 
+	test('route.id does not rerun layout if unchanged', async ({ page, clicknav }) => {
+		await page.goto('/load/invalidation/route/shared/unchanged-x');
+		expect(await page.textContent('h1')).toBe('route.id: /load/invalidation/route/shared/[x]');
+		const id = await page.textContent('h2');
+
+		await clicknav('[href="/load/invalidation/route/shared/unchanged-y"]');
+		expect(await page.textContent('h1')).toBe('route.id: /load/invalidation/route/shared/[x]');
+		expect(await page.textContent('h2')).toBe(id);
+	});
+
 	test('$page.url can safely be mutated', async ({ page }) => {
 		await page.goto('/load/mutated-url?q=initial');
 		await expect(page.getByText('initial')).toBeVisible();
diff --git a/packages/kit/test/apps/options/svelte.config.js b/packages/kit/test/apps/options/svelte.config.js
index bae1cda49..b41b487d6 100644
--- a/packages/kit/test/apps/options/svelte.config.js
+++ b/packages/kit/test/apps/options/svelte.config.js
@@ -5,7 +5,8 @@ const config = {
 		embedded: true,
 		csp: {
 			directives: {
-				'script-src': ['self']
+				'script-src': ['self'],
+				'require-trusted-types-for': ['script']
 			}
 		},
 		files: {
diff --git a/packages/kit/test/apps/options/test/test.js b/packages/kit/test/apps/options/test/test.js
index 588a7cbe5..97fb3bc36 100644
--- a/packages/kit/test/apps/options/test/test.js
+++ b/packages/kit/test/apps/options/test/test.js
@@ -124,6 +124,13 @@ test.describe('CSP', () => {
 
 		await close();
 	});
+
+	test("quotes 'script'", async ({ page }) => {
+		const response = await page.goto(`/path-base`);
+		expect(response.headers()['content-security-policy']).toMatch(
+			/require-trusted-types-for 'script'/
+		);
+	});
 });
 
 test.describe('Custom extensions', () => {
diff --git a/packages/kit/test/prerendering/basics/src/routes/fetch-endpoint/also-not-buffered/+page.js b/packages/kit/test/prerendering/basics/src/routes/fetch-endpoint/also-not-buffered/+page.js
new file mode 100644
index 000000000..de4123c2a
--- /dev/null
+++ b/packages/kit/test/prerendering/basics/src/routes/fetch-endpoint/also-not-buffered/+page.js
@@ -0,0 +1,9 @@
+/** @type {import('@sveltejs/kit').Load} */
+export async function load({ fetch }) {
+	const url = '/fetch-endpoint/not-buffered.json';
+	const res = await fetch(url);
+
+	return {
+		headers: res.headers
+	};
+}
diff --git a/packages/kit/test/prerendering/basics/src/routes/fetch-endpoint/also-not-buffered/+page.svelte b/packages/kit/test/prerendering/basics/src/routes/fetch-endpoint/also-not-buffered/+page.svelte
new file mode 100644
index 000000000..bf73bfc65
--- /dev/null
+++ b/packages/kit/test/prerendering/basics/src/routes/fetch-endpoint/also-not-buffered/+page.svelte
@@ -0,0 +1,6 @@
+<script>
+	/** @type {import('./$types').PageData} */
+	export let data;
+</script>
+
+<h1>content-type: {data.headers.get('content-type')}</h1>
diff --git a/packages/kit/test/prerendering/basics/src/routes/fetch-endpoint/not-buffered.json/+server.js b/packages/kit/test/prerendering/basics/src/routes/fetch-endpoint/not-buffered.json/+server.js
index 82d3cb7b8..692b07ced 100644
--- a/packages/kit/test/prerendering/basics/src/routes/fetch-endpoint/not-buffered.json/+server.js
+++ b/packages/kit/test/prerendering/basics/src/routes/fetch-endpoint/not-buffered.json/+server.js
@@ -1,5 +1,10 @@
 import { json } from '@sveltejs/kit';
 
+let called = false;
+
 export async function GET() {
+	if (called) throw new Error('should only be called once');
+	called = true;
+
 	return json({ answer: 42 });
 }