You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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?
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?
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.
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.
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"
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?
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.
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?
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?
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.
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.
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.
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).
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.
I’ve also had to use different code paths due to varying behavior across environments. Sometimes, simply relying on feature detection isn’t enough—especially when dealing with buggy implementations.
For example, when I need to get a Blob or File from the filesystem, I can use fs.openAsBlob or bun.file(). Deno, however, lacks a proper way to create a Blob backed by the filesystem, so in that case, it’s often better to use an entirely different approach. Bun’s own Blob/File classes are so wildly non–spec compliant that I don’t even want to rely on feature detection to account for their bugs. Sometimes, the best solution is to use different code paths depending on the environment.
JavaScript performance can also vary between engines. For example, one JS pattern might run slowly in Bun (which uses WebKit) but perform much better in V8. In such cases, detecting whether the runtime is Chromium-based or not can help you choose the optimal function or implementation.
I believe the best cross-environment solution is to use navigator.userAgentData. It lets you determine the engine, browser, platform, and whether the device is mobile or not.
Before userAgentData, distinguishing between Chrome and Brave was already a challenge.
At one point, I used esbuild in a service worker to compile CSS on demand, targeting the browser it was running in. Since CSS feature detection isn’t possible in a service worker, the only way to adapt properly was by identifying the actual browser.
In short: feature detection isn’t a bulletproof solution. Sometimes, the most practical approach is to do a bit of sniffing. That’s why I believe userAgentData should be part of the minimum common API in WinterCG.
If we do it, we'll enter an arms race on software tryin to work around bugs vs. software trying to shed off those workarounds because they know the bug has been fixed meanwhile. Unfortunately, that race will inevitable happen whether we actively participate or not.
However, if we do, we may gain some means of designing the battle ground. How would we plan ahead for lying to the workaround enthusiasts' detections that they can still appreciate?
We should strive for something more fine-grained than just { product + version number }, because the more precisely we allow them to hone in on the specific bug they're trying to workaround, the more precisely we can elide their specific workaround once we know we've fixed it better than they had.
Should we establish a pattern where they first have to show their police badge before we show them our ID? We could establish that the proper way of sniffing shall include a UUID specific to the workaround they're deciding on, so we can ship specific workarounds for their outdated workarounds.
Activity
voxpelli commentedon Oct 28, 2024
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?
hybrist commentedon 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#exportswith abrowser/defaultcondition 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 commentedon Oct 28, 2024
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 commentedon 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 commentedon Oct 28, 2024
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 commentedon 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.
hybrist commentedon 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.
hybrist commentedon 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 commentedon 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.
hybrist commentedon Oct 28, 2024
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?
hybrist commentedon 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 commentedon Oct 28, 2024
Totally agree, that’s why I think it should be two different entry points, and as you said:
You could eg do something like:
That way you can save the value from the entry points and check it wherever, right?
ekwoka commentedon Oct 29, 2024
If you do any preprocessing/bundling/transpiling of your code, you can use the bundlers DEFINE mechanism to inline inject
trueorfalseto bundle out different entry points you have yourexportspoint at. So each only has the one that matters for it.ljharb commentedon 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.
hybrist commentedon 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 commentedon 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.
jimmywarting commentedon May 17, 2025
I’ve also had to use different code paths due to varying behavior across environments. Sometimes, simply relying on feature detection isn’t enough—especially when dealing with buggy implementations.
For example, when I need to get a
BloborFilefrom the filesystem, I can usefs.openAsBloborbun.file(). Deno, however, lacks a proper way to create aBlobbacked by the filesystem, so in that case, it’s often better to use an entirely different approach. Bun’s ownBlob/Fileclasses are so wildly non–spec compliant that I don’t even want to rely on feature detection to account for their bugs. Sometimes, the best solution is to use different code paths depending on the environment.JavaScript performance can also vary between engines. For example, one JS pattern might run slowly in Bun (which uses WebKit) but perform much better in V8. In such cases, detecting whether the runtime is Chromium-based or not can help you choose the optimal function or implementation.
I believe the best cross-environment solution is to use
navigator.userAgentData. It lets you determine the engine, browser, platform, and whether the device is mobile or not.Before
userAgentData, distinguishing between Chrome and Brave was already a challenge.At one point, I used esbuild in a service worker to compile CSS on demand, targeting the browser it was running in. Since CSS feature detection isn’t possible in a service worker, the only way to adapt properly was by identifying the actual browser.
In short: feature detection isn’t a bulletproof solution. Sometimes, the most practical approach is to do a bit of sniffing. That’s why I believe
userAgentDatashould be part of the minimum common API in WinterCG.mk-pmb commentedon May 18, 2025
If we do it, we'll enter an arms race on software tryin to work around bugs vs. software trying to shed off those workarounds because they know the bug has been fixed meanwhile. Unfortunately, that race will inevitable happen whether we actively participate or not.
However, if we do, we may gain some means of designing the battle ground. How would we plan ahead for lying to the workaround enthusiasts' detections that they can still appreciate?
We should strive for something more fine-grained than just { product + version number }, because the more precisely we allow them to hone in on the specific bug they're trying to workaround, the more precisely we can elide their specific workaround once we know we've fixed it better than they had.
Should we establish a pattern where they first have to show their police badge before we show them our ID? We could establish that the proper way of sniffing shall include a UUID specific to the workaround they're deciding on, so we can ship specific workarounds for their outdated workarounds.