Migrating mw.Uri to URL
In MediaWiki 1.43, the mediawiki.Uri ResourceLoader module providing the mw.Uri class has been deprecated in favor of the native URL class available in web browsers.
This page contains advice on how to update your code.
Benefits
[edit]- No dependencies – faster loading
- Exactly matches the browser behavior in all edge cases
- URL constructor will never crash on a valid URL (T106244)
Important differences
[edit]Absolute and relative URLs
[edit]
The URL class can only represent absolute URLs (e.g. https://example.com/foo). In order to parse a relative URL (like /foo, or a protocol-relative URL), you must provide a base URL to resolve or expand against – usually you want to use the URL of the current tab as your base, via location.href. The mw.Uri class used location.href as the base by default. The URL class must be given a base explicitly.
new mw.Uri( 'https://example.org/foo' ) // => mw.Uri { 'https://example.org/foo' }
new mw.Uri( '//example.org/foo' ) // => mw.Uri { 'https://example.org/foo' }
new mw.Uri( '/foo' ) // => mw.Uri { 'https://www.mediawiki.org/foo' }
new URL( 'https://example.org/foo' ) // => URL { 'https://example.org/foo' }
new URL( '//example.org/foo' ) // throws TypeError
new URL( '//example.org/foo', location.href ) // => URL { 'https://example.org/foo' }
new URL( '/foo' ) // throws TypeError
new URL( '/foo', location.href ) // => URL { 'https://www.mediawiki.org/foo' }
Parsing URLs without protocol
[edit]mw.Uri allowed loose parsing of URLs without protocol, e.g. example.com/foo. The URL class interprets these as the filename in a relative URL instead, which matches how browsers expand URLs in HTML tags (<a href> and <img src>), CSS (background url), and other JavaScript APIs (like XHR, jQuery.ajax, and fetch ).
This was rarely intentionally relied upon, but it may be useful when processing user input. You can support this by catching the exception, and if the input contains no colon, try again with a default protocol like http:// prepended.
new mw.Uri( 'example.org/foo' ) // => mw.Uri { 'https://example.org/foo' }
new mw.Uri( 'foo.php/bar' ) // => mw.Uri { 'https://foo.php/bar' }
new URL( 'example.org/foo' ) // throws TypeError
new URL( 'example.org/foo', location.href ) // => URL { 'https://www.mediawiki.org/w/example.org/foo' }
new URL( 'foo.php/bar' ) // throws TypeError
new URL( 'foo.php/bar', location.href ) // => URL { 'https://www.mediawiki.org/w/foo.php/bar' }
let input = 'www.example.org/foo';
let res;
try {
res = new URL( input );
} catch ( e ) {
if ( input.startsWith( '//' ) ) {
res = new URL( 'https:' + input ); // "//www.mediawiki.org"
} else if ( !input.includes( ':' ) ) {
res = new URL( 'http://' + input ); // "www.example.org"
} else {
throw e;
}
}
Array parameters
[edit]mw.Uri special-cased URLs with "array parameters" in the format used by PHP code, e.g. https://example.com/foo?a[]=1&a[]=2. URL class does not.
In order to parse such array parameters, use mw.util.getArrayParam().
uri = new mw.Uri( 'http://example.com/foo?a[]=1&a[]=2', { arrayParams: true } );
uri.query.a // => [ "1", "2" ]
url = new URL( 'http://example.com/foo?a[]=1&a[]=2' );
mw.util.getArrayParam( 'a', url.searchParams ) // => [ "1", "2" ]
url.searchParams.getAll( 'a[]' ) // => [ "1", "2" ]
Duplicate parameters
[edit]mw.Uri and URL treat duplicate query parameters when parsing differently.
uri = new mw.Uri( 'http://example.com/foo?a=1&a=2' );
uri.query.a // => [ "1", "2" ]
uri = new mw.Uri( 'http://example.com/foo?a=1&a=2', { overrideKeys: true } );
uri.query.a // => "2" — last value wins
uri = new mw.Uri( 'http://example.com/foo?a=1&a=2', { arrayParams: true } );
uri.query.a // => "2" — 'arrayParams' implies 'overrideKeys'
url = new URL( 'http://example.com/foo?a=1&a=2' );
url.searchParams.get( 'a' ) // => "1" — first value wins
url.searchParams.getAll( 'a' ) // => [ "1", "2" ]
Corresponding code
[edit]Parsing a URL
[edit]| Using mw.Uri class | Using URL class |
|---|---|
uri = new mw.Uri( 'http://usr:pwd@example.com:81/mysite/mypage.php?foo=bar&baz#section' );
|
url = new URL( 'http://usr:pwd@example.com:81/mysite/mypage.php?foo=bar&baz#section' );
|
uri = new mw.Uri(); // current page URL
|
url = new URL( location.href ); // current page URL
|
Getting and setting fields
[edit]Note that some of the fields have different names.
| Using mw.Uri class | Using URL class |
|---|---|
uri.protocol // => "http"
uri.user // => "usr"
uri.password // => "pwd"
uri.getUserInfo() // => "usr:pwd"
uri.host // => "example.com"
uri.port // => "81"
uri.getHostPort() // => "example.com:81"
uri.getAuthority() // => "usr%3Apwd@example.com:81"
// (no equivalent)
uri.path // => "/mysite/mypage.php"
uri.getQueryString() // => "foo=bar&baz"
uri.query // => { foo: "bar", baz: null }
uri.fragment // => "section"
uri.getRelativePath() // => "/mysite/mypage.php?foo=bar&baz#section"
|
url.protocol // => "http:" — NOTE: ends with ':'
url.username // => "usr"
url.password // => "pwd"
// (no equivalent)
url.hostname // => "example.com"
url.port // => "81"
url.host // => "example.com:81"
// (no equivalent)
url.origin // => "http://example.com:81"
url.pathname // => "/mysite/mypage.php"
url.search // => "?foo=bar&baz" — NOTE: starts with '?'
url.searchParams // => URLSearchParams { foo → "bar", baz → "" }
url.hash // => "#section" — NOTE: starts with '#'
// (no equivalent)
|
In both cases, all of the fields (except searchParams – see below) can be directly set, e.g. uri.host = 'www.mediawiki.org' / url.hostname = 'www.mediawiki.org'.
The leading ? and # in url.search and url.hash can be removed with .slice( 1 ).
The trailing : in url.protocol can be removed with .slice( 0, -1 ).
When setting these fields, the leading ? and # and the trailing : are optional.
Converting a parsed URL object to string
[edit]| Using mw.Uri class | Using URL class |
|---|---|
uri.toString()
|
url.toString()
// or:
url.href
|
Getting and setting query parameters
[edit]| Using mw.Uri class | Using URL class | |
|---|---|---|
| Checking if query param is set | uri.query.foo !== undefined
|
url.searchParams.has( 'foo' )
|
| Getting query param value | uri.query.foo
|
url.searchParams.get( 'foo' )
|
| Setting query param value | uri.query.foo = 'bar';
|
url.searchParams.set( 'foo', 'bar' );
|
| Appending query param value | url.searchParams.append( 'foo', 'bar' );
url.searchParams.append( 'foo', 'baz' );
| |
| Removing query param | delete uri.query.foo;
|
url.searchParams.delete( 'foo' );
|
| Replacing all query params | uri.query = { foo: 'bar' };
|
url.search = new URLSearchParams( { foo: 'bar' } ).toString();
|
| Extending query params | uri.extend( params );
|
for ( const key in params ) {
url.searchParams.set( key, params[ key ] );
}
|
Other tips
[edit]Converting between plain object and URL query parameters
[edit]
In mw.Uri class, uri.query is a plain JS object. To get or set the query parameters in the same format using the URL class, use:
Object.fromEntries( url.searchParams ); // => { a: 'b', c: 'd' }
// URL.searchParams is read-only, thus update URL.search
url.search = new URLSearchParams( { a: 'b', c: 'd' } ).toString();
Formatting a pretty URL without unneeded encoding
[edit]The mw.Uri class preserves the URL encoding of inputs, while the URL class converts everything to the canonical decoded form. You can output pretty URLs as follows:
uri = new mw.Uri( 'https://el.wikipedia.org/wiki/Βικιπαίδεια' );
uri.toString() // => "https://el.wikipedia.org/wiki/Βικιπαίδεια"
// Ugly:
url = new URL( 'https://el.wikipedia.org/wiki/Βικιπαίδεια' );
url.toString() // => "https://el.wikipedia.org/wiki/%CE%92%CE%B9%CE%BA%CE%B9%CF%80%CE%B1%CE%AF%CE%B4%CE%B5%CE%B9%CE%B1"
// Pretty:
let prettyUrl = url.toString();
try {
const decodedUrl = decodeURI( url.toString() );
// Check that the decoded URL is parsed to the same canonical URL
if ( new URL( decodedUrl ).toString() === url.toString() ) {
prettyUrl = decodedUrl;
}
} catch ( err ) {}
prettyUrl // => "https://el.wikipedia.org/wiki/Βικιπαίδεια"