diff --git a/.changeset/cyan-ants-float.md b/.changeset/cyan-ants-float.md new file mode 100644 index 000000000000..4a7193bc350e --- /dev/null +++ b/.changeset/cyan-ants-float.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': minor +--- + +feat: allow $app/paths to be used without an app diff --git a/packages/kit/package.json b/packages/kit/package.json index c0c49b882454..d44fabdd82ca 100644 --- a/packages/kit/package.json +++ b/packages/kit/package.json @@ -60,7 +60,7 @@ "lint": "prettier --check . --config ../../.prettierrc --ignore-path .gitignore && eslint src/**", "check": "tsc", "check:all": "tsc && pnpm -r --filter=\"./**\" check", - "format": "pnpm lint --write", + "format": "prettier --write . --config ../../.prettierrc --ignore-path .gitignore", "test": "pnpm test:unit && pnpm test:integration", "test:integration": "pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test", "test:cross-platform:dev": "pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test:cross-platform:dev", diff --git a/packages/kit/src/constants.js b/packages/kit/src/constants.js index 8dc9aab27904..60c17e2beca2 100644 --- a/packages/kit/src/constants.js +++ b/packages/kit/src/constants.js @@ -1,5 +1,7 @@ -// in `vite dev` and `vite preview`, we use a fake asset path so that we can -// serve local assets while verifying that requests are correctly prefixed +/** + * A fake asset path used in `vite dev` and `vite preview`, so that we can + * serve local assets while verifying that requests are correctly prefixed + */ export const SVELTE_KIT_ASSETS = '/_svelte_kit_assets'; export const GENERATED_COMMENT = '// this file is generated — do not edit it\n'; diff --git a/packages/kit/src/core/postbuild/analyse.js b/packages/kit/src/core/postbuild/analyse.js index 12f61b06fecd..7aef3c55fbd6 100644 --- a/packages/kit/src/core/postbuild/analyse.js +++ b/packages/kit/src/core/postbuild/analyse.js @@ -1,6 +1,6 @@ import { join } from 'node:path'; import { pathToFileURL } from 'node:url'; -import { get_option } from '../../runtime/server/utils.js'; +import { get_option } from '../../utils/options.js'; import { validate_common_exports, validate_page_server_exports, diff --git a/packages/kit/src/core/postbuild/fallback.js b/packages/kit/src/core/postbuild/fallback.js index 6d22c998b93e..de49c1681cfe 100644 --- a/packages/kit/src/core/postbuild/fallback.js +++ b/packages/kit/src/core/postbuild/fallback.js @@ -15,9 +15,7 @@ installPolyfills(); const server_root = join(config.outDir, 'output'); /** @type {import('types').ServerInternalModule} */ -const { set_building, set_paths } = await import( - pathToFileURL(`${server_root}/server/internal.js`).href -); +const { set_building } = await import(pathToFileURL(`${server_root}/server/internal.js`).href); /** @type {import('types').ServerModule} */ const { Server } = await import(pathToFileURL(`${server_root}/server/index.js`).href); @@ -26,7 +24,6 @@ const { Server } = await import(pathToFileURL(`${server_root}/server/index.js`). const manifest = (await import(pathToFileURL(manifest_path).href)).manifest; set_building(true); -set_paths(config.paths); const server = new Server(manifest); await server.init({ env: JSON.parse(env) }); diff --git a/packages/kit/src/core/postbuild/prerender.js b/packages/kit/src/core/postbuild/prerender.js index feee804b8dee..515d815e3303 100644 --- a/packages/kit/src/core/postbuild/prerender.js +++ b/packages/kit/src/core/postbuild/prerender.js @@ -100,8 +100,6 @@ async function prerender({ out, manifest_path, metadata, verbose, env }) { /** @type {Map} */ const saved = new Map(); - internal.set_paths(config.paths); - const server = new Server(manifest); await server.init({ env }); diff --git a/packages/kit/src/core/sync/write_server.js b/packages/kit/src/core/sync/write_server.js index 395a80f080f5..c4e81d262ab2 100644 --- a/packages/kit/src/core/sync/write_server.js +++ b/packages/kit/src/core/sync/write_server.js @@ -25,9 +25,8 @@ const server_template = ({ error_page }) => ` import root from '../root.svelte'; -import { set_building, set_paths, set_private_env, set_public_env, set_version } from '${runtime_directory}/shared.js'; +import { set_assets, set_building, set_private_env, set_public_env, set_version } from '${runtime_directory}/shared.js'; -set_paths(${s(config.kit.paths)}); set_version(${s(config.kit.version.name)}); export const options = { @@ -58,7 +57,7 @@ export function get_hooks() { return ${hooks ? `import(${s(hooks)})` : '{}'}; } -export { set_building, set_paths, set_private_env, set_public_env }; +export { set_assets, set_building, set_private_env, set_public_env }; `; // TODO need to re-run this whenever src/app.html or src/error.html are diff --git a/packages/kit/src/exports/vite/dev/index.js b/packages/kit/src/exports/vite/dev/index.js index 927bc921a5f8..5d7ca57ff606 100644 --- a/packages/kit/src/exports/vite/dev/index.js +++ b/packages/kit/src/exports/vite/dev/index.js @@ -438,20 +438,17 @@ export async function dev(vite, vite_config, svelte_config) { return; } - // we have to import `Server` before calling `set_paths` + // we have to import `Server` before calling `set_assets` const { Server } = /** @type {import('types').ServerModule} */ ( await vite.ssrLoadModule(`${runtime_base}/server/index.js`) ); - const { set_paths, set_version, set_fix_stack_trace } = + const { set_assets, set_version, set_fix_stack_trace } = /** @type {import('types').ServerInternalModule} */ ( await vite.ssrLoadModule(`${runtime_base}/shared.js`) ); - set_paths({ - base: svelte_config.kit.paths.base, - assets - }); + set_assets(assets); set_version(svelte_config.kit.version.name); diff --git a/packages/kit/src/exports/vite/index.js b/packages/kit/src/exports/vite/index.js index 6d7a639c0d2b..fe600f3b40f6 100644 --- a/packages/kit/src/exports/vite/index.js +++ b/packages/kit/src/exports/vite/index.js @@ -22,6 +22,7 @@ import { get_config_aliases, get_env } from './utils.js'; import { write_client_manifest } from '../../core/sync/write_client_manifest.js'; import prerender from '../../core/postbuild/prerender.js'; import analyse from '../../core/postbuild/analyse.js'; +import { s } from '../../utils/misc.js'; export { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; @@ -253,9 +254,9 @@ function kit({ svelte_config }) { new_config.build.ssr = !secondary_build_started; new_config.define = { - __SVELTEKIT_ADAPTER_NAME__: JSON.stringify(kit.adapter?.name), - __SVELTEKIT_APP_VERSION_FILE__: JSON.stringify(`${kit.appDir}/version.json`), - __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: JSON.stringify(kit.version.pollInterval), + __SVELTEKIT_ADAPTER_NAME__: s(kit.adapter?.name), + __SVELTEKIT_APP_VERSION_FILE__: s(`${kit.appDir}/version.json`), + __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: s(kit.version.pollInterval), __SVELTEKIT_DEV__: 'false', __SVELTEKIT_EMBEDDED__: kit.embedded ? 'true' : 'false' }; @@ -315,7 +316,9 @@ function kit({ svelte_config }) { async resolveId(id) { // treat $env/static/[public|private] as virtual - if (id.startsWith('$env/') || id === '$service-worker') return `\0${id}`; + if (id.startsWith('$env/') || id === '$internal/paths' || id === '$service-worker') { + return `\0${id}`; + } }, async load(id, options) { @@ -351,6 +354,15 @@ function kit({ svelte_config }) { ); case '\0$service-worker': return create_service_worker_module(svelte_config); + case '\0$internal/paths': + const { assets, base } = svelte_config.kit.paths; + return `export const base = ${s(base)}; +export let assets = ${s(assets)}; + +/** @param {string} path */ +export function set_assets(path) { + assets = path; +}`; } } }; @@ -552,7 +564,7 @@ function kit({ svelte_config }) { this.emitFile({ type: 'asset', fileName: `${kit.appDir}/version.json`, - source: JSON.stringify({ version: kit.version.name }) + source: s({ version: kit.version.name }) }); }, @@ -788,9 +800,9 @@ export const build = []; export const files = [ ${create_assets(config) .filter((asset) => config.kit.serviceWorker.files(asset.file)) - .map((asset) => `${JSON.stringify(`${config.kit.paths.base}/${asset.file}`)}`) + .map((asset) => `${s(`${config.kit.paths.base}/${asset.file}`)}`) .join(',\n\t\t\t\t')} ]; export const prerendered = []; -export const version = ${JSON.stringify(config.kit.version.name)}; +export const version = ${s(config.kit.version.name)}; `; diff --git a/packages/kit/src/exports/vite/preview/index.js b/packages/kit/src/exports/vite/preview/index.js index 1ff2ffb754f2..f70024bce238 100644 --- a/packages/kit/src/exports/vite/preview/index.js +++ b/packages/kit/src/exports/vite/preview/index.js @@ -37,14 +37,14 @@ export async function preview(vite, vite_config, svelte_config) { const dir = join(svelte_config.kit.outDir, 'output/server'); /** @type {import('types').ServerInternalModule} */ - const { set_paths } = await import(pathToFileURL(join(dir, 'internal.js')).href); + const { set_assets } = await import(pathToFileURL(join(dir, 'internal.js')).href); /** @type {import('types').ServerModule} */ const { Server } = await import(pathToFileURL(join(dir, 'index.js')).href); const { manifest } = await import(pathToFileURL(join(dir, 'manifest.js')).href); - set_paths({ base, assets }); + set_assets(assets); const server = new Server(manifest); await server.init({ diff --git a/packages/kit/src/runtime/app/paths.js b/packages/kit/src/runtime/app/paths.js index 5a433b6ddeaa..31d76ad8d7f8 100644 --- a/packages/kit/src/runtime/app/paths.js +++ b/packages/kit/src/runtime/app/paths.js @@ -1 +1 @@ -export { base, assets } from '../shared.js'; +export { base, assets } from '$internal/paths'; diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js index 4db7fce48907..85f979e900a3 100644 --- a/packages/kit/src/runtime/client/client.js +++ b/packages/kit/src/runtime/client/client.js @@ -26,6 +26,7 @@ import { parse } from './parse.js'; import Root from '__GENERATED__/root.svelte'; import { nodes, server_loads, dictionary, matchers, hooks } from '__CLIENT__/manifest.js'; +import { base } from '$internal/paths'; import { HttpError, Redirect } from '../control.js'; import { stores } from './singletons.js'; import { unwrap_promises } from '../../utils/promises.js'; @@ -66,11 +67,10 @@ function update_scroll_positions(index) { /** * @param {{ * target: HTMLElement; - * base: string; * }} opts * @returns {import('./types').Client} */ -export function create_client({ target, base }) { +export function create_client({ target }) { const container = __SVELTEKIT_EMBEDDED__ ? target : document.documentElement; /** @type {Array<((url: URL) => boolean)>} */ const invalidated = []; diff --git a/packages/kit/src/runtime/client/start.js b/packages/kit/src/runtime/client/start.js index 1083898a97c1..aed3870f4e6d 100644 --- a/packages/kit/src/runtime/client/start.js +++ b/packages/kit/src/runtime/client/start.js @@ -1,23 +1,20 @@ import { DEV } from 'esm-env'; import { create_client } from './client.js'; import { init } from './singletons.js'; -import { set_paths, set_version, set_public_env } from '../shared.js'; +import { set_assets, set_version, set_public_env } from '../shared.js'; /** * @param {{ + * assets: string; * env: Record; * hydrate: Parameters[0]; - * paths: { - * assets: string; - * base: string; - * }, * target: HTMLElement; * version: string; * }} opts */ -export async function start({ env, hydrate, paths, target, version }) { +export async function start({ assets, env, hydrate, target, version }) { set_public_env(env); - set_paths(paths); + set_assets(assets); set_version(version); if (DEV && target === document.body) { @@ -27,8 +24,7 @@ export async function start({ env, hydrate, paths, target, version }) { } const client = create_client({ - target, - base: paths.base + target }); init({ client }); diff --git a/packages/kit/src/runtime/client/utils.js b/packages/kit/src/runtime/client/utils.js index 8c924c35b666..1c3c392af649 100644 --- a/packages/kit/src/runtime/client/utils.js +++ b/packages/kit/src/runtime/client/utils.js @@ -1,6 +1,7 @@ import { BROWSER, DEV } from 'esm-env'; import { writable } from 'svelte/store'; -import { assets, version } from '../shared.js'; +import { assets } from '$internal/paths'; +import { version } from '../shared.js'; import { PRELOAD_PRIORITIES } from './constants.js'; /* global __SVELTEKIT_APP_VERSION_FILE__, __SVELTEKIT_APP_VERSION_POLL_INTERVAL__ */ diff --git a/packages/kit/src/runtime/server/fetch.js b/packages/kit/src/runtime/server/fetch.js index 9bb5b1dc92be..ebde07df2b66 100644 --- a/packages/kit/src/runtime/server/fetch.js +++ b/packages/kit/src/runtime/server/fetch.js @@ -1,6 +1,6 @@ import * as set_cookie_parser from 'set-cookie-parser'; import { respond } from './respond.js'; -import * as paths from '../shared.js'; +import * as paths from '$internal/paths'; /** * @param {{ diff --git a/packages/kit/src/runtime/server/page/index.js b/packages/kit/src/runtime/server/page/index.js index a4f79ebd0595..3bcebe191151 100644 --- a/packages/kit/src/runtime/server/page/index.js +++ b/packages/kit/src/runtime/server/page/index.js @@ -4,7 +4,6 @@ import { normalize_error } from '../../../utils/error.js'; import { add_data_suffix } from '../../../utils/url.js'; import { HttpError, Redirect } from '../../control.js'; import { - get_option, redirect_response, static_error_page, handle_error_and_jsonify, @@ -19,6 +18,7 @@ import { import { load_data, load_server_data } from './load_data.js'; import { render_response } from './render.js'; import { respond_with_error } from './respond_with_error.js'; +import { get_option } from '../../../utils/options.js'; /** * @param {import('types').RequestEvent} event diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js index 215ca2b3fef6..7ccacaed3791 100644 --- a/packages/kit/src/runtime/server/page/render.js +++ b/packages/kit/src/runtime/server/page/render.js @@ -1,13 +1,14 @@ import * as devalue from 'devalue'; import { readable, writable } from 'svelte/store'; import { DEV } from 'esm-env'; +import { assets, base } from '$internal/paths'; import { hash } from '../../hash.js'; import { serialize_data } from './serialize_data.js'; import { s } from '../../../utils/misc.js'; import { Csp } from './csp.js'; import { uneval_action_response } from './actions.js'; import { clarify_devalue_error } from '../utils.js'; -import { assets, base, version, public_env } from '../../shared.js'; +import { version, public_env } from '../../shared.js'; import { text } from '../../../exports/index.js'; // TODO rename this function/module @@ -265,8 +266,8 @@ export async function render_response({ if (page_config.csr) { const opts = [ + `assets: ${s(assets)}`, `env: ${s(public_env)}`, - `paths: ${s({ assets, base })}`, `target: document.querySelector('[data-sveltekit-hydrate="${target}"]').parentNode`, `version: ${s(version)}` ]; 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 61082761ceae..591100446d3f 100644 --- a/packages/kit/src/runtime/server/page/respond_with_error.js +++ b/packages/kit/src/runtime/server/page/respond_with_error.js @@ -2,11 +2,11 @@ import { render_response } from './render.js'; import { load_data, load_server_data } from './load_data.js'; import { handle_error_and_jsonify, - get_option, static_error_page, redirect_response, GENERIC_ERROR } from '../utils.js'; +import { get_option } from '../../../utils/options.js'; import { HttpError, Redirect } from '../../control.js'; /** diff --git a/packages/kit/src/runtime/server/respond.js b/packages/kit/src/runtime/server/respond.js index 1cd4047b0b9f..7b228363024b 100644 --- a/packages/kit/src/runtime/server/respond.js +++ b/packages/kit/src/runtime/server/respond.js @@ -1,10 +1,11 @@ import { DEV } from 'esm-env'; +import { base } from '$internal/paths'; import { is_endpoint_request, render_endpoint } from './endpoint.js'; import { render_page } from './page/index.js'; import { render_response } from './page/render.js'; import { respond_with_error } from './page/respond_with_error.js'; import { is_form_content_type } from '../../utils/http.js'; -import { GENERIC_ERROR, get_option, handle_fatal_error, redirect_response } from './utils.js'; +import { GENERIC_ERROR, handle_fatal_error, redirect_response } from './utils.js'; import { decode_pathname, decode_params, @@ -23,8 +24,8 @@ import { validate_page_server_exports, validate_server_exports } from '../../utils/exports.js'; +import { get_option } from '../../utils/options.js'; import { error, json, text } from '../../exports/index.js'; -import * as paths from '../shared.js'; /* global __SVELTEKIT_ADAPTER_NAME__ */ @@ -70,11 +71,11 @@ export async function respond(request, options, manifest, state) { /** @type {Record} */ let params = {}; - if (paths.base && !state.prerendering?.fallback) { - if (!decoded.startsWith(paths.base)) { + if (base && !state.prerendering?.fallback) { + if (!decoded.startsWith(base)) { return text('Not found', { status: 404 }); } - decoded = decoded.slice(paths.base.length) || '/'; + decoded = decoded.slice(base.length) || '/'; } const is_data_request = has_data_suffix(decoded); diff --git a/packages/kit/src/runtime/server/utils.js b/packages/kit/src/runtime/server/utils.js index 49f873e41375..bb9bb65dfd07 100644 --- a/packages/kit/src/runtime/server/utils.js +++ b/packages/kit/src/runtime/server/utils.js @@ -51,23 +51,6 @@ export function allowed_methods(mod) { return allowed; } -/** - * @template {'prerender' | 'ssr' | 'csr' | 'trailingSlash'} Option - * @template {Option extends 'prerender' ? import('types').PrerenderOption : Option extends 'trailingSlash' ? import('types').TrailingSlash : boolean} Value - * - * @param {Array} nodes - * @param {Option} option - * - * @returns {Value | undefined} - */ -export function get_option(nodes, option) { - return nodes.reduce((value, node) => { - return /** @type {any} TypeScript's too dumb to understand this */ ( - node?.universal?.[option] ?? node?.server?.[option] ?? value - ); - }, /** @type {Value | undefined} */ (undefined)); -} - /** * Return as a response that renders the error.html * diff --git a/packages/kit/src/runtime/shared.js b/packages/kit/src/runtime/shared.js index 0cb8fd91def0..40c372a51b45 100644 --- a/packages/kit/src/runtime/shared.js +++ b/packages/kit/src/runtime/shared.js @@ -1,5 +1,5 @@ -export let assets = ''; -export let base = ''; +export { set_assets } from '$internal/paths'; + export let building = false; export let version = ''; @@ -12,12 +12,6 @@ export let public_env = {}; /** @param {string} stack */ export let fix_stack_trace = (stack) => stack; -/** @param {{ base: string, assets: string }} paths */ -export function set_paths(paths) { - base = paths.base; - assets = paths.assets || base; -} - /** @param {boolean} value */ export function set_building(value) { building = value; diff --git a/packages/kit/src/utils/options.js b/packages/kit/src/utils/options.js new file mode 100644 index 000000000000..a1c9088a4c74 --- /dev/null +++ b/packages/kit/src/utils/options.js @@ -0,0 +1,16 @@ +/** + * @template {'prerender' | 'ssr' | 'csr' | 'trailingSlash'} Option + * @template {Option extends 'prerender' ? import('types').PrerenderOption : Option extends 'trailingSlash' ? import('types').TrailingSlash : boolean} Value + * + * @param {Array} nodes + * @param {Option} option + * + * @returns {Value | undefined} + */ +export function get_option(nodes, option) { + return nodes.reduce((value, node) => { + return /** @type {any} TypeScript's too dumb to understand this */ ( + node?.universal?.[option] ?? node?.server?.[option] ?? value + ); + }, /** @type {Value | undefined} */ (undefined)); +} diff --git a/packages/kit/test/apps/options/test/test.js b/packages/kit/test/apps/options/test/test.js index 026d847f437e..685c49400774 100644 --- a/packages/kit/test/apps/options/test/test.js +++ b/packages/kit/test/apps/options/test/test.js @@ -42,7 +42,7 @@ test.describe('base path', () => { }); } - test('sets_paths', async ({ page }) => { + test('paths available on server side', async ({ page }) => { await page.goto('/path-base/base/'); expect(await page.textContent('[data-source="base"]')).toBe('/path-base'); expect(await page.textContent('[data-source="assets"]')).toBe('/_svelte_kit_assets'); diff --git a/packages/kit/types/ambient.d.ts b/packages/kit/types/ambient.d.ts index 474cd297b084..79801c8a3928 100644 --- a/packages/kit/types/ambient.d.ts +++ b/packages/kit/types/ambient.d.ts @@ -431,3 +431,10 @@ declare module '@sveltejs/kit/vite' { export function sveltekit(): Promise; export { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; } + +/** Internal version of $app/paths */ +declare module '$internal/paths' { + export const base: `/${string}`; + export let assets: `https://${string}` | `http://${string}`; + export function set_assets(path: string): void; +} diff --git a/packages/kit/types/internal.d.ts b/packages/kit/types/internal.d.ts index 17d03141fcf4..0f053a16706e 100644 --- a/packages/kit/types/internal.d.ts +++ b/packages/kit/types/internal.d.ts @@ -1,4 +1,3 @@ -import { OutputChunk } from 'rollup'; import { SvelteComponent } from 'svelte/internal'; import { Config, @@ -30,7 +29,7 @@ export interface ServerModule { export interface ServerInternalModule { set_building(building: boolean): void; - set_paths(paths: { base: string; assets: string }): void; + set_assets(path: string): void; set_private_env(environment: Record): void; set_public_env(environment: Record): void; set_version(version: string): void; diff --git a/sites/kit.svelte.dev/src/lib/docs/server/render.js b/sites/kit.svelte.dev/src/lib/docs/server/render.js index e295909e2cff..d31435c50ec3 100644 --- a/sites/kit.svelte.dev/src/lib/docs/server/render.js +++ b/sites/kit.svelte.dev/src/lib/docs/server/render.js @@ -69,7 +69,7 @@ export function replace_placeholders(content) { let import_block = ''; - if (module.exports.length > 0) { + if (module.exports.length > 0 && !module.name.startsWith('$internal')) { // deduplication is necessary for now, because of `error()` overload const exports = Array.from(new Set(module.exports.map((x) => x.name)));