npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

eslib

v0.2.2

Published

Safe, extensible prototypes for TypeScript and JavaScript

Downloads

123

Readme

Build Status npm mit

Safe, extensible prototypes for TypeScript and JavaScript

Work in progress.

Example

import 'eslib'

[1, 2, 3, 4]
  .chunk(2)
  .head() // [1, 2]

Why you should use ESlib (aka. "This is blasphemy!")

If you've ever written production JavaScript before, you've at some point needed to install a utility library to do what JavaScript can't do out of the box. Or worse, you've rolled your own utility functions. Because of JS's limited standard library, for most projects you need tools like Lodash, Underscore, Ramda, or JQuery to provide those missing utility functions.

Here's an example (I've written code like this more times than I care to admit):

import { fromPairs, mapValues } from 'lodash'

let data = [1, 2, 3, 4]
let array = mapValues(fromPairs(
  data
    .filter(i => i > 2)
    .map((item, index) => [item, index])
), i => i * 2)

The problem with this code is that it mixes abstractions. I can rewrite it to only use only functions (which I am told are all the rage):

import { filter, fromPairs, mapValues, zipWith } from 'lodash'

let data = [1, 2, 3, 4]
let array = mapValues(
  fromPairs(
    zipWith(
      filter(data, i => i > 2),
      (item, index) => index
    )
  ),
  i => i * 2
)

Or, I can use Lodash's chain utility to write it in an Object Oriented style instead:

import { chain } from 'lodash'

let data = [1, 2, 3, 4]
let array = chain(data)
  .filter(i => i > 2)
  .zipWith((item, index) => index)
  .fromPairs()
  .mapValues(i => i * 2)
  .value()

But what if I'm not using Lodash? And what if I don't dig the extra verbosity that explicitly using Lodash brings? In languages like Scala, you can add scoped implicit methods to prototypes (or, Scala's equivalent). What would it look like to do the same thing in JavaScript?

let data = [1, 2, 3, 4]
let array = data
  .filter(i => i > 2)
  .zipWithIndex()
  .fromPairs()
  .mapValues(i => i * 2)

Wait, wait - isn't extending the prototype in JavaScript unsafe? Well, yes. Extending the prototype is bad for a few reasons:

  1. Libraries might extend the prototype in unexpected ways
  2. Those extensions might conflict with one another
  3. If two libraries add a method with the same name to a prototype, they might be incompatible

What's interesting is that these problems are mostly unique to dynamically typed languages. What's also interesting is that best practices depend on your language of choice: JavaScript people don't like prototype extensions, but Ruby people love them. If you haven't worked with statically typed languages before, you might be surprised to learn that static type systems can make prototype extensions safe - you'll know exactly how the prototype has been extended (you'll even get autocomplete for it), and you'll know at compile time (ie. when you write your code, before you run it) if there are any conflicting extensions.

ESLib takes advantage of this to give JavaScript the standard library it deserves, guilt-free.

Because JavaScript doesn't support scoped prototype extensions, and because ESlib globally scopes extensions, ESlib adds an extra safety precaution: every extension must be given a version and an author. Version strings follow semver, and author strings should correspond to the repo (for example, 'eslib/lodash'). ESlib checks that libs competing for the same method name are provided by the same author, and have compatible versions.

Why you should not use ESlib

  • If you're a functional programming purist, this style of programming is Object Oriented, and is probably not for you
  • If you're writing performance critical code, prototype extensions slow down scope lookups. Luckily, the sort of slowdown we're talking about won't be noticeable in 99.99% of applications (see benchmarks for more info)

Usage (Consuming it)

To consume a ESlib extension package, all you need to do is import it. For example, for the Lodash package, all you need to do is:

  1. Install the extension:
npm install @eslib/lodash -S
  1. Import the extension with the following line to your index (entry) file:
import '@eslib/lodash'

Usage (Authoring Extensions)

ESlib provides a simple API for registering extensions. For example, I can use it to provide a size method for objects:

import { assign } from 'eslib'

// define a size function
function size() {
  return Object.keys(this).length
}

// register it
assign(Object.prototype, { size }, 'bcherny/size', '1.0.0')

// use it
{ a: 1 }.size() // 1

Let's break down the 4 parameters I passed to assign:

  • Object.prototype - The object I want to assign my method to
  • { size } - The method "size" should map to the function size we defined above
  • 'bcherny/size' - The method's author (it can be any string), used by ESlib to check methods for compatability
  • '1.0.0' - The method's version (a semver string), used by ESlib to check methods for compatability

If you're using TypeScript, you should augment the typings for the object you're extending too:

declare global {
  interface Object {
    size(): number
  }
}

How it works

Extending built-in types is straight-forward (just add a method/object on it or its prototype), so all ESlib needs to do is ensure the extension is made safely (ie. it doesn't conflict with other extensions, override native functionality in a backwards-incompatible way, or use a reserved word). There are a few approaches ESlib could have taken to enforce API compatibility:

  1. A static type system (TypeScript, Flow, or Closure annotations)
  2. Semver, like what NPM does at the package level
  3. Running unit tests from one extension against another extension's implementation (this relies on unit tests testing for the specific edge cases where the implementations differ, which is too optimistic)
  4. Generating tests based on an extension's API and running those against another extension's implementation (too slow to do at runtime, and would introduce a mandatory build step)
  5. Block level import scoping (too verbose to use in JavaScript, see esdiscuss discussion)

Scala for example uses 1 and 5. ESlib uses 1 and 2. If you have an opinion on this, please file an issue!

Tests

npm test

License

MIT