Repository: sveltejs/kit (JavaScript)

Issue:
Make "version" available from $app/environment
### Describe the problem

Caching is great, it makes things fast. But using a cached page after an app deploy means all the script references are wrong and "bad things". So some form of "app version" needs to be part of any cache key.

There is a generated version string, used for the update checks. That could be read from `build/client/_app/version.json`except ... it won't be there during the build if pre-rendering.

So you have to check if running in `dev` or `prerendering` mode before reading the file.

### Describe the proposed solution

I would like to be able to access the version string from `$app/environment`

### Alternatives considered

Maybe it's already exposed from '$service-worker'?

But I don't know if that only works if using a service-worker, and it feels odd importing it from there when it isn't _really_ anything todo with a service-worker.

### Importance

nice to have

### Additional Information

_No response_


Discussion:
Somewhat related to #7144 (see comments).

In the meantime, I also needed some form of app versioning for quick (mostly) client-side checks (to automatically clear some outdated localStorage objects), and until kit's `appVersion` is exposed as proposed, we could resort to defining a basic version key using a vite global var:

```ts
// vite.config.ts
import { sveltekit } from '@sveltejs/kit/vite';
import type { UserConfig } from 'vite';

const config: UserConfig = {
  plugins: [sveltekit()],
  define: {
    __VITE_DEV_APP_VERSION__: JSON.stringify(Date.now().toString()),
  },
};

export default config;
```

```ts
// lib/utils/version.ts
import { dev } from '$app/environment';

export const version = dev ? __VITE_DEV_APP_VERSION__ : __SVELTEKIT_APP_VERSION__;
```



Patch:
diff --git a/.changeset/friendly-plums-notice.md b/.changeset/friendly-plums-notice.md
new file mode 100644
index 000000000000..d1f5cd192cc1
--- /dev/null
+++ b/.changeset/friendly-plums-notice.md
@@ -0,0 +1,5 @@
+---
+'@sveltejs/kit': patch
+---
+
+Expose version from `$app/environment`
diff --git a/packages/kit/src/exports/vite/build/build_server.js b/packages/kit/src/exports/vite/build/build_server.js
index 0af93efd8ea8..36c655f88b86 100644
--- a/packages/kit/src/exports/vite/build/build_server.js
+++ b/packages/kit/src/exports/vite/build/build_server.js
@@ -27,7 +27,7 @@ const server_template = ({ config, hooks, has_service_worker, runtime, template,
 import root from '__GENERATED__/root.svelte';
 import { respond } from '${runtime}/server/index.js';
 import { set_paths, assets, base } from '${runtime}/paths.js';
-import { set_prerendering } from '${runtime}/env.js';
+import { set_prerendering, set_version } from '${runtime}/env.js';
 import { set_private_env } from '${runtime}/env-private.js';
 import { set_public_env } from '${runtime}/env-public.js';
 
@@ -44,6 +44,7 @@ const error_template = ({ status, message }) => ${s(error_page)
 let read = null;
 
 set_paths(${s(config.kit.paths)});
+set_version(${s(config.kit.version.name)});
 
 let default_protocol = 'https';
 
@@ -86,7 +87,8 @@ export class Server {
 			app_template,
 			app_template_contains_nonce: ${template.includes('%sveltekit.nonce%')},
 			error_template,
-			trailing_slash: ${s(config.kit.trailingSlash)}
+			trailing_slash: ${s(config.kit.trailingSlash)},
+			version: ${s(config.kit.version.name)}
 		};
 	}
 
diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js
index 6944f30dd952..115271861278 100644
--- a/packages/kit/src/exports/vite/dev/index.js
+++ b/packages/kit/src/exports/vite/dev/index.js
@@ -268,6 +268,11 @@ export async function dev(vite, vite_config, svelte_config) {
 		}
 	});
 
+	// set `import { version } from '$app/environment'`
+	(await vite.ssrLoadModule(`${runtime_prefix}/env.js`)).set_version(
+		svelte_config.kit.version.name
+	);
+
 	return () => {
 		const serve_static_middleware = vite.middlewares.stack.find(
 			(middleware) =>
@@ -472,7 +477,8 @@ export async function dev(vite, vite_config, svelte_config) {
 						service_worker:
 							svelte_config.kit.serviceWorker.register &&
 							!!resolve_entry(svelte_config.kit.files.serviceWorker),
-						trailing_slash: svelte_config.kit.trailingSlash
+						trailing_slash: svelte_config.kit.trailingSlash,
+						version: svelte_config.kit.version.name
 					},
 					{
 						getClientAddress: () => {
diff --git a/packages/kit/src/runtime/app/environment.js b/packages/kit/src/runtime/app/environment.js
index f7b96f8c134f..56853b8cd1dc 100644
--- a/packages/kit/src/runtime/app/environment.js
+++ b/packages/kit/src/runtime/app/environment.js
@@ -8,4 +8,4 @@ export const browser = !import.meta.env.SSR;
  */
 export const dev = __SVELTEKIT_DEV__;
 
-export { prerendering } from '../env.js';
+export { prerendering, version } from '../env.js';
diff --git a/packages/kit/src/runtime/client/start.js b/packages/kit/src/runtime/client/start.js
index 03b49829cbb5..00b0fd9a986d 100644
--- a/packages/kit/src/runtime/client/start.js
+++ b/packages/kit/src/runtime/client/start.js
@@ -2,6 +2,7 @@ import { create_client } from './client.js';
 import { init } from './singletons.js';
 import { set_paths } from '../paths.js';
 import { set_public_env } from '../env-public.js';
+import { set_version } from '../env.js';
 
 /**
  * @param {{
@@ -13,11 +14,13 @@ import { set_public_env } from '../env-public.js';
  *   },
  *   target: Element;
  *   trailing_slash: import('types').TrailingSlash;
+ *   version: string;
  * }} opts
  */
-export async function start({ env, hydrate, paths, target, trailing_slash }) {
+export async function start({ env, hydrate, paths, target, trailing_slash, version }) {
 	set_public_env(env);
 	set_paths(paths);
+	set_version(version);
 
 	if (__SVELTEKIT_DEV__ && target === document.body) {
 		console.warn(
diff --git a/packages/kit/src/runtime/env.js b/packages/kit/src/runtime/env.js
index cf8181252bcd..73c9bb9277a0 100644
--- a/packages/kit/src/runtime/env.js
+++ b/packages/kit/src/runtime/env.js
@@ -1,6 +1,12 @@
 export let prerendering = false;
+export let version = '';
 
 /** @param {boolean} value */
 export function set_prerendering(value) {
 	prerendering = value;
 }
+
+/** @param {string} value */
+export function set_version(value) {
+	version = value;
+}
diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js
index cd0e6a329eb1..bcde19fdeece 100644
--- a/packages/kit/src/runtime/server/page/render.js
+++ b/packages/kit/src/runtime/server/page/render.js
@@ -281,7 +281,8 @@ export async function render_response({
 				}` : 'null'},
 				paths: ${s(options.paths)},
 				target: document.querySelector('[data-sveltekit-hydrate="${target}"]').parentNode,
-				trailing_slash: ${s(options.trailing_slash)}
+				trailing_slash: ${s(options.trailing_slash)},
+				version: ${s(options.version)}
 			});
 		`;
 
diff --git a/packages/kit/types/ambient.d.ts b/packages/kit/types/ambient.d.ts
index d10b0e329033..3e11f5d17019 100644
--- a/packages/kit/types/ambient.d.ts
+++ b/packages/kit/types/ambient.d.ts
@@ -85,6 +85,11 @@ declare module '$app/environment' {
 	 * `true` when prerendering, `false` otherwise.
 	 */
 	export const prerendering: boolean;
+
+	/**
+	 * The value of `config.kit.version.name`
+	 */
+	export const version: string;
 }
 
 /**
diff --git a/packages/kit/types/internal.d.ts b/packages/kit/types/internal.d.ts
index 1edb814bd9ad..aa8dea77ba71 100644
--- a/packages/kit/types/internal.d.ts
+++ b/packages/kit/types/internal.d.ts
@@ -313,6 +313,7 @@ export interface SSROptions {
 	app_template_contains_nonce: boolean;
 	error_template({ message, status }: { message: string; status: number }): string;
 	trailing_slash: TrailingSlash;
+	version: string;
 }
 
 export interface SSRErrorPage {


Test patch:
diff --git a/packages/kit/test/apps/basics/src/routes/app-environment/+page.svelte b/packages/kit/test/apps/basics/src/routes/app-environment/+page.svelte
new file mode 100644
index 000000000000..317b7a8ce903
--- /dev/null
+++ b/packages/kit/test/apps/basics/src/routes/app-environment/+page.svelte
@@ -0,0 +1,5 @@
+<script>
+	import { version } from '$app/environment';
+</script>
+
+<h1>{version}</h1>
diff --git a/packages/kit/test/apps/basics/svelte.config.js b/packages/kit/test/apps/basics/svelte.config.js
index 67d403fb0e0c..fa9ea301b9a1 100644
--- a/packages/kit/test/apps/basics/svelte.config.js
+++ b/packages/kit/test/apps/basics/svelte.config.js
@@ -3,6 +3,10 @@ const config = {
 	kit: {
 		prerender: {
 			handleHttpError: 'warn'
+		},
+
+		version: {
+			name: 'TEST_VERSION'
 		}
 	}
 };
diff --git a/packages/kit/test/apps/basics/test/test.js b/packages/kit/test/apps/basics/test/test.js
index 46a003d51f65..2ee921709940 100644
--- a/packages/kit/test/apps/basics/test/test.js
+++ b/packages/kit/test/apps/basics/test/test.js
@@ -1051,6 +1051,13 @@ test.describe('Page options', () => {
 	});
 });
 
+test.describe('$app/environment', () => {
+	test('includes version', async ({ page }) => {
+		await page.goto('/app-environment');
+		expect(await page.textContent('h1')).toBe('TEST_VERSION');
+	});
+});
+
 test.describe('$app/paths', () => {
 	test('includes paths', async ({ page }) => {
 		await page.goto('/paths');