DEV Community

Cover image for PolyFull - enhance js capabilities
Giovanni Cardamone
Giovanni Cardamone

Posted on

PolyFull - enhance js capabilities

Javascript is a very nice language (at least for someone 😂) but it's still missing some core functionalities...

are you wondering which functionality are missing in the language? well, to remove an element from an array like now looks like this:

someArray.splice(someArray.indexOf(elementYouWantToRemove), 1)
Enter fullscreen mode Exit fullscreen mode

and if you have to remove each occurrence of that element can be even worse:

for (let i = 0; i < someArray.length; i++) {
    if (elementYouWantToRemove === someArray[i]) {
        someArray.splice(i, 1)
        i--
    }
}
Enter fullscreen mode Exit fullscreen mode

not sure why javascript doesn't have such a basic functionality, something like this will be great:

someArray.remove(someElement)
Enter fullscreen mode Exit fullscreen mode

well, i have a great news for you! 🔥

this and many many other functionalities are available directly into the language throught polyfull

all you need to do to unlock that functionalities is to import it in the index of your project:

import 'polyfull'
Enter fullscreen mode Exit fullscreen mode

⚠️ THIS IS INTENDED TO USE ONLY IN FINAL APPLICATIONS.

if you use this in library, any other applications that use your library will have polyfull injected as well.

please DO NOT use this if you are building a library, use this only where you have control of your node interpreter.

also, if you are using some other polyfiller, be sure that there is no overlaps, or something will broke. Thanks!

and you can use a lot of functionalities, here is an example:

import 'polyfull'

// ArrayConstructor
Array.zip([1, 2, 3], ['a', 'b', 'c']) // => [[1, 'a'], [2, 'b'], [3, 'c']]
Array.collapse([1], [2, 3], [4, 5, 6]) // => [1, 2, 3, 4, 5, 6]
Array.intersect([1, 2, 3], [2, 3, 4]) // => [2, 3]
Array.unique([1, 2], [2, 3], [3, 4]) // [1, 2, 3, 4]

// Array
[1, 2, 3].remove(2) // => [1, 3]
[1, 2, 3].removeIndex(2) // => [1, 2]
[1, 2, 3].first() // => 1
[1, 2, 3].last() // => 3

// DateConstructor
Date.current() // => new Date(Date.now())

// Date
new Date(0).addHours(1) // => 1970-01-01T01:00:00.000Z
new Date(0).isBefore(new Date(Date.now())) // => true
new Date(0).isAfter(new Date(Date.now())) // => false
new Date(0).diff(new Date()) // => how many ms passed from 1970? :D

// NumberConstructor
Number.random() // => -789.0123
Number.random(0) // => 789.0123
Number.random(0, 100) // => 89.0123
Number.randomInt(0) // => 42

// Number
7.0.isPrime() // => true
3.0.pow(2) // => 6
40.0.goldenRation() // => [24.72~, 15.28~]
50.0.percentage(20) // => 10

// Promise
await Promise.allProperties({
  a: Promise.resolve(1),
  b: Promise.resolve(2),
}) // => { a: 1, b: 2 }

await Promise.allPropertiesSettled({
  a: Promise.resolve(1),
  b: Promise.reject(2)
}) // => {
//   a: { status: 'fulfilled', value: 1 },
//   b: { status: 'rejected', reason: 2 }
// }

// String
'hello'.reverse() // => "olleh"
'racecar'.isPalindrome() // => true
'0x01'.isNumeric() // => true
'polyfull'.equalsIgnoreCase('POLYFULL') // => true

// And Many Many Others!!
Enter fullscreen mode Exit fullscreen mode

Remember to leave a ⭐ if you like it!

https://github.com/GiovanniCardamone/polyfull

Discussion (22)

Collapse
lukeshiru profile image
LUKESHIRU

You might want to reconsider the implementation of this lib. Is well known that changing native objects is a bad practice. You could instead have something like PolyfullArray, PolyfullPromise and so on, maybe. A quick Google search will tell you everything about the reasons why changing native objects is not a good idea (even if you have tests).

Cheers!

Collapse
giovannicardamone profile image
Giovanni Cardamone Author • Edited

at the first line of README you can find this:

warning THIS IS INTENDED TO USE ONLY IN FINAL APPLICATIONS.

if you use this in library, any other applications that use your library will have polyfull injected as well.
Enter fullscreen mode Exit fullscreen mode

if you use it in final application (whatever it is) you should be fine.

if you use with another library that change native object, some trouble can occurs if there is name collisions.

This is intended to use "alone" or at least with another that have no overlaps.

Collapse
lukeshiru profile image
LUKESHIRU

So is an experiment not be used in real world scenarios, then? Ok, if that's the case, then is ok.

Thread Thread
giovannicardamone profile image
Giovanni Cardamone Author • Edited

if you are building a cli, a microservice, something else where you control your node interpreter, this tool can make your life a bit easier.

If you are building a library, do not use this. Is literally in the first line

(i also make it a bit clear why in the readme after your reply).

Thread Thread
lukeshiru profile image
LUKESHIRU

Even when building a CLI package using something like mem-fs and yargs is pretty common, and those might be using Array, Promise and so on internally. You need to use Polyfull completely "alone" in order for it to be safe to use, and at that point folks will prefer to use something else instead. I'm not saying this to block you, on the contrary, my advice is to create new classes that extended the native ones instead of changing them directly, because is the best compromise for you to keep the polyfull kinda similar to what you have, without the bad practices that would make it unattracted to pretty much everyone.

Thread Thread
giovannicardamone profile image
Giovanni Cardamone Author

polyfull is not overriding language standard methods, so should be now way of broke another library, the only way this can happen is if is polyfilling the same signature with some other method.

Elsewhere i see no way you can broke it.

Thread Thread
lukeshiru profile image
LUKESHIRU • Edited

polyfull is not overriding language standard methods

Yet!

If one of your methods gets added to native JS, then suddenly polyfull will start breaking stuff. For example, let's say I make a CLI using mem-fs, and mem-fs internally is doing array[0], and I, using your library, write array.first(2) making use of that useful offset ... everything is good and dandy until JS adds first with a slightly different implementation, and then mem-fs gets updated to use the official implementation, which breaks because you overwrite it with your implementation. And if you update it to remove that, I still need to go to every place where I used your first and change it to the native usage.
If you instead did something like PolyfullArray, or just a first function, then I wouldn't have that problem, ever.

Again, google the issues of changing native objects, this is just one of many.

Thread Thread
giovannicardamone profile image
Giovanni Cardamone Author

yes, and that is why versions exists

Thread Thread
lukeshiru profile image
LUKESHIRU

Whatever dude, whatever. Peace out, god bless!

Thread Thread
giovannicardamone profile image
Giovanni Cardamone Author
Thread Thread
lukeshiru profile image
LUKESHIRU

I know, I have several NPM packages, but if you think you're smarter than everyone before you, that's on you. I'm telling you is a bad practice, and we kinda all agree on that. Yours is not the first library messing with native objects prototypes. So that's why, again ...

Whatever dude, whatever. Peace out, god bless!

Thread Thread
giovannicardamone profile image
Giovanni Cardamone Author

at this point is bit annoying, but let's give a fair reply:

Polyfilling native object is a common pratice (i don't think i have explain to you, you seems smart enought, but i will do for everyone else reading this post), due to lack of functionality missing in previous version of javascript, an example can be core-js.

The aim of core-js is to provide compatibility function where are missing.

the aim of polyfull is a bit different, and should be handled carefully.

first of all, none of ECMAScript specification should be replaced. (and i am doing this at current version ES2021)

second, i have to provide a way to make some version of polyfull incompatible if a new release of EcmaScript have name collision with polyfull. (and this can be done with flags in package.json)

of course you are creating a bit of incompatibility if you are using previous version of polyfull with new version of ecmascript if an incompatibility occurs, or if you have to make porting from an older version to a new one (i am perfectly aware of this).

but i think you can agree with me that some stuff is missing in nodejs, and it's fucking annoying.

If you need functionality to manipulate array and this stuff you can simply install lodash. but my library isn't targeting this advantages.

programmer are lazy, and i am bored of searching over and over to remove an element from array or generate a random number.

This library is just a shortaround to have some basic functionality that aren't builded in js language in two decades.

Also jsDoc annotations like @deprecated can be useful to spot where naming collisions occurs.

You are free to not use this library, the name and the warning are pretty much representing what this library do and where it can be used. And where should NOT be used.

i gonna use it in some project where i am just to lazy to do something like this:

import ArrayEnhancedOrWhatever from 'polyfull'

const myArray = [1, 2, 3]

ArrayEnhancedOrWhatever.remove(myArray, 2)
Enter fullscreen mode Exit fullscreen mode

instead of

const myArray = [1, 2, 3]
myArray.remove(2)
Enter fullscreen mode Exit fullscreen mode

furthermore, thanks for any tips and advice! And thanks for the time you spent here :D

Thread Thread
miketalbot profile image
Mike Talbot • Edited

I'd just point out that Sugar has been doing this kind of thing for a long while. They've now made the extension functions "opt-in" so it can be used in a library or in a final application. In final applications I have used it in both ways.

Modifying the prototypes of objects is a contentious subject, I have a huge amount of respect for the stuff @lukeshiru writes and creates, but probably in this case fall more on the side of "it's fine if you aren't building a library". The Sugar comments on modes and other content on the site will express the opinions here far better than I can though :)

Collapse
lionelrowe profile image
lionel-rowe • Edited

This isn't polyfilling, it's monkey patching. Polyfills are to add support for features that are in the spec but not yet supported by all environments. Polyfills are considered safe, because all they're doing is improving consistency between the different environments.

Monkey patching, however, is stuff that will almost certainly never be in the spec, but might clash with other things added to the spec later on, or with other rogue libraries that decide to monkey patch stuff, because it's all global.

There is one way and only one way to monkey patch safely: by using unique symbols:

// my-fancy-lib/index.js

export const remove = Symbol('remove')

Array.prototype[remove] = function(val) {
    const idx = this.indexOf(val)

    if (idx !== -1) this.splice(idx, 1)

    return this
}

// my-app/src/my-lib-consumer.js

import { remove } from 'my-fancy-lib'

const arr = [1, 2, 3]

arr[remove](2)[remove](3) // [1]
Enter fullscreen mode Exit fullscreen mode

That works fine and won't pollute any global namespaces, because the symbol used to access the new method is only accessible by importing it directly from the library (or by doing something convoluted like Object.getOwnPropertySymbols(Array.prototype).find(({ description }) => description === 'remove'), which a consumer of your library wouldn't plausibly do by mistake).

As shiny and cool as symbols are, however, in my opinion it's better not to monkey patch at all, and instead to just use plain old functions that take the target array as a parameter. If you want to keep the nice left-to-right syntax you get from method chaining, you can implement the API with higher-order functions and use a pipe function to compose them:

// my-fancy-lib/index.js

export const pipe = (initialVal, ...fns) => fns.reduce((val, fn) => fn(val), initialVal)

export const remove = (val) => (arr) => {
    const idx = arr.indexOf(val)

    return [
        ...arr.slice(0, Math.max(idx, 0)),
        ...arr.slice(idx + 1),
    ]
}

// my-app/src/my-lib-consumer.js

import { pipe, remove } from 'my-fancy-lib'

const arr = [1, 2, 3]

pipe(arr, remove(2), remove(3)) // [1]
Enter fullscreen mode Exit fullscreen mode

This version also has the added advantage of not mutating the original array.

Collapse
misobelica profile image
Mišo

I see LUKESHIRU got a bit dogmatic here. But someone had to be 1st 😀 Anyway, I don't want to get involved into that discussion. I believe everyone can decide. But I remember SugarJS has nice long article about the risks, how to deal with them and alternatives to choose in their own library. Nice reading everyone :) sugarjs.com/extending/

Collapse
jonrandy profile image
Jon Randy

Great care should be taken when extending the built in types. The potential to break stuff is very high

Collapse
giovannicardamone profile image
Giovanni Cardamone Author

I know, that's why i have 100% code coverage

Collapse
lionelrowe profile image
lionel-rowe

Having 100% code coverage has nothing to do with the potential to break things. Even if your code is perfect, you can still break things by monkey patching.

Collapse
insidewhy profile image
insidewhy • Edited

Most of this stuff is available in lodash without the pitfalls of having so many global functions. This is really bad practice, nobody should use this.

Also nobody else mentioned, besides all the other problems, you can't tree-shake away any of the methods you aren't using.

Collapse
ifierygod profile image
Goran Kortjie

Alt Text

Collapse
ifierygod profile image
Goran Kortjie

I know this is wrong...