question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

RFC: support f(browser) => array-of-missing-features

See original GitHub issue

What

I need to build a targeted polyfill for browser X, but I do not know the polyfill-abe features that browser X needs.

  • e.g. provide a function f, where f(safari, 12) => ["ResizeObserver", "INTL.XYZ", ..., other-missing-features]

This is highly desirable functionality, because:

  • compiler toolchains may already compensate for some features, which may not need to be polyfilled, so those could be filtered out when generating polyfills. For example, f(safari, 12).filter(feature => !DONT_POLYFILL_THESE_HANDLED_FEATURES.some(rgx => rgx.test(feature))
  • a polyfill supporting features A & B & C may work with multilple browsers, so rather than going directly from browser => polyfill-bundle, instead, i can go from browser => features => polyfill-bundle and recycle my polyfill bundle between a few browsers. it is certainly useful to polyfill by features, not by browser target.
  • visualizing missing features can help me understand what sort of constraints i should put into my application development process and build pipeline

There is a related issue that says use polyfill-service-url-builder, but that library is more or less a function of src-js => polyfill-js, which is fundamentally disjoint with this issue. This is browser-identifier => list-of-features.

Details

  • explain what prompted this request — e.g. is it something that you regularly make a workaround for?

I have a few applications to manage. Each one has different browser support requirements, but I do not know the exact features I need to polyfill. I could, however, with just a tiny bit of extra information already contained in polyfill-library.

var Features_to_polyfill = 
   Features_used_by_app -  // known (user-space)
   Features_supported_by_compiler - // known (user-space)
   Features_not_in_browser // known, **but not exposed, by polyfill-library**
  • describe what the new feature would do and how it would be used
const pf = require('polyfill-library')
pf.getMissingFeatures(browser, version) // [list, of, features]
  • explain what alternatives you have explored / considered
  • where possible, attach designs for the style of the new feature

polyfill-library has all of this great data, it just does not expose it conveniently. it can be easily supported by this library, using existing APIs. However,

  • supporting this feature externally (that is, not from this library) requires knowledge about the meta.browsers schema, which I didn’t see clearly documented. this is a risk in maintaining such functionality in user space.
  • mapping browserslist naming convetions => polyfill-library’s meta.browsers schema may be challenging. hopefully contributors can articulate where the browsers TOML/meta schema convention is maintained? it would be awesome to unify that schema.

Here is an implementation. FWIW, in writing this, I believe I uncovered a few small bugs in the current library.

import assert from 'assert'
import * as pf from 'polyfill-library'

/**
 * Read & parse all polyfill-library meta.json files, indexed by 
 * feature name.
 * Forgive the cuteness, this is an expensive op, so i cached
 * the result
 */
export const getMetaByFeature = (() => {
  const metaByFeatureName: Record<string, pf.PolyfillMeta> = {}
  let cache: Promise<Record<string, pf.PolyfillMeta>> | null = null
  return () => {
    if (cache) return cache
    cache = Promise.resolve().then(async () => {
      const features = await pf.listAllPolyfills()
      await Promise.all(features.map(async feature => {
        const meta = await pf.describePolyfill(feature)
        if (meta) {
          metaByFeatureName[feature] = meta
        } else {
          // eslint-disable-next-line no-console
          console.error(`no meta for feature: ${meta}`)
        }
      }));
      return metaByFeatureName
    })
    return cache
  }
})()

export const getMissingFeatures = async (browserName: string, version: number, filter?: (feat: string) => boolean) => {
  const metaByFeatureName = await getMetaByFeature()
  const featuresToPolyfill = Object.entries(metaByFeatureName).filter(([feature, meta]) => {
    if (filter) {
      const isFiltered = filter(feature)
      if (isFiltered) return false
    }
    // @todo file a @types/* bug, but likely, this is polyfill-library bug for matched browsers
    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
    if (!meta.browsers) {
      console.log(`meta missing for feature: ${feature}`)
      return false
    }
    const polyfillExprForBrowser = meta.browsers[browserName]
    if (!polyfillExprForBrowser) return false
    if (polyfillExprForBrowser.endsWith('*')) return true
    if (polyfillExprForBrowser.startsWith("<")) {
      // cases:
      // <1
      // <1.2.3
      // <=1.2.3
      // <= 1.2.3
      const [, lte, polyFillOnLessVersion] = polyfillExprForBrowser.match(/^<(=?)[^\d]*(\d+(\.?(.\d+)?)*)$/) || []
      assert.ok(!!polyFillOnLessVersion, `<VERSION match failed ${polyfillExprForBrowser}`)
      const rhs = parseInt(polyFillOnLessVersion, 10)
      const shouldPolyfill = lte ? version <= rhs : version < rhs
      return shouldPolyfill
    }
    const [, upperRangeMatch] = polyfillExprForBrowser.match(/.*-.*(\d+(\.?(.\d+)?)*)$/) || []
    if (upperRangeMatch) {
      return version <= parseInt(upperRangeMatch,10)
    }
    const [, exactVersionMatch] = polyfillExprForBrowser.match(/^(\d+(\.?(.\d+)?)*)$/) || [];
    if (exactVersionMatch) {
      return version < parseInt(exactVersionMatch,10)
    }
    if (polyfillExprForBrowser.startsWith('>')) {
      console.log(`bogus range missing for feature: ${feature} // ${polyfillExprForBrowser} // ${browserName}@${version}`)
      return false
    }
    throw new Error(`unhandled case ${polyfillExprForBrowser}`)
  }).map(([feature]) => feature)
  console.log(JSON.stringify(featuresToPolyfill, null, 2))
  return featuresToPolyfill
}

getMissingFeatures("safari", 12, feature => [
  /ESAbstract/,
  /ArrayIterator/,
  /~locale/
].some(rgx => rgx.test(feature)))


/**
Output:

meta missing for feature: AudioContext
bogus range missing for feature: HTMLInputElement.prototype.valueAsDate // >=10.1 // safari@12
bogus range missing for feature: Promise.prototype.finally // >7 <11.1 // safari@12
[
  "Element.prototype.inert",
  "Intl.DateTimeFormat",
  "Intl.DateTimeFormat.~timeZone.all",
  "Intl.DisplayNames",
  "Intl.DateTimeFormat.~timeZone.golden",
  "Intl.ListFormat",
  "Intl.Locale",
  "Intl.NumberFormat",
  "Intl.PluralRules",
  "Intl.RelativeTimeFormat",
  "MediaQueryList.prototype.addEventListener",
  "ResizeObserver",
  "String.prototype.replaceAll",
  "UserTiming",
  "WebAnimations",
  "_DOMTokenList",
  "_Iterator",
  "_StringIterator",
  "_mutation",
  "requestIdleCallback",
  "smoothscroll",
  "setImmediate",
  "screen.orientation"
]
*/

In executing the above script, I believe I have discovered a few small bugs in polyfill-library:

  • meta missing for feature: AudioContext
    • yep, no meta.json at all for this file!
  • bogus range missing for feature: HTMLInputElement.prototype.valueAsDate // >=10.1 // safari@12
    • I believe this says “polyfill for safari 10.1 and later”, which I do not think is correct
  • bogus range missing for feature: Promise.prototype.finally // >7 <11.1 // safari@12
    • this should probably be just <11.1, not >7 && <11.1? unless some other polyfill is expected to capture it?

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:2
  • Comments:6 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
cdaringecommented, Jul 12, 2022

I’ll reopen if I find compelling new ideas 😃

0reactions
romainmenkecommented, Jul 12, 2022

@cdaringe going over a few older issues

Is this still an open issue on your end?

Given the limited bandwidth we currently have for polyfill-library a feature like this might be easier to realise as a separate package.

Read more comments on GitHub >

github_iconTop Results From Across the Web

rfcs/0685-new-browser-support-policy.md at master - GitHub
New Browser Support Policy. Summary. Establishes a new browser support policy for the next major release of Ember and Ember Data.
Read more >
RFC INDEX
The Stream field gives the document's stream (see RFC 4844), followed by Area and WG when relevant. The DOI field gives the Digital...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found