Coder Social home page Coder Social logo

medialize / uri.js Goto Github PK

View Code? Open in Web Editor NEW
6.3K 118.0 474.0 3.13 MB

Javascript URL mutation library

Home Page: http://medialize.github.io/URI.js/

License: MIT License

JavaScript 74.22% HTML 23.65% CSS 1.89% PHP 0.24%
uri-template javascript url-parser url uri rfc-3986 rfc-6570 rfc-2732

uri.js's Introduction

URI.js

CDNJS


IMPORTANT: You may not need URI.js anymore! Modern browsers provide the URL and URLSearchParams interfaces.


NOTE: The npm package name changed to urijs


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");

Things are looking up with URL and the URL spec but until we can safely rely on that API, have a look at URI.js for a 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.

API Example

// mutating URLs
URI("http://example.org/foo.html?hello=world")
  .username("rodneyrehm")
    // -> http://[email protected]/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.

Using URI.js

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, bowercdn, cdnjs and manually from the build page:

# using bower
bower install uri.js

# using npm
npm install urijs

Browser

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.

Node.js and NPM

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

RequireJS

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');
});

Minify

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==

Resources

Documents specifying how URLs work:

Informal stuff

How other environments do things

Discussion on Hacker News

Forks / Code-borrow

  • node-dom-urls passy's partial implementation of the W3C URL Spec Draft for Node
  • urlutils cofounders' window.URL constructor for Node

Alternatives

If you don't like URI.js, you may like one of the following libraries. (If yours is not listed, drop me a line…)

Polyfill

URL Manipulation

URL Parsers

URI Template

Various

Authors

Contains Code From

License

URI.js is published under the MIT license. Until version 1.13.2 URI.js was also published under the GPL v3 license - but as this dual-licensing causes more questions than helps anyone, it was dropped with version 1.14.0.

Changelog

moved to Changelog

uri.js's People

Contributors

alekseyleshko avatar alesandroortiz avatar alfredbez avatar ankon avatar b-viguier avatar bantic avatar bergie avatar christianharms avatar coox avatar cxuesong avatar djcsdy avatar fgribreau avatar fishead avatar fredr avatar grimen avatar hgezim avatar jasonkarns avatar jeff-phillips-18 avatar joladev avatar jordanmilne avatar justinmchase avatar keanu-delgado avatar konstantinblaesi avatar mistralys avatar mutewinter avatar nostalgiaz avatar nullivex avatar rodneyrehm avatar samuelcole avatar waitingcheung avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

uri.js's Issues

Add accessors for Media Fragments URI

The Media Fragments URI Specification defines how dimensions (spatial, temporal, track, …) can be specified. <img src="foo.png#xywh=10,20,100,40"> would specify to show a cropped version of the actual image (100x40 pixels at a 10x20 pixel offset). <audio src="/audio.ogg#t=5,20"> wouls specify to play the audio file from 5th second till 20th second.

URI('foo.png#xywh=10,20,100,40').mediaFragment() could return {x: 10, y: 20, w:100, height:40}, while URI('foo.png').mediaFragment({y: 20, w:100, x: 10, height:40}) could lead to foo.png#xywh=10,20,100,40.

Things get more interesting with converting different representations of the same information, as the Temporal Dimension notation shows.

Resources

Trailing slashes in URL normalization

I write an application in which I need to create a hash map of link text with the normalized URL of the link as a key. I am researching normalization of URLs.

Is it considered a safe normalization to normalize trailing slashes?

RFC 3986, section 6.2.3. ("Scheme-Based Normalization") states:

For example, because the "http" scheme makes use of an authority component, has a default port of "80", and defines an empty path to be equivalent to "/", the following four URIs are equivalent:
http://example.com
http://example.com/
http://example.com:/
http://example.com:80/

RFC 2616, Section 3.2.3 ("URI Comparison") of the HTTP states:

When comparing two URIs to decide if they match or not, a client SHOULD use a case-sensitive octet-by-octet comparison of the entire URIs, with these exceptions:

  • A port that is empty or not given is equivalent to the default port for that URI-reference;
  • Comparisons of host names MUST be case-insensitive;
  • Comparisons of scheme names MUST be case-insensitive;
  • An empty abs_path is equivalent to an abs_path of "/".

I understand that the safe normalization is scheme dependent; i.e. a safe normalization within the HTTP scheme may not be safe in other schemes, so a generic URI normalizer cannot safely normalize trailing slashes.

I also realise that the an empty abs_path and an empty segment of abs_path are not equivalent, and thus RFC 2616 does not give you license to normalize trailing slashes for anything but the empty abs_path.

However, would you consider implementing methods to perform "reasonably safe" normalizations, but not perform them in the general normalization() method (such as those listed here: Normalizations that Usually Preserve Semantics (Wikipedia))?

Removing the scheme does not work

The removing scheme example from doesn't seem to work.

// remove scheme
var uri = new URI("http://example.org/foo/hello.html");
// ...
uri.protocol(null);
uri.toString() === 'example.org/foo/hello.html'; 

Instead, the above returns "//example.org/foo/hello.html".

.port and .host mutate URL differently based on call order

Here's the failing test

var URI       = require('URIjs');
var address = { port: 8099, family: 2, address: '127.0.0.1' }
var u1 = URI('/').protocol('http').port(address.port).host(address.address).toString()
var u2 = URI('/').protocol('http').host(address.address).port(address.port).toString()

require('assert').equal(u1, u2);

absoluteTo() for two single files results in an absolute path

Unclear on what the real usage of absoluteTo() is, I assumed it was similar to Python's urlparse.urljoin.

So for example it will base a filename on top of another relative path:

>>> (new URI("aFile")).absoluteTo("aDir/").toString()
"aDir/aFile"

Especially interesting, when the path passed to absoluteTo() is a path to a file resource, too:

>>> (new URI("aFile")).absoluteTo("aDir/anotherFile").toString()
"aDir/aFile"

However, when applied to two file resources, a slash is added which results in an absolute path:

>>> (new URI("aFile")).absoluteTo("anotherFile").toString()
"/aFile"

I would expect just

"aFile"

as there is no valuable path information in the given string.

I don't know if I am misusing the method, or if this really is a bug. In the latter case I will be happy to provide a fix.

Publish to NPM

I see a package.json file and a few references to module.exports but either I'm blind, I haven't entered the right search-terms, or this isn't on NPM.

URI Template - RFC6570

RFC6570 specifies a "placeholder pattern" for generating URIs from variables.

Consider implementing URI.template('UriTemplateString', {data:null}) according to RFC6570. Investigate reversing a template to regular expression patterns to extract specific data points from a given URL based off such an URI Template string.

URL-Testing

some people have compiled a set of tests (for browser compliance testing): url-testing. consider testing URI.js against that list.

Update search/query instead of add

I've been trying to find a way to update an existing search argument if it exists, or have it added if it doesn't exist.

Something like:

var uri = URI("http://localhost?foo=bar")
uri.updateSearch("foo", "baz");
uri.toString(); => "http://localhost?foo=baz"

Is this possible with the current API? It seems addSearch will only append another argument, for example:

var uri = URI("http://localhost?foo=bar")
uri.addSearch("foo", "baz");
uri.toString(); => "http://localhost?foo=bar&foo=baz"

SCRIPT5022: Element "source" does not have either property: href, src, action jquery.URI.js, line 131 character 9

I am sort of at a loss on this one.

I am using IE9. Browser mode: IE8. Document Mode: IE8 standards

The files are included in the following order:

/URI/IPv6.js
/URI/punycode.js
/URI/SecondLevelDomains.js
/URI/URI.js
/URI/URITemplate.js
/URI/jquery.URI.js
/URI/URI.fragmentQuery.js
/URI/URI.fragmentURI.js

If I include "jquery.URI.js" in my html page, I get the following error:
SCRIPT5022: Element "source" does not have either property: href, src, action jquery.URI.js, line 131 character 9

There are no references to using the URI plugin on the page in question, or any files that includes, so I assume it is something to do with the way jquery.URI.js initializes itself.

RequireJS errors

When using the 1.8.3 build version, I get an error when precompiling with RequireJS:
Running "requirejs:compile" (requirejs) task

Tracing dependencies for: config
Error: /ep/jsmvc/spinaltap/assets/js/libs/URI.min.js has two many anonymous
modules in it.
at
/ep/jsmvc/spinaltap/node_modules/grunt-contrib-requirejs/node_modules/require
js/bin/r.js:20155:35

Switching to the source version results in no error. Also I note that even if I uncheck the other modules in the build (punycode, IPv6, etc), the built file still requires them.

I'm using requireJS 2.1.2

absoluteTo in IE7 always prefixes base directory to path

The condition:

if (resolved.path()[0] !== '/') {
    resolved._parts.path = base.directory() + '/' + resolved._parts.path;
    resolved.normalizePath();
}

is always true in IE7 because it doesn't access strings like arrays. perhaps charAt(0) instead?

Get query string parameter value?

With URI.js, it's easy to SET parameters.

I've read all the docs and hope I'm missing something, but I can't find a good way to GET the query string parameter value.

What I'm having to do now is this:

if (url.search(true).hasOwnProperty("my_parameter_name")) {
  console.log("parameter value is" + url.search(true).my_parameter_name);
}

Is there a better way?

Problem using relativeTo() with strict descendants

For some reason, it looks to me like relativeTo() returns unexpected results when the first URI is a strict descendant of the second. For example, I would have expected this to work:

>>> new URI('/base/path/with/subdir/inner.html').relativeTo('/base/path/top.html').toString()
"./with/subdir/inner.html"

Instead, I get this result which seems wrong to me:

>>> new URI('/base/path/with/subdir/inner.html').relativeTo('/base/path/top.html').toString()
"./inner.html"

Reversing them, however, works as expected:

>>> new URI('/base/path/top.html').relativeTo('/base/path/with/subdir/inner.html').toString()
"../../top.html"

Is this a bug, or am I misusing the method in some way? Is this something relativeTo() should be expected to support?

.query and .fragment don't work as advertised

On the website, code like this doesn't operate as advertised.

URI("http://example.org/foo.html?hello=world").query('').fragment('').toString()

This is what I expect:

"http://example.org/foo.html"

This is what I really get:

"http://example.org/foo.html?#"

Implement hasQuery/hasSearch to check for query parameters

It would be nice to have a very simple helper to check for existing/matching query parameters, except I missed something in the code :-D

My approach for now, I was not sure if it makes sense to implement the same argument structure as in add/remove Query.

@@ -551,6 +551,24 @@ URI.removeQuery = function(data, name, value) {
         throw new TypeError("URI.addQuery() accepts an object, string as the first parameter");
     }
 };
+URI.hasQuery = function(data, name, value) {
+    var i, length, key;
+
+    if (!typeof name == "string") {
+        throw new TypeError("URI.hasQuery accepts a string as first parameter");
+    }
+
+    if (typeof name == "string") {
+        if (value !== undefined && data[name] === value) {
+            return true
+        } else if (value === undefined && name in data) {
+            return true
+        }
+    }
+
+    return false;
+};
+

 URI.commonPath = function(one, two) {
     var length = Math.min(one.length, two.length);
@@ -1293,9 +1311,14 @@ p.removeQuery = function(name, value, build) {
     this.build(!build);
     return this;
 };
+p.hasQuery = function(name, value, build) {
+    var data = URI.parseQuery(this._parts.query);
+    return URI.hasQuery(data, name, value);
+};
 p.setSearch = p.setQuery;
 p.addSearch = p.addQuery;
 p.removeSearch = p.removeQuery;
+p.hasSearch = p.hasQuery;

 // sanitizing URLs
 p.normalize = function() {

Global "duplicates" parameter

Hi and thanks for this great lib!

Working with an URI instance, I cannot find a way to prevent it to remove duplicates (as URI.buildQuery() offers).

Is it possible to have a global flag like : URI.allowDuplicates = true
And / or something on an instance like : uri.allowDuplicates(true)

Thanks!

Error: global leak detected: t

Testing some code using Mocha and it's saying there's a global leak somewhere in URIjs:

URI = require 'URIjs'

describe 'location', ->
  test 'subdomain', ->
    # occurs here
    console.log URI("http://viatropos.rituwall.com")

URI.query(true) throws if the url is clean

for example

console.log(URI("http://google.com?s=q").search(true)); //OK
console.log(URI("http://google.com?s=").search(true)); //OK
console.log(URI("http://google.com?s").search(true)); //OK
console.log(URI("http://google.com").search(true)); //Throws

I'm sure is something simple to fix, but I don't have time right now. I can fix it later, but I wanted to submit this before I forgot about it

BTW: Really cool library :)

.query() Array-Parameters won't fill PHP's $_GET as intended

Current Version

This Code:

.query({ foo: "bar", hello: ["world", "mars"] });

Leads to this:

// -> http://example.com/bar/foo.xml?foo=bar&hello=world&hello=mars

I think that's kinda senseless. Cause this is what we see on the Server-Side:

$_GET = array(
  'foo' => 'bar',
  'hello' => 'mars'
);

I.e. "hello=world" is missing and no one can work with it.

Improved Version

Better:

// -> http://example.com/bar/foo.xml?foo=bar&hello[]=world&hello[]=mars

Results in this:

$_GET = array(
  'foo' => 'bar',
  'hello' => array(
    0 => 'world',
    1 => 'mars'
  )
);

Tiny little mistake in the documentation

There seems to be a little mistake in the documentation for toString() on the website.

The example looks as follows

var uri = URI("http://example.com");
var s = uri.toString();
typeof s === "string";
s === "http://example.com";

But it seems like toString() actually adds a trailing slash so it should look like

var uri = URI("http://example.com");
var s = uri.toString();
typeof s === "string";
s === "http://example.com/";

document.location's protocol property may contain a colon

While playing with URI.js, I stumbled across a problem. Here's some sample code to illustrate the point, as copied from my Chrome's script console.

URI(document.location).toString()
"https:://example.com/somePage.html"

This seems to boil down to how URI handles the protocol.

URI(document.location).protocol()
"https:"

URI(document.location.toString()).protocol()
"https"

document.location.protocol
"https:"

It looks like .protocol can contain a colon and that seems to mess up URI.build on line 365:

    if (parts.protocol) {
        t += parts.protocol + ":";
    }

I'm wondering if this would make sense:

    if (parts.protocol && parts.protocol.charAt(parts.protocol.length - 1) != ':') {
        t += parts.protocol + ":";
    }

Using URI inside a Firefox Add-on.

The Firefox extension environment uses CommonJS to require files. However, it isn't possible to use npm packages.

Basically that means that I have to place the src/uri.js file directly into my project and then require it like this:

// /lib/services/some_file.js

if (typeof exports !== 'undefined') {
  var URI = require('../uri');

  // Some tests to try and figure out wtf is going on..
  console.log("IS URI defined", typeof URI != 'undefined'); // true
  console.log("IS URI defined", URI instanceof Array);  // false
  console.log("IS URI defined", URI instanceof Function); // false
  console.log("IS URI defined", URI instanceof Object); // false 
  console.log("IS URI defined", URI.constructor == String); // false
  console.log("IS URI defined", URI.constructor == Number); // false 
  console.log("IS URI defined", URI.constructor == Boolean); // false
  console.log "IS URI defined", Object.prototype.toString.call(URI); // [object Object]
}


Whenever I try to use URI in the file (with ot without the new keyword) I get the following error:

TypeError: URI is not a function

Anyone got any idea what is going on? I'm using URI v1.7.1 and I've just upgraded from v1.5.0 which was working with this same setup.

.domain() and Domain suffixes with two parts (i.e. co.uk)

Technically, URI.js is most likely returning the correct value, however I was wondering how the following issue could be resolved most elegantly:

URI("http://amazon.co.uk").domain()
"co.uk"

... the value I was expecting was "amazon.co.uk".
So, whenever the suffix of a domain consists out of two parts (co.uk, com.tr) domain() returns a value which is not really expected/useful.
Any help for how to treat these composed domain suffixes would be appreciated. Thanks.

Manage subdomains

It would be nice if the library supports subdomain management as well.

Add a subdomain:

URI("http://example.org/foo.html?hello=world").addSubdomain("signup");
Returns

`````` http://signup.example.org/foo.html?hello=world```

Add another subdomain:

URI("http://signup.example.org/foo.html?hello=world").addSubdomain("another");
Returns
http://another.signup.example.org/foo.html?hello=world

Remove a subdomain:

URI("http://example.org/foo.html?hello=world").removeSubdomain("nonexistant");
Returns
http://example.org/foo.html?hello=world

Remove a non-existant subdomain:

URI("http://example.org/foo.html?hello=world").removeSubdomain("nonexistant");
Returns
http://example.org/foo.html?hello=world

Encode64

I would propose to add the option to encode and decode Base64 in the querystring.

example:

p1=test&p2=testing
encrypted after: p1=dGVzdA==&p2=dGVzdGluZw==

also bringing another option to encrypt the names of the parameters:
p1=test&p2=testing
encrypted after: cDE==dGVzdA==&cDI==dGVzdGluZw==

Wrong protocol identification for relative URL with colons

I think to have found a minor issue, in URI parsing.

You can replicate this issue using the Wikipedia links. For example, parsing a local URL on Wikipedia:

<a href="/wiki/Help:IPA">....</a>

the URI.parse() method returns for protocol

/wiki/Help

and for path

IPA

and this is clearly wrong.

Get rid of String#substr

You may want to consider using String#substring or String#slice instead of the non-standard String#substr.

Modifying URI should update all _parts?

While validating and fixing user input (which is far from valid URIs always) I've come to this problem

Consider the following:

var uri = new URI('ExAmPle.Org:80');
if(!uri.protocol()) {
  uri.protocol('http');
}

It will indeed add the protocol http to both the _string and in _parts
BUT it will not rebuild the rest of the _parts
So a call to normalize() will fail since _parts.hostname === null

_parts will look like this after uri.protocol('http'):

duplicateQueryParameters: false
fragment: null
hostname: null
password: null
path: "ExAmPle.Org:80"
port: null
protocol: "http"
query: null
urn: null
username: null

When it should look like this:

duplicateQueryParameters: false
fragment: null
hostname: "ExAmPle.Org"
password: null
path: "/"
port: "80"
protocol: "http"
query: null
urn: null
username: null

It might not be a great idea (performance-wise) to re-parse the URI every time it is modified
so other ideas are welcome.

...Or another solution to this.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.