Comparing version 1.1.0 to 1.2.0
## Change Log | ||
### v1.2.0 on 2022-09-11 | ||
- Add support for relative URLs and empty URLs | ||
- New methods searchObject, import, export, clone, qsToString, qsToObject | ||
- New FluentURLSearchParams methods toObject | ||
- Allow FluentURLSearchParams#set to accept an object | ||
- Use new codecov uploader | ||
- Update npm dev dependencies | ||
### v1.1.0 on 2022-05-12 | ||
@@ -4,0 +13,0 @@ |
{ | ||
"name": "fluent-url", | ||
"version": "1.1.0", | ||
"version": "1.2.0", | ||
"description": "A chainable version of the global URL class", | ||
@@ -34,7 +34,7 @@ "keywords": [ | ||
"devDependencies": { | ||
"eslint": "^8.15.0", | ||
"eslint": "^8.23.0", | ||
"eslint-config-prettier": "^8.5.0", | ||
"jest": "^28.1.0", | ||
"prettier": "^2.6.2" | ||
"jest": "^29.0.2", | ||
"prettier": "^2.7.1" | ||
} | ||
} |
# fluent-url | ||
[![Build status](https://ci.appveyor.com/api/projects/status/3q83wy4x2fu34p58?svg=true)](https://ci.appveyor.com/project/kensnyder/fluent-url) | ||
[![Code Coverage](https://codecov.io/gh/kensnyder/fluent-url/branch/master/graph/badge.svg?v=1.1.0)](https://codecov.io/gh/kensnyder/fluent-url) | ||
[![ISC License](https://img.shields.io/npm/l/fluent-url.svg?v=1.1.0)](https://opensource.org/licenses/ISC) | ||
[![Code Coverage](https://codecov.io/gh/kensnyder/fluent-url/branch/master/graph/badge.svg?v=1.2.0)](https://codecov.io/gh/kensnyder/fluent-url) | ||
[![ISC License](https://img.shields.io/npm/l/fluent-url.svg?v=1.2.0)](https://opensource.org/licenses/ISC) | ||
@@ -25,3 +25,4 @@ A chainable version of the global URL class | ||
const endpoint = url('https://api.example.com/search', { term: 'Foo' }); | ||
endpoint.href(); // https://api.example.com/search?term=Foo | ||
endpoint.href(); | ||
// => "https://api.example.com/search?term=Foo" | ||
@@ -39,3 +40,4 @@ // chainable example | ||
.hash('#username') | ||
.href(); // https://api.example.com:8080/login?a=one&b=two#username | ||
.href(); | ||
// => "https://api.example.com:8080/login?a=one&b=two#username" | ||
@@ -48,3 +50,8 @@ // extend search query instead of overwriting it | ||
}); | ||
endpoint.href(); // https://api.example.com/search?term=Foo&limit=10&sort=created_at | ||
endpoint.href(); | ||
// => "https://api.example.com/search?term=Foo&limit=10&sort=created_at" | ||
// manipulate relative URLs | ||
url('/search?term=Foo').qsAppend('limit', 10).href(); | ||
// => "/search?term=Foo&limit=10" | ||
``` | ||
@@ -59,26 +66,34 @@ | ||
url(myUrl, searchParams); // searchParams is a plain object or instance of URLSearchParams | ||
url(relative, baseUrl); // relative is the path and baseUrl is the domain | ||
url(relative, baseUrl, searchParams); // searchParams is a string, plain object or instance of URLSearchParams | ||
url(relative, base); // relative is the path and base is a string with protocol, port, domain | ||
url(relative, base, searchParams); // searchParams is a string, plain object or instance of URLSearchParams | ||
``` | ||
## Main Methods | ||
## URL Built-in Methods | ||
| URL built-in | FluentURL get | FluentURL set | Description | Example | | ||
| ------------ | ------------- | ---------------------- | -------------------------------------------- | ------------------------------ | | ||
| hash | .hash() | .hash(newHash) | The hash string including "#" | #foo | | ||
| host | .host() | .host(newHost) | The domain name including port if applicable | example.com | | ||
| hostname | .hostname() | .hostname(newHost) | The domain name excluding port | example.com | | ||
| href | .href() | .href(newHref) | The entire URL - same as .toString() | http://example.com/foo?bar#baz | | ||
| origin | .origin() | N/A | The protocol, domain and port | http://example.com:8443 | | ||
| password | .password() | .password(newPassword) | The password | ftp-password | | ||
| pathname | .pathname() | .pathname(newPathname) | The path including leading slash | /admin | | ||
| port | .port() | .port(newPort) | The port number as a string | 8080 | | ||
| protocol | .protocol() | .protocol(newProtocol) | The scheme | https: | | ||
| search | .search() | .search(newSearch†) | The search string†, including "?" | ?a=one&b=two | | ||
| searchParams | .searchParams | .searchParams = params | The URLSearchParams object for this URL | new URLSearchParams('a=1') | | ||
| username | .username() | .username(newUsername) | The username string | ftpuser | | ||
| toString() | .toString() | N/A | The entire URL | http://example.com/foo?bar#baz | | ||
| URL built-in | FluentURL get | FluentURL set | Description | Example | | ||
| ------------ | ------------- | ---------------------- | ---------------------------------------------------------- | ------------------------------ | | ||
| .hash | .hash() | .hash(newHash) | The hash string including "#" | #foo | | ||
| .host | .host() | .host(newHost) | The domain name including port if applicable | sub.example.com:8443 | | ||
| .hostname | .hostname() | .hostname(newHost) | The domain name excluding port | sub.example.com | | ||
| .href | .href() | .href(newHref) | The entire URL - same as .toString() | http://example.com/foo?bar#baz | | ||
| .origin | .origin() | N/A | The protocol, domain and port | http://example.com:8443 | | ||
| .password | .password() | .password(newPassword) | The password | ftp-password | | ||
| .pathname | .pathname() | .pathname(newPathname) | The path including leading slash | /admin | | ||
| .port | .port() | .port(newPort) | The port number as a string | 8080 | | ||
| .protocol | .protocol() | .protocol(newProtocol) | The scheme | https: | | ||
| .search | .search() | .search(newSearch) | The search string, plain object, or URLSearchParams object | ?a=one&b=two | | ||
| .toString() | .toString() | N/A | The entire URL | http://example.com/foo?bar#baz | | ||
| .username | .username() | .username(newUsername) | The username string | ftpuser | | ||
† _newSearch can be a string, URLSearchParams object, or plain object._ | ||
## Custom Methods | ||
| Method | Description | | ||
| -------------------- | ------------------------------------------------------------- | | ||
| .clone() | Create a new URL with the same values | | ||
| .export(props) | Export all or the given subset of url attributes | | ||
| .import(values) | Import the given pieces | | ||
| .isRelative() | True if the URL is relative (e.g. starts with a dot or slash) | | ||
| .makeRelative() | Control whether URL is relative | | ||
| .searchObject([obj]) | Get or set query string params as object | | ||
## Query String Methods | ||
@@ -85,0 +100,0 @@ |
const FluentURLSearchParams = require('./FluentURLSearchParams.js'); | ||
class FluentURL { | ||
constructor(url, base, searchParams) { | ||
constructor(url = undefined, base = undefined, searchParams = undefined) { | ||
if (typeof base === 'object' && arguments.length === 2) { | ||
@@ -9,8 +9,10 @@ searchParams = base; | ||
} | ||
if (url instanceof URL) { | ||
this.URL = url; | ||
if (url) { | ||
this.href(url, base); | ||
this.fluentParams = new FluentURLSearchParams(this.URL.searchParams); | ||
} else { | ||
this.URL = new URL(url, base); | ||
// created with no arguments or all empty arguments | ||
this.href('/'); | ||
this.fluentParams = new FluentURLSearchParams({}); | ||
} | ||
this.fluentParams = new FluentURLSearchParams(this.URL.searchParams); | ||
if (searchParams) { | ||
@@ -20,2 +22,10 @@ this.qsExtend(searchParams); | ||
} | ||
isRelative() { | ||
return !!this._ignoreBase; | ||
} | ||
makeRelative() { | ||
this.port('').username('').password(''); | ||
this._ignoreBase = this.protocol() + '//' + this.hostname(); | ||
return this; | ||
} | ||
hash(newHash) { | ||
@@ -54,2 +64,5 @@ if (arguments.length === 0) { | ||
if (arguments.length === 0) { | ||
if (this._ignoreBase) { | ||
return undefined; | ||
} | ||
return this.URL.host; | ||
@@ -62,2 +75,5 @@ } | ||
if (arguments.length === 0) { | ||
if (this._ignoreBase) { | ||
return undefined; | ||
} | ||
return this.URL.hostname; | ||
@@ -68,10 +84,33 @@ } | ||
} | ||
href(newHref) { | ||
href(url, base = undefined) { | ||
if (arguments.length === 0) { | ||
return this.URL.href; | ||
this.URL.search = this.fluentParams.toString(); | ||
const href = this.URL.href; | ||
if (this.isRelative()) { | ||
return href.replace(this._ignoreBase, ''); | ||
} | ||
return href; | ||
} | ||
this.URL.href = newHref; | ||
if (url instanceof URL) { | ||
this.URL = url; | ||
} else { | ||
const match = String(url).match(/^\.{0,2}\//); | ||
if (match && !base) { | ||
this._ignoreBase = 'http://fluent-url-base'; | ||
if (match[0].startsWith('.')) { | ||
this._ignoreBase += '/relative'; | ||
} | ||
url = this._ignoreBase + url; | ||
} else { | ||
this._ignoreBase = undefined; | ||
} | ||
this.URL = new URL(url, base); | ||
} | ||
this.fluentParams = new FluentURLSearchParams(this.URL.searchParams); | ||
return this; | ||
} | ||
origin() { | ||
if (this._ignoreBase) { | ||
return undefined; | ||
} | ||
return this.URL.origin; | ||
@@ -81,2 +120,5 @@ } | ||
if (arguments.length === 0) { | ||
if (this._ignoreBase) { | ||
return undefined; | ||
} | ||
return this.URL.password; | ||
@@ -96,2 +138,5 @@ } | ||
if (arguments.length === 0) { | ||
if (this._ignoreBase) { | ||
return undefined; | ||
} | ||
return this.URL.port; | ||
@@ -104,2 +149,5 @@ } | ||
if (arguments.length === 0) { | ||
if (this._ignoreBase) { | ||
return undefined; | ||
} | ||
return this.URL.protocol; | ||
@@ -112,3 +160,4 @@ } | ||
if (arguments.length === 0) { | ||
return this.URL.search; | ||
const search = this.fluentParams.toString(); | ||
return search ? '?' + search : ''; | ||
} | ||
@@ -122,7 +171,54 @@ if (typeof newSearch === 'string') { | ||
} | ||
searchObject(object) { | ||
if (arguments.length === 0) { | ||
return this.qsToObject(); | ||
} | ||
this.qsSetAll(object); | ||
return this; | ||
} | ||
toString() { | ||
return this.URL.href; | ||
return this.href(); | ||
} | ||
export( | ||
withProps = [ | ||
'hash', | ||
'host', | ||
'hostname', | ||
'href', | ||
'isRelative', | ||
'origin', | ||
'password', | ||
'pathname', | ||
'port', | ||
'protocol', | ||
'search', | ||
'searchObject', | ||
'username', | ||
] | ||
) { | ||
const obj = {}; | ||
for (const prop of withProps) { | ||
if (typeof this[prop] === 'function') { | ||
obj[prop] = this[prop](); | ||
} | ||
} | ||
return obj; | ||
} | ||
import(values) { | ||
for (const [prop, value] of Object.entries(values)) { | ||
if (typeof this[prop] === 'function') { | ||
this[prop](value); | ||
} | ||
} | ||
return this; | ||
} | ||
clone() { | ||
this.URL.search = this.fluentParams.toString(); | ||
return new FluentURL(this.URL); | ||
} | ||
username(newUsername) { | ||
if (arguments.length === 0) { | ||
if (this._ignoreBase) { | ||
return undefined; | ||
} | ||
return this.URL.username; | ||
@@ -145,3 +241,3 @@ } | ||
} | ||
qsDeleteAll(name) { | ||
qsDeleteAll() { | ||
this.fluentParams.reset(); | ||
@@ -191,2 +287,8 @@ return this; | ||
} | ||
qsToString() { | ||
return this.fluentParams.toString(); | ||
} | ||
qsToObject() { | ||
return this.fluentParams.toObject(); | ||
} | ||
} | ||
@@ -193,0 +295,0 @@ |
const FluentURL = require('./FluentURL.js'); | ||
const FluentURLSearchParams = require('./FluentURLSearchParams.js'); | ||
describe('FluentURL', () => { | ||
describe('FluentURL constructor', () => { | ||
const testUrl = 'https://user:pass@sub.example.com:8443/foo/?q=hello#hash'; | ||
@@ -30,2 +30,59 @@ | ||
}); | ||
it('should construct with no arguments', () => { | ||
const url = new FluentURL(); | ||
url | ||
.protocol('https') | ||
.hostname('example.com') | ||
.pathname('/foo') | ||
.searchObject({ a: 'one' }); | ||
expect(url.toString()).toBe('https://example.com/foo?a=one'); | ||
}); | ||
}); | ||
describe('FluentURL methods', () => { | ||
const testUrl = 'https://user:pass@sub.example.com:8443/foo/?q=hello#hash'; | ||
it('should export all details', () => { | ||
const url = new FluentURL(testUrl); | ||
expect(url.export()).toEqual({ | ||
hash: '#hash', | ||
host: 'sub.example.com:8443', | ||
hostname: 'sub.example.com', | ||
href: testUrl, | ||
isRelative: false, | ||
origin: 'https://sub.example.com:8443', | ||
password: 'pass', | ||
pathname: '/foo/', | ||
port: '8443', | ||
protocol: 'https:', | ||
search: '?q=hello', | ||
searchObject: { q: 'hello' }, | ||
username: 'user', | ||
}); | ||
}); | ||
it('should export only valid requested details', () => { | ||
const url = new FluentURL(testUrl); | ||
expect(url.export(['host', 'protocol', 'notAThing'])).toEqual({ | ||
host: 'sub.example.com:8443', | ||
protocol: 'https:', | ||
}); | ||
}); | ||
it('should import requested values', () => { | ||
const url = new FluentURL(); | ||
url.import({ | ||
hash: '#hash', | ||
hostname: 'sub.example.com', | ||
password: 'pass', | ||
pathname: '/foo/', | ||
port: '8443', | ||
protocol: 'https:', | ||
searchObject: { q: 'hello' }, | ||
username: 'user', | ||
notAThing: 42, | ||
}); | ||
expect(url.href()).toBe(testUrl); | ||
}); | ||
it('should clone ok', () => { | ||
const url1 = new FluentURL(testUrl).searchObject({ a: 'one' }); | ||
const url2 = url1.clone(); | ||
expect(url1.href()).toBe(url2.href()); | ||
}); | ||
it('should get and set hash', () => { | ||
@@ -52,4 +109,5 @@ const url = new FluentURL(testUrl); | ||
expect(url.href()).toBe(testUrl); | ||
expect(url.href('http://httpbin.org/foo/?bar#baz')).toBe(url); | ||
expect(url.href()).toBe('http://httpbin.org/foo/?bar#baz'); | ||
const newHref = 'http://httpbin.org/foo/?bar=1#baz'; | ||
expect(url.href(newHref)).toBe(url); | ||
expect(url.href()).toBe(newHref); | ||
}); | ||
@@ -113,2 +171,11 @@ it('should get origin', () => { | ||
}); | ||
it('should get search as object', () => { | ||
const url = new FluentURL('http://example.com/search?q=hello'); | ||
expect(url.searchObject()).toEqual({ q: 'hello' }); | ||
}); | ||
it('should set search from object', () => { | ||
const url = new FluentURL('http://example.com/search?q=hello'); | ||
url.searchObject({ a: 'one', b: 'two' }); | ||
expect(url.search()).toEqual('?a=one&b=two'); | ||
}); | ||
it('should serialize with toString', () => { | ||
@@ -242,3 +309,61 @@ const url = new FluentURL(testUrl); | ||
}); | ||
it('should support qsToString', () => { | ||
const url = new FluentURL('https://example.com/foo/?a=1&b=2'); | ||
expect(url.qsToString()).toEqual('a=1&b=2'); | ||
}); | ||
}); | ||
describe('FluentURL relative URLs', () => { | ||
const testUrl = '/foo/?q=hello#hash'; | ||
it('should return relative path', () => { | ||
const url = new FluentURL(testUrl); | ||
expect(url.href()).toBe('/foo/?q=hello#hash'); | ||
expect(url.isRelative()).toBe(true); | ||
}); | ||
it('should return relative path with ../', () => { | ||
const url = new FluentURL('../foo/?q=hello#hash'); | ||
expect(url.href()).toBe('../foo/?q=hello#hash'); | ||
expect(url.isRelative()).toBe(true); | ||
}); | ||
it('should return relative path with ./', () => { | ||
const url = new FluentURL('./foo/?q=hello#hash'); | ||
expect(url.href()).toBe('./foo/?q=hello#hash'); | ||
expect(url.isRelative()).toBe(true); | ||
}); | ||
it('should return null for protocol', () => { | ||
const url = new FluentURL(testUrl); | ||
expect(url.protocol()).toBe(undefined); | ||
}); | ||
it('should return null for port', () => { | ||
const url = new FluentURL(testUrl); | ||
expect(url.port()).toBe(undefined); | ||
}); | ||
it('should return null for host', () => { | ||
const url = new FluentURL(testUrl); | ||
expect(url.host()).toBe(undefined); | ||
}); | ||
it('should return null for hostname', () => { | ||
const url = new FluentURL(testUrl); | ||
expect(url.hostname()).toBe(undefined); | ||
}); | ||
it('should return null for username', () => { | ||
const url = new FluentURL(testUrl); | ||
expect(url.username()).toBe(undefined); | ||
}); | ||
it('should return null for password', () => { | ||
const url = new FluentURL(testUrl); | ||
expect(url.password()).toBe(undefined); | ||
}); | ||
it('should return null for origin', () => { | ||
const url = new FluentURL(testUrl); | ||
expect(url.origin()).toBe(undefined); | ||
}); | ||
it('should allowing making absolute URL relative', () => { | ||
const url = new FluentURL( | ||
'https://user:password@example.com:8443' + testUrl | ||
); | ||
url.makeRelative(); | ||
expect(url.href()).toBe(testUrl); | ||
}); | ||
}); | ||
describe('FluentURL hash routing', () => { | ||
@@ -245,0 +370,0 @@ const testUrl = 'https://example.com/home#/dashboard?msg=Hello'; |
@@ -66,3 +66,8 @@ class FluentURLSearchParams { | ||
set(name, value) { | ||
this.searchParams.set(name, value); | ||
if (typeof name === 'object' && arguments.length === 1) { | ||
this.reset(); | ||
this.extend(name); | ||
} else { | ||
this.searchParams.set(name, value); | ||
} | ||
return this; | ||
@@ -81,2 +86,5 @@ } | ||
} | ||
toObject() { | ||
return Object.fromEntries(this.searchParams.entries()); | ||
} | ||
values() { | ||
@@ -83,0 +91,0 @@ return this.searchParams.values(); |
@@ -15,2 +15,7 @@ const FluentURLSearchParams = require('./FluentURLSearchParams.js'); | ||
}); | ||
it('should support setting with object', () => { | ||
const params = new FluentURLSearchParams(sample); | ||
expect(params.set({ b: '2', c: '3' })).toBe(params); | ||
expect(params.toString()).toBe('b=2&c=3'); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
40310
831
170
14