Proposal: `RuntimeSupport.{ navigator(), import.mata() }`

I think one of the pain points when you write code, that sometimes you try to do stuff that are not supported in the current runtime (node, browser, bun, ...), or maybe looking for an ECMA feature and you need to know if it's supported,

it would be great if we can have a class that can tell us if a certain thing implemented or not, it would be great.

a good usage would be:

let url; 
if(RuntimeSupport.import.meta.url())
  url = import.meta.url;
else
  url = document.currentScript.src;

where RuntimeSupport could be implemented like this:


// This will be provided by the runtime, and maybe later extended by polyfills
const RuntimeSupportedFeatures = {
  import: {
    meta: {
      dirname: true,
      filename: true,
      url: true,
      resolve: true,
    },
  },
};

/**@return {ProxyHandler} */
const getHandler = (keys = []) => ({
  get: (_, key) => {
    return new Proxy(() => {}, getHandler([...keys, key]));
  },
  apply: () => {
    let value = RuntimeSupportedFeatures;

    for (let index = 0; index < keys.length; index++) {
      if (!value) return false;

      const key = keys[index];

      value = value[key];
    }

    return !!value;
  },
});

var RuntimeSupport = new Proxy({}, getHandler());

for sure we have smarter people than me that can figure out a better API or implementation, but that what I thought of

Why not just use normal feature detection? In your case, that would be "url" in import.meta.

Or

const url = import.meta.url ?? document.currentScript.src;

@bergus some times certain features will throw an error when you try to access it in different runtimes, for instance try to check if import is present using typeof import === 'undefined' in a CJS node environment it will throw an error for just having this import keyword, but for bun you can use require and import at the same time, and we don't know what new runtimes will do in such or different cases, it might be good if all of them provided a way to check before use

The Script and Module parse goals are ambiguous, so you simply can't consistently make a file that works in both - you have to use the file extension, generally, to indicate what format it is, and you can only execute it with the format it was intended for.

2 Likes

@ljharb Yes you might be right, but the proposal is more than only import and require, it's about the new features that may and may not be included in certain runtime

Not for nothing, but this is very much the driving use case behind Parser Augmentation. Syntactic language features can't be polyfilled or even tested for, which means your options as a developer are (a) don't use the new features, (b) consign yourself to always having to transpile your code, or (c) don't support older engines. Even a PA implementation that doesn't support runtime syntax polyfills (in other words, one with a static parser) provides a way for the source text to ask the parser what syntax features are available - you could use it in a similar sort of way as C's #ifdef directive. You could write a single file that parses correctly both as a CJS script using require and module.exports and also as an ES module using import and export, just by including syntax directives to guard the contextually-invalid syntax from the runtime parser.

Of course, this only works if the PA syntax itself is old enough that it's part of the ecosystem baseline, which tends to take around a decade or so. That's why it's so valuable to get it added to the ECMA262 spec now - it might only get minimal runtime support in the near-term, but ten years down the line you'll have an answer for "how do I write a file that supports different versions/variants of ECMAScript syntax?"

1 Like