RFC: support f(browser) => array-of-missing-features
See original GitHub issueWhat
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
, wheref(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 frombrowser => polyfill-bundle
, instead, i can go frombrowser => 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!
- yep, no
- 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:
- Created 2 years ago
- Reactions:2
- Comments:6 (3 by maintainers)
Top GitHub Comments
I’ll reopen if I find compelling new ideas 😃
@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.