├── .gitignore ├── .travis.yml ├── index.js ├── browser.js ├── LICENSE ├── package.json ├── README.md └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | browser-built.js 2 | node_modules/ 3 | package-lock.json 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 6 4 | - 8 5 | - node 6 | script: npm test 7 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const hasURL = require("hasurl"); 3 | 4 | const {URL, URLSearchParams} = require( hasURL() ? "url" : "whatwg-url" ); 5 | 6 | const shim = () => 7 | { 8 | global.URL = URL; 9 | global.URLSearchParams = URLSearchParams; 10 | }; 11 | 12 | 13 | 14 | module.exports = { shim, URL, URLSearchParams }; 15 | -------------------------------------------------------------------------------- /browser.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var output = {}; 3 | var g, hasNative; 4 | 5 | 6 | 7 | if (typeof window !== "undefined") 8 | { 9 | g = window; 10 | } 11 | else if (typeof global !== "undefined") 12 | { 13 | g = global; 14 | } 15 | else if (typeof self !== "undefined") 16 | { 17 | g = self; 18 | } 19 | else 20 | { 21 | g = this; 22 | } 23 | 24 | 25 | 26 | try 27 | { 28 | var url = new g.URL("http://domain.com"); 29 | var params = new g.URLSearchParams("?param=value") 30 | 31 | hasNative = "searchParams" in url && params.get("param") === "value"; 32 | } 33 | catch (error) 34 | { 35 | hasNative = false; 36 | } 37 | 38 | 39 | 40 | if (hasNative) 41 | { 42 | output.URL = g.URL; 43 | output.URLSearchParams = g.URLSearchParams; 44 | } 45 | else 46 | { 47 | var lib = require("whatwg-url"); 48 | 49 | output.URL = lib.URL; 50 | output.URLSearchParams = lib.URLSearchParams; 51 | } 52 | 53 | 54 | 55 | output.shim = function() 56 | { 57 | g.URL = output.URL; 58 | g.URLSearchParams = output.URLSearchParams; 59 | }; 60 | 61 | 62 | 63 | module.exports = output; 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Steven Vachon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "universal-url", 3 | "description": "WHATWG URL for Node & Browser.", 4 | "version": "2.0.0", 5 | "license": "MIT", 6 | "author": "Steven Vachon (https://svachon.com)", 7 | "repository": "github:stevenvachon/universal-url", 8 | "browser": "browser.js", 9 | "dependencies": { 10 | "hasurl": "^1.0.0", 11 | "whatwg-url": "^7.0.0" 12 | }, 13 | "devDependencies": { 14 | "@babel/core": "^7.4.3", 15 | "@babel/preset-env": "^7.4.3", 16 | "babelify": "^10.0.0", 17 | "browserify": "^16.2.3", 18 | "chai": "^4.2.0", 19 | "common-shakeify": "~0.6.0", 20 | "gzip-size-cli": "^3.0.0", 21 | "mocha": "^6.1.4", 22 | "puppeteer": "^1.14.0", 23 | "rimraf": "^2.6.3", 24 | "terser": "^3.17.0" 25 | }, 26 | "engines": { 27 | "node": ">= 6" 28 | }, 29 | "scripts": { 30 | "posttest": "terser browser-built.js --compress --mangle | gzip-size && rimraf browser-built.js", 31 | "pretest": "browserify browser.js --global-transform [ babelify --presets [ @babel/env ] ] --plugin=common-shakeify --standalone=UniversalURL --outfile=browser-built.js", 32 | "test": "mocha test.js --check-leaks --globals=URL,URLSearchParams --bail" 33 | }, 34 | "files": [ 35 | "browser.js", 36 | "index.js" 37 | ], 38 | "keywords": [ 39 | "uri", 40 | "url", 41 | "whatwg" 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # universal-url [![NPM Version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Monitor][greenkeeper-image]][greenkeeper-url] 2 | 3 | > WHATWG [`URL`](https://developer.mozilla.org/en/docs/Web/API/URL) for Node & Browser. 4 | 5 | 6 | * For Node.js versions `>= 8`, the native implementation will be used. 7 | * For Node.js versions `< 8`, a [shim](https://npmjs.com/whatwg-url) will be used. 8 | * For web browsers without a native implementation, the same shim will be used. 9 | 10 | 11 | ## Installation 12 | 13 | [Node.js](http://nodejs.org) `>= 6` is required. To install, type this at the command line: 14 | ```shell 15 | npm install universal-url 16 | ``` 17 | 18 | 19 | ## Usage 20 | 21 | ```js 22 | const {URL, URLSearchParams} = require('universal-url'); 23 | 24 | const url = new URL('http://domain/'); 25 | const params = new URLSearchParams('?param=value'); 26 | ``` 27 | 28 | Global shim: 29 | ```js 30 | require('universal-url').shim(); 31 | 32 | const url = new URL('http://domain/'); 33 | const params = new URLSearchParams('?param=value'); 34 | ``` 35 | 36 | 37 | ## Browserify/etc 38 | 39 | The bundled file size of this library can be large for a web browser. If this is a problem, try using [universal-url-lite](https://npmjs.com/universal-url-lite) in your build as an alias for this module. 40 | 41 | 42 | [npm-image]: https://img.shields.io/npm/v/universal-url.svg 43 | [npm-url]: https://npmjs.org/package/universal-url 44 | [travis-image]: https://img.shields.io/travis/stevenvachon/universal-url.svg 45 | [travis-url]: https://travis-ci.org/stevenvachon/universal-url 46 | [greenkeeper-image]: https://badges.greenkeeper.io/stevenvachon/universal-url.svg 47 | [greenkeeper-url]: https://greenkeeper.io/ 48 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const {after, afterEach, before, beforeEach, describe, it} = require("mocha"); 3 | const {expect} = require("chai"); 4 | const puppeteer = require("puppeteer"); 5 | 6 | let browser, page; 7 | 8 | 9 | 10 | const openBrowser = () => 11 | { 12 | return puppeteer.launch({ args: ["--no-sandbox"] }) 13 | .then(puppeteerInstance => 14 | { 15 | browser = puppeteerInstance; 16 | return puppeteerInstance.newPage(); 17 | }) 18 | .then(pageInstance => 19 | { 20 | page = pageInstance; 21 | }); 22 | }; 23 | 24 | 25 | 26 | const it_browser_URL = ({checkHost, useGlobal}) => 27 | { 28 | it(`works${useGlobal ? " globally" : ""}`, () => 29 | { 30 | return page.evaluate(useGlobal => 31 | { 32 | let URL; 33 | 34 | if (useGlobal) 35 | { 36 | UniversalURL.shim(); 37 | URL = window.URL; 38 | } 39 | else 40 | { 41 | URL = UniversalURL.URL; 42 | } 43 | 44 | const url = new URL("http://ᄯᄯᄯ.ExAmPlE/?param=value#hash"); 45 | 46 | // Cannot return a native instance 47 | return { 48 | hostname: url.hostname, 49 | search: url.search, 50 | param: url.searchParams.get("param") 51 | }; 52 | }, useGlobal) 53 | .then(result => 54 | { 55 | if (checkHost) 56 | { 57 | expect(result.hostname).to.equal("xn--brdaa.example"); 58 | } 59 | 60 | expect(result.search).to.equal("?param=value"); 61 | expect(result.param).to.equal("value"); 62 | }); 63 | }); 64 | }; 65 | 66 | 67 | 68 | const it_browser_URLSearchParams = ({useGlobal}) => 69 | { 70 | it(`works${useGlobal ? " globally" : ""}`, () => 71 | { 72 | return page.evaluate(useGlobal => 73 | { 74 | let URLSearchParams; 75 | 76 | if (useGlobal) 77 | { 78 | UniversalURL.shim(); 79 | URLSearchParams = window.URLSearchParams; 80 | } 81 | else 82 | { 83 | URLSearchParams = UniversalURL.URLSearchParams 84 | } 85 | 86 | const params = new URLSearchParams("?p1=v&p2=&p2=v&p2"); 87 | 88 | // Cannot return a native instance 89 | return { 90 | params: params, 91 | p1: params.get("p1"), 92 | p2: params.get("p2"), 93 | p2all: params.getAll("p2") 94 | }; 95 | }, useGlobal) 96 | .then(result => 97 | { 98 | expect(result.params).to.not.be.undefined; 99 | expect(result.p1).to.equal("v"); 100 | expect(result.p2).to.equal(""); 101 | expect(result.p2all).to.deep.equal(["", "v", ""]); 102 | }); 103 | });; 104 | }; 105 | 106 | 107 | 108 | const it_node_URL = ({lib, useGlobal}) => 109 | { 110 | it(`works${useGlobal ? " globally" : ""}`, () => 111 | { 112 | let URL; 113 | 114 | if (useGlobal) 115 | { 116 | lib.shim(); 117 | URL = global.URL; 118 | } 119 | else 120 | { 121 | URL = lib.URL; 122 | } 123 | 124 | const url = new URL("http://ᄯᄯᄯ.ExAmPlE/?param=value#hash"); 125 | 126 | expect( url.hostname ).to.equal("xn--brdaa.example"); 127 | expect( url.search ).to.equal("?param=value"); 128 | expect( url.searchParams ).to.not.be.undefined; 129 | expect( url.searchParams.get("param") ).to.equal("value"); 130 | }); 131 | }; 132 | 133 | 134 | 135 | const it_node_URLSearchParams = ({lib, useGlobal}) => 136 | { 137 | it(`works${useGlobal ? " globally" : ""}`, () => 138 | { 139 | let URLSearchParams; 140 | 141 | if (useGlobal) 142 | { 143 | lib.shim(); 144 | URLSearchParams = global.URLSearchParams; 145 | } 146 | else 147 | { 148 | URLSearchParams = lib.URLSearchParams; 149 | } 150 | 151 | const params = new URLSearchParams("?p1=v&p2=&p2=v&p2"); 152 | 153 | expect( params ).to.not.be.undefined; 154 | expect( params.get("p1") ).to.equal("v"); 155 | expect( params.get("p2") ).to.equal(""); 156 | expect( params.getAll("p2") ).to.deep.equal(["", "v", ""]); 157 | }); 158 | }; 159 | 160 | 161 | 162 | describe("Node.js", () => 163 | { 164 | const lib = require("./"); 165 | let originalURL, originalURLSearchParams; 166 | 167 | 168 | 169 | before(() => 170 | { 171 | originalURL = global.URL; 172 | originalURLSearchParams = global.URLSearchParams; 173 | }); 174 | 175 | afterEach(() => 176 | { 177 | global.URL = originalURL; 178 | global.URLSearchParams = originalURLSearchParams; 179 | }); 180 | 181 | 182 | 183 | describe("URL", () => 184 | { 185 | it_node_URL({ lib, useGlobal:false }); 186 | it_node_URL({ lib, useGlobal:true }); 187 | }); 188 | 189 | 190 | 191 | describe("URLSearchParams", () => 192 | { 193 | it_node_URLSearchParams({ lib, useGlobal:false }); 194 | it_node_URLSearchParams({ lib, useGlobal:true }); 195 | }); 196 | }); 197 | 198 | 199 | 200 | describe("Web Browser (without native)", () => 201 | { 202 | before(() => openBrowser()); 203 | 204 | beforeEach(() => 205 | { 206 | return page.reload() 207 | .then(() => page.evaluate(() => 208 | { 209 | window.URL = undefined; 210 | window.URLSearchParams = undefined; 211 | })) 212 | .then(() => page.addScriptTag({ path: "browser-built.js" })); 213 | }); 214 | 215 | after(() => browser.close()); 216 | 217 | 218 | 219 | describe("URL", () => 220 | { 221 | it_browser_URL({ checkHost:true, useGlobal:false }); 222 | it_browser_URL({ checkHost:true, useGlobal:true }); 223 | }); 224 | 225 | 226 | 227 | describe("URLSearchParams", () => 228 | { 229 | it_browser_URLSearchParams({ useGlobal:false }); 230 | it_browser_URLSearchParams({ useGlobal:true }); 231 | }); 232 | }); 233 | 234 | 235 | 236 | describe("Web Browser (with native)", () => 237 | { 238 | before(() => openBrowser()); 239 | 240 | beforeEach(() => 241 | { 242 | return page.reload() 243 | .then(() => page.addScriptTag({ path: "browser-built.js" })); 244 | }); 245 | 246 | after(() => browser.close()); 247 | 248 | 249 | 250 | describe("URL", () => 251 | { 252 | // TODO :: `checkHost:true` when Chrome correctly converts from Unicode to ASCII 253 | it_browser_URL({ checkHost:false, useGlobal:false }); 254 | it_browser_URL({ checkHost:false, useGlobal:true }); 255 | }); 256 | 257 | 258 | 259 | describe("URLSearchParams", () => 260 | { 261 | it_browser_URLSearchParams({ useGlobal:false }); 262 | it_browser_URLSearchParams({ useGlobal:true }); 263 | }); 264 | }); 265 | --------------------------------------------------------------------------------