wintercg / proposal-common-minimum-api

https://common-min-api.proposal.wintercg.org/
Other
216 stars 15 forks source link

There's no common, global runtime identifier. How do you know which runtime your script is running in? #62

Closed guest271314 closed 7 months ago

guest271314 commented 7 months ago

The rudiments of a synchronous function to return the name of the runtime the script is running in https://gist.github.com/guest271314/13ae833d27395f3db7d3b683b02cc611

const runtime = () => {
  // TODO: More direct and exclusionary way to do this for QuickJS
  if (
    !globalThis.hasOwnProperty("hasOwn") &&
    Array.isArray(globalThis.scriptArgs)
  ) {
    return "qjs";
  }
  if (Object.hasOwn(globalThis, "tjs")) {
    return globalThis.tjs.args[0];
  }
  if (Object.hasOwn(globalThis, "process")) {
    if (globalThis.process.argv0 === "node") {
      return globalThis.process.argv0;
    }
    if (
      globalThis.process.argv0 === "bun" 
      && globalThis.process.isBun) {
      return globalThis.process.argv0;
    }
  }
  if (Object.hasOwn(globalThis, "Deno")) {
    return Object.keys(globalThis.Deno.version).shift();
  }
};

console.log(runtime());
ljharb commented 7 months ago

Why is that something you need to know?

guest271314 commented 7 months ago

node is not shipped with a built-in WebSocket server implementation. QuickJS and txiki.js are not shipped with a built-in HTTP(S) server. txiki.js does not implement TextEncoderStream() or TextDecoderStream(). Bun does not implement Node.js worker_threads. Node.js implements CommonJS as the default module loader, Deno, QuickJS, txiki.js do not. Bun has a mechanism for using CommonJS and Ecmascript Modules in the same file. The last time I checked Node.js is still debating that implementation. And, of course, no two JavaScript runtimes implement STDIO the same, as STDIO is not defined in ECMA-262. And so forth.

So when we are testing the JavaScript runtimes and want to spawn a child process to use a different JavaScript runtime - using the same script - that information will be useful to know what features can and cannot be used in the given runtime agnostic script.

Ultimately I think we can use something like web-platform-tests dashboard]https://wpt.fyi/results/?label=experimental&label=master&aligned) have to programmatically list and visually display which JavaScript runtimes support which features

GeoffreyBooth commented 7 months ago

navigator.userAgent is supported in all runtimes.

guest271314 commented 7 months ago

navigator.userAgent is supported in all runtimes.

Didn't think of that. Not supported in QuickJS or txiki.js. Though I haven't gotten any confirmation those JavaScript runtimes are excluded from consideration here. Have not testes Hermes, Graal, et al.

ljharb commented 7 months ago

Seems like, just like on the browser for decades, it’d be an antipattern to use the user agent for that, and you should feature test instead.

guest271314 commented 7 months ago

Seems like, just like on the browser for decades, it’d be an antipattern to use the user agent for that, and you should feature test instead.

I did think about spoofing the browser user agent.

navigator.userAgent is indeed listed over here https://common-min-api.proposal.wintercg.org/.

The idea is to not have to feature test. Perhaps an immutable, readonly string or object.

ljharb commented 7 months ago

Feature tests are the best practice, for a ton of reasons, and i don’t see that changing.

guest271314 commented 7 months ago

When you feature test what is returned from the function, from where?

ljharb commented 7 months ago

That depends on the feature - but generally you test whether a feature is available before you use it, and that way, you can handle any platform that supports the features you need.

guest271314 commented 7 months ago

That depends on the feature - but generally you test whether a feature is available before you use it, and that way, you can handle any platform that supports the features you need.

Yes, but what features to test? And once you get results, ostensibly given a feature test we use try..catch..finally, what are you returning from the function from where (as in from where in the runtime are we getting the runtime identifier) in the given JavaScript runtime?

guest271314 commented 7 months ago

You can see where this is going... If everybody is running their own feature test then there's no common way to do that right now.

ljharb commented 7 months ago

There doesn't exist a generic, universal answer, nor can there ever. You have to name a specific feature, and then I could suggest how to feature-test that specific thing, and it will be unique and different for each thing. That's how the entire JS ecosystem does things (now, and for the past decade or two).

guest271314 commented 7 months ago

Let's say we want to use qjs to read STDIN, node for Ed25519 support, and deno for a WebSocket server - using the same script.

We feature test qjs for something defined on globalThis, yet qjs lacks navigator.userAgent, which we probably shouldn't be using anyway for this case.

There doesn't exist a generic, universal answer, nor can there ever. You have to name a specific feature, and then I could suggest how to feature-test that specific thing, and it will be unique and different for each thing. That's how the entire JS ecosystem does things (now, and for the past decade or two).

I know nothing like a common JavaScript runtime identifier exists now, besides navigator.userAgent, inherited from the browser. Thus this issue.

The

nor can there ever.

is not how I think.

Somebody in the wild once claimed something like npm can't be eliminated from JavaScript. bun install provides a means to install Node.js-specific packages without npm on the machine. I have implemented multiple workarounds for what specification authors and implementers have decided to not support. So there is no "ever" in programming that I can point to.

That's how the entire JS ecosystem does things (now, and for the past decade or two).

Right. Isn't the point of this effort to cease that JavaScript runtime fiefdom-like behaviour?

ljharb commented 7 months ago

No, wintercg has nothing to do with feature testing, and even among browsers (which wintercg is emulating) feature testing remains eternally required and recommended.

guest271314 commented 7 months ago

I know Workerd maintainers refuse to support serving an uploaded ReadableStream to a subsequent request in the server. In Deno we can serve an uploaded ReadableStream to a subsequent request, using Response. I don't think Node.js has any built-in server implementation that supports sending a WHATWG Fetch Standard Response() from the server. That would be a rather elaborate feature test, though possible.

guest271314 commented 7 months ago

I think it is reasonable for a common JavaScript runtime identifier to be written out. You don't think navigator.userAgent is appropriate, recommend feature testing, yet proffer no common API or means to do so yourself, or on behalf of the stakholders you represent. So we are back to square one.

ljharb commented 7 months ago

Such a runtime identifier wouldn’t reliably tell you what features are there, because bugs can happen, which is why it hasn’t been reliable in browsers to do so since forever. Feature testing remains the only reliable approach., no matter how complex the test is.

guest271314 commented 7 months ago

I think you are missing the part about after feature testing what you return from the function, from where in the global runtime.

The common runtime identifier alone will tell you what runtime your code is running in at that given moment.

So we feature test, then return exactly what from the function? A user-defined string or object, that is unique to that user who decided what to feature test for?

AFAIK there is nothing like https://wpt.fyi/ for the stakeholders in this effort and repository, which there can be, so users' in the field will have at least a canonical source to read and derive feature tests based on the data therein.

panva commented 7 months ago

AFAIK there is nothing like https://wpt.fyi/ for the stakeholders in this effort and repository

WPT Report uploads are not limited to just browser vendors.

https://wpt.fyi/results/?label=master&label=stable&product=node.js&product=deno

guest271314 commented 7 months ago

@panva That is is relevant to client usage. Node.js doesn't have a built-in WebSocket server. (Inspector is not really intended to be used as a server).

guest271314 commented 7 months ago

@panva an There needs to be something like that for this repository. See https://github.com/wintercg/proposal-common-minimum-api/issues/62#issuecomment-1849040092.

I'm pretty sure WHATWG Fetch Standard and W3C ServiceWorker standard don't prohibit serving data from one POST or QUERY request to a subsequent request, see https://github.com/cloudflare/workerd/issues/423, https://gist.github.com/guest271314/4847c70be203ee7cd4c8a6d6a9bca1d3.

panva commented 7 months ago

I'm not following neither your line of argumentation not actually understand how it relates to the original post and title.

guest271314 commented 7 months ago

Very simple: Specify a common JavaScript runtime identifier. Read-only. We simply can't run the same code written out in https://common-min-api.proposal.wintercg.org/ in the JavaScript runtimes the stakeholders who created this repository maintain.

When we are reliably able to get an immutable, read-only string from the given runtime, we can write source code include those checks in the source code to do take a certain course in the source code.

E.g.,

if (globalThis.runtime === "workerd") {
  // We cannot serve an uploaded ReadableStream to a subsequent request
  // without having to try to make that request and handle errors
}
guest271314 commented 7 months ago

I'm not following neither your line of argumentation not actually understand how it relates to the original post and title.

Another example. This single JavaScript file https://github.com/guest271314/offscreen-webrtc/blob/main/background.js is the source for a ServiceWorker, an offscreen document, and an arbitrary Web page the script is injected into from the browser extension.

The idea is to be able to use the same JavaScript source file across multiple JavaScript runtimes, with the ability to just retrieve a string from the global object to switch, branch code in the same script - without real-time feature testing, just using the runtime identifier string.

panva commented 7 months ago

You just keep on describing navigator.userAgent from the common minimal API.

https://common-min-api.proposal.wintercg.org/#requirements-for-navigatoruseragent

The globalThis.navigator.userAgent property is provided such that application code can reliably identify the runtime within which it is running.

guest271314 commented 7 months ago

Alright. I originally didn't think of that. Your colleague suggested feature testing which lead me to re-open the issue, because then the question arises what we are testing for, and what we return from the function or set as the value of a property, because although WPT might display results of Node.js or Deno, clearly that is only for client usage, not all usage, because Node.js doen't have a built-in WebSocket server. Therefore I think, either way, WinterCG should either include an asterisk next to WebSocket for Node.js or include details about what is being tested, because those results are not reflecting Node.js WebSocket server implementaton that does not exist.

mk-pmb commented 7 months ago

The point about feature testing is that the entire original question indicates a probably-wrong approach. You (almost) never need to know which runtime you're in. You only need to know the capabilities of the current runtime. Which can change in the next version. Some of the capabilities may even be dependent on available hardware. So don't engage in a race to update your code to predict the capabilities based on auxiliary information (runtime name/ hardware), but instead check at runtime whether a feature works, and if it doesn't, use another feature.

Like when some of the runtimes provide a websocket server library under different names, just try and load one opportunistically. If it fails, try the next one. If all of them fail, load a TCP server library and your own implementation of the websocket protocol.

One of the very few cases where a runtime identifier could be useful is for generating error reports. And for runtimes that have some way of identifying themselves (e.g. process.versions in node) you can even feature-test for exactly that.

guest271314 commented 7 months ago

A runtime identifier will be useful for me for the reasons I described above.

If you are going to suggest feature testing then you had better be actually constantly testing multiple JavaScript runtimes so you can recommend what to test and from where to return what.

So far the only features that have been specifically mentioned to test are the one I pointed out that are different between the JavaScript runtimes I continuously test.

We are not talking about libraries, just the JavaScript runtimes, which I fetch the latest version of every day or so. Libraries are a totally different issue. That brings CommonJS into the discussion, which is a Node.js-specific implementation that Bun somehow figured out a way to support in the same script as Ecmascript Modules are used - before Node.js has.

We are not just talking about the node executable itself. That's all I write to my machine. The same with deno, bun, qjs, tjs, etc.

There are over 20 JavaScript runtimes or interpreters other than node on this list alone https://gist.github.com/guest271314/bd292fc33e1b30dede0643a283fadc6a.

node does not have a monopoly on JavaScript runtimes circa 2023.

Since I read and dig through commits, blame, issues, PR, release notes, and test the nightly releases of JavaScript runtimes and browsers constantly I kind of know what runtimes support what. It would be simple to just get the value of globalThis.runtime, which navigator.userAgent returns in some cases.

txiki.js does not have a navigator.userAgent from the last time I built, sometime within the past couple weeks or so, has an open issue to support Common Minimum API.

QuickJS doesn't have navigator.userAgent either. Granted there's no signal from Bellard to support this proposal, QuickJS is found in WebAssembly/WASI environments far more than any other JavaScript runtime, at 915 KB after strip. node v22.0.0-nightly202312091ba508d51b is still 83.3 MB after strip.

guest271314 commented 7 months ago

Just for completeness here the txiki.js issue to support Minimum Common Web Platform API https://github.com/saghul/txiki.js/issues/418.

mk-pmb commented 6 months ago

There are over 20 JavaScript runtimes or interpreters other than node on this list alone

Do we already have tools to make it easy to run a piece of code in all of them and compare (error) output? That way I could try to build a demo for how to get a runtume identifier based on feature testing.

pi0 commented 6 months ago

For reference, unjs/std-env has a standard simple runtime export that follows wintercg runtime-key convention.