# Comparison With Other Libraries

There are a few libraries out there allowing you to parse a hostname into
its top-level domain, etc. A common trait is that they all rely on the
[public suffix list](https://publicsuffix.org/list/) project which
maintains a list of all public suffixes.

Nonetheless, the features and performance characteristics of the
libraries available in the JavaScript ecosystem vary a lot. Some
libraries allow to parse URLs, som e require a hostname as input, etc.

This document aims at comparing the most popular libraries available along
several axes:

1. Features
2. Performance
3. Loading time
4. Memory used

Libraries (with approximate number of downloads per week from NPM):

- psl: https://www.npmjs.com/package/psl (4.7M/w)
- tld.js: https://www.npmjs.com/package/tldjs (174k/w)
- parse-domain: https://www.npmjs.com/package/parse-domain (44k/w)
- haraka-tld: https://www.npmjs.com/package/haraka-tld (271/w)
- tldts: https://www.npmjs.com/package/tldts (240/w)
- uBlock's publicsuffixlist.js: https://github.com/gorhill/uBlock/blob/master/src/lib/publicsuffixlist.js

**DISCLAIMER**:

- All measurements were performed using Node.js 11.6.0 (latest at the time of writing).
- Measurements were performed on a X1 Carbon 2016, i7 U6600 with 16GB of RAM.
- These are micro-benchmarks which might not reflect real use-cases although I
  tried to get as close as possible.

## Feature Matrix

| Library                 | IDNA support | URLs support | IP support | getDomain | getPublicSuffix | ICANN/Private | Ships lists |
| :---------------------- | -----------: | -----------: | ---------: | --------: | --------------: | ------------: | ----------: |
| **tldt**                |            X |            X |          X |         X |               X |             X |           X |
| tld.js                  |            X |            X |          X |         X |               X |             X |           X |
| psl                     |            X |              |            |         X |               X |               |           X |
| parse-domain            |            X |            X |            |         X |               X |             X |           X |
| haraka-tld              |            X |            X |            |         X |               X |               |           X |
| uBlock publicsuffixlist |            ? |              |            |         X |               X |               |             |

# Memory Usage

Here we try to estimate the memory used by each library. The
measurements are done using the `./comparison/bench_memory.js` script,
which will load each file ten times and measure the average memory usage
before and after GC using `process.memoryUsage()`. The result are then
compared to a reference memory usage computed in the same way using
`noop_test.js` which does not import anything.

| Library                |  Before GC | After GC |
| :--------------------- | ---------: | -------: |
| **tldts-experimental** |      461KB |   229 KB |
| parse-domain           |   2.579 MB |  1.310MB |
| psl                    |   2.199 MB |  1.537MB |
| tldjs                  |   2.621 MB |  1.714MB |
| tldts                  |   3.094 MB |  1.792MB |
| ublock publicsuffix    | 4.529 MB\* |  2.399MB |
| haraka-tld             |   4.405 MB |  2.595MB |

(\*) The memory of uBlock cannot be estimated correctly as for this
benchmarks the lists were inlined in the source code, which is not how
it's used in production.

# Performance Matrix

Here we measure the performance of three common operations performed with a
domain parsing library: getting the public suffix of a hostname, getting the
domain (tld + sld) and getting the subdomain.

A few notes about this benchmark:

- The inputs used are always already valid hostnames (no URLs, although some
  libraries support it)
- The selection of hostnames can be seen in `./comparison/bench_performance.js`
  and was selected to contain a mix of non-existing suffixes, ICANN rules,
  private rules as well as wildcards and exceptions.
- All hostnames were ASCII (puny-encoded if needed before-hand)
- All libraries were used in their default setup (no option given, with the
  exception of `tldts-no-parse` which runs `tldts` disabling the parsing phase
  and assuming that the input is already a valid hostname).

The results are expressed in terms of operations per second (where each
operation is calling the function once on a hostname).

| Library                | getPublicSuffix |     getDomain | getSubdomain |
| :--------------------- | --------------: | ------------: | -----------: |
| **tldts-experimental** |       1_898_446 |     1_690_572 |    1_615_166 |
| tldts no parsing       |       1_780_469 |     1_515_703 |    1_502_692 |
| **tldts**              |   **1_280_063** | **1_134_956** |    1_125_362 |
| tld.js                 |       1_141_414 |     1_049_180 |    1_125_362 |
| ublock publicsuffix    |         620_816 |       567_664 |            ? |
| parse-domain           |         554_355 |       528_217 |      551_008 |
| haraka-tld             |               ? |       105_321 |            ? |
| psl                    |           1_654 |         1_693 |        1_673 |

Here we see that the performance varies a lot between libraries, for the same
operations. `tldts` is almost **1000** faster than `psl`, which is the most
popular library.

# Loading Time

One point of comparison which can be important in some contexts is the loading
time of the bundle itself (or time it takes to parse the code and initialize
it). It can have a big impact if you use the library on very slow devices (like
mobiles) and here again, not all the libraries are equal.

The benchmark code can be found in `./comparison/bench_startup.sh`. It measures
the time it takes to import each of the libraries. The measurements are
performed using the [bench](https://hackage.haskell.org/package/bench) CLI,
looking at the `mean` time returned for each.

Note that this benchmark was performed using the cjs bundle. The
performance might be different in another environment or different
bundle (e.g.: UMD in a browser).

| Library                | Mean (ms) |
| :--------------------- | --------: |
| Ref (no `require`)     |     48.21 |
| **tldts-experimental** | **47.93** |
| psl                    |     53.77 |
| tld.js                 |     58.74 |
| parse-domain           |     61.96 |
| tldts                  |     64.48 |
| ublock                 |     78.05 |
| haraka-tld             |     84.93 |

Note that some libraries like `ublock` or `haraka-tld` perform some form of parsing
of the rules at loading-time, which incurs an initial cost when importing the
library.

# Bundles Matrix

Comparison of bundle sizes, when applicable (not all libraries provide bundles):

| Library                |    Normal | Minified | Gzipped |
| :--------------------- | --------: | -------: | ------: |
| **tldts-experimental** | **100KB** | **94KB** |    38KB |
| tldts                  |     140KB | **95KB** |    37KB |
| psl                    |     138KB |    122KB |    39KB |
| tld.js                 |     209KB |    141KB |    40KB |
| parse-domain           |         ? |        ? |       ? |
| ublock                 |         ? |        ? |       ? |
| haraka-tld             |         ? |        ? |       ? |

# Dependencies Matrix

Here is a comparison of dependencies for each library:

| Library      | Dependencies |
| :----------- | :----------- |
| **tldts**    | (none)       |
| psl          | punycode     |
| tld.js       | punycode     |
| ublock       | punycode     |
| haraka-tld   | punycode     |
| parse-domain | ?            |
