I always want to shoot myself in the head when looking at code like the following:
var url = "http://example.org/foo?bar=baz";
var separator = url.indexOf('?') > -1 ? '&' : '?';
url += separator + encodeURIComponent("foo") + "=" + encodeURIComponent("bar");
I still can't believe javascript - the f**ing backbone-language of the web - doesn't offer an API for mutating URLs. Browsers (Firefox) don't expose the Location
object (the structure behind window.location). Yes, one could think of decomposed IDL attributes as a native URL management library. But it relies on the DOM element <a>, it's slow and doesn't offer any convenience at all.
How about a nice, clean and simple API for mutating URIs:
var url = new URI("http://example.org/foo?bar=baz");
url.addQuery("foo", "bar");
URI.js is here to help with that.
// mutating URLs
URI("http://example.org/foo.html?hello=world")
.username("rodneyrehm")
// -> http://rodneyrehm@example.org/foo.html?hello=world
.username("")
// -> http://example.org/foo.html?hello=world
.directory("bar")
// -> http://example.org/bar/foo.html?hello=world
.suffix("xml")
// -> http://example.org/bar/foo.xml?hello=world
.query("")
// -> http://example.org/bar/foo.xml
.tld("com")
// -> http://example.com/bar/foo.xml
.query({ foo: "bar", hello: ["world", "mars"] });
// -> http://example.com/bar/foo.xml?foo=bar&hello=world&hello=mars
// cleaning things up
URI("?&foo=bar&&foo=bar&foo=baz&")
.normalizeQuery();
// -> ?foo=bar&foo=baz
// working with relative paths
URI("/foo/bar/baz.html")
.relativeTo("/foo/bar/world.html");
// -> ./baz.html
URI("/foo/bar/baz.html")
.relativeTo("/foo/bar/sub/world.html")
// -> ../baz.html
.absoluteTo("/foo/bar/sub/world.html");
// -> /foo/bar/baz.html
// URI Templates
URI.expand("/foo/{dir}/{file}", {
dir: "bar",
file: "world.html"
});
// -> /foo/bar/world.html
See the About Page and API Docs for more stuff.
URI.js (without plugins) has a gzipped weight of about 7KB - if you include all extensions you end up at about 13KB. So unless you need second level domain support and use URI templates, we suggest you don't include them in your build. If you don't need a full featured URI mangler, it may be worth looking into the much smaller parser-only alternatives listed below.
URI.js is available through npm, bower, Jam and manually from the build page:
# using bower
bower install uri.js
# using Jam
jam install URIjs
# using npm
npm install URIjs
I guess you'll manage to use the build tool or follow the instructions below to combine and minify the various files into URI.min.js - and I'm fairly certain you know how to <script src=".../URI.min.js"></script>
that sucker, too.
Install with npm install URIjs
or add "URIjs"
to the dependencies in your package.json
.
// load URI.js
var URI = require('URIjs');
// load an optional module (e.g. URITemplate)
var URITemplate = require('URIjs/src/URITemplate');
URI("/foo/bar/baz.html")
.relativeTo("/foo/bar/sub/world.html")
// -> ../baz.html
Clone the URI.js repository or use a package manager to get URI.js into your project.
require.config({
paths: {
URIjs: 'where-you-put-uri.js/src'
}
});
require(['URIjs/URI'], function(URI) {
console.log("URI.js and dependencies: ", URI("//amazon.co.uk").is('sld') ? 'loaded' : 'failed');
});
require(['URIjs/URITemplate'], function(URITemplate) {
console.log("URITemplate.js and dependencies: ", URITemplate._cache ? 'loaded' : 'failed');
});
See the build tool or use Google Closure Compiler:
// ==ClosureCompiler==
// @compilation_level SIMPLE_OPTIMIZATIONS
// @output_file_name URI.min.js
// @code_url http://medialize.github.io/URI.js/src/IPv6.js
// @code_url http://medialize.github.io/URI.js/src/punycode.js
// @code_url http://medialize.github.io/URI.js/src/SecondLevelDomains.js
// @code_url http://medialize.github.io/URI.js/src/URI.js
// @code_url http://medialize.github.io/URI.js/src/URITemplate.js
// ==/ClosureCompiler==
Documents specifying how URLs work:
- URL - Living Standard
- RFC 3986 - Uniform Resource Identifier (URI): Generic Syntax
- RFC 3987 - Internationalized Resource Identifiers (IRI)
- RFC 2732 - Format for Literal IPv6 Addresses in URL's
- Punycode: A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA)
- application/x-www-form-urlencoded (Query String Parameters) and application/x-www-form-urlencoded encoding algorithm
- What every web developer must know about URL encoding
Informal stuff
How other environments do things
If you don't like URI.js, you may like one of the following libraries. (If yours is not listed, drop me a line…)
- The simple URL Mutation "Hack" (jsPerf comparison)
- URL.js
- furl (Python)
- mediawiki Uri (needs mw and jQuery)
- jurlp
- jsUri
- The simple URL Mutation "Hack" (jsPerf comparison)
- URI Parser
- jQuery-URL-Parser
- Google Closure Uri
- URI.js by Gary Court
- uri-template (supporting extraction as well) by Rezigne
- uri-templates (supporting extraction as well) by Geraint Luff
- uri-templates by Marc Portier
- uri-templates by Geraint Luff (including reverse operation)
- URI Template JS by Franz Antesberger
- Temple by Brett Stimmerman
- (jsperf comparison)
- TLD.js - second level domain names
- Public Suffix - second level domain names
- uri-collection - underscore based utility for working with many URIs
- punycode.js - Mathias Bynens
- IPv6.js - Rich Brown - (rewrite of the original)
URI.js is published under the MIT license and GPL v3.
- changes to package management manifests only
- fixing IPv6 normalization (bad variable name) - (Issue #145)
- adding grunt and jshint
- changing code style to 2 spaces indentation, single quote strings
- applying
'use strict';
everywhere - fixing jshint warnings
- fixing
URI.parseHost()
andURI.buildHost()
to properly parse and build the IPv6 examples given in RFC2732 Format for Literal IPv6 Addresses in URL's - (Issue #144) - adding performance improvements to SecondLevelDomain - (PR #122, gorhill)
- fixing
.encodeQuery()
and.decodeQuery()
to respectURI.escapeQuerySpace
- (Issue #137) - fixing fragment plugins to return URI for simpler loading - (Issue #139)
- fixing
.absoluteTo()
to comply with RFC3986 Reference Resolution Examples - (Issue #113) - fixing
.normalizePath()
to maintain leading parent references (../
) for relative paths, while removing them for absolute paths - (Issue #133) - fixing
URI.protocol_expression
to properly accept.
in compliance with RFC 3986 - Scheme - (Issue #132) - fixing
URI.withinString()
to not use backtracking prone regular expressionURI.find_uri_expression
anymore - (Issue #131) - fixing
URI.withinString()
to accept optionsignore
andignoreHtml
to allow better control over which detected URLs get handled - (Issue #117) - fixing
URI.withinString()
to accept optionstart
to specify the RegExp used for finding the beginning of an URL (defaults to/\b(?:([a-z][a-z0-9.+-]*:\/\/)|www\.)/gi
) - (Issue #115)
- fixing regression for Node.js introduced by
fixing unsafe eval by using UMD's root
- (Issue #107) - fixing parser to accept malformed userinfo (non-encoded email address) - (Issue #108)
- fixing inconsistent
.relativeTo()
results caused by inconsistent URI component handling - (Issue #103) - fixing unsafe eval by using UMD's root - (Issue #105)
- fixing
.segment()
to allow appending an empty element - (Issue #106) - fixing
.segment()
to collapse empty elements in array notation
- adding
.segmentCoded()
to provide en/decoding interface to.segment()
- (Issue #79) - optimize
.relativeTo()
results - (Issue #78, Issue #95) - removing obsolete code fragments from
URI.parse()
andrelativeTo()
- (Issue #100) - adding setting
URI.escapeQuerySpace
to control if query string should escape spaces using+
or%20
- (Issue #74) - updating Punycode.js to version 1.2.3
- fixing internal
strictEncodeURIComponent()
to work in Firefox 3.6 - (Issue #91) - fixing
.normalizePath()
to properly resolve/.
and/.//
to/
- (Issue #97) - fixing
.path()
to return empty string if there is no path - (Issue #82) - fixing crashing of
URI.decodeQuery()
on malformed input - now returns original undecoded data - (Issue #87, Issue #92) - fixing build tool - (Issue #83)
- fixing for-loop to make closure compiler happy - (Issue #93)
- adding
URI.noConflict()
- (Issue #84) - fixing whitespace in code - (Issue #84)
- fixing
.readable()
to decode the hash value as well - (Issue #90) - prevent
jquery.URI.js
from temporarily usingwindow.location
as thehref
of an empty attribute of a DOM element - (Issue #94) - fixing internal
getType()
for IE8 with undefined value - (Issue #96) - adding DOM elements to URI constructor - (Issue #77):
- optimize
jquery.URI.js
to use new DOM element infrastructure
- fixing
relativeTo()
- (Issue #75) - fixing
normalizePath()
to not prepend./
to relative paths - (Issue #76)
- adding
absoluteTo()
to properly resolve relative scheme - (Issue #71)
- adding
hasQuery()
- (Issue #71) - fixing URI property detection to return 'src' if none was detected (
jquery.URI.js
) - (Issue #69)
- fixing IE9 compatibility with location import:
URI(location)
- fixing string character access for IE7 - (Issue #67), (Issue #68)
- adding
.setQuery()
- (Issue #64) - adding callback argument to
.query()
- adding jQuery 1.9.1 to the test suite
- adding
.fragmentPrefix()
to configure prefix of fragmentURI and fragmentQuery extensions - (Issue #55) - adding docs for
.toString()
,.valueOf()
and.href()
- (Issue #56) - fixing
.relativeTo()
for descendants - (Issue #57)
- fixing build() to properly omit empty query and fragment (Issue #53)
- adding
.resource()
as compound of [path, query, fragment] - adding jQuery 1.8.x compatibility for jQuery.URI.js (remaining backwards compatibility!)
- adding default ports for gopher, ws, wss
- adding
.duplicateQueryParameters()
to control ifkey=value
duplicates have to be preserved or reduced (Issue #51) - updating Punycode.js to version 1.1.1
- improving AMD/Node using UMD returnExports - (Issue #44, Issue #47)
- fixing
.addQuery("empty")
to properly add?empty
- (Issue #46) - fixing parsing of badly formatted userinfo
http://username:pass:word@hostname
- fixing parsing of Windows-Drive-Letter paths
file://C:/WINDOWS/foo.txt
- fixing
URI(location)
to properly parse the URL - (Issue #52) - fixing type error for fragment abuse demos - (Issue #50)
- adding documentation for various encode/decode functions
- adding some pointers on possible problems with URLs to About URIs
- adding tests for fragment abuse and splitting tests into separate scopes
- adding meta-data for Jam and Bower
Note: QUnit seems to be having some difficulties on IE8. While the jQuery-plugin tests fail, the plugin itself works. We're still trying to figure out what's making QUnit "lose its config state".
- fixing parsing of
/wiki/Help:IPA
- (Issue #49)
- fixing
strictEncodeURIComponent()
to properly encode*
to%2A
- fixing IE9's incorrect report of
img.href
being available - (Issue #48)
- fixing SLD detection in
.tld()
-foot.se
would detectt.se
- (Issue #42) - fixing
.absoluteTo()
to comply with RFC 3986 Section 5.2.2 - (Issue #41) - fixing
location
not being available in non-browser environments like node.js (Issue #45 grimen)
- fixing
.segment()
's append operation - (Issue #39)
- fixing URI() constructor passing of
base
- (Issue #33 LarryBattle) - adding
.segment()
accessor - (Issue #34) - upgrading
URI.encode()
to strict URI encoding according to RFC3986 - adding
URI.encodeReserved()
to exclude reserved characters (according to RFC3986) from being encoded - adding URI Template (RFC 6570) support with
URITemplate()
- fixing
.absoluteTo()
to join two relative paths properly - (Issue #29) - adding
.clone()
to copy an URI instance
.directory()
now returns empty string if there is no directory- fixing
.absoluteTo()
to join two relative paths properly - (Issue #29)
- adding URN (
javascript:
,mailto:
, ...) support - adding
.scheme()
as alias of.protocol()
- adding
.userinfo()
to comply with terminology of RFC 3986 - adding jQuery Plugin
src/jquery.URI.js
- fixing relative scheme URLs - (Issue #19 byroot)
- adding Second Level Domain (SLD) Support - (Issue #17)
- fixing global scope leakage - (Issue #15 mark-rushakoff)
- adding CommonJS compatibility - (Issue #11, Evangenieur)
- adding
URI.iso8859()
andURI.unicode()
to switch base charsets - (Issue #10, mortenn) - adding
.iso8859()
and.unicode()
to convert an URI's escape encoding
- updating Punycode.js to version 0.3.0
- adding edge-case tests ("jim")
- fixing edge-cases in .protocol(), .port(), .subdomain(), .domain(), .tld(), .filename()
- fixing parsing of hostname in
.hostname()
- adding
.subdomain()
convenience accessor - improving internal deferred build handling
- fixing thrown Error for
URI("http://example.org").query(true)
- (Issue #6) - adding examples for extending URI.js for fragment abuse, see src/URI.fragmentQuery.js and src/URI.fragmentURI.js - (Issue #2)
- adding
.equals()
for URL comparison - fixing encoding/decoding for
.pathname()
,.directory()
,.filename()
and.suffix()
according to RFC 3986 3.3 - fixing escape spaces in query strings with
+
according to application/x-www-form-urlencoded - fixing to allow
URI.buildQuery()
to build duplicate key=value combinations - fixing
URI(string, string)
constructor to conform with the specification - adding
.readable()
for humanly readable representation of encoded URIs - fixing bug where @ in pathname would be parsed as part of the authority
- adding
URI.withinString()
- adding
.normalizeProtocol()
to lowercase protocols - fixing
.normalizeHostname()
to lowercase hostnames - fixing String.substr() to be replaced by String.substring() - (Issue #1)
- fixing parsing "?foo" to
{foo: null}
Algorithm for collecting URL parameters - fixing building
{foo: null, bar: ""}
to "?foo&bar=" Algorithm for serializing URL parameters - fixing RegExp escaping
- Initial URI.js