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

Detect common API vs browser #77

Open
jkrems opened this issue Oct 28, 2024 · 16 comments
Open

Detect common API vs browser #77

jkrems opened this issue Oct 28, 2024 · 16 comments

Comments

@jkrems
Copy link

jkrems commented Oct 28, 2024

Some code that runs in either the browser or a non-browser environment may have dedicated code paths for each, e.g. because certain code isn't expected to work- or to be exposed in browsers (or vice versa). The only API that is exposed in the minimum common API appears to be navigator.userAgent and it looks fragile when it comes to detecting browser vs non-browser environments.

Are there plans to have a clear signal that can be used to detect the presence of the minimum common API?

@voxpelli
Copy link

The advisable thing I think would be to do feature detection for the things you need rather than trying to infer a larger package of features from the existence of a single parameter?

@jkrems
Copy link
Author

jkrems commented Oct 28, 2024

That doesn't really help with this use case. This is about cases where strictly different code should run - even if the code would technically be valid in both environments. The prior art here is things like Vite's import.meta.env.SSR. The point is to detect "this is not running in the browser" and to have it not be tied to specific runtimes. It's not about detecting specific features.

The current state-of-the-art general solution is to use package.json#exports with a browser/default condition pair but that doesn't help with things like inline conditions. So I was curious if this would be in scope for the minimum common API.

@mk-pmb
Copy link

mk-pmb commented Oct 28, 2024

The point is to detect "this is not running in the browser"

Maybe it would help our understanding to see an example where this distinction is useful. Especially to illustrate where the line between "browser" and non-browser lies. Because a lot of things that people would usually use a browser for, can also be done in a node.js REPL, so when does a node.js REPL become a "browser"?

For selecting the default UI type, one could take hints from whether stdin is a TTY and how many pointer devices are available. Or whether a DOM root node is already initialized, and what CLI arguments are provided. Even then, please give people with exotic devices an override option.

@nektro
Copy link

nektro commented Oct 28, 2024

my understanding was that one of the goals of common api was to make these kinds of checks obsolete
outside that, i imagine the most common way to check that at the moment would be typeof process !== "undefined"

@voxpelli
Copy link

The current state-of-the-art general solution is to use package.json#exports with a browser/default condition pair but that doesn't help with things like inline conditions

You can use these different entry points to make a value available yourself? Forwarding it into your code or setting it in some shared global context?

@mk-pmb
Copy link

mk-pmb commented Oct 28, 2024

Yes, you can use those entrypoints to invoke your main code with an additional option injected into whatever method you'd use to accept user options.

@jkrems
Copy link
Author

jkrems commented Oct 28, 2024

I think my concern here is: Any inline check is becoming fragile if there's some point in the future where that "marker feature" may end up being part of this spec.

Right now it should be relative safe to check for "typeof window". But the existence of the browser-like API standard (and the lack of a clear limit) makes it hard to tell if that will continue to be true.

@jkrems
Copy link
Author

jkrems commented Oct 28, 2024

Straw-ish example:

I have a UI component. It gets pre-rendered outside of a browser. It gets hydrated in a browser. I have a setTimeout to do some tracking if the user stuck around on the UI. I don't want to run that code outside of the browser, it doesn't make sense to run it there.

The question is: Can I safely check for "window" or do I need something more complex to guard the logic to be future safe against iterations of this spec?

@mk-pmb
Copy link

mk-pmb commented Oct 28, 2024

Not sure if you even tried to answer my question about what qualifies as a browser. If you did, I couldn't find that part.

@jkrems
Copy link
Author

jkrems commented Oct 28, 2024

Not sure if you even tried to answer my question about what qualifies as a browser. If you did, I couldn't find that part.

I could try to play a Sokrates game but I feel like my practical example gave a pretty concrete answer? I don't think an average person would confuse a REPL with Chrome in this context?

@jkrems
Copy link
Author

jkrems commented Oct 28, 2024

It is, of course, completely understandable if the answer here is "this isn't the place to solve problems that are somewhat specific to writing web applications". And if so, this concern isn't relevant for this spec and would have to be solved elsewhere.

@voxpelli
Copy link

Any inline check is becoming fragile if there's some point in the future where that "marker feature" may end up being part of this spec.

Totally agree, that’s why I think it should be two different entry points, and as you said:

use package.json#exports with a browser/default condition pair

You could eg do something like:

import { setIsBrowser } from './browser-check.js';

setIsBrowser(true);
import { getIsBrowser } from './browser-check.js';

if (getIsBrowser()) {
  setTimeout(() => {}, 1000);
}

That way you can save the value from the entry points and check it wherever, right?

@ekwoka
Copy link

ekwoka commented Oct 29, 2024

Any inline check is becoming fragile if there's some point in the future where that "marker feature" may end up being part of this spec.

If you do any preprocessing/bundling/transpiling of your code, you can use the bundlers DEFINE mechanism to inline inject true or false to bundle out different entry points you have your exports point at. So each only has the one that matters for it.

@ljharb
Copy link
Member

ljharb commented Oct 29, 2024

The entire point of wintercg imo is that you shouldn’t have to care which environment it is (or at least, you reduce where you need to care), only whether the needed features are present.

Thus, env-specific needs probably need env-specific solutions, not standard ones.

@jkrems
Copy link
Author

jkrems commented Oct 29, 2024

Makes sense. I'll go back to trying to standardize this at the build tool level instead. It does make sense that a "browser-compatible runtime environment" would be treated as a build target, just like a browser would be (in this context).

@mk-pmb
Copy link

mk-pmb commented Oct 30, 2024

Yes, letting your build tools decide is a good idea. That way, when I load a JS-rewrite of Firefox into my node.js REPL and visit your website, it will still be recognised as a browser, hopefully.

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

No branches or pull requests

6 participants