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?
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.
@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.
@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?
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?
@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.
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.
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.
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?
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.
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.
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_
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;
}
Most helpful comment
Node.js 8 will soon be out of LTS. That means Node.js modules will start targeting Node.js 10 and use the
URLandURLSearchParamsglobals, so this is about to become a much more widespread problem.