Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/full-bats-itch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': minor
---

feat: experimental async SSR
6 changes: 6 additions & 0 deletions packages/kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@
"generate:version": "node scripts/generate-version.js",
"generate:types": "node scripts/generate-dts.js"
},
"imports": {
"#app/paths": {
"browser": "./src/runtime/app/paths/client.js",
"default": "./src/runtime/app/paths/server.js"
}
},
"exports": {
"./package.json": "./package.json",
".": {
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/scripts/generate-dts.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ await createBundle({
'$app/environment': 'src/runtime/app/environment/types.d.ts',
'$app/forms': 'src/runtime/app/forms.js',
'$app/navigation': 'src/runtime/app/navigation.js',
'$app/paths': 'src/runtime/app/paths/types.d.ts',
'$app/paths': 'src/runtime/app/paths/public.d.ts',
'$app/server': 'src/runtime/app/server/index.js',
'$app/state': 'src/runtime/app/state/index.js',
'$app/stores': 'src/runtime/app/stores.js'
Expand Down
3 changes: 2 additions & 1 deletion packages/kit/src/core/sync/write_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,13 @@ const server_template = ({
}) => `
import root from '../root.${isSvelte5Plus() ? 'js' : 'svelte'}';
import { set_building, set_prerendering } from '__sveltekit/environment';
import { set_assets } from '__sveltekit/paths';
import { set_assets } from '$app/paths/internal/server';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems pretty close $app/paths and i'm getting flashbacks from libraries messing with svelte runtime internals.
Do we have to prevent users from accessing internal somehow? maybe $internal would be one more step away from $app to make it clearer? Or is there a case for import maps here?

I do like the replacement of __sveltekit which struck me as odd before

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the same as svelte/internal — accessible, but disallowed by types. I don't believe it's possible to not make it publicly accessible to someone who really wants to shoot themselves in the foot, but I also don't think that's a real problem. It won't show up as an auto-import option, and if you do import */internal in user code you'll be rewarded with a red squiggly

import { set_manifest, set_read_implementation } from '__sveltekit/server';
import { set_private_env, set_public_env } from '${runtime_directory}/shared-server.js';

export const options = {
app_template_contains_nonce: ${template.includes('%sveltekit.nonce%')},
async: ${s(!!config.compilerOptions?.experimental?.async)},
csp: ${s(config.kit.csp)},
csrf_check_origin: ${s(config.kit.csrf.checkOrigin && !config.kit.csrf.trustedOrigins.includes('*'))},
csrf_trusted_origins: ${s(config.kit.csrf.trustedOrigins)},
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/exports/vite/dev/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,7 @@ export async function dev(vite, vite_config, svelte_config, get_remotes) {
);
set_fix_stack_trace(fix_stack_trace);

const { set_assets } = await vite.ssrLoadModule('__sveltekit/paths');
const { set_assets } = await vite.ssrLoadModule('$app/paths/internal/server');
set_assets(assets);

const server = new Server(manifest);
Expand Down
66 changes: 14 additions & 52 deletions packages/kit/src/exports/vite/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ import {
env_static_public,
service_worker,
sveltekit_environment,
sveltekit_paths,
sveltekit_server
} from './module_ids.js';
import { import_peer } from '../../utils/import.js';
Expand Down Expand Up @@ -318,26 +317,34 @@ async function kit({ svelte_config }) {
// because they for example use esbuild.build with `platform: 'browser'`
'esm-env',
// This forces `$app/*` modules to be bundled, since they depend on
// virtual modules like `__sveltekit/paths` (this isn't a valid bare
// virtual modules like `__sveltekit/environment` (this isn't a valid bare
// import, but it works with vite-node's externalization logic, which
// uses basic concatenation)
'@sveltejs/kit/src/runtime'
]
}
};

const define = {
__SVELTEKIT_APP_DIR__: s(kit.appDir),
__SVELTEKIT_EMBEDDED__: s(kit.embedded),
__SVELTEKIT_EXPERIMENTAL__REMOTE_FUNCTIONS__: s(kit.experimental.remoteFunctions),
__SVELTEKIT_PATHS_ASSETS__: s(kit.paths.assets),
__SVELTEKIT_PATHS_BASE__: s(kit.paths.base),
__SVELTEKIT_PATHS_RELATIVE__: s(kit.paths.relative),
__SVELTEKIT_CLIENT_ROUTING__: s(kit.router.resolution === 'client'),
__SVELTEKIT_SERVER_TRACING_ENABLED__: s(kit.experimental.tracing.server)
};

if (is_build) {
if (!new_config.build) new_config.build = {};
new_config.build.ssr = !secondary_build_started;

new_config.define = {
...define,
__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_EMBEDDED__: s(kit.embedded),
__SVELTEKIT_EXPERIMENTAL__REMOTE_FUNCTIONS__: s(kit.experimental.remoteFunctions),
__SVELTEKIT_CLIENT_ROUTING__: s(kit.router.resolution === 'client'),
__SVELTEKIT_SERVER_TRACING_ENABLED__: s(kit.experimental.tracing.server),
__SVELTEKIT_PAYLOAD__: new_config.build.ssr
? '{}'
: `globalThis.__sveltekit_${version_hash}`
Expand All @@ -348,11 +355,8 @@ async function kit({ svelte_config }) {
}
} else {
new_config.define = {
...define,
__SVELTEKIT_APP_VERSION_POLL_INTERVAL__: '0',
__SVELTEKIT_EMBEDDED__: s(kit.embedded),
__SVELTEKIT_EXPERIMENTAL__REMOTE_FUNCTIONS__: s(kit.experimental.remoteFunctions),
__SVELTEKIT_CLIENT_ROUTING__: s(kit.router.resolution === 'client'),
__SVELTEKIT_SERVER_TRACING_ENABLED__: s(kit.experimental.tracing.server),
__SVELTEKIT_PAYLOAD__: 'globalThis.__sveltekit_dev'
};

Expand Down Expand Up @@ -460,48 +464,6 @@ async function kit({ svelte_config }) {
case service_worker:
return create_service_worker_module(svelte_config);

// for internal use only. it's published as $app/paths externally
// we use this alias so that we won't collide with user aliases
case sveltekit_paths: {
const { assets, base } = svelte_config.kit.paths;

// use the values defined in `global`, but fall back to hard-coded values
// for the sake of things like Vitest which may import this module
// outside the context of a page
if (browser) {
return dedent`
export const base = ${global}?.base ?? ${s(base)};
export const assets = ${global}?.assets ?? ${assets ? s(assets) : 'base'};
export const app_dir = ${s(kit.appDir)};
`;
}

return dedent`
export let base = ${s(base)};
export let assets = ${assets ? s(assets) : 'base'};
export const app_dir = ${s(kit.appDir)};

export const relative = ${svelte_config.kit.paths.relative};

const initial = { base, assets };

export function override(paths) {
base = paths.base;
assets = paths.assets;
}

export function reset() {
base = initial.base;
assets = initial.assets;
}

/** @param {string} path */
export function set_assets(path) {
assets = initial.assets = path;
}
`;
}

case sveltekit_environment: {
const { version } = svelte_config.kit;

Expand Down
1 change: 0 additions & 1 deletion packages/kit/src/exports/vite/module_ids.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ export const env_dynamic_public = '\0virtual:env/dynamic/public';
export const service_worker = '\0virtual:service-worker';

export const sveltekit_environment = '\0virtual:__sveltekit/environment';
export const sveltekit_paths = '\0virtual:__sveltekit/paths';
export const sveltekit_server = '\0virtual:__sveltekit/server';

export const app_server = posixify(
Expand Down
57 changes: 57 additions & 0 deletions packages/kit/src/runtime/app/paths/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/** @import { Asset, RouteId, Pathname, ResolvedPathname } from '$app/types' */
/** @import { ResolveArgs } from './types.js' */
import { base, assets } from './internal/client.js';
import { resolve_route } from '../../../utils/routing.js';

/**
* Resolve the URL of an asset in your `static` directory, by prefixing it with [`config.kit.paths.assets`](https://svelte.dev/docs/kit/configuration#paths) if configured, or otherwise by prefixing it with the base path.
*
* During server rendering, the base path is relative and depends on the page currently being rendered.
*
* @example
* ```svelte
* <script>
* import { asset } from '$app/paths';
* </script>
*
* <img alt="a potato" src={asset('/potato.jpg')} />
* ```
* @since 2.26
*
* @param {Asset} file
* @returns {string}
*/
export function asset(file) {
return (assets || base) + file;
}

/**
* Resolve a pathname by prefixing it with the base path, if any, or resolve a route ID by populating dynamic segments with parameters.
*
* During server rendering, the base path is relative and depends on the page currently being rendered.
*
* @example
* ```js
* import { resolve } from '$app/paths';
*
* // using a pathname
* const resolved = resolve(`/blog/hello-world`);
*
* // using a route ID plus parameters
* const resolved = resolve('/blog/[slug]', {
* slug: 'hello-world'
* });
* ```
* @since 2.26
*
* @template {RouteId | Pathname} T
* @param {ResolveArgs<T>} args
* @returns {ResolvedPathname}
*/
export function resolve(...args) {
// The type error is correct here, and if someone doesn't pass params when they should there's a runtime error,
// but we don't want to adjust the internal resolve_route function to accept `undefined`, hence the type cast.
return base + resolve_route(args[0], /** @type {Record<string, string>} */ (args[1]));
}

export { base, assets, resolve as resolveRoute };
17 changes: 1 addition & 16 deletions packages/kit/src/runtime/app/paths/index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1 @@
import { base, assets } from '__sveltekit/paths';
import { resolve_route } from '../../../utils/routing.js';

/** @type {import('./types.d.ts').asset} */
export function asset(file) {
return (assets || base) + file;
}

/** @type {import('./types.d.ts').resolve} */
export function resolve(id, params) {
// The type error is correct here, and if someone doesn't pass params when they should there's a runtime error,
// but we don't want to adjust the internal resolve_route function to accept `undefined`, hence the type cast.
return base + resolve_route(id, /** @type {Record<string, string>} */ (params));
}

export { base, assets, resolve as resolveRoute };
export * from '#app/paths';
3 changes: 3 additions & 0 deletions packages/kit/src/runtime/app/paths/internal/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const base = __SVELTEKIT_PAYLOAD__?.base ?? __SVELTEKIT_PATHS_BASE__;
export const assets = __SVELTEKIT_PAYLOAD__?.assets ?? base ?? __SVELTEKIT_PATHS_ASSETS__;
export const app_dir = __SVELTEKIT_APP_DIR__;
24 changes: 24 additions & 0 deletions packages/kit/src/runtime/app/paths/internal/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export let base = __SVELTEKIT_PATHS_BASE__;
export let assets = __SVELTEKIT_PATHS_ASSETS__ || base;
export const app_dir = __SVELTEKIT_APP_DIR__;
export const relative = __SVELTEKIT_PATHS_RELATIVE__;

const initial = { base, assets };

/**
* @param {{ base: string, assets: string }} paths
*/
export function override(paths) {
base = paths.base;
assets = paths.assets;
}

export function reset() {
base = initial.base;
assets = initial.assets;
}

/** @param {string} path */
export function set_assets(path) {
assets = initial.assets = path;
}
29 changes: 29 additions & 0 deletions packages/kit/src/runtime/app/paths/public.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { RouteId, Pathname, ResolvedPathname } from '$app/types';
import { ResolveArgs } from './types.js';

export { resolve, asset } from './client.js';

/**
* A string that matches [`config.kit.paths.base`](https://svelte.dev/docs/kit/configuration#paths).
*
* Example usage: `<a href="{base}/your-page">Link</a>`
*
* @deprecated Use [`resolve(...)`](https://svelte.dev/docs/kit/$app-paths#resolve) instead
*/
export let base: '' | `/${string}`;

/**
* An absolute path that matches [`config.kit.paths.assets`](https://svelte.dev/docs/kit/configuration#paths).
*
* > [!NOTE] If a value for `config.kit.paths.assets` is specified, it will be replaced with `'/_svelte_kit_assets'` during `vite dev` or `vite preview`, since the assets don't yet live at their eventual URL.
*
* @deprecated Use [`asset(...)`](https://svelte.dev/docs/kit/$app-paths#asset) instead
*/
export let assets: '' | `https://${string}` | `http://${string}` | '/_svelte_kit_assets';

/**
* @deprecated Use [`resolve(...)`](https://svelte.dev/docs/kit/$app-paths#resolve) instead
*/
export function resolveRoute<T extends RouteId | Pathname>(
...args: ResolveArgs<T>
): ResolvedPathname;
31 changes: 31 additions & 0 deletions packages/kit/src/runtime/app/paths/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { base, assets, relative } from './internal/server.js';
import { resolve_route } from '../../../utils/routing.js';
import { get_request_store } from '../../../exports/internal/server.js'; // TODO not sure why we can't use `@sveltejs/kit/internal/server` here

/** @type {import('./client.js').asset} */
export function asset(file) {
// @ts-expect-error we use the `resolve` mechanism, but with the 'wrong' input
return assets ? assets + file : resolve(file);
}

/** @type {import('./client.js').resolve} */
export function resolve(id, params) {
const resolved = resolve_route(id, /** @type {Record<string, string>} */ (params));

if (relative) {
const { event, state } = get_request_store();

if (state.prerendering?.fallback) {
return resolved;
}

const segments = event.url.pathname.slice(base.length).split('/').slice(2);
const prefix = segments.map(() => '..').join('/') || '.';

return prefix + resolved;
}

return resolved;
}

export { base, assets, resolve as resolveRoute };
Loading
Loading