From c11c4f110329a53dc8cd4957d948112c0b92e463 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 31 Jan 2022 15:57:19 +0000 Subject: [PATCH 01/34] Version Packages (next) --- .changeset/pre.json | 1 + packages/kit/CHANGELOG.md | 6 ++++++ packages/kit/package.json | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index d2295bf6044a..e2c199524ec2 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -520,6 +520,7 @@ "sixty-pianos-fix", "sixty-rice-cough", "sixty-terms-decide", + "slimy-cougars-double", "slow-buses-beam", "slow-fans-own", "slow-suns-reply", diff --git a/packages/kit/CHANGELOG.md b/packages/kit/CHANGELOG.md index f0db7b4a16b3..5f691726dd98 100644 --- a/packages/kit/CHANGELOG.md +++ b/packages/kit/CHANGELOG.md @@ -1,5 +1,11 @@ # @sveltejs/kit +## 1.0.0-next.252 + +### Patch Changes + +- remove nonexistent `url` store from `$app/stores` ambient types ([#3640](https://github.com/sveltejs/kit/pull/3640)) + ## 1.0.0-next.251 ### Patch Changes diff --git a/packages/kit/package.json b/packages/kit/package.json index c3ae0a3f52b6..5128606513a7 100644 --- a/packages/kit/package.json +++ b/packages/kit/package.json @@ -1,6 +1,6 @@ { "name": "@sveltejs/kit", - "version": "1.0.0-next.251", + "version": "1.0.0-next.252", "repository": { "type": "git", "url": "https://github.com/sveltejs/kit", From e0cfda6ceca83f048e15cd31e24179b23d4b1e14 Mon Sep 17 00:00:00 2001 From: Philipp Zerelles Date: Mon, 31 Jan 2022 17:12:09 +0100 Subject: [PATCH 02/34] Remove optional function and add "reload" option to router's onError configuration --- documentation/docs/14-configuration.md | 5 +++- packages/kit/src/core/build/build_server.js | 5 +++- packages/kit/src/core/config/index.spec.js | 5 +++- packages/kit/src/core/config/options.js | 8 ++++++- packages/kit/src/runtime/client/renderer.js | 9 ++++++-- packages/kit/src/runtime/client/router.js | 11 ++++++--- packages/kit/src/runtime/client/start.js | 23 +++++++++++-------- .../kit/src/runtime/server/page/render.js | 5 +++- .../kit/src/runtime/server/page/respond.js | 2 +- .../runtime/server/page/respond_with_error.js | 2 +- packages/kit/types/config.d.ts | 7 +++++- packages/kit/types/internal.d.ts | 8 ++++++- 12 files changed, 67 insertions(+), 23 deletions(-) diff --git a/documentation/docs/14-configuration.md b/documentation/docs/14-configuration.md index a6470eb0f38e..abf9f6d42267 100644 --- a/documentation/docs/14-configuration.md +++ b/documentation/docs/14-configuration.md @@ -19,7 +19,10 @@ const config = { appDir: '_app', browser: { hydrate: true, - router: true + router: { + enabled: true, + onError: 'fail' + }, }, csp: { mode: 'auto', diff --git a/packages/kit/src/core/build/build_server.js b/packages/kit/src/core/build/build_server.js index ae8b50bc2c63..b64368f4f2f6 100644 --- a/packages/kit/src/core/build/build_server.js +++ b/packages/kit/src/core/build/build_server.js @@ -88,7 +88,10 @@ export class App { read, root, service_worker: ${has_service_worker ? "base + '/service-worker.js'" : 'null'}, - router: ${s(config.kit.browser.router)}, + router: { + enabled: ${s(config.kit.browser.router.enabled)}, + onError: ${s(config.kit.browser.router.onError)} + }, target: ${s(config.kit.target)}, template, template_contains_nonce: ${template.includes('%svelte.nonce%')}, diff --git a/packages/kit/src/core/config/index.spec.js b/packages/kit/src/core/config/index.spec.js index 7dced035f013..c2012f4fc5eb 100644 --- a/packages/kit/src/core/config/index.spec.js +++ b/packages/kit/src/core/config/index.spec.js @@ -16,7 +16,10 @@ const get_defaults = (prefix = '') => ({ appDir: '_app', browser: { hydrate: true, - router: true + router: { + enabled: true, + onError: 'fail' + } }, csp: { mode: 'auto', diff --git a/packages/kit/src/core/config/options.js b/packages/kit/src/core/config/options.js index 29df696d0395..28f65ffdd37e 100644 --- a/packages/kit/src/core/config/options.js +++ b/packages/kit/src/core/config/options.js @@ -59,7 +59,13 @@ const options = object( browser: object({ hydrate: boolean(true), - router: boolean(true) + router: object({ + enabled: boolean(true), + onError: validate('fail', (input, keypath) => { + if (['reload', 'fail'].includes(input)) return input; + throw new Error(`${keypath} should be either "reload" or "fail"`); + }) + }) }), csp: object({ diff --git a/packages/kit/src/runtime/client/renderer.js b/packages/kit/src/runtime/client/renderer.js index 5a5856d5a5d8..6698c2c0af82 100644 --- a/packages/kit/src/runtime/client/renderer.js +++ b/packages/kit/src/runtime/client/renderer.js @@ -6,6 +6,7 @@ import { normalize } from '../load.js'; /** * @typedef {import('types/internal').CSRComponent} CSRComponent + * @typedef {import('types/config').RouteOnErrorValue} RouteOnErrorValue * @typedef {{ from: URL; to: URL }} Navigating */ @@ -228,7 +229,7 @@ export class Renderer { * @param {import('./types').NavigationInfo} info * @param {string[]} chain * @param {boolean} no_cache - * @param {{hash?: string, scroll: { x: number, y: number } | null, keepfocus: boolean}} [opts] + * @param {{hash?: string, scroll: { x: number, y: number } | null, keepfocus: boolean, onError: RouteOnErrorValue}} [opts] */ async handle_navigation(info, chain, no_cache, opts) { if (this.started) { @@ -245,10 +246,11 @@ export class Renderer { * @param {import('./types').NavigationInfo} info * @param {string[]} chain * @param {boolean} no_cache - * @param {{hash?: string, scroll: { x: number, y: number } | null, keepfocus: boolean}} [opts] + * @param {{hash?: string, scroll: { x: number, y: number } | null, keepfocus: boolean, onError: RouteOnErrorValue}} [opts] */ async update(info, chain, no_cache, opts) { const token = (this.token = {}); + const onError = opts?.onError || 'fail'; let navigation_result = await this._get_navigation_result(info, no_cache); // abort if user navigated during update @@ -276,6 +278,9 @@ export class Renderer { return; } + } else if (navigation_result.props?.page?.status >= 400 && onError !== 'fail') { + location.href = info.url.href; + return; } this.updating = true; diff --git a/packages/kit/src/runtime/client/router.js b/packages/kit/src/runtime/client/router.js index ef6c5981099d..fb8455f80f44 100644 --- a/packages/kit/src/runtime/client/router.js +++ b/packages/kit/src/runtime/client/router.js @@ -35,10 +35,11 @@ export class Router { * base: string; * routes: import('types/internal').CSRRoute[]; * trailing_slash: import('types/internal').TrailingSlash; - * renderer: import('./renderer').Renderer + * renderer: import('./renderer').Renderer; + * onError?: import('types/config').RouteOnErrorValue; * }} opts */ - constructor({ base, routes, trailing_slash, renderer }) { + constructor({ base, routes, trailing_slash, renderer, onError }) { this.base = base; this.routes = routes; this.trailing_slash = trailing_slash; @@ -49,6 +50,9 @@ export class Router { this.renderer = renderer; renderer.router = this; + /** @type {import('types/config').RouteOnErrorValue} */ + this.onError = onError || 'fail'; + this.enabled = true; // make it possible to reset focus @@ -407,7 +411,8 @@ export class Router { await this.renderer.handle_navigation(info, chain, false, { scroll, - keepfocus + keepfocus, + onError: this.onError }); this.navigating--; diff --git a/packages/kit/src/runtime/client/start.js b/packages/kit/src/runtime/client/start.js index 57f4d65c2cfb..975a5b6b238a 100644 --- a/packages/kit/src/runtime/client/start.js +++ b/packages/kit/src/runtime/client/start.js @@ -14,7 +14,10 @@ import { set_paths } from '../paths.js'; * }, * target: Node; * session: any; - * route: boolean; + * route: { + * enabled: boolean; + * onError?: import('types/config').RouteOnErrorValue; + * }, * spa: boolean; * trailing_slash: import('types/internal').TrailingSlash; * hydrate: { @@ -38,14 +41,16 @@ export async function start({ paths, target, session, route, spa, trailing_slash session }); - const router = route - ? new Router({ - base: paths.base, - routes, - trailing_slash, - renderer - }) - : null; + const router = + route && route.enabled + ? new Router({ + base: paths.base, + routes, + trailing_slash, + renderer, + onError: route.onError + }) + : null; init({ router, renderer }); set_paths(paths); diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index bacafb1a0713..2a33fcb89f29 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -155,7 +155,10 @@ export async function render_response({ session: ${try_serialize($session, (error) => { throw new Error(`Failed to serialize session data: ${error.message}`); })}, - route: ${!!page_config.router}, + route: { + enabled: ${!!page_config.router}, + onError: ${s(options.router.onError)} + }, spa: ${!ssr}, trailing_slash: ${s(options.trailing_slash)}, hydrate: ${ssr && page_config.hydrate ? `{ diff --git a/packages/kit/src/runtime/server/page/respond.js b/packages/kit/src/runtime/server/page/respond.js index 6a2b746addb7..31d5f9f84995 100644 --- a/packages/kit/src/runtime/server/page/respond.js +++ b/packages/kit/src/runtime/server/page/respond.js @@ -250,7 +250,7 @@ function get_page_config(leaf, options) { } return { - router: 'router' in leaf ? !!leaf.router : options.router, + router: 'router' in leaf ? !!leaf.router : options.router.enabled, hydrate: 'hydrate' in leaf ? !!leaf.hydrate : options.hydrate }; } diff --git a/packages/kit/src/runtime/server/page/respond_with_error.js b/packages/kit/src/runtime/server/page/respond_with_error.js index 8eb5c1730e29..d8532164d967 100644 --- a/packages/kit/src/runtime/server/page/respond_with_error.js +++ b/packages/kit/src/runtime/server/page/respond_with_error.js @@ -65,7 +65,7 @@ export async function respond_with_error({ event, options, state, $session, stat $session, page_config: { hydrate: options.hydrate, - router: options.router + router: options.router.enabled }, stuff: error_loaded.stuff, status, diff --git a/packages/kit/types/config.d.ts b/packages/kit/types/config.d.ts index ecba3176e8ac..d9246610f3b9 100644 --- a/packages/kit/types/config.d.ts +++ b/packages/kit/types/config.d.ts @@ -111,6 +111,8 @@ export interface PrerenderErrorHandler { export type PrerenderOnErrorValue = 'fail' | 'continue' | PrerenderErrorHandler; +export type RouteOnErrorValue = 'fail' | 'reload'; + export interface Config { compilerOptions?: CompileOptions; extensions?: string[]; @@ -120,7 +122,10 @@ export interface Config { appDir?: string; browser?: { hydrate?: boolean; - router?: boolean; + router?: { + enabled: boolean; + onError?: RouteOnErrorValue; + }; }; csp?: { mode?: 'hash' | 'nonce' | 'auto'; diff --git a/packages/kit/types/internal.d.ts b/packages/kit/types/internal.d.ts index 294ed943db1d..94b2956eba5c 100644 --- a/packages/kit/types/internal.d.ts +++ b/packages/kit/types/internal.d.ts @@ -5,6 +5,9 @@ import { Fallthrough, RequestHandler } from './endpoint'; import { Either } from './helper'; import { ExternalFetch, GetSession, Handle, HandleError, RequestEvent } from './hooks'; import { Load } from './page'; +import { RouteOnErrorValue } from './config'; + +type PageId = string; export interface PrerenderDependency { response: Response; @@ -138,7 +141,10 @@ export interface SSRRenderOptions { prerender: boolean; read(file: string): Buffer; root: SSRComponent['default']; - router: boolean; + router: { + enabled: boolean; + onError: RouteOnErrorValue; + }; service_worker?: string; target: string; template({ From 612a7b6514f921803809efab09a320604bca3966 Mon Sep 17 00:00:00 2001 From: Philipp Zerelles Date: Mon, 31 Jan 2022 17:12:09 +0100 Subject: [PATCH 03/34] Add app version and only reload if navigation fails and version changed --- documentation/docs/05-modules.md | 2 + .../prerender/fixtures/large-page/input.html | 12 +++- .../prerender/fixtures/large-page/output.json | 1 + packages/kit/src/core/build/build_client.js | 8 +++ packages/kit/src/runtime/app/env.js | 4 ++ packages/kit/src/runtime/app/stores.js | 21 ++++++- packages/kit/src/runtime/client/renderer.js | 57 ++++++++++++++++++- .../kit/src/runtime/server/page/render.js | 13 ++++- packages/kit/types/ambient-modules.d.ts | 10 ++++ 9 files changed, 122 insertions(+), 6 deletions(-) diff --git a/documentation/docs/05-modules.md b/documentation/docs/05-modules.md index 63ee39601015..e61320619b45 100644 --- a/documentation/docs/05-modules.md +++ b/documentation/docs/05-modules.md @@ -71,6 +71,8 @@ The stores themselves attach to the correct context at the point of subscription - `page` contains an object with the current [`url`](https://developer.mozilla.org/en-US/docs/Web/API/URL), [`params`](#loading-input-params), [`stuff`](#loading-output-stuff), [`status`](#loading-output-status) and [`error`](#loading-output-error). - `session` is a [writable store](https://svelte.dev/tutorial/writable-stores) whose initial value is whatever was returned from [`getSession`](#hooks-getsession). It can be written to, but this will _not_ cause changes to persist on the server — this is something you must implement yourself. +- `updated` is a [writable store](https://svelte.dev/tutorial/writable-stores) whose initial value is false. The app's version will be checked every minute for changes, setting the value to true if the version does not match the version at the time the store was created. It can be written to when custom logic is required to detect updates. There is a `check` function to force the version check immediately, returning the result. + ### $lib This is a simple alias to `src/lib`, or whatever directory is specified as [`config.kit.files.lib`]. It allows you to access common components and utility modules without `../../../../` nonsense. diff --git a/packages/kit/src/core/adapt/prerender/fixtures/large-page/input.html b/packages/kit/src/core/adapt/prerender/fixtures/large-page/input.html index 76c52dc6baba..16a228beea46 100644 --- a/packages/kit/src/core/adapt/prerender/fixtures/large-page/input.html +++ b/packages/kit/src/core/adapt/prerender/fixtures/large-page/input.html @@ -1732,7 +1732,7 @@

$app/stores

  • getStores is a convenience function around getContext that - returns { navigating, page, session }. This needs to be called at the + returns { navigating, page, session, updating }. This needs to be called at the top-level or synchronously during component or page initialisation.
@@ -1786,6 +1786,16 @@

$app/stores

>. It can be written to, but this will not cause changes to persist on the server — this is something you must implement yourself. +
  • + updating is a + writable store + whose initial value is false. The app's version will be checked every minute for changes, setting the value to true if the version does not match the version at the time the store was created. It can be written to when custom logic is required to detect updates. There is a check function to force the version check immediately, returning the result. +
  • $lib

    diff --git a/packages/kit/src/core/adapt/prerender/fixtures/large-page/output.json b/packages/kit/src/core/adapt/prerender/fixtures/large-page/output.json index aca0482282df..df68b27a8bbe 100644 --- a/packages/kit/src/core/adapt/prerender/fixtures/large-page/output.json +++ b/packages/kit/src/core/adapt/prerender/fixtures/large-page/output.json @@ -90,6 +90,7 @@ "#loading-output-stuff", "https://svelte.dev/tutorial/writable-stores", "#hooks-getsession", + "https://svelte.dev/tutorial/writable-stores", "#service-workers", "#configuration", "#configuration", diff --git a/packages/kit/src/core/build/build_client.js b/packages/kit/src/core/build/build_client.js index 47cbd4ddc411..26a793c7636d 100644 --- a/packages/kit/src/core/build/build_client.js +++ b/packages/kit/src/core/build/build_client.js @@ -29,6 +29,8 @@ export async function build_client({ output_dir, client_entry_file }) { + if (!process.env.VITE_APP_VERSION) process.env.VITE_APP_VERSION = new Date().toISOString(); + create_app({ manifest_data, output: `${SVELTE_KIT}/generated`, @@ -105,6 +107,12 @@ export async function build_client({ const entry_css = new Set(); find_deps(entry, vite_manifest, entry_js, entry_css); + fs.writeFileSync( + `${client_out_dir}/version.json`, + JSON.stringify(process.env.VITE_APP_VERSION), + 'utf-8' + ); + return { assets, chunks, diff --git a/packages/kit/src/runtime/app/env.js b/packages/kit/src/runtime/app/env.js index f422aa5091b3..14b0ccfabe95 100644 --- a/packages/kit/src/runtime/app/env.js +++ b/packages/kit/src/runtime/app/env.js @@ -14,5 +14,9 @@ export const mode = import.meta.env.MODE; * @type {import('$app/env').amp} */ export const amp = !!import.meta.env.VITE_SVELTEKIT_AMP; +/** + * @type {import('$app/env').version} + */ +export const version = import.meta.env.VITE_APP_VERSION; export { prerendering } from '../env.js'; diff --git a/packages/kit/src/runtime/app/stores.js b/packages/kit/src/runtime/app/stores.js index 0b1accd4d25c..239f7b96ac79 100644 --- a/packages/kit/src/runtime/app/stores.js +++ b/packages/kit/src/runtime/app/stores.js @@ -32,7 +32,8 @@ export const getStores = () => { subscribe: stores.navigating.subscribe }; }, - session: stores.session + session: stores.session, + updated: stores.updated }; }; @@ -77,3 +78,21 @@ export const session = { set: () => throw_error('set'), update: () => throw_error('update') }; + +/** @type {typeof import('$app/stores').updated} */ +export const updated = { + subscribe(fn) { + const store = getStores().updated; + + if (browser) { + updated.set = store.set; + updated.update = store.update; + updated.check = store.check; + } + + return store.subscribe(fn); + }, + set: () => throw_error('set'), + update: () => throw_error('update'), + check: () => throw_error('check') +}; diff --git a/packages/kit/src/runtime/client/renderer.js b/packages/kit/src/runtime/client/renderer.js index 6698c2c0af82..ff2b6f356f63 100644 --- a/packages/kit/src/runtime/client/renderer.js +++ b/packages/kit/src/runtime/client/renderer.js @@ -40,6 +40,44 @@ function notifiable_store(value) { return { notify, set, subscribe }; } +/** + * @param {any} value + * @param {(set: (new_value: any) => void) => any} fn + * @param {number | undefined} interval + */ +function checkable_store(value, fn, interval) { + const { set, update, subscribe } = writable(value); + + setInterval(() => { + fn(set); + }, interval); + + return { + set, + update, + subscribe, + check: () => fn(set) + }; +} + +/** + * @returns {Promise} + */ +async function has_version_changed() { + if (import.meta.env.DEV) return false; + + const headers = new Headers(); + headers.append('pragma', 'no-cache'); + headers.append('cache-control', 'no-cache'); + + const current_version = import.meta.env.VITE_APP_VERSION; + const new_version = await fetch('_app/version.json', { headers }) + .then((res) => res.json()) + .catch(() => undefined); + + return new_version && current_version && new_version !== current_version; +} + /** * @param {RequestInfo} resource * @param {RequestInit} [opts] @@ -109,7 +147,17 @@ export class Renderer { url: notifiable_store({}), page: notifiable_store({}), navigating: writable(/** @type {Navigating | null} */ (null)), - session: writable(session) + session: writable(session), + updated: checkable_store( + false, + (set) => { + return has_version_changed().then((changed) => { + if (changed) set(true); + return changed; + }); + }, + 300000 + ) }; this.$session = null; @@ -279,8 +327,11 @@ export class Renderer { return; } } else if (navigation_result.props?.page?.status >= 400 && onError !== 'fail') { - location.href = info.url.href; - return; + const updated = await this.stores.updated.check(); + if (updated) { + location.href = info.url.href; + return; + } } this.updating = true; diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index 2a33fcb89f29..df063a2a773b 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -80,12 +80,23 @@ export async function render_response({ const session = writable($session); + const updated = () => { + const { set, update, subscribe } = writable(false); + return { + set, + update, + subscribe, + check: () => {} + }; + }; + /** @type {Record} */ const props = { stores: { page: writable(null), navigating: writable(null), - session + session, + updated }, page: { url: state.prerender ? create_prerendering_url_proxy(url) : url, diff --git a/packages/kit/types/ambient-modules.d.ts b/packages/kit/types/ambient-modules.d.ts index 083519b0bd24..ebe2a3c82855 100644 --- a/packages/kit/types/ambient-modules.d.ts +++ b/packages/kit/types/ambient-modules.d.ts @@ -23,6 +23,10 @@ declare module '$app/env' { * By default, `svelte-kit dev` runs with `mode=development` and `svelte-kit build` runs with `mode=production`. */ export const mode: string; + /** + * Version of the app + */ + export const version: string; } declare module '$app/navigation' { @@ -110,6 +114,7 @@ declare module '$app/stores' { navigating: typeof navigating; page: typeof page; session: Writable; + updated: Writable & { check: () => boolean }; }; /** * A readable store whose value contains page data. @@ -132,6 +137,11 @@ declare module '$app/stores' { * It can be written to, but this will not cause changes to persist on the server — this is something you must implement yourself. */ export const session: Writable; + /** + * A writable store indicating if the site was updated since the store was created. + * It can be written to when custom logic is required to detect updates. + */ + export const updated: Writable & { check: () => boolean }; } declare module '$service-worker' { From 859ea09d316fa32f9350eda206dff7c7019c0336 Mon Sep 17 00:00:00 2001 From: pz-mxu <66033561+pz-mxu@users.noreply.github.com> Date: Tue, 1 Feb 2022 10:08:45 +0100 Subject: [PATCH 04/34] Remove unused code --- packages/kit/types/internal.d.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/kit/types/internal.d.ts b/packages/kit/types/internal.d.ts index 94b2956eba5c..64777fea4ad3 100644 --- a/packages/kit/types/internal.d.ts +++ b/packages/kit/types/internal.d.ts @@ -7,8 +7,6 @@ import { ExternalFetch, GetSession, Handle, HandleError, RequestEvent } from './ import { Load } from './page'; import { RouteOnErrorValue } from './config'; -type PageId = string; - export interface PrerenderDependency { response: Response; body: null | string | Uint8Array; From 36fce1570c27f1699b7bb624110efdcbf6b27eac Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 1 Feb 2022 08:38:27 -0500 Subject: [PATCH 05/34] Update packages/kit/src/core/build/build_client.js --- packages/kit/src/core/build/build_client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/core/build/build_client.js b/packages/kit/src/core/build/build_client.js index 26a793c7636d..77e44790323c 100644 --- a/packages/kit/src/core/build/build_client.js +++ b/packages/kit/src/core/build/build_client.js @@ -109,7 +109,7 @@ export async function build_client({ fs.writeFileSync( `${client_out_dir}/version.json`, - JSON.stringify(process.env.VITE_APP_VERSION), + JSON.stringify({ version: process.env.VITE_APP_VERSION }) 'utf-8' ); From cc6846795fee137c0a5e7fb97fb8a9f27a9b02bb Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 1 Feb 2022 08:38:33 -0500 Subject: [PATCH 06/34] Update packages/kit/src/core/build/build_client.js --- packages/kit/src/core/build/build_client.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/kit/src/core/build/build_client.js b/packages/kit/src/core/build/build_client.js index 77e44790323c..c5c737c17b7f 100644 --- a/packages/kit/src/core/build/build_client.js +++ b/packages/kit/src/core/build/build_client.js @@ -110,7 +110,6 @@ export async function build_client({ fs.writeFileSync( `${client_out_dir}/version.json`, JSON.stringify({ version: process.env.VITE_APP_VERSION }) - 'utf-8' ); return { From 05fb7d573037db7329fb798d6b6e80220c46efde Mon Sep 17 00:00:00 2001 From: Philipp Zerelles Date: Tue, 1 Feb 2022 13:30:22 +0100 Subject: [PATCH 07/34] Make version and check interval configurable --- documentation/docs/14-configuration.md | 11 ++++++++ packages/kit/src/core/build/build_client.js | 7 +++--- packages/kit/src/core/config/index.spec.js | 6 ++++- packages/kit/src/core/config/options.js | 5 ++++ packages/kit/src/runtime/app/env.js | 2 +- packages/kit/src/runtime/client/renderer.js | 28 ++++++++++++++------- packages/kit/types/ambient-modules.d.ts | 2 +- packages/kit/types/config.d.ts | 4 +++ packages/kit/types/internal.d.ts | 3 +-- pnpm-lock.yaml | 4 +-- 10 files changed, 53 insertions(+), 19 deletions(-) diff --git a/documentation/docs/14-configuration.md b/documentation/docs/14-configuration.md index abf9f6d42267..5b699ecca6c4 100644 --- a/documentation/docs/14-configuration.md +++ b/documentation/docs/14-configuration.md @@ -71,6 +71,10 @@ const config = { }, target: null, trailingSlash: 'never', + version: { + name: Date.now(), + pollInterval: 60_000, + }, vite: () => ({}) }, @@ -256,6 +260,13 @@ Whether to remove, append, or ignore trailing slashes when resolving URLs to rou > Ignoring trailing slashes is not recommended — the semantics of relative paths differ between the two cases (`./y` from `/x` is `/y`, but from `/x/` is `/x/y`), and `/x` and `/x/` are treated as separate URLs which is harmful to SEO. If you use this option, ensure that you implement logic for conditionally adding or removing trailing slashes from `request.path` inside your [`handle`](#hooks-handle) function. +### version + +An object containing zero or more of the following values: + +- `name` - current app version string +- `pollInterval` - interval in milliseconds to poll for version changes + ### vite A [Vite config object](https://vitejs.dev/config), or a function that returns one. You can pass [Vite and Rollup plugins](https://github.com/vitejs/awesome-vite#plugins) via [the `plugins` option](https://vitejs.dev/config/#plugins) to customize your build in advanced ways such as supporting image optimization, Tauri, WASM, Workbox, and more. SvelteKit will prevent you from setting certain build-related options since it depends on certain configuration values. diff --git a/packages/kit/src/core/build/build_client.js b/packages/kit/src/core/build/build_client.js index 26a793c7636d..47ddc750aaf9 100644 --- a/packages/kit/src/core/build/build_client.js +++ b/packages/kit/src/core/build/build_client.js @@ -29,7 +29,9 @@ export async function build_client({ output_dir, client_entry_file }) { - if (!process.env.VITE_APP_VERSION) process.env.VITE_APP_VERSION = new Date().toISOString(); + process.env.VITE_APP_VERSION = config.kit.version?.name || Date.now().toString(); + process.env.VITE_APP_VERSION_FILE = `${config.kit.appDir}/version.json`; + process.env.VITE_APP_VERSION_POLL_INTERVAL = `${config.kit.version?.pollInterval || 60_000}`; create_app({ manifest_data, @@ -109,8 +111,7 @@ export async function build_client({ fs.writeFileSync( `${client_out_dir}/version.json`, - JSON.stringify(process.env.VITE_APP_VERSION), - 'utf-8' + JSON.stringify({ version: process.env.VITE_APP_VERSION }) ); return { diff --git a/packages/kit/src/core/config/index.spec.js b/packages/kit/src/core/config/index.spec.js index c2012f4fc5eb..b90aa14cd4d8 100644 --- a/packages/kit/src/core/config/index.spec.js +++ b/packages/kit/src/core/config/index.spec.js @@ -99,7 +99,11 @@ const get_defaults = (prefix = '') => ({ router: undefined, ssr: undefined, target: null, - trailingSlash: 'never' + trailingSlash: 'never', + version: { + name: null, + pollInterval: 60_000 + } } }); diff --git a/packages/kit/src/core/config/options.js b/packages/kit/src/core/config/options.js index 28f65ffdd37e..fd9f3c0c57ec 100644 --- a/packages/kit/src/core/config/options.js +++ b/packages/kit/src/core/config/options.js @@ -261,6 +261,11 @@ const options = object( trailingSlash: list(['never', 'always', 'ignore']), + version: object({ + name: string(null), + pollInterval: number(60_000) + }), + vite: validate( () => ({}), (input, keypath) => { diff --git a/packages/kit/src/runtime/app/env.js b/packages/kit/src/runtime/app/env.js index 14b0ccfabe95..eadd57f146e9 100644 --- a/packages/kit/src/runtime/app/env.js +++ b/packages/kit/src/runtime/app/env.js @@ -17,6 +17,6 @@ export const amp = !!import.meta.env.VITE_SVELTEKIT_AMP; /** * @type {import('$app/env').version} */ -export const version = import.meta.env.VITE_APP_VERSION; +export const version = import.meta.env.VITE_APP_VERSION?.toString(); export { prerendering } from '../env.js'; diff --git a/packages/kit/src/runtime/client/renderer.js b/packages/kit/src/runtime/client/renderer.js index ff2b6f356f63..8feccea887f0 100644 --- a/packages/kit/src/runtime/client/renderer.js +++ b/packages/kit/src/runtime/client/renderer.js @@ -3,6 +3,7 @@ import { writable } from 'svelte/store'; import { coalesce_to_error } from '../../utils/error.js'; import { hash } from '../hash.js'; import { normalize } from '../load.js'; +import { base } from '../paths.js'; /** * @typedef {import('types/internal').CSRComponent} CSRComponent @@ -64,18 +65,22 @@ function checkable_store(value, fn, interval) { * @returns {Promise} */ async function has_version_changed() { - if (import.meta.env.DEV) return false; - - const headers = new Headers(); - headers.append('pragma', 'no-cache'); - headers.append('cache-control', 'no-cache'); + if (import.meta.env.DEV || import.meta.env.SSR) return false; + const file = import.meta.env.VITE_APP_VERSION_FILE; const current_version = import.meta.env.VITE_APP_VERSION; - const new_version = await fetch('_app/version.json', { headers }) + if (!file || !current_version) return false; + + const new_version = await fetch(`${base}/${file}`, { + headers: { + pragma: 'no-cache', + 'cache-control': 'no-cache' + } + }) .then((res) => res.json()) - .catch(() => undefined); + .then((res) => res.version); - return new_version && current_version && new_version !== current_version; + return new_version && new_version !== current_version; } /** @@ -143,6 +148,11 @@ export class Renderer { promise: null }; + const versionPollInterval = + typeof import.meta.env.VITE_APP_VERSION_POLL_INTERVAL === 'string' + ? parseInt(import.meta.env.VITE_APP_VERSION_POLL_INTERVAL) + : 60_000; + this.stores = { url: notifiable_store({}), page: notifiable_store({}), @@ -156,7 +166,7 @@ export class Renderer { return changed; }); }, - 300000 + versionPollInterval ) }; diff --git a/packages/kit/types/ambient-modules.d.ts b/packages/kit/types/ambient-modules.d.ts index ebe2a3c82855..efbfe291bea5 100644 --- a/packages/kit/types/ambient-modules.d.ts +++ b/packages/kit/types/ambient-modules.d.ts @@ -26,7 +26,7 @@ declare module '$app/env' { /** * Version of the app */ - export const version: string; + export const version: string | undefined; } declare module '$app/navigation' { diff --git a/packages/kit/types/config.d.ts b/packages/kit/types/config.d.ts index d9246610f3b9..5c621b695c02 100644 --- a/packages/kit/types/config.d.ts +++ b/packages/kit/types/config.d.ts @@ -170,6 +170,10 @@ export interface Config { }; target?: string; trailingSlash?: TrailingSlash; + version?: { + name?: string; + pollInterval?: number; + }; vite?: ViteConfig | (() => ViteConfig); }; preprocess?: any; diff --git a/packages/kit/types/internal.d.ts b/packages/kit/types/internal.d.ts index 64777fea4ad3..2103528c535a 100644 --- a/packages/kit/types/internal.d.ts +++ b/packages/kit/types/internal.d.ts @@ -1,11 +1,10 @@ import { OutputAsset, OutputChunk } from 'rollup'; -import { ValidatedConfig } from './config'; +import { ValidatedConfig, RouteOnErrorValue } from './config'; import { InternalApp, SSRManifest } from './app'; import { Fallthrough, RequestHandler } from './endpoint'; import { Either } from './helper'; import { ExternalFetch, GetSession, Handle, HandleError, RequestEvent } from './hooks'; import { Load } from './page'; -import { RouteOnErrorValue } from './config'; export interface PrerenderDependency { response: Response; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 71fa8a064381..c2ca47ed9fe6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -207,7 +207,7 @@ importers: specifiers: '@fontsource/fira-mono': ^4.5.0 '@lukeed/uuid': ^2.0.0 - '@sveltejs/adapter-auto': workspace:* + '@sveltejs/adapter-static': workspace:* '@sveltejs/kit': workspace:* cookie: ^0.4.1 svelte: ^3.44.0 @@ -218,7 +218,7 @@ importers: '@lukeed/uuid': 2.0.0 cookie: 0.4.1 devDependencies: - '@sveltejs/adapter-auto': link:../../../adapter-auto + '@sveltejs/adapter-static': link:../../../adapter-static '@sveltejs/kit': link:../../../kit svelte: 3.44.2 svelte-preprocess: 4.9.8_svelte@3.44.2+typescript@4.5.2 From 629920b710f2a8dd6828f2572a458389392c6e1f Mon Sep 17 00:00:00 2001 From: Philipp Zerelles Date: Tue, 1 Feb 2022 15:27:57 +0100 Subject: [PATCH 08/34] Restore original router configuration without error option --- documentation/docs/14-configuration.md | 5 +--- packages/kit/src/core/build/build_server.js | 5 +--- packages/kit/src/core/config/index.spec.js | 5 +--- packages/kit/src/core/config/options.js | 8 +------ packages/kit/src/runtime/client/renderer.js | 8 +++---- packages/kit/src/runtime/client/router.js | 11 +++------ packages/kit/src/runtime/client/start.js | 23 ++++++++----------- .../kit/src/runtime/server/page/render.js | 5 +--- .../kit/src/runtime/server/page/respond.js | 2 +- .../runtime/server/page/respond_with_error.js | 2 +- packages/kit/types/config.d.ts | 7 +----- packages/kit/types/internal.d.ts | 7 ++---- 12 files changed, 25 insertions(+), 63 deletions(-) diff --git a/documentation/docs/14-configuration.md b/documentation/docs/14-configuration.md index 5b699ecca6c4..ee243b2284c7 100644 --- a/documentation/docs/14-configuration.md +++ b/documentation/docs/14-configuration.md @@ -19,10 +19,7 @@ const config = { appDir: '_app', browser: { hydrate: true, - router: { - enabled: true, - onError: 'fail' - }, + router: true }, csp: { mode: 'auto', diff --git a/packages/kit/src/core/build/build_server.js b/packages/kit/src/core/build/build_server.js index b64368f4f2f6..ae8b50bc2c63 100644 --- a/packages/kit/src/core/build/build_server.js +++ b/packages/kit/src/core/build/build_server.js @@ -88,10 +88,7 @@ export class App { read, root, service_worker: ${has_service_worker ? "base + '/service-worker.js'" : 'null'}, - router: { - enabled: ${s(config.kit.browser.router.enabled)}, - onError: ${s(config.kit.browser.router.onError)} - }, + router: ${s(config.kit.browser.router)}, target: ${s(config.kit.target)}, template, template_contains_nonce: ${template.includes('%svelte.nonce%')}, diff --git a/packages/kit/src/core/config/index.spec.js b/packages/kit/src/core/config/index.spec.js index b90aa14cd4d8..c543177e4727 100644 --- a/packages/kit/src/core/config/index.spec.js +++ b/packages/kit/src/core/config/index.spec.js @@ -16,10 +16,7 @@ const get_defaults = (prefix = '') => ({ appDir: '_app', browser: { hydrate: true, - router: { - enabled: true, - onError: 'fail' - } + router: true }, csp: { mode: 'auto', diff --git a/packages/kit/src/core/config/options.js b/packages/kit/src/core/config/options.js index fd9f3c0c57ec..cc6cb9327435 100644 --- a/packages/kit/src/core/config/options.js +++ b/packages/kit/src/core/config/options.js @@ -59,13 +59,7 @@ const options = object( browser: object({ hydrate: boolean(true), - router: object({ - enabled: boolean(true), - onError: validate('fail', (input, keypath) => { - if (['reload', 'fail'].includes(input)) return input; - throw new Error(`${keypath} should be either "reload" or "fail"`); - }) - }) + router: boolean(true) }), csp: object({ diff --git a/packages/kit/src/runtime/client/renderer.js b/packages/kit/src/runtime/client/renderer.js index 8feccea887f0..66863ab56a42 100644 --- a/packages/kit/src/runtime/client/renderer.js +++ b/packages/kit/src/runtime/client/renderer.js @@ -7,7 +7,6 @@ import { base } from '../paths.js'; /** * @typedef {import('types/internal').CSRComponent} CSRComponent - * @typedef {import('types/config').RouteOnErrorValue} RouteOnErrorValue * @typedef {{ from: URL; to: URL }} Navigating */ @@ -287,7 +286,7 @@ export class Renderer { * @param {import('./types').NavigationInfo} info * @param {string[]} chain * @param {boolean} no_cache - * @param {{hash?: string, scroll: { x: number, y: number } | null, keepfocus: boolean, onError: RouteOnErrorValue}} [opts] + * @param {{hash?: string, scroll: { x: number, y: number } | null, keepfocus: boolean}} [opts] */ async handle_navigation(info, chain, no_cache, opts) { if (this.started) { @@ -304,11 +303,10 @@ export class Renderer { * @param {import('./types').NavigationInfo} info * @param {string[]} chain * @param {boolean} no_cache - * @param {{hash?: string, scroll: { x: number, y: number } | null, keepfocus: boolean, onError: RouteOnErrorValue}} [opts] + * @param {{hash?: string, scroll: { x: number, y: number } | null, keepfocus: boolean}} [opts] */ async update(info, chain, no_cache, opts) { const token = (this.token = {}); - const onError = opts?.onError || 'fail'; let navigation_result = await this._get_navigation_result(info, no_cache); // abort if user navigated during update @@ -336,7 +334,7 @@ export class Renderer { return; } - } else if (navigation_result.props?.page?.status >= 400 && onError !== 'fail') { + } else if (navigation_result.props?.page?.status >= 400) { const updated = await this.stores.updated.check(); if (updated) { location.href = info.url.href; diff --git a/packages/kit/src/runtime/client/router.js b/packages/kit/src/runtime/client/router.js index fb8455f80f44..ef6c5981099d 100644 --- a/packages/kit/src/runtime/client/router.js +++ b/packages/kit/src/runtime/client/router.js @@ -35,11 +35,10 @@ export class Router { * base: string; * routes: import('types/internal').CSRRoute[]; * trailing_slash: import('types/internal').TrailingSlash; - * renderer: import('./renderer').Renderer; - * onError?: import('types/config').RouteOnErrorValue; + * renderer: import('./renderer').Renderer * }} opts */ - constructor({ base, routes, trailing_slash, renderer, onError }) { + constructor({ base, routes, trailing_slash, renderer }) { this.base = base; this.routes = routes; this.trailing_slash = trailing_slash; @@ -50,9 +49,6 @@ export class Router { this.renderer = renderer; renderer.router = this; - /** @type {import('types/config').RouteOnErrorValue} */ - this.onError = onError || 'fail'; - this.enabled = true; // make it possible to reset focus @@ -411,8 +407,7 @@ export class Router { await this.renderer.handle_navigation(info, chain, false, { scroll, - keepfocus, - onError: this.onError + keepfocus }); this.navigating--; diff --git a/packages/kit/src/runtime/client/start.js b/packages/kit/src/runtime/client/start.js index 975a5b6b238a..57f4d65c2cfb 100644 --- a/packages/kit/src/runtime/client/start.js +++ b/packages/kit/src/runtime/client/start.js @@ -14,10 +14,7 @@ import { set_paths } from '../paths.js'; * }, * target: Node; * session: any; - * route: { - * enabled: boolean; - * onError?: import('types/config').RouteOnErrorValue; - * }, + * route: boolean; * spa: boolean; * trailing_slash: import('types/internal').TrailingSlash; * hydrate: { @@ -41,16 +38,14 @@ export async function start({ paths, target, session, route, spa, trailing_slash session }); - const router = - route && route.enabled - ? new Router({ - base: paths.base, - routes, - trailing_slash, - renderer, - onError: route.onError - }) - : null; + const router = route + ? new Router({ + base: paths.base, + routes, + trailing_slash, + renderer + }) + : null; init({ router, renderer }); set_paths(paths); diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index df063a2a773b..eb53b39ad11f 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -166,10 +166,7 @@ export async function render_response({ session: ${try_serialize($session, (error) => { throw new Error(`Failed to serialize session data: ${error.message}`); })}, - route: { - enabled: ${!!page_config.router}, - onError: ${s(options.router.onError)} - }, + route: ${!!page_config.router}, spa: ${!ssr}, trailing_slash: ${s(options.trailing_slash)}, hydrate: ${ssr && page_config.hydrate ? `{ diff --git a/packages/kit/src/runtime/server/page/respond.js b/packages/kit/src/runtime/server/page/respond.js index 31d5f9f84995..6a2b746addb7 100644 --- a/packages/kit/src/runtime/server/page/respond.js +++ b/packages/kit/src/runtime/server/page/respond.js @@ -250,7 +250,7 @@ function get_page_config(leaf, options) { } return { - router: 'router' in leaf ? !!leaf.router : options.router.enabled, + router: 'router' in leaf ? !!leaf.router : options.router, hydrate: 'hydrate' in leaf ? !!leaf.hydrate : options.hydrate }; } diff --git a/packages/kit/src/runtime/server/page/respond_with_error.js b/packages/kit/src/runtime/server/page/respond_with_error.js index d8532164d967..8eb5c1730e29 100644 --- a/packages/kit/src/runtime/server/page/respond_with_error.js +++ b/packages/kit/src/runtime/server/page/respond_with_error.js @@ -65,7 +65,7 @@ export async function respond_with_error({ event, options, state, $session, stat $session, page_config: { hydrate: options.hydrate, - router: options.router.enabled + router: options.router }, stuff: error_loaded.stuff, status, diff --git a/packages/kit/types/config.d.ts b/packages/kit/types/config.d.ts index 5c621b695c02..1ccf11aec0f8 100644 --- a/packages/kit/types/config.d.ts +++ b/packages/kit/types/config.d.ts @@ -111,8 +111,6 @@ export interface PrerenderErrorHandler { export type PrerenderOnErrorValue = 'fail' | 'continue' | PrerenderErrorHandler; -export type RouteOnErrorValue = 'fail' | 'reload'; - export interface Config { compilerOptions?: CompileOptions; extensions?: string[]; @@ -122,10 +120,7 @@ export interface Config { appDir?: string; browser?: { hydrate?: boolean; - router?: { - enabled: boolean; - onError?: RouteOnErrorValue; - }; + router?: boolean; }; csp?: { mode?: 'hash' | 'nonce' | 'auto'; diff --git a/packages/kit/types/internal.d.ts b/packages/kit/types/internal.d.ts index 2103528c535a..294ed943db1d 100644 --- a/packages/kit/types/internal.d.ts +++ b/packages/kit/types/internal.d.ts @@ -1,5 +1,5 @@ import { OutputAsset, OutputChunk } from 'rollup'; -import { ValidatedConfig, RouteOnErrorValue } from './config'; +import { ValidatedConfig } from './config'; import { InternalApp, SSRManifest } from './app'; import { Fallthrough, RequestHandler } from './endpoint'; import { Either } from './helper'; @@ -138,10 +138,7 @@ export interface SSRRenderOptions { prerender: boolean; read(file: string): Buffer; root: SSRComponent['default']; - router: { - enabled: boolean; - onError: RouteOnErrorValue; - }; + router: boolean; service_worker?: string; target: string; template({ From 64fa8446f8c11dca027abbf189919d0fc621941f Mon Sep 17 00:00:00 2001 From: Philipp Zerelles Date: Tue, 1 Feb 2022 15:38:03 +0100 Subject: [PATCH 09/34] Fix instantiation of "updated" store --- packages/kit/src/runtime/server/page/render.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index eb53b39ad11f..a1dd67df00a5 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -80,7 +80,7 @@ export async function render_response({ const session = writable($session); - const updated = () => { + const updated = (() => { const { set, update, subscribe } = writable(false); return { set, @@ -88,7 +88,7 @@ export async function render_response({ subscribe, check: () => {} }; - }; + })(); /** @type {Record} */ const props = { From ec09520c92f2f8a0921d98c641fc6bc19596310d Mon Sep 17 00:00:00 2001 From: Philipp Zerelles Date: Tue, 1 Feb 2022 15:49:32 +0100 Subject: [PATCH 10/34] Fix pnpm-lock.yaml --- pnpm-lock.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c2ca47ed9fe6..71fa8a064381 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -207,7 +207,7 @@ importers: specifiers: '@fontsource/fira-mono': ^4.5.0 '@lukeed/uuid': ^2.0.0 - '@sveltejs/adapter-static': workspace:* + '@sveltejs/adapter-auto': workspace:* '@sveltejs/kit': workspace:* cookie: ^0.4.1 svelte: ^3.44.0 @@ -218,7 +218,7 @@ importers: '@lukeed/uuid': 2.0.0 cookie: 0.4.1 devDependencies: - '@sveltejs/adapter-static': link:../../../adapter-static + '@sveltejs/adapter-auto': link:../../../adapter-auto '@sveltejs/kit': link:../../../kit svelte: 3.44.2 svelte-preprocess: 4.9.8_svelte@3.44.2+typescript@4.5.2 From 344b32a2cf5e931bcf4b9f65c407d3f61d88ee61 Mon Sep 17 00:00:00 2001 From: pz-mxu <66033561+pz-mxu@users.noreply.github.com> Date: Tue, 1 Feb 2022 15:54:16 +0100 Subject: [PATCH 11/34] Update packages/kit/src/core/build/build_client.js Co-authored-by: Rich Harris --- packages/kit/src/core/build/build_client.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kit/src/core/build/build_client.js b/packages/kit/src/core/build/build_client.js index 4ec29489b3a8..34b61164cb33 100644 --- a/packages/kit/src/core/build/build_client.js +++ b/packages/kit/src/core/build/build_client.js @@ -29,9 +29,9 @@ export async function build_client({ output_dir, client_entry_file }) { - process.env.VITE_APP_VERSION = config.kit.version?.name || Date.now().toString(); + process.env.VITE_APP_VERSION = config.kit.version.name; process.env.VITE_APP_VERSION_FILE = `${config.kit.appDir}/version.json`; - process.env.VITE_APP_VERSION_POLL_INTERVAL = `${config.kit.version?.pollInterval || 60_000}`; + process.env.VITE_APP_VERSION_POLL_INTERVAL = `${config.kit.version.pollInterval}`; create_app({ manifest_data, From f122617e8b9f9967dd94f2fc23623e0d8634a403 Mon Sep 17 00:00:00 2001 From: pz-mxu <66033561+pz-mxu@users.noreply.github.com> Date: Tue, 1 Feb 2022 15:55:21 +0100 Subject: [PATCH 12/34] Update packages/kit/src/runtime/app/env.js Co-authored-by: Rich Harris --- packages/kit/src/runtime/app/env.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/runtime/app/env.js b/packages/kit/src/runtime/app/env.js index eadd57f146e9..14b0ccfabe95 100644 --- a/packages/kit/src/runtime/app/env.js +++ b/packages/kit/src/runtime/app/env.js @@ -17,6 +17,6 @@ export const amp = !!import.meta.env.VITE_SVELTEKIT_AMP; /** * @type {import('$app/env').version} */ -export const version = import.meta.env.VITE_APP_VERSION?.toString(); +export const version = import.meta.env.VITE_APP_VERSION; export { prerendering } from '../env.js'; From 7263f32820ebcc32adaa4d2198b01f2f70dc0f21 Mon Sep 17 00:00:00 2001 From: pz-mxu <66033561+pz-mxu@users.noreply.github.com> Date: Tue, 1 Feb 2022 17:10:48 +0100 Subject: [PATCH 13/34] Update packages/kit/src/core/config/options.js Co-authored-by: Rich Harris --- packages/kit/src/core/config/options.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/core/config/options.js b/packages/kit/src/core/config/options.js index cc6cb9327435..1d746097a905 100644 --- a/packages/kit/src/core/config/options.js +++ b/packages/kit/src/core/config/options.js @@ -256,7 +256,7 @@ const options = object( trailingSlash: list(['never', 'always', 'ignore']), version: object({ - name: string(null), + name: string(Date.now().toString()), pollInterval: number(60_000) }), From 603bc06dc4a321b1eac26aa4edd4fc98522de956 Mon Sep 17 00:00:00 2001 From: Philipp Zerelles Date: Tue, 1 Feb 2022 17:26:42 +0100 Subject: [PATCH 14/34] Patch tests to work with version timestamp --- packages/kit/src/core/config/index.spec.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/kit/src/core/config/index.spec.js b/packages/kit/src/core/config/index.spec.js index c543177e4727..c9cec897d778 100644 --- a/packages/kit/src/core/config/index.spec.js +++ b/packages/kit/src/core/config/index.spec.js @@ -98,7 +98,7 @@ const get_defaults = (prefix = '') => ({ target: null, trailingSlash: 'never', version: { - name: null, + name: Date.now().toString(), pollInterval: 60_000 } } @@ -114,7 +114,10 @@ test('fills in defaults', () => { remove_keys(validated, ([, v]) => typeof v === 'function'); - assert.equal(validated, get_defaults()); + const defaults = get_defaults(); + defaults.kit.version.name = validated.kit.version.name; + + assert.equal(validated, defaults); }); test('errors on invalid values', () => { @@ -163,6 +166,9 @@ test('fills in partial blanks', () => { kit: { files: { assets: 'public' + }, + version: { + name: '0' } } }); @@ -176,6 +182,7 @@ test('fills in partial blanks', () => { const config = get_defaults(); config.kit.files.assets = 'public'; + config.kit.version.name = '0'; assert.equal(validated, config); }); @@ -338,7 +345,10 @@ test('load default config (esm)', async () => { const config = await load_config({ cwd }); remove_keys(config, ([, v]) => typeof v === 'function'); - assert.equal(config, get_defaults(cwd + '/')); + const defaults = get_defaults(cwd + '/'); + defaults.kit.version.name = config.kit.version.name; + + assert.equal(config, defaults); }); test('errors on loading config with incorrect default export', async () => { From 32b7a5f84e8b8afe61d3be3b9f4b5476a7845253 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 1 Feb 2022 11:54:12 -0500 Subject: [PATCH 15/34] Update packages/kit/src/core/adapt/prerender/fixtures/large-page/input.html --- .../adapt/prerender/fixtures/large-page/input.html | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/packages/kit/src/core/adapt/prerender/fixtures/large-page/input.html b/packages/kit/src/core/adapt/prerender/fixtures/large-page/input.html index 16a228beea46..3924fc3f0570 100644 --- a/packages/kit/src/core/adapt/prerender/fixtures/large-page/input.html +++ b/packages/kit/src/core/adapt/prerender/fixtures/large-page/input.html @@ -1786,16 +1786,6 @@

    $app/stores

    >. It can be written to, but this will not cause changes to persist on the server — this is something you must implement yourself. -
  • - updating is a - writable store - whose initial value is false. The app's version will be checked every minute for changes, setting the value to true if the version does not match the version at the time the store was created. It can be written to when custom logic is required to detect updates. There is a check function to force the version check immediately, returning the result. -
  • $lib

    From d8733ef1f0401d40272d833752c369a170b0adc3 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 1 Feb 2022 11:54:17 -0500 Subject: [PATCH 16/34] Update packages/kit/src/core/adapt/prerender/fixtures/large-page/output.json --- .../kit/src/core/adapt/prerender/fixtures/large-page/output.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/kit/src/core/adapt/prerender/fixtures/large-page/output.json b/packages/kit/src/core/adapt/prerender/fixtures/large-page/output.json index df68b27a8bbe..aca0482282df 100644 --- a/packages/kit/src/core/adapt/prerender/fixtures/large-page/output.json +++ b/packages/kit/src/core/adapt/prerender/fixtures/large-page/output.json @@ -90,7 +90,6 @@ "#loading-output-stuff", "https://svelte.dev/tutorial/writable-stores", "#hooks-getsession", - "https://svelte.dev/tutorial/writable-stores", "#service-workers", "#configuration", "#configuration", From c0506d3db6017ff02980a0504771343c3181f660 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 1 Feb 2022 12:20:03 -0500 Subject: [PATCH 17/34] Update packages/kit/src/core/adapt/prerender/fixtures/large-page/input.html --- .../kit/src/core/adapt/prerender/fixtures/large-page/input.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/core/adapt/prerender/fixtures/large-page/input.html b/packages/kit/src/core/adapt/prerender/fixtures/large-page/input.html index 3924fc3f0570..76c52dc6baba 100644 --- a/packages/kit/src/core/adapt/prerender/fixtures/large-page/input.html +++ b/packages/kit/src/core/adapt/prerender/fixtures/large-page/input.html @@ -1732,7 +1732,7 @@

    $app/stores

    • getStores is a convenience function around getContext that - returns { navigating, page, session, updating }. This needs to be called at the + returns { navigating, page, session }. This needs to be called at the top-level or synchronously during component or page initialisation.
    From 6db21b7915007afb534252e118d6659373ee22d1 Mon Sep 17 00:00:00 2001 From: Philipp Zerelles Date: Tue, 1 Feb 2022 18:22:34 +0100 Subject: [PATCH 18/34] Remove version from $app/env --- packages/kit/src/runtime/app/env.js | 4 ---- packages/kit/types/ambient-modules.d.ts | 4 ---- 2 files changed, 8 deletions(-) diff --git a/packages/kit/src/runtime/app/env.js b/packages/kit/src/runtime/app/env.js index 14b0ccfabe95..f422aa5091b3 100644 --- a/packages/kit/src/runtime/app/env.js +++ b/packages/kit/src/runtime/app/env.js @@ -14,9 +14,5 @@ export const mode = import.meta.env.MODE; * @type {import('$app/env').amp} */ export const amp = !!import.meta.env.VITE_SVELTEKIT_AMP; -/** - * @type {import('$app/env').version} - */ -export const version = import.meta.env.VITE_APP_VERSION; export { prerendering } from '../env.js'; diff --git a/packages/kit/types/ambient-modules.d.ts b/packages/kit/types/ambient-modules.d.ts index efbfe291bea5..adce6f6e00f9 100644 --- a/packages/kit/types/ambient-modules.d.ts +++ b/packages/kit/types/ambient-modules.d.ts @@ -23,10 +23,6 @@ declare module '$app/env' { * By default, `svelte-kit dev` runs with `mode=development` and `svelte-kit build` runs with `mode=production`. */ export const mode: string; - /** - * Version of the app - */ - export const version: string | undefined; } declare module '$app/navigation' { From c6788b5a6eca80db621f31c6c0200f5c80be209e Mon Sep 17 00:00:00 2001 From: pz-mxu <66033561+pz-mxu@users.noreply.github.com> Date: Tue, 1 Feb 2022 18:26:45 +0100 Subject: [PATCH 19/34] Apply suggestions from code review Co-authored-by: Rich Harris --- packages/kit/src/runtime/client/renderer.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/kit/src/runtime/client/renderer.js b/packages/kit/src/runtime/client/renderer.js index 66863ab56a42..ffd528c1b2d9 100644 --- a/packages/kit/src/runtime/client/renderer.js +++ b/packages/kit/src/runtime/client/renderer.js @@ -147,10 +147,7 @@ export class Renderer { promise: null }; - const versionPollInterval = - typeof import.meta.env.VITE_APP_VERSION_POLL_INTERVAL === 'string' - ? parseInt(import.meta.env.VITE_APP_VERSION_POLL_INTERVAL) - : 60_000; + const version_poll_interval = +import.meta.env.VITE_APP_VERSION_POLL_INTERVAL; this.stores = { url: notifiable_store({}), @@ -165,7 +162,7 @@ export class Renderer { return changed; }); }, - versionPollInterval + version_poll_interval ) }; From 5483f58fff9a787f9e2278bca56011d7ce6cfe40 Mon Sep 17 00:00:00 2001 From: Philipp Zerelles Date: Tue, 1 Feb 2022 18:49:48 +0100 Subject: [PATCH 20/34] Use default version poll interval to 0 (don't poll) --- documentation/docs/14-configuration.md | 2 +- packages/kit/src/core/config/index.spec.js | 2 +- packages/kit/src/core/config/options.js | 2 +- packages/kit/src/runtime/client/renderer.js | 15 ++++++++++----- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/documentation/docs/14-configuration.md b/documentation/docs/14-configuration.md index ee243b2284c7..cad639ab513d 100644 --- a/documentation/docs/14-configuration.md +++ b/documentation/docs/14-configuration.md @@ -70,7 +70,7 @@ const config = { trailingSlash: 'never', version: { name: Date.now(), - pollInterval: 60_000, + pollInterval: 0, }, vite: () => ({}) }, diff --git a/packages/kit/src/core/config/index.spec.js b/packages/kit/src/core/config/index.spec.js index c9cec897d778..6c30cd609da5 100644 --- a/packages/kit/src/core/config/index.spec.js +++ b/packages/kit/src/core/config/index.spec.js @@ -99,7 +99,7 @@ const get_defaults = (prefix = '') => ({ trailingSlash: 'never', version: { name: Date.now().toString(), - pollInterval: 60_000 + pollInterval: 0 } } }); diff --git a/packages/kit/src/core/config/options.js b/packages/kit/src/core/config/options.js index 1d746097a905..0d8e8aab3931 100644 --- a/packages/kit/src/core/config/options.js +++ b/packages/kit/src/core/config/options.js @@ -257,7 +257,7 @@ const options = object( version: object({ name: string(Date.now().toString()), - pollInterval: number(60_000) + pollInterval: number(0) }), vite: validate( diff --git a/packages/kit/src/runtime/client/renderer.js b/packages/kit/src/runtime/client/renderer.js index ffd528c1b2d9..e75da8a9b972 100644 --- a/packages/kit/src/runtime/client/renderer.js +++ b/packages/kit/src/runtime/client/renderer.js @@ -43,14 +43,16 @@ function notifiable_store(value) { /** * @param {any} value * @param {(set: (new_value: any) => void) => any} fn - * @param {number | undefined} interval + * @param {number} interval */ function checkable_store(value, fn, interval) { const { set, update, subscribe } = writable(value); - setInterval(() => { - fn(set); - }, interval); + if (interval) { + setInterval(() => { + fn(set); + }, interval); + } return { set, @@ -147,7 +149,10 @@ export class Renderer { promise: null }; - const version_poll_interval = +import.meta.env.VITE_APP_VERSION_POLL_INTERVAL; + const version_poll_interval = + typeof import.meta.env.VITE_APP_VERSION_POLL_INTERVAL === 'string' + ? parseInt(import.meta.env.VITE_APP_VERSION_POLL_INTERVAL) + : 0; this.stores = { url: notifiable_store({}), From 4c0a744a39a518a6ec28f32ccf485d538f98581c Mon Sep 17 00:00:00 2001 From: Philipp Zerelles Date: Tue, 1 Feb 2022 19:06:20 +0100 Subject: [PATCH 21/34] Only poll if interval is a positive number --- packages/kit/src/runtime/client/renderer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/runtime/client/renderer.js b/packages/kit/src/runtime/client/renderer.js index e75da8a9b972..ce709a5252a4 100644 --- a/packages/kit/src/runtime/client/renderer.js +++ b/packages/kit/src/runtime/client/renderer.js @@ -48,7 +48,7 @@ function notifiable_store(value) { function checkable_store(value, fn, interval) { const { set, update, subscribe } = writable(value); - if (interval) { + if (interval > 0) { setInterval(() => { fn(set); }, interval); From 9174109a93f98720d2220521b00c788113657724 Mon Sep 17 00:00:00 2001 From: Philipp Zerelles Date: Tue, 1 Feb 2022 19:34:53 +0100 Subject: [PATCH 22/34] Better documentation of "updated" store --- documentation/docs/05-modules.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/documentation/docs/05-modules.md b/documentation/docs/05-modules.md index e61320619b45..fda78bc46405 100644 --- a/documentation/docs/05-modules.md +++ b/documentation/docs/05-modules.md @@ -56,22 +56,21 @@ import { base, assets } from '$app/paths'; ### $app/stores ```js -import { getStores, navigating, page, session } from '$app/stores'; +import { getStores, navigating, page, session, updated } from '$app/stores'; ``` Stores are _contextual_ — they are added to the [context](https://svelte.dev/tutorial/context-api) of your root component. This means that `session` and `page` are unique to each request on the server, rather than shared between multiple requests handled by the same server simultaneously, which is what makes it safe to include user-specific data in `session`. Because of that, the stores are not free-floating objects: they must be accessed during component initialisation, like anything else that would be accessed with `getContext`. -- `getStores` is a convenience function around `getContext` that returns `{ navigating, page, session }`. This needs to be called at the top-level or synchronously during component or page initialisation. +- `getStores` is a convenience function around `getContext` that returns `{ navigating, page, session, updated }`. This needs to be called at the top-level or synchronously during component or page initialisation. The stores themselves attach to the correct context at the point of subscription, which means you can import and use them directly in components without boilerplate. However, it still needs to be called synchronously on component or page initialisation when the `$`-prefix isn't used. Use `getStores` to safely `.subscribe` asynchronously instead. - `navigating` is a [readable store](https://svelte.dev/tutorial/readable-stores). When navigating starts, its value is `{ from, to }`, where `from` and `to` are both [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) instances. When navigating finishes, its value reverts to `null`. - `page` contains an object with the current [`url`](https://developer.mozilla.org/en-US/docs/Web/API/URL), [`params`](#loading-input-params), [`stuff`](#loading-output-stuff), [`status`](#loading-output-status) and [`error`](#loading-output-error). - `session` is a [writable store](https://svelte.dev/tutorial/writable-stores) whose initial value is whatever was returned from [`getSession`](#hooks-getsession). It can be written to, but this will _not_ cause changes to persist on the server — this is something you must implement yourself. - -- `updated` is a [writable store](https://svelte.dev/tutorial/writable-stores) whose initial value is false. The app's version will be checked every minute for changes, setting the value to true if the version does not match the version at the time the store was created. It can be written to when custom logic is required to detect updates. There is a `check` function to force the version check immediately, returning the result. +- `updated` is a [writable store](https://svelte.dev/tutorial/writable-stores) whose initial value is false. The app's version, which can be specified as [`config.kit.version.name`], will optionally be checked for changes regularly, setting the value to true if the version does not match the version at the time the store was created. There is a `check` function to force the version check immediately, returning the result. The interval for regular checks can be specified in ms as [`config.kit.version.pollInterval`]. A value of `0` will disable regular checks. The store can be written to when custom logic is required to detect updates. ### $lib From ff1a9302525bf564464013294d8961bafe7dd057 Mon Sep 17 00:00:00 2001 From: pz-mxu <66033561+pz-mxu@users.noreply.github.com> Date: Tue, 1 Feb 2022 19:48:47 +0100 Subject: [PATCH 23/34] Apply suggestions from code review Co-authored-by: Rich Harris --- documentation/docs/05-modules.md | 2 +- packages/kit/src/runtime/app/stores.js | 4 ---- packages/kit/src/runtime/client/renderer.js | 2 -- packages/kit/src/runtime/server/page/render.js | 11 ++--------- packages/kit/types/ambient-modules.d.ts | 4 ++-- 5 files changed, 5 insertions(+), 18 deletions(-) diff --git a/documentation/docs/05-modules.md b/documentation/docs/05-modules.md index fda78bc46405..37734412dd1f 100644 --- a/documentation/docs/05-modules.md +++ b/documentation/docs/05-modules.md @@ -70,7 +70,7 @@ The stores themselves attach to the correct context at the point of subscription - `navigating` is a [readable store](https://svelte.dev/tutorial/readable-stores). When navigating starts, its value is `{ from, to }`, where `from` and `to` are both [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) instances. When navigating finishes, its value reverts to `null`. - `page` contains an object with the current [`url`](https://developer.mozilla.org/en-US/docs/Web/API/URL), [`params`](#loading-input-params), [`stuff`](#loading-output-stuff), [`status`](#loading-output-status) and [`error`](#loading-output-error). - `session` is a [writable store](https://svelte.dev/tutorial/writable-stores) whose initial value is whatever was returned from [`getSession`](#hooks-getsession). It can be written to, but this will _not_ cause changes to persist on the server — this is something you must implement yourself. -- `updated` is a [writable store](https://svelte.dev/tutorial/writable-stores) whose initial value is false. The app's version, which can be specified as [`config.kit.version.name`], will optionally be checked for changes regularly, setting the value to true if the version does not match the version at the time the store was created. There is a `check` function to force the version check immediately, returning the result. The interval for regular checks can be specified in ms as [`config.kit.version.pollInterval`]. A value of `0` will disable regular checks. The store can be written to when custom logic is required to detect updates. +- `updated` is a [readable store](https://svelte.dev/tutorial/readable-stores) whose initial value is false. If [`version.pollInterval`](#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. ### $lib diff --git a/packages/kit/src/runtime/app/stores.js b/packages/kit/src/runtime/app/stores.js index 239f7b96ac79..1b040d3ce4bc 100644 --- a/packages/kit/src/runtime/app/stores.js +++ b/packages/kit/src/runtime/app/stores.js @@ -85,14 +85,10 @@ export const updated = { const store = getStores().updated; if (browser) { - updated.set = store.set; - updated.update = store.update; updated.check = store.check; } return store.subscribe(fn); }, - set: () => throw_error('set'), - update: () => throw_error('update'), check: () => throw_error('check') }; diff --git a/packages/kit/src/runtime/client/renderer.js b/packages/kit/src/runtime/client/renderer.js index ce709a5252a4..872e039454d1 100644 --- a/packages/kit/src/runtime/client/renderer.js +++ b/packages/kit/src/runtime/client/renderer.js @@ -55,8 +55,6 @@ function checkable_store(value, fn, interval) { } return { - set, - update, subscribe, check: () => fn(set) }; diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index a1dd67df00a5..29b259a00947 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -80,15 +80,8 @@ export async function render_response({ const session = writable($session); - const updated = (() => { - const { set, update, subscribe } = writable(false); - return { - set, - update, - subscribe, - check: () => {} - }; - })(); + const updated = readable(false); + updated.check = () => {}; /** @type {Record} */ const props = { diff --git a/packages/kit/types/ambient-modules.d.ts b/packages/kit/types/ambient-modules.d.ts index adce6f6e00f9..264b9780ba28 100644 --- a/packages/kit/types/ambient-modules.d.ts +++ b/packages/kit/types/ambient-modules.d.ts @@ -110,7 +110,7 @@ declare module '$app/stores' { navigating: typeof navigating; page: typeof page; session: Writable; - updated: Writable & { check: () => boolean }; + updated: typeof updated; }; /** * A readable store whose value contains page data. @@ -137,7 +137,7 @@ declare module '$app/stores' { * A writable store indicating if the site was updated since the store was created. * It can be written to when custom logic is required to detect updates. */ - export const updated: Writable & { check: () => boolean }; + export const updated: Readable & { check: () => boolean }; } declare module '$service-worker' { From 75e4de555f9f9bc75ee1a5569df67929cc3cef1f Mon Sep 17 00:00:00 2001 From: pz-mxu <66033561+pz-mxu@users.noreply.github.com> Date: Tue, 1 Feb 2022 19:54:44 +0100 Subject: [PATCH 24/34] Update documentation/docs/14-configuration.md Co-authored-by: Rich Harris --- documentation/docs/14-configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/14-configuration.md b/documentation/docs/14-configuration.md index cad639ab513d..edc6cf1a6e0b 100644 --- a/documentation/docs/14-configuration.md +++ b/documentation/docs/14-configuration.md @@ -69,7 +69,7 @@ const config = { target: null, trailingSlash: 'never', version: { - name: Date.now(), + name: Date.now().toString(), pollInterval: 0, }, vite: () => ({}) From 0ee276bd9dcb217204a88492565211f247b5deeb Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 1 Feb 2022 14:03:32 -0500 Subject: [PATCH 25/34] reuse updated store --- packages/kit/src/runtime/server/page/render.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index 29b259a00947..e2f60523238e 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -1,5 +1,5 @@ import devalue from 'devalue'; -import { writable } from 'svelte/store'; +import { readable, writable } from 'svelte/store'; import { coalesce_to_error } from '../../../utils/error.js'; import { hash } from '../../hash.js'; import { escape_html_attr } from '../../../utils/escape.js'; @@ -9,6 +9,11 @@ import { Csp, csp_ready } from './csp.js'; // TODO rename this function/module +const updated = { + ...readable(false), + check: () => {} +}; + /** * @param {{ * branch: Array; @@ -80,9 +85,6 @@ export async function render_response({ const session = writable($session); - const updated = readable(false); - updated.check = () => {}; - /** @type {Record} */ const props = { stores: { From 17367b0add3d30ab85643fb6d7a4a87d018c3bb7 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 1 Feb 2022 14:04:04 -0500 Subject: [PATCH 26/34] return false --- packages/kit/src/runtime/server/page/render.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index e2f60523238e..8e05147f04ed 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -11,7 +11,7 @@ import { Csp, csp_ready } from './csp.js'; const updated = { ...readable(false), - check: () => {} + check: () => false }; /** From cf01a3b32b086e2df04d4e2644aefd0b09af7a5c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 1 Feb 2022 14:16:36 -0500 Subject: [PATCH 27/34] always set poll interval --- packages/kit/src/core/dev/plugin.js | 2 ++ packages/kit/src/runtime/client/renderer.js | 7 +++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/kit/src/core/dev/plugin.js b/packages/kit/src/core/dev/plugin.js index 5e95dfe23b5c..fa9df47cee13 100644 --- a/packages/kit/src/core/dev/plugin.js +++ b/packages/kit/src/core/dev/plugin.js @@ -27,6 +27,8 @@ export async function create_plugin(config, cwd) { amp = (await import('./amp_hook.js')).handle; } + process.env.VITE_APP_VERSION_POLL_INTERVAL = '0'; + /** @type {import('types/internal').Respond} */ const respond = (await import(`${runtime}/server/index.js`)).respond; diff --git a/packages/kit/src/runtime/client/renderer.js b/packages/kit/src/runtime/client/renderer.js index 872e039454d1..f66194b78560 100644 --- a/packages/kit/src/runtime/client/renderer.js +++ b/packages/kit/src/runtime/client/renderer.js @@ -147,10 +147,9 @@ export class Renderer { promise: null }; - const version_poll_interval = - typeof import.meta.env.VITE_APP_VERSION_POLL_INTERVAL === 'string' - ? parseInt(import.meta.env.VITE_APP_VERSION_POLL_INTERVAL) - : 0; + const version_poll_interval = +( + /** @type {string} */ (import.meta.env.VITE_APP_VERSION_POLL_INTERVAL) + ); this.stores = { url: notifiable_store({}), From f5055f572901abb32e39c01ed5043b1feda7b77c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 1 Feb 2022 14:20:06 -0500 Subject: [PATCH 28/34] lint --- packages/kit/src/runtime/client/renderer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kit/src/runtime/client/renderer.js b/packages/kit/src/runtime/client/renderer.js index f66194b78560..818fa2a64567 100644 --- a/packages/kit/src/runtime/client/renderer.js +++ b/packages/kit/src/runtime/client/renderer.js @@ -46,7 +46,7 @@ function notifiable_store(value) { * @param {number} interval */ function checkable_store(value, fn, interval) { - const { set, update, subscribe } = writable(value); + const { set, subscribe } = writable(value); if (interval > 0) { setInterval(() => { From 042033e642d5382508134b814fc2755d9095c17f Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 1 Feb 2022 14:51:51 -0500 Subject: [PATCH 29/34] make updated store stop polling once change is detected --- packages/kit/src/runtime/client/renderer.js | 83 +++++++++------------ 1 file changed, 36 insertions(+), 47 deletions(-) diff --git a/packages/kit/src/runtime/client/renderer.js b/packages/kit/src/runtime/client/renderer.js index 818fa2a64567..05d77704f6ae 100644 --- a/packages/kit/src/runtime/client/renderer.js +++ b/packages/kit/src/runtime/client/renderer.js @@ -40,46 +40,48 @@ function notifiable_store(value) { return { notify, set, subscribe }; } -/** - * @param {any} value - * @param {(set: (new_value: any) => void) => any} fn - * @param {number} interval - */ -function checkable_store(value, fn, interval) { - const { set, subscribe } = writable(value); +function create_updated_store() { + const { set, subscribe } = writable(false); - if (interval > 0) { - setInterval(() => { - fn(set); - }, interval); - } + const interval = +(/** @type {string} */ (import.meta.env.VITE_APP_VERSION_POLL_INTERVAL)); + const initial = import.meta.env.VITE_APP_VERSION; - return { - subscribe, - check: () => fn(set) - }; -} + /** @type {NodeJS.Timeout} */ + let timeout; -/** - * @returns {Promise} - */ -async function has_version_changed() { - if (import.meta.env.DEV || import.meta.env.SSR) return false; + async function check() { + if (import.meta.env.DEV || import.meta.env.SSR) return false; + + clearTimeout(timeout); - const file = import.meta.env.VITE_APP_VERSION_FILE; - const current_version = import.meta.env.VITE_APP_VERSION; - if (!file || !current_version) return false; + if (interval) timeout = setTimeout(check, interval); - const new_version = await fetch(`${base}/${file}`, { - headers: { - pragma: 'no-cache', - 'cache-control': 'no-cache' + const file = import.meta.env.VITE_APP_VERSION_FILE; + + const res = await fetch(`${base}/${file}`, { + headers: { + pragma: 'no-cache', + 'cache-control': 'no-cache' + } + }); + + if (res.ok) { + const { version } = await res.json(); + if (version !== initial) { + set(true); + clearTimeout(timeout); + } + } else { + throw new Error(`Version check failed: ${res.status}`); } - }) - .then((res) => res.json()) - .then((res) => res.version); + } + + if (interval) timeout = setTimeout(check, interval); - return new_version && new_version !== current_version; + return { + subscribe, + check + }; } /** @@ -147,25 +149,12 @@ export class Renderer { promise: null }; - const version_poll_interval = +( - /** @type {string} */ (import.meta.env.VITE_APP_VERSION_POLL_INTERVAL) - ); - this.stores = { url: notifiable_store({}), page: notifiable_store({}), navigating: writable(/** @type {Navigating | null} */ (null)), session: writable(session), - updated: checkable_store( - false, - (set) => { - return has_version_changed().then((changed) => { - if (changed) set(true); - return changed; - }); - }, - version_poll_interval - ) + updated: create_updated_store() }; this.$session = null; From 78aad47085404fe3013e94f8070c1c3f4a6f68b4 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 1 Feb 2022 14:54:17 -0500 Subject: [PATCH 30/34] use VITE_SVELTEKIT_ namespace --- packages/kit/src/core/build/build_client.js | 8 ++++---- packages/kit/src/core/dev/plugin.js | 2 +- packages/kit/src/runtime/client/renderer.js | 8 +++++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/kit/src/core/build/build_client.js b/packages/kit/src/core/build/build_client.js index 34b61164cb33..5ef786309da5 100644 --- a/packages/kit/src/core/build/build_client.js +++ b/packages/kit/src/core/build/build_client.js @@ -29,9 +29,9 @@ export async function build_client({ output_dir, client_entry_file }) { - process.env.VITE_APP_VERSION = config.kit.version.name; - process.env.VITE_APP_VERSION_FILE = `${config.kit.appDir}/version.json`; - process.env.VITE_APP_VERSION_POLL_INTERVAL = `${config.kit.version.pollInterval}`; + process.env.VITE_SVELTEKIT_APP_VERSION = config.kit.version.name; + process.env.VITE_SVELTEKIT_APP_VERSION_FILE = `${config.kit.appDir}/version.json`; + process.env.VITE_SVELTEKIT_APP_VERSION_POLL_INTERVAL = `${config.kit.version.pollInterval}`; create_app({ manifest_data, @@ -111,7 +111,7 @@ export async function build_client({ fs.writeFileSync( `${client_out_dir}/version.json`, - JSON.stringify({ version: process.env.VITE_APP_VERSION }) + JSON.stringify({ version: process.env.VITE_SVELTEKIT_APP_VERSION }) ); return { diff --git a/packages/kit/src/core/dev/plugin.js b/packages/kit/src/core/dev/plugin.js index fa9df47cee13..a35314d02f17 100644 --- a/packages/kit/src/core/dev/plugin.js +++ b/packages/kit/src/core/dev/plugin.js @@ -27,7 +27,7 @@ export async function create_plugin(config, cwd) { amp = (await import('./amp_hook.js')).handle; } - process.env.VITE_APP_VERSION_POLL_INTERVAL = '0'; + process.env.VITE_SVELTEKIT_APP_VERSION_POLL_INTERVAL = '0'; /** @type {import('types/internal').Respond} */ const respond = (await import(`${runtime}/server/index.js`)).respond; diff --git a/packages/kit/src/runtime/client/renderer.js b/packages/kit/src/runtime/client/renderer.js index 05d77704f6ae..3ae1f73f04b1 100644 --- a/packages/kit/src/runtime/client/renderer.js +++ b/packages/kit/src/runtime/client/renderer.js @@ -43,8 +43,10 @@ function notifiable_store(value) { function create_updated_store() { const { set, subscribe } = writable(false); - const interval = +(/** @type {string} */ (import.meta.env.VITE_APP_VERSION_POLL_INTERVAL)); - const initial = import.meta.env.VITE_APP_VERSION; + const interval = +( + /** @type {string} */ (import.meta.env.VITE_SVELTEKIT_APP_VERSION_POLL_INTERVAL) + ); + const initial = import.meta.env.VITE_SVELTEKIT_APP_VERSION; /** @type {NodeJS.Timeout} */ let timeout; @@ -56,7 +58,7 @@ function create_updated_store() { if (interval) timeout = setTimeout(check, interval); - const file = import.meta.env.VITE_APP_VERSION_FILE; + const file = import.meta.env.VITE_SVELTEKIT_APP_VERSION_FILE; const res = await fetch(`${base}/${file}`, { headers: { From d9631dc8983a7a966cb4fc15c9afe9a57d5354b9 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 1 Feb 2022 14:55:06 -0500 Subject: [PATCH 31/34] Update documentation/docs/14-configuration.md --- documentation/docs/14-configuration.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/documentation/docs/14-configuration.md b/documentation/docs/14-configuration.md index edc6cf1a6e0b..488ff0f77a28 100644 --- a/documentation/docs/14-configuration.md +++ b/documentation/docs/14-configuration.md @@ -264,6 +264,9 @@ An object containing zero or more of the following values: - `name` - current app version string - `pollInterval` - interval in milliseconds to poll for version changes +Client-side navigation can be buggy if you deploy a new version of your app while people are using it. If the code for the new page is already loaded, it may have stale content; if it isn't, the app's route manifest may point to a JavaScript file that no longer exists. SvelteKit solves this problem by falling back to traditional full-page navigation if it detects that a new version has been deployed, using the `name` specified here (which defaults to a timestamp of the build). + +If you set `pollInterval` to a non-zero value, SvelteKit will poll for new versions in the background and set the value of the [`updated`](#modules-$app-stores) store to `true` when it detects one. ### vite A [Vite config object](https://vitejs.dev/config), or a function that returns one. You can pass [Vite and Rollup plugins](https://github.com/vitejs/awesome-vite#plugins) via [the `plugins` option](https://vitejs.dev/config/#plugins) to customize your build in advanced ways such as supporting image optimization, Tauri, WASM, Workbox, and more. SvelteKit will prevent you from setting certain build-related options since it depends on certain configuration values. From 731e31e85c94e54074c774986e6722e8ba5383ef Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 1 Feb 2022 14:56:06 -0500 Subject: [PATCH 32/34] Update documentation/docs/14-configuration.md --- documentation/docs/14-configuration.md | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/docs/14-configuration.md b/documentation/docs/14-configuration.md index 488ff0f77a28..d7006965cff6 100644 --- a/documentation/docs/14-configuration.md +++ b/documentation/docs/14-configuration.md @@ -267,6 +267,7 @@ An object containing zero or more of the following values: Client-side navigation can be buggy if you deploy a new version of your app while people are using it. If the code for the new page is already loaded, it may have stale content; if it isn't, the app's route manifest may point to a JavaScript file that no longer exists. SvelteKit solves this problem by falling back to traditional full-page navigation if it detects that a new version has been deployed, using the `name` specified here (which defaults to a timestamp of the build). If you set `pollInterval` to a non-zero value, SvelteKit will poll for new versions in the background and set the value of the [`updated`](#modules-$app-stores) store to `true` when it detects one. + ### vite A [Vite config object](https://vitejs.dev/config), or a function that returns one. You can pass [Vite and Rollup plugins](https://github.com/vitejs/awesome-vite#plugins) via [the `plugins` option](https://vitejs.dev/config/#plugins) to customize your build in advanced ways such as supporting image optimization, Tauri, WASM, Workbox, and more. SvelteKit will prevent you from setting certain build-related options since it depends on certain configuration values. From 120cc1968d74c1e3a824bcb4b38e1b8cbb8e9eb9 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 1 Feb 2022 15:17:39 -0500 Subject: [PATCH 33/34] oops --- packages/kit/src/runtime/client/renderer.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/kit/src/runtime/client/renderer.js b/packages/kit/src/runtime/client/renderer.js index 3ae1f73f04b1..1c109494db56 100644 --- a/packages/kit/src/runtime/client/renderer.js +++ b/packages/kit/src/runtime/client/renderer.js @@ -69,10 +69,14 @@ function create_updated_store() { if (res.ok) { const { version } = await res.json(); - if (version !== initial) { + const updated = version !== initial; + + if (updated) { set(true); clearTimeout(timeout); } + + return updated; } else { throw new Error(`Version check failed: ${res.status}`); } From ede1b40a26e318bcca455f95616f07f21e0aba53 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 1 Feb 2022 15:18:51 -0500 Subject: [PATCH 34/34] changeset --- .changeset/nice-steaks-change.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/nice-steaks-change.md diff --git a/.changeset/nice-steaks-change.md b/.changeset/nice-steaks-change.md new file mode 100644 index 000000000000..9f975779ffe2 --- /dev/null +++ b/.changeset/nice-steaks-change.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +Add version config and expose updated store