Repository: sveltejs/kit
Pull request #8965 (2023-02-09 07:58:26)

docs: service worker types
The additional reference is needed for $service-worker to be properly typed

Came out of https://github.com/sveltejs/kit/commit/6c7f9b201e5b70f456e5f34d43b9380ffa907db6#commitcomment-99882092 (cc @bart)

### Please don't delete this checklist! Before submitting the PR, please make sure you do the following:
- [x] It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
- [x] This message body should clearly illustrate what problems it solves.
- [ ] Ideally, include a test that fails without this PR but passes with it.

### Tests
- [ ] Run the tests with `pnpm test` and lint the project with `pnpm lint` and `pnpm check`

### Changesets
- [ ] If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running `pnpm changeset` and following the prompts. Changesets that add features should be `minor` and those that fix bugs should be `patch`. Please prefix changeset messages with `feat:`, `fix:`, or `chore:`.



Patch:
diff --git a/.changeset/dirty-days-fix.md b/.changeset/dirty-days-fix.md
new file mode 100644
index 000000000..75c205093
--- /dev/null
+++ b/.changeset/dirty-days-fix.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+fix: correctly include exported http methods in allow header
diff --git a/.changeset/famous-hornets-invite.md b/.changeset/famous-hornets-invite.md
new file mode 100644
index 000000000..7dec00bed
--- /dev/null
+++ b/.changeset/famous-hornets-invite.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+chore: polyfill File from node:buffer
diff --git a/.changeset/good-carrots-flow.md b/.changeset/good-carrots-flow.md
new file mode 100644
index 000000000..27e047633
--- /dev/null
+++ b/.changeset/good-carrots-flow.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/adapter-node': patch
+---
+
+fix: use `strictRequires: true` when bundling output
diff --git a/.changeset/large-toes-fetch.md b/.changeset/large-toes-fetch.md
new file mode 100644
index 000000000..f9695953c
--- /dev/null
+++ b/.changeset/large-toes-fetch.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+fix: print useful error when subscribing to SvelteKit's stores at the wrong time during SSR
diff --git a/.changeset/perfect-snails-crash.md b/.changeset/perfect-snails-crash.md
new file mode 100644
index 000000000..a7456faf7
--- /dev/null
+++ b/.changeset/perfect-snails-crash.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/adapter-netlify': patch
+---
+
+chore: simplify functions-internal cleanup
diff --git a/.changeset/rich-chefs-refuse.md b/.changeset/rich-chefs-refuse.md
new file mode 100644
index 000000000..7a27232ab
--- /dev/null
+++ b/.changeset/rich-chefs-refuse.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/adapter-netlify': patch
+---
+
+fix: correctly compare routes when generating split functions
diff --git a/.changeset/six-poems-exist.md b/.changeset/six-poems-exist.md
new file mode 100644
index 000000000..612f7e646
--- /dev/null
+++ b/.changeset/six-poems-exist.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+fix: ignore external links when automatically preloading
diff --git a/documentation/docs/30-advanced/40-service-workers.md b/documentation/docs/30-advanced/40-service-workers.md
index 09c1b8a8f..92da584e5 100644
--- a/documentation/docs/30-advanced/40-service-workers.md
+++ b/documentation/docs/30-advanced/40-service-workers.md
@@ -24,6 +24,7 @@ The following example caches the built app and any files in `static` eagerly, an
 
 ```js
 // @errors: 2339
+/// <reference types="@sveltejs/kit" />
 import { build, files, version } from '$service-worker';
 
 // Create a unique cache name for this deployment
@@ -108,6 +109,7 @@ navigator.serviceWorker.register('/service-worker.js', {
 Setting up proper types for service workers requires some manual setup. Inside your `service-worker.js`, add the following to the top of your file:
 
 ```original-js
+/// <reference types="@sveltejs/kit" />
 /// <reference no-default-lib="true"/>
 /// <reference lib="esnext" />
 /// <reference lib="webworker" />
@@ -115,6 +117,7 @@ Setting up proper types for service workers requires some manual setup. Inside y
 const sw = /** @type {ServiceWorkerGlobalScope} */ (/** @type {unknown} */ (self));
 ```
 ```generated-ts
+/// <reference types="@sveltejs/kit" />
 /// <reference no-default-lib="true"/>
 /// <reference lib="esnext" />
 /// <reference lib="webworker" />
@@ -122,7 +125,7 @@ const sw = /** @type {ServiceWorkerGlobalScope} */ (/** @type {unknown} */ (self
 const sw = self as unknown as ServiceWorkerGlobalScope;
 ```
 
-This disables access to DOM typings like `HTMLElement` which are not available inside a service worker and instantiates the correct globals. The reassignment of `self` to `sw` allows you to type cast it in the process (there are a couple of ways to do this, but the easiest that requires no additional files). Use `sw` instead of `self` in the rest of the file.
+This disables access to DOM typings like `HTMLElement` which are not available inside a service worker and instantiates the correct globals. The reassignment of `self` to `sw` allows you to type cast it in the process (there are a couple of ways to do this, but the easiest that requires no additional files). Use `sw` instead of `self` in the rest of the file. The reference to the SvelteKit types ensures that the `$service-worker` import has proper type definitions.
 
 ## Other solutions
 
diff --git a/packages/adapter-netlify/index.js b/packages/adapter-netlify/index.js
index 987d55ca0..74d21f5df 100644
--- a/packages/adapter-netlify/index.js
+++ b/packages/adapter-netlify/index.js
@@ -1,16 +1,8 @@
-import {
-	appendFileSync,
-	existsSync,
-	readFileSync,
-	writeFileSync,
-	unlinkSync,
-	createReadStream
-} from 'fs';
+import { appendFileSync, existsSync, readdirSync, readFileSync, writeFileSync } from 'fs';
 import { dirname, join, resolve, posix } from 'path';
 import { fileURLToPath } from 'url';
 import esbuild from 'esbuild';
 import toml from '@iarna/toml';
-import { createInterface } from 'readline';
 
 /**
  * @typedef {{
@@ -41,6 +33,8 @@ const edge_set_in_env_var =
 	process.env.NETLIFY_SVELTEKIT_USE_EDGE === 'true' ||
 	process.env.NETLIFY_SVELTEKIT_USE_EDGE === '1';
 
+const FUNCTION_PREFIX = 'sveltekit-';
+
 /** @type {import('.').default} */
 export default function ({ split = false, edge = edge_set_in_env_var } = {}) {
 	return {
@@ -59,40 +53,6 @@ export default function ({ split = false, edge = edge_set_in_env_var } = {}) {
 			// "build" is the default publish directory when Netlify detects SvelteKit
 			const publish = get_publish_directory(netlify_config, builder) || 'build';
 
-			const redirects_file_path = join(publish, '_redirects');
-
-			// If redirects file exists - empty any netlify generated files in functions-internal
-			// Without removing other files that may have been auto generated by integrations
-			if (existsSync(redirects_file_path)) {
-				// Read each line of the file
-				const fileStream = createReadStream(redirects_file_path);
-				const rl = createInterface({
-					input: fileStream,
-					crlfDelay: Infinity
-				});
-
-				// Create an array of lines
-				const lines = [];
-				for await (const line of rl) {
-					lines.push(line);
-				}
-
-				const functions_internal = join('.netlify', 'functions-internal');
-
-				// Loop through redirects, and delete corresponding functions-internal files
-				lines.forEach((line) => {
-					if (line) {
-						// example line /.netlify/functions/{function_name} 200
-						const path = line.split(' ')[1];
-						const function_name = path.split('/').pop();
-						const mjsFile = join(functions_internal, `${function_name}.mjs`);
-						const jsonFile = join(functions_internal, `${function_name}.json`);
-						if (existsSync(mjsFile)) unlinkSync(mjsFile);
-						if (existsSync(jsonFile)) unlinkSync(jsonFile);
-					}
-				});
-			}
-
 			// empty out existing build directories
 			builder.rimraf(publish);
 			builder.rimraf('.netlify/edge-functions');
@@ -100,6 +60,14 @@ export default function ({ split = false, edge = edge_set_in_env_var } = {}) {
 			builder.rimraf('.netlify/package.json');
 			builder.rimraf('.netlify/serverless.js');
 
+			if (existsSync('.netlify/functions-internal')) {
+				for (const file of readdirSync('.netlify/functions-internal')) {
+					if (file.startsWith(FUNCTION_PREFIX)) {
+						builder.rimraf(join('.netlify/functions-internal', file));
+					}
+				}
+			}
+
 			builder.log.minor(`Publishing to "${publish}"`);
 
 			builder.log.minor('Copying assets...');
@@ -195,7 +163,7 @@ async function generate_edge_functions({ builder }) {
  * @param { boolean } params.split
  */
 async function generate_lambda_functions({ builder, publish, split }) {
-	builder.mkdirp('.netlify/functions-internal');
+	builder.mkdirp('.netlify/functions-internal/.svelte-kit');
 
 	/** @type {string[]} */
 	const redirects = [];
@@ -236,7 +204,8 @@ async function generate_lambda_functions({ builder, publish, split }) {
 			}
 
 			const pattern = `/${parts.join('/')}`;
-			const name = parts.join('-').replace(/[:.]/g, '_').replace('*', '__rest') || 'index';
+			const name =
+				FUNCTION_PREFIX + (parts.join('-').replace(/[:.]/g, '_').replace('*', '__rest') || 'index');
 
 			// skip routes with identical patterns, they were already folded into another function
 			if (seen.has(pattern)) continue;
@@ -244,10 +213,11 @@ async function generate_lambda_functions({ builder, publish, split }) {
 
 			// figure out which lower priority routes should be considered fallbacks
 			for (let j = i + 1; j < builder.routes.length; j += 1) {
-				if (routes[j].prerender === true) continue;
+				const other = builder.routes[j];
+				if (other.prerender === true) continue;
 
-				if (matches(route.segments, routes[j].segments)) {
-					routes.push(builder.routes[j]);
+				if (matches(route.segments, other.segments)) {
+					routes.push(other);
 				}
 			}
 
@@ -271,9 +241,9 @@ async function generate_lambda_functions({ builder, publish, split }) {
 
 		const fn = `import { init } from '../serverless.js';\n\nexport const handler = init(${manifest});\n`;
 
-		writeFileSync(`.netlify/functions-internal/render.json`, fn_config);
-		writeFileSync('.netlify/functions-internal/render.mjs', fn);
-		redirects.push('* /.netlify/functions/render 200');
+		writeFileSync(`.netlify/functions-internal/${FUNCTION_PREFIX}render.json`, fn_config);
+		writeFileSync(`.netlify/functions-internal/${FUNCTION_PREFIX}render.mjs`, fn);
+		redirects.push(`* /.netlify/functions/${FUNCTION_PREFIX}render 200`);
 	}
 
 	// this should happen at the end, after builder.writeClient(...),
diff --git a/packages/adapter-node/index.js b/packages/adapter-node/index.js
index ac16008bc..85371fd97 100644
--- a/packages/adapter-node/index.js
+++ b/packages/adapter-node/index.js
@@ -57,7 +57,7 @@ export default function (opts = {}) {
 					// dependencies could have deep exports, so we need a regex
 					...Object.keys(pkg.dependencies || {}).map((d) => new RegExp(`^${d}(\\/.*)?$`))
 				],
-				plugins: [nodeResolve({ preferBuiltins: true }), commonjs(), json()]
+				plugins: [nodeResolve({ preferBuiltins: true }), commonjs({ strictRequires: true }), json()]
 			});
 
 			await bundle.write({
diff --git a/packages/kit/src/exports/node/polyfills.js b/packages/kit/src/exports/node/polyfills.js
index 28f9de5f9..950c64525 100644
--- a/packages/kit/src/exports/node/polyfills.js
+++ b/packages/kit/src/exports/node/polyfills.js
@@ -1,6 +1,10 @@
 import { ReadableStream, TransformStream, WritableStream } from 'node:stream/web';
+import buffer from 'node:buffer';
 import { webcrypto as crypto } from 'node:crypto';
-import { fetch, Response, Request, Headers, FormData } from 'undici';
+import { fetch, Response, Request, Headers, FormData, File as UndiciFile } from 'undici';
+
+// @ts-expect-error
+const File = buffer.File ?? UndiciFile;
 
 /** @type {Record<string, any>} */
 const globals = {
@@ -12,7 +16,8 @@ const globals = {
 	ReadableStream,
 	TransformStream,
 	WritableStream,
-	FormData
+	FormData,
+	File
 };
 
 // exported for dev/preview and node environments
diff --git a/packages/kit/src/runtime/app/stores.js b/packages/kit/src/runtime/app/stores.js
index 5c79a7ad3..67727a119 100644
--- a/packages/kit/src/runtime/app/stores.js
+++ b/packages/kit/src/runtime/app/stores.js
@@ -23,7 +23,7 @@ export const getStores = () => {
 export const page = {
 	/** @param {(value: any) => void} fn */
 	subscribe(fn) {
-		const store = getStores().page;
+		const store = __SVELTEKIT_DEV__ ? get_store('page') : getStores().page;
 		return store.subscribe(fn);
 	}
 };
@@ -31,7 +31,7 @@ export const page = {
 /** @type {typeof import('$app/stores').navigating} */
 export const navigating = {
 	subscribe(fn) {
-		const store = getStores().navigating;
+		const store = __SVELTEKIT_DEV__ ? get_store('navigating') : getStores().navigating;
 		return store.subscribe(fn);
 	}
 };
@@ -39,7 +39,7 @@ export const navigating = {
 /** @type {typeof import('$app/stores').updated} */
 export const updated = {
 	subscribe(fn) {
-		const store = getStores().updated;
+		const store = __SVELTEKIT_DEV__ ? get_store('updated') : getStores().updated;
 
 		if (browser) {
 			updated.check = store.check;
@@ -55,3 +55,18 @@ export const updated = {
 		);
 	}
 };
+
+/**
+ * @template {keyof ReturnType<typeof getStores>} Name
+ * @param {Name} name
+ * @returns {ReturnType<typeof getStores>[Name]}
+ */
+function get_store(name) {
+	try {
+		return getStores()[name];
+	} catch (e) {
+		throw new Error(
+			`Cannot subscribe to '${name}' store on the server outside of a Svelte component, as it is bound to the current request via component context. This prevents state from leaking between users.`
+		);
+	}
+}
diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js
index f5c53e7dd..bcf81c2b7 100644
--- a/packages/kit/src/runtime/client/client.js
+++ b/packages/kit/src/runtime/client/client.js
@@ -221,14 +221,8 @@ export function create_client({ target }) {
 		});
 	}
 
-	/** @param {URL} url */
-	async function preload_data(url) {
-		const intent = get_navigation_intent(url, false);
-
-		if (!intent) {
-			throw new Error(`Attempted to preload a URL that does not belong to this app: ${url}`);
-		}
-
+	/** @param {import('./types').NavigationIntent} intent */
+	async function preload_data(intent) {
 		load_cache = {
 			id: intent.id,
 			promise: load_route(intent).then((result) => {
@@ -1261,7 +1255,8 @@ export function create_client({ target }) {
 
 			if (!options.reload) {
 				if (priority <= options.preload_data) {
-					preload_data(/** @type {URL} */ (url));
+					const intent = get_navigation_intent(/** @type {URL} */ (url), false);
+					if (intent) preload_data(intent);
 				} else if (priority <= options.preload_code) {
 					preload_code(get_url_path(/** @type {URL} */ (url)));
 				}
@@ -1347,7 +1342,13 @@ export function create_client({ target }) {
 
 		preload_data: async (href) => {
 			const url = new URL(href, get_base_uri(document));
-			await preload_data(url);
+			const intent = get_navigation_intent(url, false);
+
+			if (!intent) {
+				throw new Error(`Attempted to preload a URL that does not belong to this app: ${url}`);
+			}
+
+			await preload_data(intent);
 		},
 
 		preload_code,
diff --git a/packages/kit/src/runtime/server/utils.js b/packages/kit/src/runtime/server/utils.js
index ed8c4803d..68dad34d3 100644
--- a/packages/kit/src/runtime/server/utils.js
+++ b/packages/kit/src/runtime/server/utils.js
@@ -41,7 +41,7 @@ export function method_not_allowed(mod, method) {
 export function allowed_methods(mod) {
 	const allowed = [];
 
-	for (const method in ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']) {
+	for (const method of ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']) {
 		if (method in mod) allowed.push(method);
 	}
 
diff --git a/packages/kit/types/ambient.d.ts b/packages/kit/types/ambient.d.ts
index 606475b4c..5573b72ce 100644
--- a/packages/kit/types/ambient.d.ts
+++ b/packages/kit/types/ambient.d.ts
@@ -292,16 +292,22 @@ declare module '$app/stores' {
 
 	/**
 	 * A readable store whose value contains page data.
+	 *
+	 * On the server, this store can only be subscribed to during component initialization. In the browser, it can be subscribed to at any time.
 	 */
 	export const page: Readable<Page>;
 	/**
 	 * A readable store.
 	 * When navigating starts, its value is a `Navigation` object with `from`, `to`, `type` and (if `type === 'popstate'`) `delta` properties.
 	 * When navigating finishes, its value reverts to `null`.
+	 *
+	 * On the server, this store can only be subscribed to during component initialization. In the browser, it can be subscribed to at any time.
 	 */
 	export const navigating: Readable<Navigation | null>;
 	/**
-	 *  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.
+	 * 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.
+	 *
+	 * On the server, this store can only be subscribed to during component initialization. In the browser, it can be subscribed to at any time.
 	 */
 	export const updated: Readable<boolean> & { check(): Promise<boolean> };
 


Test patch:
diff --git a/packages/kit/test/apps/basics/test/server.test.js b/packages/kit/test/apps/basics/test/server.test.js
index cad3f9f88..f2e09a28e 100644
--- a/packages/kit/test/apps/basics/test/server.test.js
+++ b/packages/kit/test/apps/basics/test/server.test.js
@@ -108,6 +108,13 @@ test.describe('Endpoints', () => {
 		});
 	});
 
+	test('invalid request method returns allow header', async ({ request }) => {
+		const response = await request.post('/endpoint-output/body');
+
+		expect(response.status()).toBe(405);
+		expect(response.headers()['allow'].includes('GET'));
+	});
+
 	// TODO all the remaining tests in this section are really only testing
 	// setResponse, since we're not otherwise changing anything on the response.
 	// might be worth making these unit tests instead