type Nullable = T | null; type Host = { hostname: Nullable; port: Nullable; }; type Url = { protocol: Nullable; slashes: Nullable; auth: Nullable; host: Nullable; port: Nullable; hostname: Nullable; hosts: Host[]; hash: Nullable; search: Nullable; query: Nullable; pathname: Nullable; path: Nullable; href: Nullable; }; export const parse = (url: string): Url => { let remaining = url; const result: Url = { protocol: null, slashes: null, auth: null, host: null, port: null, hostname: null, hosts: [], hash: null, search: null, query: null, pathname: null, path: null, href: remaining, }; let parts = remaining.split('//'); if (parts.length > 1) { [result.protocol] = parts; result.slashes = true; remaining = parts.slice(1).join('//'); } parts = remaining.split('@'); if (parts.length > 1) { [result.auth] = parts; remaining = parts.slice(1).join('@'); } parts = remaining.split('/'); remaining = parts.slice(1).join('/'); result.hosts = parts[0].split(',').map((host, index) => { const [hostname, port] = host.split(':'); if (index === 0) { result.host = host; result.hostname = hostname; result.port = port || null; } return { hostname, port: port || null }; }); const hashIndex = remaining.indexOf('#'); if (hashIndex > -1) { parts = remaining.split('#'); result.hash = `#${parts.pop()}`; remaining = parts.join('#'); } result.path = `/${remaining}`; const [pathname, query] = remaining.split('?'); result.pathname = `/${pathname || ''}`; result.query = query || null; result.search = query ? `?${query}` : null; return result; };