Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[chore] improved typing for runtime and tests #1995

Merged
merged 1 commit into from
Jul 22, 2021
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/itchy-days-wonder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

[chore] improved typing for runtime and tests
2 changes: 1 addition & 1 deletion packages/kit/src/core/adapter-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*
* This is intended to be used with both requests and responses, to have a consistent body parsing across adapters.
*
* @param {string?} content_type The `content-type` header of a request/response.
* @param {string|undefined|null} content_type The `content-type` header of a request/response.
* @returns {boolean}
*/
export function isContentTypeTextual(content_type) {
Expand Down
8 changes: 4 additions & 4 deletions packages/kit/src/core/dev/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { get_server } from '../server/index.js';
import { __fetch_polyfill } from '../../install-fetch.js';
import { SVELTE_KIT } from '../constants.js';

/** @typedef {{ cwd?: string, port: number, host: string, https: boolean, config: import('types/config').ValidatedConfig }} Options */
/** @typedef {{ cwd?: string, port: number, host?: string, https: boolean, config: import('types/config').ValidatedConfig }} Options */
/** @typedef {import('types/internal').SSRComponent} SSRComponent */

/** @param {Options} opts */
Expand Down Expand Up @@ -47,9 +47,8 @@ class Watcher extends EventEmitter {
this.config = config;

/**
* @type {vite.ViteDevServer}
* @type {vite.ViteDevServer | undefined}
*/
// @ts-ignore
this.vite;

process.on('exit', () => {
Expand Down Expand Up @@ -198,6 +197,7 @@ class Watcher extends EventEmitter {
pattern: route.pattern,
params: get_params(route.params),
load: async () => {
if (!this.vite) throw new Error('Vite server has not been initialized');
const url = path.resolve(this.cwd, route.file);
return await this.vite.ssrLoadModule(url);
}
Expand Down Expand Up @@ -281,7 +281,7 @@ async function create_handler(vite, config, dir, cwd, manifest) {

if (req.url === '/favicon.ico') return;

/** @type {import('types/internal').Hooks} */
/** @type {Partial<import('types/internal').Hooks>} */
const hooks = resolve_entry(config.kit.files.hooks)
? await vite.ssrLoadModule(`/${config.kit.files.hooks}`)
: {};
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/core/start/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const mutable = (dir) =>
/**
* @param {{
* port: number;
* host: string;
* host?: string;
* config: import('types/config').ValidatedConfig;
* https?: boolean;
* cwd?: string;
Expand Down
16 changes: 12 additions & 4 deletions packages/kit/src/runtime/app/navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const prefetchRoutes = import.meta.env.SSR ? guard('prefetchRoutes') : pr
* @type {import('$app/navigation').goto}
*/
async function goto_(href, opts) {
// @ts-ignore
return router.goto(href, opts, []);
}

Expand All @@ -27,13 +28,15 @@ async function goto_(href, opts) {
*/
async function invalidate_(resource) {
const { href } = new URL(resource, location.href);
// @ts-ignore
return router.renderer.invalidate(href);
}

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

Expand All @@ -42,10 +45,15 @@ function prefetch_(href) {
*/
async function prefetchRoutes_(pathnames) {
const matching = pathnames
? router.routes.filter((route) => pathnames.some((pathname) => route[0].test(pathname)))
: router.routes;

const promises = matching.map((r) => r.length !== 1 && Promise.all(r[1].map((load) => load())));
? // @ts-ignore
router.routes.filter((route) => pathnames.some((pathname) => route[0].test(pathname)))
: // @ts-ignore
router.routes;

const promises = matching
.filter((r) => r && r.length > 1)
// @ts-ignore
.map((r) => Promise.all(r[1].map((load) => load())));

await Promise.all(promises);
}
21 changes: 11 additions & 10 deletions packages/kit/src/runtime/client/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ function initial_fetch(resource, opts) {
}

const script = document.querySelector(selector);
if (script) {
if (script && script.textContent) {
const { body, ...init } = JSON.parse(script.textContent);
return Promise.resolve(new Response(body, init));
}
Expand All @@ -69,8 +69,8 @@ export class Renderer {
this.fallback = fallback;
this.host = host;

/** @type {import('./router').Router} */
this.router = null;
/** @type {import('./router').Router | undefined} */
this.router;

this.target = target;

Expand Down Expand Up @@ -133,13 +133,13 @@ export class Renderer {
/** @type {Record<string, any>} */
let context = {};

/** @type {import('./types').NavigationResult} */
/** @type {import('./types').NavigationResult | undefined} */
let result;

/** @type {number} */
/** @type {number | undefined} */
let new_status;

/** @type {Error} new_error */
/** @type {Error | undefined} new_error */
let new_error;

try {
Expand All @@ -150,8 +150,8 @@ export class Renderer {
module: await nodes[i],
page,
context,
status: is_leaf && status,
error: is_leaf && error
status: is_leaf ? status : undefined,
error: is_leaf ? error : undefined
});

branch.push(node);
Expand Down Expand Up @@ -535,8 +535,9 @@ export class Renderer {
async _load({ route, path, query }, no_cache) {
const key = `${path}?${query}`;

if (!no_cache && this.cache.has(key)) {
return this.cache.get(key);
if (!no_cache) {
const cached = this.cache.get(key);
if (cached) return cached;
}

const [pattern, a, b, get_params] = route;
Expand Down
15 changes: 9 additions & 6 deletions packages/kit/src/runtime/client/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ function scroll_state() {
}

/**
* @param {Node} node
* @returns {HTMLAnchorElement | SVGAElement}
* @param {Node | null} node
* @returns {HTMLAnchorElement | SVGAElement | null}
*/
function find_anchor(node) {
while (node && node.nodeName.toUpperCase() !== 'A') node = node.parentNode; // SVG <a> elements have a lowercase name
Expand Down Expand Up @@ -121,7 +121,7 @@ export class Router {
// Ignore if tag has
// 1. 'download' attribute
// 2. 'rel' attribute includes external
const rel = a.getAttribute('rel') && a.getAttribute('rel').split(/\s+/);
const rel = (a.getAttribute('rel') || '').split(/\s+/);

if (a.hasAttribute('download') || (rel && rel.includes('external'))) {
return;
Expand Down Expand Up @@ -162,7 +162,7 @@ export class Router {

/**
* @param {URL} url
* @returns {import('./types').NavigationInfo}
* @returns {import('./types').NavigationInfo | undefined}
*/
parse(url) {
if (this.owns(url)) {
Expand Down Expand Up @@ -221,12 +221,13 @@ export class Router {
throw new Error('Attempted to prefetch a URL that does not belong to this app');
}

// @ts-ignore
return this.renderer.load(info);
}

/**
* @param {URL} url
* @param {{ x: number, y: number }} scroll
* @param {{ x: number, y: number }?} scroll
* @param {boolean} keepfocus
* @param {string[]} chain
* @param {string} [hash]
Expand All @@ -246,19 +247,21 @@ export class Router {
(has_trailing_slash && this.trailing_slash === 'never') ||
(!has_trailing_slash &&
this.trailing_slash === 'always' &&
!info.path.split('/').pop().includes('.'));
!(info.path.split('/').pop() || '').includes('.'));

if (incorrect) {
info.path = has_trailing_slash ? info.path.slice(0, -1) : info.path + '/';
history.replaceState({}, '', `${this.base}${info.path}${location.search}`);
}
}

// @ts-ignore6
this.renderer.notify({
path: info.path,
query: info.query
});

// @ts-ignore
await this.renderer.update(info, chain, false);

if (!keepfocus) {
Expand Down
4 changes: 2 additions & 2 deletions packages/kit/src/runtime/client/singletons.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/** @type {import('./router').Router} */
/** @type {import('./router').Router?} */
export let router;

/** @type {string} */
Expand All @@ -7,7 +7,7 @@ export let base = '';
/** @type {string} */
export let assets = '/.';

/** @param {import('./router').Router} _ */
/** @param {import('./router').Router?} _ */
export function init(_) {
router = _;
}
Expand Down
21 changes: 11 additions & 10 deletions packages/kit/src/runtime/client/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ export async function start({ paths, target, session, host, route, spa, trailing
throw new Error('Missing target element. See https://kit.svelte.dev/docs#configuration-target');
}

const router =
route &&
new Router({
base: paths.base,
routes,
trailing_slash
});
const router = route
? new Router({
base: paths.base,
routes,
trailing_slash
})
: null;

const renderer = new Renderer({
Root,
Expand All @@ -50,9 +50,10 @@ export async function start({ paths, target, session, host, route, spa, trailing
set_paths(paths);

if (hydrate) await renderer.start(hydrate);
if (route) router.init(renderer);

if (spa) router.goto(location.href, { replaceState: true }, []);
if (router) {
router.init(renderer);
if (spa) router.goto(location.href, { replaceState: true }, []);
}

dispatchEvent(new CustomEvent('sveltekit:start'));
}
91 changes: 49 additions & 42 deletions packages/kit/src/runtime/server/endpoint.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,47 +26,54 @@ export default async function render_route(request, route) {
/** @type {import('types/endpoint').RequestHandler} */
const handler = mod[request.method.toLowerCase().replace('delete', 'del')]; // 'delete' is a reserved word

if (handler) {
const match = route.pattern.exec(request.path);
const params = route.params(match);

const response = await handler({ ...request, params });
const preface = `Invalid response from route ${request.path}`;

if (response) {
if (typeof response !== 'object') {
return error(`${preface}: expected an object, got ${typeof response}`);
}

let { status = 200, body, headers = {} } = response;

headers = lowercase_keys(headers);
const type = headers['content-type'];

const is_type_textual = isContentTypeTextual(type);

if (!is_type_textual && !(body instanceof Uint8Array || is_string(body))) {
return error(
`${preface}: body must be an instance of string or Uint8Array if content-type is not a supported textual content-type`
);
}

/** @type {import('types/hooks').StrictBody} */
let normalized_body;

// ensure the body is an object
if (
(typeof body === 'object' || typeof body === 'undefined') &&
!(body instanceof Uint8Array) &&
(!type || type.startsWith('application/json'))
) {
headers = { ...headers, 'content-type': 'application/json; charset=utf-8' };
normalized_body = JSON.stringify(typeof body === 'undefined' ? {} : body);
} else {
normalized_body = /** @type {import('types/hooks').StrictBody} */ (body);
}

return { status, body: normalized_body, headers };
}
if (!handler) {
return error('no handler');
}

const match = route.pattern.exec(request.path);
if (!match) {
return error('could not parse parameters from request path');
}

const params = route.params(match);

const response = await handler({ ...request, params });
const preface = `Invalid response from route ${request.path}`;

if (!response) {
return error('no response');
}
if (typeof response !== 'object') {
return error(`${preface}: expected an object, got ${typeof response}`);
}

let { status = 200, body, headers = {} } = response;

headers = lowercase_keys(headers);
const type = headers['content-type'];

const is_type_textual = isContentTypeTextual(type);

if (!is_type_textual && !(body instanceof Uint8Array || is_string(body))) {
return error(
`${preface}: body must be an instance of string or Uint8Array if content-type is not a supported textual content-type`
);
}

/** @type {import('types/hooks').StrictBody} */
let normalized_body;

// ensure the body is an object
if (
(typeof body === 'object' || typeof body === 'undefined') &&
!(body instanceof Uint8Array) &&
(!type || type.startsWith('application/json'))
) {
headers = { ...headers, 'content-type': 'application/json; charset=utf-8' };
normalized_body = JSON.stringify(typeof body === 'undefined' ? {} : body);
} else {
normalized_body = /** @type {import('types/hooks').StrictBody} */ (body);
}

return { status, body: normalized_body, headers };
}
Loading