Skip to content

Commit

Permalink
Add ambient $app types (#917)
Browse files Browse the repository at this point in the history
This adds ambient type definitions for `$app/..` imports. This removes the need for a funky tsconfig referencing stuff from the generated code. Another benefit is the better type definitions and proper documentation.
  • Loading branch information
dummdidumm authored Apr 11, 2021
1 parent 59a1e06 commit 39b6967
Show file tree
Hide file tree
Showing 11 changed files with 149 additions and 32 deletions.
6 changes: 6 additions & 0 deletions .changeset/itchy-birds-admire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'create-svelte': patch
'@sveltejs/kit': patch
---

Add ambient type definitions for \$app imports
2 changes: 0 additions & 2 deletions packages/create-svelte/template-additions/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@
"allowJs": true,
"checkJs": true,
"paths": {
"$app/*": [".svelte/dev/runtime/app/*", ".svelte/build/runtime/app/*"],
"$service-worker": [".svelte/build/runtime/service-worker"],
"$lib/*": ["src/lib/*"]
}
},
Expand Down
10 changes: 10 additions & 0 deletions packages/kit/src/runtime/app/env.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
/**
* @type {import('$app/env').browser}
*/
export const browser = !import.meta.env.SSR;
/**
* @type {import('$app/env').dev}
*/
export const dev = !!import.meta.env.DEV;
/**
* @type {import('$app/env').amp}
*/
export const amp = !!import.meta.env.VITE_SVELTEKIT_AMP;

export { prerendering } from '../env.js';
14 changes: 7 additions & 7 deletions packages/kit/src/runtime/app/navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,22 @@ export const prefetch = import.meta.env.SSR ? guard('prefetch') : prefetch_;
export const prefetchRoutes = import.meta.env.SSR ? guard('prefetchRoutes') : prefetchRoutes_;

/**
* @param {string} href
* @param {{
* noscroll?: boolean;
* replaceState?: boolean;
* }} [opts]
* @type {import('$app/navigation').goto}
*/
async function goto_(href, opts) {
return router.goto(href, opts, []);
}

/** @param {string} href */
/**
* @type {import('$app/navigation').prefetch}
*/
function prefetch_(href) {
return router.prefetch(new URL(href, get_base_uri(document)));
}

/** @param {string[]} [pathnames] */
/**
* @type {import('$app/navigation').prefetchRoutes}
*/
async function prefetchRoutes_(pathnames) {
const matching = pathnames
? router.routes.filter((route) => pathnames.some((pathname) => route[0].test(pathname)))
Expand Down
20 changes: 7 additions & 13 deletions packages/kit/src/runtime/app/stores.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,37 +13,31 @@ export function stores() {
return getStores();
}

/** @typedef {import('svelte/store').Readable<{
* host: string;
* path: string;
* query: URLSearchParams;
* params: Record<string, string>
* }>} PageStore */

/**
* @type {import('$app/stores').getStores}
*/
export const getStores = () => {
const stores = getContext('__svelte__');

return {
/** @type {PageStore} */
page: {
subscribe: stores.page.subscribe
},
/** @type {import('svelte/store').Readable<boolean>} */
navigating: {
subscribe: stores.navigating.subscribe
},
// @ts-ignore - deprecated, not part of type definitions, but still callable
get preloading() {
console.error('stores.preloading is deprecated; use stores.navigating instead');
return {
subscribe: stores.navigating.subscribe
};
},
/** @type {import('svelte/store').Writable<any>} */
session: stores.session
};
};

/** @type {PageStore} */
/** @type {typeof import('$app/stores').page} */
export const page = {
/** @param {(value: any) => void} fn */
subscribe(fn) {
Expand All @@ -52,7 +46,7 @@ export const page = {
}
};

/** @type {import('svelte/store').Readable<boolean>} */
/** @type {typeof import('$app/stores').navigating} */
export const navigating = {
/** @param {(value: any) => void} fn */
subscribe(fn) {
Expand All @@ -70,7 +64,7 @@ const error = (verb) => {
);
};

/** @type {import('svelte/store').Writable<any>} */
/** @type {typeof import('$app/stores').session} */
export const session = {
subscribe(fn) {
const store = getStores().session;
Expand Down
3 changes: 2 additions & 1 deletion packages/kit/src/runtime/client/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CSRComponent, CSRRoute, Page } from '../../../types.internal';
import { Page } from '../../../types';
import { CSRComponent, CSRRoute } from '../../../types.internal';

export type NavigationInfo = {
id: string;
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/runtime/server/page/load_node.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const s = JSON.stringify;
* request: import('types').Request;
* options: import('types.internal').SSRRenderOptions;
* route: import('types.internal').SSRPage;
* page: import('types.internal').Page;
* page: import('types').Page;
* node: import('types.internal').SSRNode;
* $session: any;
* context: Record<string, any>;
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/runtime/server/page/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const s = JSON.stringify;
* status: number;
* error: Error,
* branch: import('./types').Loaded[];
* page: import('types.internal').Page
* page: import('types').Page
* }} opts
*/
export async function render_response({
Expand Down
106 changes: 106 additions & 0 deletions packages/kit/types.ambient.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
declare module '$app/env' {
/**
* Whether or not app is in AMP mode.
*/
export const amp: boolean;
/**
* Whether the app is running in the browser or on the server.
*/
export const browser: boolean;
/**
* `true` in development mode, `false` in production.
*/
export const dev: boolean;
}

declare module '$app/navigation' {
/**
* Returns a Promise that resolves when SvelteKit navigates (or fails to navigate, in which case the promise rejects) to the specified href.
*
* @param href Where to navigate to
* @param opts Optional. If `replaceState` is `true`, a new history entry won't be created. If `noscroll` is `true`, the browser won't scroll to the top of the page after navigation.
*/
export function goto(
href: string,
opts?: { replaceState?: boolean; noscroll?: boolean }
): Promise<any>;
/**
* Programmatically prefetches the given page, which means a) ensuring that the code for the page is loaded, and b) calling the page's load function with the appropriate options.
* This is the same behaviour that SvelteKit triggers when the user taps or mouses over an `<a>` element with `sveltekit:prefetch`.
* If the next navigation is to `href`, the values returned from load will be used, making navigation instantaneous.
* Returns a Promise that resolves when the prefetch is complete.
*
* @param href Page to prefetch
*/
export function prefetch(href: string): Promise<any>;
/**
* Programmatically prefetches the code for routes that haven't yet been fetched.
* Typically, you might call this to speed up subsequent navigation.
* If no argument is given, all routes will be fetched, otherwise you can specify routes by any matching pathname such as `/about` (to match `src/routes/about.svelte`)
* or `/blog/*` (to match `src/routes/blog/[slug].svelte`). Unlike prefetch, this won't call preload for individual pages.
* Returns a Promise that resolves when the routes have been prefetched.
*/
export function prefetchRoutes(routes?: string[]): Promise<any>;
}

declare module '$app/paths' {
/**
* A root-relative (i.e. begins with a `/`) string that matches `config.kit.files.base` in your project configuration.
*/
export const base: string;
/**
* A root-relative or absolute path that matches `config.kit.files.assets` (after it has been resolved against base).
*/
export const assets: string;
}

declare module '$app/stores' {
import { Readable, Writable } from 'svelte/store';
import { Page } from '@sveltejs/kit';

/**
* A convenience function around `getContext` that returns `{ navigating, page, session }`.
* Most of the time, you won't need to use it.
*/
export function getStores(): {
navigating: Readable<{ from: string; to: string } | null>;
page: Readable<Page>;
session: Writable<any>;
};
/**
* A readable store whose value reflects the object passed to load functions.
*/
export const page: Readable<Page>;
/**
* A readable store.
* When navigating starts, its value is `{ from, to }`, where from and to both mirror the page store value.
* When navigating finishes, its value reverts to `null`.
*/
export const navigating: Readable<{ from: string; to: string } | null>;
/**
* A writable store whose initial value is whatever was returned from `getSession`.
* 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<any>;
}

declare module '$service-worker' {
/**
* An array of URL strings representing the files generated by Vite, suitable for caching with `cache.addAll(build)`.
* This is only available to service workers.
*/
export const build: string[];
/**
* An array of URL strings representing the files in your static directory,
* or whatever directory is specified by `config.kit.files.assets`.
* This is only available to service workers.
*/
export const assets: string[];
/**
* The result of calling `Date.now()` at build time.
* It's useful for generating unique cache names inside your service worker,
* so that a later deployment of your app can invalidate old caches.
* This is only available to service workers.
*/
export const timestamp: number;
}
8 changes: 8 additions & 0 deletions packages/kit/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import './types.ambient';
import { Headers, LoadInput, LoadOutput, Logger } from './types.internal';
import { UserConfig as ViteConfig } from 'vite';

Expand Down Expand Up @@ -105,3 +106,10 @@ export type Handle<Context = any> = (
request: Request<Context>,
render: (request: Request<Context>) => Response | Promise<Response>
) => Response | Promise<Response>;

export type Page = {
host: string;
path: string;
params: Record<string, string>;
query: URLSearchParams;
};
8 changes: 1 addition & 7 deletions packages/kit/types.internal.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
Handle,
Incoming,
Load,
Page,
RequestHandler,
Response
} from './types';
Expand Down Expand Up @@ -85,13 +86,6 @@ export type App = {
// but this can't happen until TypeScript 4.3
export type Headers = Record<string, string>;

export type Page = {
host: string;
path: string;
params: Record<string, string>;
query: URLSearchParams;
};

export type LoadInput = {
page: Page;
fetch: (info: RequestInfo, init?: RequestInit) => Promise<NodeFetchResponse>;
Expand Down

0 comments on commit 39b6967

Please sign in to comment.