Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[node] Missing global URL and URLSearchParams typings #34960

Closed
ulrichb opened this issue Apr 24, 2019 · 21 comments · Fixed by #58277
Closed

[node] Missing global URL and URLSearchParams typings #34960

ulrichb opened this issue Apr 24, 2019 · 21 comments · Fixed by #58277

Comments

@ulrichb
Copy link
Contributor

ulrichb commented Apr 24, 2019

This is a follow-up issue of #25342 and #19799.

In #25269 the URL and URLSearchParams classes have been added as globals because since Node.js 10.0 they are exposed as globals.

Because lib.dom.d.ts also exposes them as globals, projects which referenced both, node and lib.dom, got "Duplicate identifier 'URL'" errors (see #25342). Therefore the global URL and URLSearchParams has been removed again.

This fix (actually a workaround for a missing declaration merging feature of TypeScript) now has the drawback that we cannot use the global URL class in Node.js-code (instead we need to import it from the url module). This is especially bad for universal modules which are supposed to work in the browser + node by just using the global URL (which works since Node.js 10).

So I want to discuss solutions for this atm. unsupported scenario here. (Without having local declarations in consuming projects.)


One solution would be to support this scenario in TypeScript (allow - exactly same? - global class definitions in multiple declaration files). @weswigham Would this be possible?

A more straight-foward solution would be to create a new DT package (e.g. node-global-url or maybe better whatwg-global-url) which can be installed additionally and selected by using e.g. "types": ["node", "node-global-url"] in a tsconfig.json for projects which face the mentioned scenario above.

Any other ideas?

@ulrichb ulrichb changed the title [node] Missing global URL, ... typings [node] Missing global URL and URLSearchParams typings Apr 24, 2019
@weswigham
Copy link
Contributor

Rewrite the global URL and the node URL such that they can declaration merge? IIRC if you write it as interface+namespace+value it should merge without dupe errors provided the types line up.

@ulrichb
Copy link
Contributor Author

ulrichb commented Apr 24, 2019

@weswigham

The problem is that we need a class in the global namespace. And here the declaration merging doesn't seem to work (which is the underlying problem behind #25342).

See this small repro.

@ulrichb
Copy link
Contributor Author

ulrichb commented Jul 5, 2019

@weswigham Any news on this? Has the "class merging in the global namespace" any chance to be supported by TS? If no, should we go for a new "node-global-url" package as mentioned in the issue description above?

@thw0rted
Copy link
Contributor

thw0rted commented Aug 2, 2019

Just got bit by this, came here to report and found that it's been open for a few months with no apparent movement.

Isn't The Real Problem that projects shouldn't be including both lib.dom and node? I mean, you're either in the browser or you're not, at least at any one time, right? Surely these two can't be the only global namespace conflicts between the two environments?

@simonbuchan
Copy link
Contributor

@thw0rted - as mentioned, isomorphic code (libraries that can run in both the browser and node without changes) often uses both, even though that gives you the intersection and not the union of the environment, which would be more correct.

@thw0rted
Copy link
Contributor

thw0rted commented Aug 7, 2019

Surely there's got to be a better way to do it than just polluting the global namespace of your X environment with the stuff that only Y provides, though, right? I have different sets of config files (npm / webpack / tsc / etc) for different environments, and can conditionally ignore certain files if they're only used in one or the other. If we don't already have a documented standard practice for writing isomorphic libraries without including conflicting baseline libraries, we certainly should.

@simonbuchan
Copy link
Contributor

MyCoolLib needs access to browser APIs if it's running in a browser, and node APIs if it's running in node. Using both lib: dom and types: node is a simple, development friendly option, and you can check with (something like) tsc --noEmit --lib dom and tsc ---noEmit --types node separately.

Unrleated, but ideally the published declaration files would not reference or declare a dependency on either, so users of the lib don't get polluted, but that's hard to do at the moment due to the lack of "internal" declaration files / declaration bundling.

@thw0rted
Copy link
Contributor

thw0rted commented Aug 8, 2019

So if using both is "development friendly" then how should this issue be resolved? I feel like there was a similar conflict between some kind of Buffer (or ArrayBuffer, or similar?) a few years back, but I can't recall the specifics. Is there some way we could rename one of the offending types to e.g. NodeURL, such that it transpiles to the right name in the emitted code?

@simonbuchan
Copy link
Contributor

Typescript for it's non-DOM included libs uses a pattern like:

interface Foo { 
  // instance stuff
}
interface FooConstructor {
  readonly prototype: Foo;
 new (...): Foo;
 // static stuff
}
declare var: FooConstructor;

This pattern means you can merge the separate declarations, as interfaces merge, and the var will be referring the the same (merged) type in both declarations, even if they are different.
This seems to be an accident of how the library files are designed such that, e.g. lib.es2017.d.ts can extend types introduced in lib.es5.d.ts.
That wouldn't fix this issue until both typescript and @types/node both switch to that style though, and that would have pretty high chance of breaking everything.
I'm really not sure what the right answer is here, without changes to typescript.

@sindresorhus
Copy link
Contributor

sindresorhus commented Nov 29, 2019

Node.js 8 will soon be out of LTS. That means Node.js modules will start targeting Node.js 10 and use the URL and URLSearchParams globals, so this is about to become a much more widespread problem.

@forivall
Copy link
Contributor

forivall commented Jan 21, 2020

An alternative that I just created for some universal* code is as follows:

declare var URL: typeof globalThis extends { URL: infer URLCtor }
  ? URLCtor
  : typeof import('url').URL;

Note that this needs to be in every file in which URL is used, as if it was declared global, it would cause a circular type definition. So it's a workaround, not a solution.

*universal: libraries that can run in both the browser and node without changes. An alternative to using the term "isomorphic", which another commenter uses above; alas, I'm something of a pedant over the semantics of the word isomorphic

@forivall
Copy link
Contributor

Addendum: The circular type definition error can be overcome by adding some additional requirement to the type inference where only the DOM lib would suffice. For example, I found that this works, but is ugly, and for current uses, i'll be using the above in my universal libs that use URL.

declare global {
  var URL: typeof globalThis extends {
    Document: new () => { querySelectorAll: any };
    URL: infer URLCtor;
  }
    ? URLCtor
    : typeof import('url').URL;
}

appsemble-bot pushed a commit to appsemble/appsemble that referenced this issue Apr 3, 2020
- `dom` has been removed from `lib` in the root TypeScript config.
  Disabling a library when it’s not needed is much harder than adding
  one that’s missing and causes simple to the point error messages.
- Packages that use browser typings, now explicitly specify the `dom`
  library.
- Types from `@appsemble/sdk` that are used by `@appsemble/types`, have
  been moved into a new `src/types.ts` file, so `@appsemble/types` can
  import these without needing to use the `dom` library. This means its
  dependants can also use `@appsemble/types` without using `lib`.
- Types in `@appsemble/utils` are now explicitly exported, because the
  TypeScript compiler started complaining about it.
- Some typings have been slightly simplified.
- NodeJS packages now need to explicitly define the globals `URL` and
  `URLSearchParams` due to
  DefinitelyTyped/DefinitelyTyped#34960
remcohaszing added a commit to remcohaszing/estree-util-value-to-estree that referenced this issue Mar 7, 2021
The workaround in
DefinitelyTyped/DefinitelyTyped#34960 (comment)
was used to provide type compatibility with NodeJS and the dom types.
@sdegutis
Copy link

sdegutis commented Apr 3, 2021

Is there any news or progress on this? I keep having to import {URL} from 'url' just to satisfy TypeScript even though it's already in the global scope in my NodeJS app.

ckerr added a commit to electron/bugbot that referenced this issue May 27, 2021
Works around an upstream typing issues with URL, URLSearchParams.
Without this, the broker's tests fail when a type for URLSearchParams
can't be found.

Looks like this might've broken as a side-effect of specifying ES2019
in tsconfig.base.json in f6254f8 ?

Xref: DefinitelyTyped/DefinitelyTyped#34960
ckerr added a commit to electron/bugbot that referenced this issue May 27, 2021
Works around an upstream typing issues with URL, URLSearchParams. Without this, the broker's tests fail when a type for URLSearchParams can't be found.

Looks like this might've broken as a side-effect of specifying ES2019 in tsconfig.base.json in f6254f8 ?

Xref: DefinitelyTyped/DefinitelyTyped#34960
antongolub added a commit to antongolub-forks/DefinitelyTyped that referenced this issue Jan 19, 2022
antongolub added a commit to antongolub-forks/DefinitelyTyped that referenced this issue Jan 20, 2022
antongolub added a commit to antongolub-forks/DefinitelyTyped that referenced this issue Jan 20, 2022
antongolub added a commit to antongolub-forks/DefinitelyTyped that referenced this issue Jan 20, 2022
typescript-bot pushed a commit that referenced this issue Feb 1, 2022
…als by @antongolub

* feat(node): declare URL and URLSearchParams as globals

closes: #34960

* chore(node): simplify URL and URLSearchParams global definitions

* docs(node): add @SInCE comments for URL and URLSearchParam globals

* refactor(node): tweak up URL global type after tsc manual tests

* docs(node): minor jsdoc improvements for global URL and URLSearchParams
@thw0rted
Copy link
Contributor

thw0rted commented Feb 1, 2022

Just to confirm, this new fix means that we no longer need to import {URL} from "url" in Node? That's good, but as I understand it that also means that if the DOM and Node implementations ever diverge in the future, authors of universal libraries will start getting merge errors, right? I guess that's a bridge to cross when we come to it.

@antongolub
Copy link
Contributor

antongolub commented Feb 1, 2022

It's hard to say what will happen in the future. But here is what we have right now: we can use @types/node with lib: ['dom'] or without (lib: ['es2020']') and remove the mentioned import {URL} from "url" workaround from our code.

@aaronatbissell
Copy link

A combination of @types/node and typescript: 3.7.3 yields:

../../node_modules/.registry.npmjs.org/@types/node/12.20.43/node_modules/@types/node/url.d.ts:137:13 - error TS2403: Subsequent variable declarations must have the same type.  Variable 'URLSearchParams' must be of type '{ new (init?: string | string[][] | URLSearchParams | Record<string, string>): URLSearchParams; prototype: URLSearchParams; }', but here has type '{ new (init?: string | string[][] | URLSearchParams | Record<string, string>): URLSearchParams; prototype: URLSearchParams; toString(): string; }'.

137         var URLSearchParams: {
                ~~~~~~~~~~~~~~~

  ../../node_modules/.registry.npmjs.org/typescript/3.7.3/node_modules/typescript/lib/lib.dom.d.ts:16107:13
    16107 declare var URLSearchParams: {
                      ~~~~~~~~~~~~~~~
    'URLSearchParams' was also declared here.

Is there a good workaround for this now?

@antongolub
Copy link
Contributor

antongolub commented Feb 1, 2022

@aaronatbissell,

I'm afraid that only TS 3.8.2+ seems to be working. This is due to URLSearchParams var declarations mismatch:

v3.7.3 lib/dom.generated.d.ts#L16087

declare var URLSearchParams: {
    prototype: URLSearchParams;
    new(init?: string[][] | Record<string, string> | string | URLSearchParams): URLSearchParams;
};

v3.8.2 lib/dom.generated.d.ts#L16136

declare var URLSearchParams: {
    prototype: URLSearchParams;
    new(init?: string[][] | Record<string, string> | string | URLSearchParams): URLSearchParams;
    toString(): string;
};

Anyway, I'll try to fix it.

UPD

  1. it's technically possible
  2. the fix literally looks like a piece of ***: we have to detect TS 3.8.x lib.dom.d.ts version by its side-effects to declare URLSeachParams var type:
        var URLSearchParams: typeof globalThis extends { SpeechRecognitionError: infer SpeechRecognitionError }  ?
            // For TS <= 3.8.2 compatibility
            // https://github.com/microsoft/TypeScript/compare/v3.7.3...v3.8.2#diff-dc0eab937d15e62545da3ed7b4f40ad6b24f15dd88fbc6ceda2bfb4ed8356eb0R15085
            {
                prototype: URLSearchParams;
                new(init?: string[][] | Record<string, string> | string | URLSearchParams): URLSearchParams;
            }: {
                prototype: URLSearchParams;
                new(init?: string[][] | Record<string, string> | string | URLSearchParams): URLSearchParams;
                toString(): string;
            };

@thw0rted
Copy link
Contributor

thw0rted commented Feb 2, 2022

Check out #25677 -- DT versioning is a pain and a half, and I don't think there's a great answer yet. That issue does link to some changes in the README, which gave us this section.

For the version-specific fixes you're looking at, the typesVersions feature, which lets you specify different typings for different TS versions. I said in the thread I linked above, I'm not sure how that interacts with different directories for different major library versions (Node already has /v12, /v14, etc), but it "should" be possible to provide different types to different TS versions. (It's just likely to result in a lot of duplication...)

@antongolub
Copy link
Contributor

antongolub commented Feb 2, 2022

I see three ways:

  1. Add '// Minimum TypeScript Version: 3.8.3' annotation to the libdef
  2. Separate typings for TS <=3.8
  3. Use the mentioned above janky workaround.

I really can't decide which is the less evil.

CC @peterblazejewicz, @weswigham, @sandersn, @gabritto

antongolub added a commit to antongolub-forks/DefinitelyTyped that referenced this issue Feb 5, 2022
antongolub added a commit to antongolub-forks/DefinitelyTyped that referenced this issue Feb 5, 2022
typescript-bot pushed a commit that referenced this issue Feb 14, 2022
… "webworker" by @antongolub

* fix(node): make global ULR decl compatible with lib "webworker"

relates #58277 #34960

* fix(node): improve global URL var type resolution

* refactor(node): handle URLSeachParams var type collision in the same way as for URL

* refactor(node): simplify type condition for global URL and URLSearchParams
martin-badin pushed a commit to martin-badin/DefinitelyTyped that referenced this issue Feb 23, 2022
…hParams as globals by @antongolub

* feat(node): declare URL and URLSearchParams as globals

closes: DefinitelyTyped#34960

* chore(node): simplify URL and URLSearchParams global definitions

* docs(node): add @SInCE comments for URL and URLSearchParam globals

* refactor(node): tweak up URL global type after tsc manual tests

* docs(node): minor jsdoc improvements for global URL and URLSearchParams
martin-badin pushed a commit to martin-badin/DefinitelyTyped that referenced this issue Feb 23, 2022
…atible with lib "webworker" by @antongolub

* fix(node): make global ULR decl compatible with lib "webworker"

relates DefinitelyTyped#58277 DefinitelyTyped#34960

* fix(node): improve global URL var type resolution

* refactor(node): handle URLSeachParams var type collision in the same way as for URL

* refactor(node): simplify type condition for global URL and URLSearchParams
legobeat added a commit to legobeat/auto-changelog that referenced this issue May 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.