#How do you use Object.groupBy()?

1 messages · Page 1 of 1 (latest)

silent hamletBOT
#
sleepeasysoftware#0

Preview:ts Object.groupBy()

austere cedar
#

!ts

silent hamletBOT
#
Object.groupBy()
//     ^^^^^^^
// Property 'groupBy' does not exist on type 'ObjectConstructor'.
austere cedar
#

So how do I use it?

true bone
#

make sure you have target set to esnext, as Object.groupBy is set for es2024

austere cedar
#

"typescript": "^5.6.3",

#

dang, I knew it was a tsconfig issue

#

tsconfig fixes are difficult to self serve

true bone
#

yeah it kinda is a matter of experience

austere cedar
#

Yeah, and I know this is not the right place to discuss it, but I think it indicates a smell with the language

#

Why is it the target? I thought that's about which polyfills is uses. Am I wrong?

true bone
#

ts only has polyfills for syntax features, not for library features

austere cedar
#

Now that I know it's about the tsconfig, I would've expected it to be a change in lib

true bone
#

lib is for more granular configuration for the same thing, for when you have a low target but you have polyfills for newer features (but not all of them)

#

in the tsconfig docs for lib:

TypeScript also includes APIs for newer JS features matching the target you specify; for example the definition for Map is available if target is ES6 or newer.
[...]
You may want to change these for a few reasons:

  • Your program doesn’t run in a browser, so you don’t want the "dom" type definitions
  • Your runtime platform provides certain JavaScript API objects (maybe through polyfills), but doesn’t yet support the full syntax of a given ECMAScript version
  • You have polyfills or native implementations for some, but not all, of a higher level ECMAScript version
austere cedar
#

Hrm. Right now I'm using "target": "es2018",. Changing that to esnext will bring lots of changes with it, changes that might break backwards compatibility for users of older browsers, right?

true bone
#

right

#

since, well, Object.groupBy is only available in newer browsers

austere cedar
#

glad I asked

#

that's weird because the compatibility graph seems to imply it's available to most

#

nm

#

I misread the top of the MDN article. I thought it said this was new in 2022, but it's new in 2024

true bone
#

if you want to provide a polyfill for this specifically, you would add es2024.object (or maybe esnext.object, depending on ts version) to tell ts about it

#

seems like esnext.object for the current release (5.6), and would be es2024.object in the next major release (5.7)

austere cedar
#

you would add es2024.object (or maybe esnext.object, depending on ts version) to tell ts about it
Add it as a lib?

#

yep, sorry

#

Sorry if you already explained this but why do I get this warning?

#

Side note: I think I'm reading that you have to run npm install after you change lib. I didn't realize there was anything like that

true bone
#

that file definitely exists in 5.6.3

austere cedar
#

no but I do get these errors:

true bone
#

lib doesn't affect modules, it's just an option for tsc

austere cedar
#
node_modules/lru-cache/dist/commonjs/index.d.ts:1032:5 - error TS2416: Property 'forEach' in type 'LRUCache<K, V, FC>' is not assignable to the same property in base type 'Map<K, V>'.
  Type '(fn: (v: V, k: K, self: LRUCache<K, V, FC>) => any, thisp?: any) => void' is not assignable to type '(callbackfn: (value: V, key: K, map: Map<K, V>) => void, thisArg?: any) => void'.
    Types of parameters 'fn' and 'callbackfn' are incompatible.
      Types of parameters 'map' and 'self' are incompatible.
        Type 'LRUCache<K, V, FC>' is not assignable to type 'Map<K, V>'.
          The types returned by 'entries().next(...)' are incompatible between these types.
            Type 'IteratorResult<[K, V], void>' is not assignable to type 'IteratorResult<[K, V], undefined>'.
              Type 'IteratorReturnResult<void>' is not assignable to type 'IteratorResult<[K, V], undefined>'.
                Type 'IteratorReturnResult<void>' is not assignable to type 'IteratorReturnResult<undefined>'.
                  Type 'void' is not assignable to type 'undefined'.

1032     forEach(fn: (v: V, k: K, self: LRUCache<K, V, FC>) => any, thisp?: any): void;```
true bone
#

that's an error internal to one of your deps, you can ignore that for now

austere cedar
#

I'm pretty sure I wasn't getting those before I modified my lib, but undoing of the change doesn't make them go away

true bone
#

you could enable skipLibCheck to handle that (given that the library should already be checked before publishing)
i don't remember if that's recommended exactly though

true bone
#

there may be caveats that i'm forgetting

austere cedar
#

I'll try that

#

esnext.object gives me Object.groupBy if it compiles

true bone
#

(btw for node projects you should use module: node16/nodenext)

true bone
austere cedar
#

Nope

true bone
#

i guess codelens has an outdated list then

austere cedar
true bone
#

probably with the schema it has

true bone
#

i have no idea what you mean by "transpiles to the browser"

austere cedar
#

This TS transpiles to a frontend app

#

it doesn't run in the backend only in the browser

#

I'm not sure if that made more sense

true bone
#

it didn't tbh

#

what do you mean by "node project", then?

#

run in the backend only in the browser
these are different distinctions
frontend vs backend is a distinction of function
server vs client (browser) is a distinction of location

#

frontend apps still need a server to send the html and js and everything

austere cedar
#

it runs on the client browser

true bone
#

is there any part that runs on node?

austere cedar
#

I know this sounds weird, but there is no server

true bone
#

ie, the server

austere cedar
#

no

true bone
#

unless you're just opening html files, you kinda need a server

austere cedar
#

okay this is kind of convoluted but there is another tool I run after tsc that builds the JS into an HTML file

true bone
#

(i guess the filesystem is the server in that case..)

austere cedar
#

Well technically I do have a server but it serves static content

true bone
#

is it on node

austere cedar
#

the server? I don't know. It's whatever my domain/host provider uses. I just ftp it to a location and it is accessible to the world

true bone
austere cedar
#

Maybe I don't understand what the module attribute means, but I thought I did

true bone
true bone
#

module: es6 means esm, module: node16 means, "idk, ask node"

#

so it's aware of package.json, of cjs/mjs file extensions

#

so there's a single source of truth about module system

austere cedar
true bone
#

ts

#

and any other tools that piggyback off of it

austere cedar
#

okay I hope this doesn't add more confusion but here's something: the JavaScript I generate must be a script

#

as in, I cannot use modules in the generated code

true bone
#

why not?

austere cedar
#

I'm using a tool that can't parse them when it generates the HTML file.

true bone
#

...what....

austere cedar
#

I didn't write this tool

#

yeah it's got over a decade of backwards compatibility concerns. It was written before modules existed

true bone
#

why are you using it

#

if it's bundling js into an html, it wouldn't need to parse anything

austere cedar
true bone
#

no dm's, please.

#

your entire workflow seems like it'd be trivialized by a single bundler tbh

austere cedar
#

Well, I am using rollup as a later step

true bone
#

iirc some do polyfills as well so that'd handle that for you

true bone
#

how long is this toolchain

austere cedar
#

no it's like this:

#

ts -> js -> rollup (to bundle) -> html

true bone
#

is it only js that's being put in html?

#

like, just a bunch of scripts?

austere cedar
#

yeah

#

but the tool does that

true bone
#

you don't need a tool for that

austere cedar
#

I can't change that

#

Right, I understand, but the tool does lots of other things that benefit me

true bone
#

care to elaborate?

austere cedar
#

I'd be happy to but I prefer to do it in a DM

true bone
#

if you don't think they should be public then you shouldn't be telling them to a stranger

austere cedar
#

we don't need to take the conversation there, just to answer that specific question

true bone
#

but aight

austere cedar
#

aight, dm?

true bone
#

the ts -> js -> bundle could be a single step though

austere cedar
#

k

#

are you talking about outFile?

true bone
#

no, that's pretty much deprecated

#

most bundlers understand ts and can go straight from ts to a bundle

austere cedar
#

oh. What then?

digital flame
#

TBH if you've got a weird setup that you can't really touch, maybe just pull in a groupBy utility, like the lodash one.

true bone
austere cedar
austere cedar
#

Or maybe it was in this one, but some super obscure issue came up that I either couldn't figure out or couldn't be solved due to limitations of the tool

true bone
#

typically if you have a bundler, you also let the bundler handle transpilation, and tsc is treated as a checker

austere cedar
#

Yeah, I think I learned that after I setup this project

true bone
#

it's not a lot of setup on the tsc side (noEmit: true, scripts > build: "tsc && <bundler command>")

#

i can't comment on the bundler side though

austere cedar
#

it's just noEmit right?

#

or whatever that tsconfig property is called

true bone
#

right

austere cedar
#

I don't mind continuing to talk about this, but when I tried to run the code, I got this runtime error: TypeError: Object.groupBy is not a function

#

Even though it now compiles

#

And I'm perfectly content to use lodash instead. It's already a dependency but I was thinking about removing it long-term. Maybe I'll just keep it in if I have to use its groupby

digital flame
#

TS doesn't insert a definition for libs, so wherever the code is running must not have groupBy yet.

true bone
#

is it being loaded before everything else?

austere cedar
#

okay after reading the lib ref doc a 3rd time, I see where I misunderstood

true bone
#

maybe i was unclear, the lib addition would only tell ts, it wouldn't automatically add the polyfill

austere cedar
#

yeah I thought it added a polyfill too. my mistake

#

let me see. I don't know if I've ever intentionally used a polyfill before

#

okay I immediately got in over my head. I thought that all I would need to do was install this: https://github.com/es-shims/Object.groupBy

But when I look at the examples they have this line: var groupBy = require('object.groupby');

I know how to translate that into ESM, but isn't the point of changing lib to make it so I can write my code without that line?

GitHub

An ESnext spec-compliant Object.groupBy shim/polyfill/replacement that works as far down as ES3. - es-shims/Object.groupBy

#

Is there an official polyfill directory meant to be used with TypeScript's lib?

true bone
#

I know how to translate that into ESM, but isn't the point of changing lib to make it so I can write my code without that line?
no, lib informs ts what's available. it tells ts what you should be able to do. it doesn't affect what you can actually do

#

to make Object.groupBy actually work, you would do

import groupBy from 'object.groupby';
Object.groupBy = groupBy;
austere cedar
#

ahhh!

true bone
#

wait hold on i forget how to use this

austere cedar
#

okay 🙂

true bone
#

ok yeah it's static lmao

#

took me a while

austere cedar
#

This feels kind of ... Why wouldn't I just depend on the polyfill and revert the lib?

#

I'm assuming that the polyfill has types I could depend on any other package's types

true bone
#

it does, for groupBy, not for Object

austere cedar
#

it does what?

true bone
#

oh wait no it doesn't, just via dt

digital flame
#

Polyfilling is useful if you've got existing code that expects certain APIs to exist... but yeah, I'd just import a groupBy function and use it, personally.

austere cedar
true bone
austere cedar
#

it's saying, they don't exist unless you manually import them and manually assign them to the Object

true bone
#

right

#

that's what a polyfill is

#

that's not what retsam means by "existing code"

digital flame
#

Like maybe I'm using a library and the author of the library assumes that Object.groupBy exists.

#

(That'd be kind of annoying given how new it is, but it's a more reasonable choice for older APIs)

true bone
# true bone it does, for `groupBy`, not for `Object`

object.groupBy doesn't actually do a polyfill. it provides an implementation of groupBy, so it has types for that function (via dt), but it doesn't declare that function on Object, because that function doesn't necessarily exist on Object

digital flame
#

If I want to use that library, either I have to monkey around with their code to fix it or, more realistically, I polyfill Object.groupBy so the library's code works

austere cedar
digital flame
#

No?

austere cedar
#

this must be super frustrating for you guys, hah. I think there's something I don't know I don't know which is causing this back-and-forth

true bone
#

it's used for a polyfill, but actually attaching the polyfill is something you have to do

#

"using a polyfill" would be attaching it to the object it's supposed to be on

digital flame
#

It's just a bit of ambiguous terminology.

austere cedar
digital flame
#

People say "polyfill" to mean "function that implements a standard API" even if it's not automatically attached.

#

I remember there was an attempt to get "ponyfill" to catch on as the term for "implementation for a standard API that you use directly, rather than monkey-patching the runtime" but it really didn't.

true bone
#

as opposed to say, corejs which binds the polyfill for you, afaict

digital flame
#

I think corejs provides both versions

austere cedar
digital flame
#

Sometimes they do.

#

Looks like core-js has a version that automatically patches the runtime and core-js-pure which is the ""ponyfill"" version

austere cedar
#

okay back to TypeScript land: why should I bother modifying lib when I could just npm install object.groupby; npm install -D @types/object.groupby; and use the library like this: import groupBy from 'object.groupby';\n const x = groupBy(...)

true bone
#

depends if you want the function or the polyfill

austere cedar
#

Is it because this only came up by me choosing a "ponyfill"?

true bone
#

which, as retsam said, you should probably just go with the free function rather than messing with polyfilling it

austere cedar
#

If I chose a different library that automatically attached the polyfill, I guess I would use lib

digital flame
#

Yeah. lib tells TS what features are supported in the runtime, either natively or via polyfills.

#

For just one function I think avoiding it is easier; polyfills can be useful in bulk - if I'm trying to make code run on IE for some reason, I can just use something like core-js and say "polyfill everything up to ES2023" and it works.

austere cedar
#

Yep that makes sense to me

#

Couldn't/doesn't core-js have types I could use to get that same functionality without touching lib?

austere cedar
#

If yes, I don't get when lib is preferred

digital flame
#

I don't think most libraries that polyfill the runtime code modify the types automatically.

austere cedar
#

By that do you mean, "most polyfills don't have ts types?"

#

getting hung up on the word "automatically"

digital flame
#

I mean the types like Object.groupBy.

#

If you do

import "core-js/polyfill-everything" // not the real import

that "automatically" attaches groupBy to Object but probably doesn't modify the TS types so that TS knows that Object.groupBy is a thing.

austere cedar
#

Okay. Think what you're saying is I would have to write the declaration for Object.groupBy after depending on the types. The types would only give me groupBy

digital flame
#

😅 Not really.

austere cedar
#

gah

silent hamletBOT
#
import "core-js/polyfill-everything" // not the real import

Object.groupBy()
//     ^^^^^^^
// Property 'groupBy' does not exist on type 'ObjectConstructor'.
austere cedar
#

I think that's what I'm saying?

digital flame
#

I'm not sure what you mean by "the types".

austere cedar
#

groupBy() exists as a type, but not on Object

digital flame
#

It doesn't exist as a standalone function at all.

austere cedar
#

oh

#

okay I thought it did

digital flame
#

Not with a real "modifies-the-global-environment" polyfill library.

austere cedar
#

I'm not sure what it means for "TS" to know

digital flame
#

Pretty sure they don't.

austere cedar
#

okay I think I might be getting it

#

if the polyfill types don't do that, lib is the only way to inform ts

#

if the polyfill types did do that, I could add the types to package.json instead of using lib?

digital flame
#

Polyfill types kind of can't do that because imports can't really have type-level side-effects

#

I don't think there's a way to write types that apply if I do import "core-js/polyfill-everything" and aren't applied if I don't.

austere cedar
#

Really? Maybe that's why am so confused. For example, I thought simply by having a declaration for a global property somewhere in your project, ts knows about it everywhere, even if I don't import it

digital flame
#

Yes, "even if you don't import it".

#

If "core-js" had a definition for

// Modifies the global Object type to have this property
interface ObjectConstructor {
   groupBy: /* definition of groupBy */
}

that defintion would be applied even if you didn't import the polyfill. (Which doesn't work when your package has lots of polyfills and the user picks which ones they import)

austere cedar
#

okay and I think you're saying that that would be bad because not everybody would want that universally

digital flame
#

Yeah. (See everyone accidentally ending up with @types/node globals because it's installed by some dependency-of-a-dependency)

austere cedar
#

until that comments, I don't think I understood the big picture ramifications

#

I think we are talking past each other a little bit because you are using something large in scope like core-js whereas I'm thinking small scope like object.groupBy

#

if I didn't want to polyfill and automatically modify Object , I would just avoid installing that library

digital flame
#

Well and make sure no dependency has it listed as a dependency.

austere cedar
#

isn't it a bad idea to use a type package as a regular dependency anyway?

digital flame
#

It wouldn't be a "type package" if it's a polyfill.

#

(Also it's correct to put @types in regular dependencies if your API depends on the types in that package)

austere cedar
digital flame
#

It's kinda bad practice for libs to depend on polyfills anyways so the dependency-of-a-dependency probably would be some sort of dev/normal dependencies mixup, but weirder mistakes have happened.

austere cedar
digital flame
#

Possibly, yeah.

#

@types packages are particularly squirrely because patch changes can be breaking.

austere cedar
digital flame
#

They do correlate

true bone
digital flame
#

@types/package: "x.y.z" corresponds to package version "x.y".

austere cedar
digital flame
#

If they're separate, yeah.

austere cedar
#

maybe that's ideal? I think I've found libs that don't line up like that

digital flame
#

It's pretty well followed. Though if a version doesn't have type changes there may not be a @types version so you just go back.

austere cedar
#

or maybe I just assumed they don't correlate because I saw the different patch number

digital flame
austere cedar
digital flame
#

Makes sense. But they do correlate. That's why patch versions are semver-breakingly dangerous for @types.

austere cedar
#

I guess there's an extremely unlikely chance that somebody caught their mistake and fixed it without bumping a version number

#

Other than that...

digital flame
#

There's also just some chance that there's spooky things going on in a cache on your machine somewhere.

#

But yeah, probably.

austere cedar
#

I've learned a lot from this conversation but I think I've taken it off topic too. Thank you both for educating me tonight. I appreciate it and I think I got the answer I'm looking for!