Skip to content

Commit

Permalink
feat!: reduce dependency on $URL for less encode/decoding
Browse files Browse the repository at this point in the history
BREAKING CHANGE: api updates and params renamed to query
  • Loading branch information
pi0 committed Dec 16, 2020
1 parent e2ac8d5 commit 38631cf
Show file tree
Hide file tree
Showing 11 changed files with 276 additions and 226 deletions.
30 changes: 16 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,6 @@

![👽 ufo](.github/banner.svg)


UFO exports URL utilities based on a URL-like interface with some improvements:

- Supporting schemeless and hostless URLs
- Supporting relative URLs
- Preserving trailing-slash status
- Decoded and mutable classs properties
- Consistent URL parser independent of environment
- Consistent encoding independent of environment

## Install

Install using npm or yarn:
Expand Down Expand Up @@ -69,20 +59,32 @@ joinURL('a', '/b', '/c')
joinURL('http://foo.com/foo?test=123#token', 'bar', 'baz')
```

### `withparams`
### `withQuery`

```ts
// Result: /foo?page=a&token=secret
withParams('/foo?page=a', { token: 'secret' })
withQuery('/foo?page=a', { token: 'secret' })
```

### `getParams`
### `getQuery`

```ts
// Result: { test: '123', unicode: '好' }
getParams('http://foo.com/foo?test=123&unicode=%E5%A5%BD')
getQuery('http://foo.com/foo?test=123&unicode=%E5%A5%BD')
```

### `$URL`

Implementing URL interface with some improvements:

- Supporting schemeless and hostless URLs
- Supporting relative URLs
- Preserving trailing-slash status
- Decoded and mutable classs properties (`protocol`, `host`, `auth`, `pathname`, `query`, `hash`)
- Consistent URL parser independent of environment
- Consistent encoding independent of environment
- Punycode support for host encoding

### `withTrailingSlash`

Ensures url ends with a trailing slash
Expand Down
12 changes: 0 additions & 12 deletions src/encoding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,18 +115,6 @@ export function decode (text: string | number = ''): string {
}
}

export function encodeSearchParam (key: string, val: string | string[]) {
if (!val) {
return key
}

if (Array.isArray(val)) {
return val.map(_val => `${encodeQueryKey(key)}=${encodeParam(_val)}`).join('&')
}

return `${encodeQueryKey(key)}=${encodeParam(val)}`
}

export function encodeHost (name: string = '') {
return toASCII(name)
}
7 changes: 4 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './utils'
export * from './parse'
export * from './ufo'
export * from './encoding'
export * from './parse'
export * from './query'
export * from './url'
export * from './utils'
78 changes: 41 additions & 37 deletions src/parse.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,39 @@
import { decode } from './encoding'
export type ParamsObject = Record<string, string | string[]>

import { hasProtocol } from './utils'
export interface ParsedURL {
protocol?: string
hostname?: string
port?: string
username?: string
password?: string
host?: string
auth?: string
pathname: string
hash?: string
params?: ParamsObject
hash: string
search: string
}

export interface ParsedAuth {
username: string
password: string
}

export interface ParsedHost {
hostname: string
port: string
}

export function parseURL (input: string = ''): ParsedURL {
if (!hasProtocol(input)) {
return parsePath(input)
}

const [protocol, auth, hostAndPath] = (input.match(/([^:/]+:)\/\/([^/@]+@)?(.*)/) || []).splice(1)
const [host = '', path = ''] = (hostAndPath.match(/([^/]*)(.*)?/) || []).splice(1)
const [hostname = '', port = ''] = (host.match(/([^/]*)(:0-9+)?/) || []).splice(1)
const { pathname, params, hash } = parsePath(path)
const [username, password] = auth ? auth.substr(0, auth.length - 1).split(':') : []
const { pathname, search, hash } = parsePath(path)

return {
protocol,
username,
password,
hostname,
port,
auth: auth ? auth.substr(0, auth.length - 1) : '',
host,
pathname,
params,
search,
hash
}
}
Expand All @@ -36,34 +43,31 @@ export function parsePath (input: string = ''): ParsedURL {

return {
pathname,
params: search ? parsedParamsToObject(parseParams(search.substr(1))) : {},
search,
hash
}
}

export function parseParams (paramsStr: string = ''): [string, string][] {
return paramsStr.split('&').map((param) => {
const [key, value] = param.split('=')
return [decode(key), decode(value)]
})
export function parseAuth (input: string = ''): ParsedAuth {
const [username, password] = input.split(':')
return {
username: decode(username),
password: decode(password)
}
}

export function hasProtocol (inputStr: string): boolean {
return /^\w+:\/\//.test(inputStr)
export function parseHost (input: string = ''): ParsedHost {
const [hostname, port] = (input.match(/([^/]*)(:0-9+)?/) || []).splice(1)
return {
hostname: decode(hostname),
port
}
}

export function parsedParamsToObject (entries: [string, string][]): Record<string, string|string[]> {
const obj: Record<string, string | string[]> = {}
for (const [key, value] of entries) {
if (obj[key]) {
if (Array.isArray(obj[key])) {
(obj[key] as string[]).push(value)
} else {
obj[key] = [obj[key] as string, value]
}
} else {
obj[key] = value
}
export function stringifyParsedURL (parsed: ParsedURL) {
const fullpath = parsed.pathname + (parsed.search ? '?' + parsed.search : '') + parsed.hash
if (!parsed.protocol) {
return fullpath
}
return obj
return parsed.protocol + '//' + (parsed.auth ? parsed.auth + '@' : '') + parsed.host + fullpath
}
43 changes: 43 additions & 0 deletions src/query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { decode, encodeQueryKey, encodeQueryValue } from './encoding'

export type QueryValue = string | string[] | undefined
export type QueryObject = Record<string, QueryValue>

export function parseQuery (paramsStr: string = ''): QueryObject {
const obj: QueryObject = {}
if (paramsStr[0] === '?') {
paramsStr = paramsStr.substr(1)
}
for (const param of paramsStr.split('&')) {
const s = param.split('=')
if (!s[0]) { continue }
const key = decode(s[0])
const value = decode(s[1])
if (obj[key]) {
if (Array.isArray(obj[key])) {
(obj[key] as string[]).push(value)
} else {
obj[key] = [obj[key] as string, value]
}
} else {
obj[key] = value
}
}
return obj
}

export function encodeQueryItem (key: string, val: QueryValue): string {
if (!val) {
return encodeQueryKey(key)
}

if (Array.isArray(val)) {
return val.map(_val => `${encodeQueryKey(key)}=${encodeQueryValue(_val)}`).join('&')
}

return `${encodeQueryKey(key)}=${encodeQueryValue(val)}`
}

export function stringifyQuery (query: QueryObject) {
return Object.keys(query).map(k => encodeQueryItem(k, query[k])).join('&')
}
109 changes: 0 additions & 109 deletions src/ufo.ts

This file was deleted.

Loading

0 comments on commit 38631cf

Please sign in to comment.