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

Handle runtime agnostic scripts and type definition conflicts #60464

Closed
6 tasks done
guest271314 opened this issue Nov 9, 2024 · 14 comments
Closed
6 tasks done

Handle runtime agnostic scripts and type definition conflicts #60464

guest271314 opened this issue Nov 9, 2024 · 14 comments
Labels
Duplicate An existing issue was already created

Comments

@guest271314
Copy link

πŸ” Search Terms

"conflicting types"

βœ… Viability Checklist

⭐ Suggestion

tsc can't handle JavaScript runtime agnostic code. Where there might be conflicting declarations/definitions.

πŸ“ƒ Motivating Example

Here's one relevant part of a piece of JavaScript runtime agnotic code that tsc Version 5.8.0-dev.20241109 throws errors for.

The code works as expected as JavaScript, without any errors being thrown, becuase there are no errors in the JavaScript code.

  • @types/node/module.d.ts has its own internal errors at dirname and filename which I'm not really concerned about
  • TypeScript's internal lib.dom.d.ts and @types/node and Bun's @types/bun all declare fetch

It appears like there's no way for tsc to make a deicsion which type to apply for fetch when the imported types all declare fetch, and no option to instruct tsc to use X type when X, XX, XXX types from libraries all refer to the same named declaration.

Of course the immediate solution is the use --skipLibCheck which makes using TypeScript utterly useless. We just use TypeScript syntax for the hell of it.

import process from "node:process";
const runtime: string = navigator.userAgent;
const buffer: ArrayBuffer = new ArrayBuffer(0, { maxByteLength: 1024 ** 2 });
const view: DataView = new DataView(buffer);
const encoder: TextEncoder = new TextEncoder();

let readable: NodeJS.ReadStream & { fd: 0 } | ReadableStream<Uint8Array>,
  writable: WritableStream<Uint8Array>,
  exit: () => void = () => {};

if (runtime.startsWith("Deno")) {
  ({ readable } = Deno.stdin);
  ({ writable } = Deno.stdout);
  ({ exit } = Deno);
}

if (runtime.startsWith("Node")) {
  readable = process.stdin;
  writable = new WritableStream({
    write(value) {
      process.stdout.write(value);
    },
  }, new CountQueuingStrategy({ highWaterMark: Infinity }));
  ({ exit } = process);
}

if (runtime.startsWith("Bun")) {
  readable = Bun.file("/dev/stdin").stream();
  writable = new WritableStream<Uint8Array>({
    async write(value) {
      await Bun.write(Bun.stdout, value);
    },
  }, new CountQueuingStrategy({ highWaterMark: Infinity }));
  ({ exit } = process);
}

Run tsc and see what happens

node_modules/.bin/tsc --esModuleInterop index.ts
node_modules/@types/node/globals.d.ts:509:14 - error TS2300: Duplicate identifier 'fetch'.

509     function fetch(
                 ~~~~~

  node_modules/bun-types/globals.d.ts:1029:6
    1029  var fetch: Fetch;
              ~~~~~
    'fetch' was also declared here.

node_modules/@types/node/module.d.ts:360:13 - error TS2687: All declarations of 'dirname' must have identical modifiers.

360             dirname: string;
                ~~~~~~~

node_modules/@types/node/module.d.ts:366:13 - error TS2687: All declarations of 'filename' must have identical modifiers.

366             filename: string;
                ~~~~~~~~

node_modules/bun-types/bun.d.ts:117:8 - error TS2420: Class 'ShellError' incorrectly implements interface 'ShellOutput'.
  Property 'bytes' is missing in type 'ShellError' but required in type 'ShellOutput'.

117  class ShellError extends Error implements ShellOutput {
           ~~~~~~~~~~

  node_modules/bun-types/bun.d.ts:434:3
    434   bytes(): Uint8Array;
          ~~~~~~~~~~~~~~~~~~~~
    'bytes' is declared here.

node_modules/bun-types/globals.d.ts:1029:6 - error TS2300: Duplicate identifier 'fetch'.

1029  var fetch: Fetch;
          ~~~~~

  node_modules/typescript/lib/lib.dom.d.ts:28708:18
    28708 declare function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
                           ~~~~~
    'fetch' was also declared here.
  node_modules/@types/node/globals.d.ts:509:14
    509     function fetch(
                     ~~~~~
    and here.

node_modules/bun-types/globals.d.ts:1939:12 - error TS2687: All declarations of 'dirname' must have identical modifiers.

1939   readonly dirname: string;
                ~~~~~~~

node_modules/bun-types/globals.d.ts:1942:12 - error TS2687: All declarations of 'filename' must have identical modifiers.

1942   readonly filename: string;
                ~~~~~~~~

node_modules/bun-types/overrides.d.ts:3:29 - error TS2305: Module '"bun"' has no exported member 'PathLike'.

3 import type { BunFile, Env, PathLike } from "bun";
                              ~~~~~~~~

node_modules/typescript/lib/lib.dom.d.ts:16004:11 - error TS2430: Interface 'MessageEvent<T>' incorrectly extends interface 'Bun.MessageEvent<T>'.
  Types of property 'ports' are incompatible.
    Type 'readonly MessagePort[]' is not assignable to type 'readonly import("worker_threads").MessagePort[]'.
      Type 'MessagePort' is missing the following properties from type 'MessagePort': ref, unref, addListener, emit, and 13 more.

16004 interface MessageEvent<T = any> extends Event {
                ~~~~~~~~~~~~

node_modules/typescript/lib/lib.dom.d.ts:26068:11 - error TS2430: Interface 'WebSocket' incorrectly extends interface 'import("/home/xubuntu/bin/node_modules/@types/ws/index").WebSocket'.
  Types of property 'binaryType' are incompatible.
    Type 'BinaryType' is not assignable to type '"arraybuffer" | "nodebuffer" | "fragments"'.
      Type '"blob"' is not assignable to type '"arraybuffer" | "nodebuffer" | "fragments"'.

26068 interface WebSocket extends EventTarget {
                ~~~~~~~~~

node_modules/typescript/lib/lib.dom.d.ts:28708:18 - error TS2300: Duplicate identifier 'fetch'.

28708 declare function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
                       ~~~~~

  node_modules/bun-types/globals.d.ts:1029:6
    1029  var fetch: Fetch;
              ~~~~~
    'fetch' was also declared here.


Found 11 errors in 6 files.

Errors  Files
     1  node_modules/@types/node/globals.d.ts:509
     2  node_modules/@types/node/module.d.ts:360
     1  node_modules/bun-types/bun.d.ts:117
     3  node_modules/bun-types/globals.d.ts:1029
     1  node_modules/bun-types/overrides.d.ts:3
     3  node_modules/typescript/lib/lib.dom.d.ts:16004

πŸ’» Use Cases

  1. What do you want to use this for? => JavaScript runtime agnostic code
  2. What shortcomings exist with current approaches? => tsc can't handle Node.js types declaring fetch and Bun types declaring fetch.
  3. What workarounds are you using in the meantime? => --skipLibCheck
@guest271314 guest271314 changed the title Handle runtime agostic scripts and type definition conflicts Handle runtime agnostic scripts and type definition conflicts Nov 9, 2024
@MartinJohns
Copy link
Contributor

This again?

@guest271314
Copy link
Author

@MartinJohns

This again?

There's no solution.

@MartinJohns
Copy link
Contributor

I mean... you already got an answer. Repeatedly opening the very same issue just because you don't like it will not really help.

@jcalz
Copy link
Contributor

jcalz commented Nov 9, 2024

Is this a duplicate of #59439? Or something else? Seems like there's some relevant history here but it's not linked.

@guest271314
Copy link
Author

@jcalz Related to an appreciable degree. I had to use // @ts-ignore and/or --skipLibCheck for tsc to not throw when handling a .ts file that includes references to Bun and NodeJS and Deno.

That's wasn't really a solution to tsc not being capable of handling possibly duplicate definitions of fetch and reporting Bun is undefined.

I observe quite a bit of exceptions for Node.js in the general TypeScript compiler options. I don't see options for runtime agnostic source code.

I'm wondering if Microsoft TypeScript is interested in handling JavaScript runtime agnostic code that references multiple runtime globals, and might have conflicting types?

@guest271314
Copy link
Author

@jcalz FWIW Taking the JavaScript runtime agnostic approach a step further
I'm thinking about combining qjs, node, deno, and bun
into a single executable (348.6 MB, still less than Rust tool chain at around 500 MB) that I can reference as
such within JavaScript and/or TypeScript in the same .js and/or .ts script, and do something like this

import { cc } from "bun:ffi";
import ( $ } from "jsr:@david/dax";
std.popen(...);
Deno.stdin.readable(...);
Bun.build(...);
NodeJS.crypto.generateKey(...);

@guillaumebrunerie
Copy link

@jcalz FWIW Taking the JavaScript runtime agnostic approach a step further I'm thinking about combining qjs, node, deno, and bun into a single executable (348.6 MB, still less than Rust tool chain at around 500 MB) that I can reference as such within JavaScript and/or TypeScript in the same .js and/or .ts script, and do something like this

import { cc } from "bun:ffi";
import ( $ } from "jsr:@david/dax";
std.popen(...);
Deno.stdin.readable(...);
Bun.build(...);
NodeJS.crypto.generateKey(...);

Good luck with that! When you're done, I suggest you also combine Chrome, Firefox, Safari, and Edge into a single executable to get a browser agnostic browser, solving the compatibility problems once and for all.

@guest271314
Copy link
Author

Good luck with that! When you're done, I suggest you also combine Chrome, Firefox, Safari, and Edge into a single executable to get a browser agnostic browser, solving the compatibility problems once and for all.

Technically I can do the polyflot thing now using only subpprocesses. I've already started, in code.

The browser landscape is simple. Eliminate Edge because that's just Chromium with Microsofy branding, eliminate Safari because nothing cutting edge is going on there. We'll keep Firefox just to demonstrate what Chromium should be doing in some cases, and keep Chromium to demonstrate the cutting edge of browser technologies.

@guest271314
Copy link
Author

@guillaumebrunerie Something like this already works in JavaScript world https://github.com/guest271314/native-messaging-piper/blob/main/nm_piper.js#L94-L119. TypeScript would throw a fit

      const command = ["/bin/bash", ["-c", script]];
      // Node.js, Deno, Bun implement subprocesses differently.
      let stream;

      if (runtime.startsWith("Node")) {
        const { Duplex } = await import("node:stream");
        const { spawn } = await import("node:child_process");
        const { stdout, stderr } = spawn(...command);
        stream = Duplex.toWeb(stdout).readable;
      }

      if (runtime.startsWith("Deno")) {
        const subprocess = new Deno.Command(command.shift(), {
          args: command.pop(),
          stdout: "piped",
          stdin: "piped",
        });
        const process = subprocess.spawn();
        process.stdin.close();
        stream = process.stdout;
      }

      if (runtime.startsWith("Bun")) {
        const subprocess = Bun.spawn(command.flat());
        stream = subprocess.stdout;
      }

Here's a hack a did a while back to make use of Firefox Nightly for capturing speakers, and sending that audio to Chromium, because at the time Chromium authors refused to capture monitor devices Capture monitor device at Nightly stream to Chromium.

@guest271314
Copy link
Author

@guillaumebrunerie My question is essentially does Microsoft TypeScript have an interest in handling runtime agnostic, polyglot scripts, where type definitions might, and probably do conflict. If not I can move on and leave TypeScript folks alone and do my own thing like I do anyway.

There's a whole bunch of "compatibility" projects, such as WinterCG.

But what if JavaScript runtimes didn't even have to concern themselves with "compatibility", specifically "Node.js" compatibility, and just did their own thing? They could eliminate the "Node.js compatibility" claims, which when vetted after beating grass in code, are generally always lacking in some way. Then JavaScript runtimes could focus on their respective features - without trying to be or "pretending" to be Node.js wintercg/runtime-keys#18 (comment)

... and issues in the module ecosystem stemming from runtimes such as Bun and Deno pretending to be Node.js

@fatcerberus
Copy link

God is joy! 🐟

@jcalz
Copy link
Contributor

jcalz commented Nov 11, 2024

NOTE: I'm not a member of the TS team, just an interested community member. Feel free to ignore me if you'd rather wait for Official Word.

crosslinking to #21965, #36057, #52433, #53971, which all have to do with trying to support compiling for multiple runtimes.

#52433 (comment)

I'm not sure how such a thing could be implemented; even if we allow type predicates or assertion predicates to refer to external things (like in #43786), it seems like a huge lift that checking navigator would possibly cause whole swaths of apparent library narrowings. There doesn't seem to be a huge demand in the community for functionality like this (the linked issues only have a handful of πŸ‘). My intuition is that the benefits to implementing this wouldn't be worth the costs. Am I missing something?

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Nov 12, 2024
@typescript-bot
Copy link
Collaborator

This issue has been marked as "Duplicate" and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@typescript-bot typescript-bot closed this as not planned Won't fix, can't repro, duplicate, stale Nov 16, 2024
@guest271314
Copy link
Author

There doesn't seem to be a huge demand in the community for functionality like this (the linked issues only have a handful of πŸ‘). My intuition is that the benefits to implementing this wouldn't be worth the costs. Am I missing something?

To me that is the only reason to use TypeScript. JavaScript runtime agnostic code. Right now Microsoft TypeScript is heavily weighted towards Node.js. There are dozens of JavaScript runtimes.

I don't think Node.js, Deno, or Bun actually use tsc to parse the Microsoft TypeScript syntax.

Missed opportunity to expand coverage. Oh well, I tried. Thanks for hearing me out anyway. Good luck.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

7 participants