farm-browserslist-generator
Version:
A library that makes generating and validating Browserslists a breeze!
1,078 lines (1,068 loc) • 89.5 kB
JavaScript
import Browserslist from 'browserslist';
import { feature, features } from 'caniuse-lite';
import objectPath from 'object-path';
import { coerce, lt, gt, gte, lte } from 'semver';
import { UAParser } from 'ua-parser-js';
import isbot from 'isbot';
import { createRequire } from 'module';
/**
* Coerces the given version
*/
function ensureSemver(browser, version) {
if ((browser === "op_mini" || browser === "android") && version === "all") {
return coerce("0.0.0");
}
else if (browser === "safari" && version === "TP") {
return SAFARI_TP_MAJOR_VERSION;
}
return coerce(version, { loose: true });
}
/**
* Coerces the given version
*/
function coerceToString(browser, version) {
return ensureSemver(browser, version).toString();
}
/**
* Compares two versions, a and b
*/
function compareVersions(a, b) {
const normalizedA = isNaN(parseFloat(a)) ? a : parseFloat(a);
const normalizedB = isNaN(parseFloat(b)) ? b : parseFloat(b);
if (typeof normalizedA === "string" && typeof normalizedB !== "string") {
return 1;
}
if (typeof normalizedB === "string" && typeof normalizedA !== "string") {
return -1;
}
if (normalizedA < normalizedB)
return -1;
if (normalizedA > normalizedB)
return 1;
return 0;
}
/**
* A Regular Expression that captures the part of a browser version that should be kept
*/
const NORMALIZE_BROWSER_VERSION_REGEXP = /(?![\d.,]+-)-*(.*)/;
const SAFARI_TP_MAJOR_VERSION = (() => {
const versions = getSortedBrowserVersions("safari");
const lastVersionBeforeTp = versions[versions.length - 2];
const coerced = coerce(lastVersionBeforeTp);
if (coerced.minor === 9) {
return coerce(coerced.major + 1);
}
else {
return coerce(`${coerced.major}.${coerced.minor + 1}.0`);
}
})();
/**
* Ensures that for any given version of a browser, if it is newer than the latest known version, the last known version will be used as a fallback
*/
function normalizeBrowserVersion(browser, givenVersion, versions = getSortedBrowserVersions(browser), allowSmaller = false) {
const givenVersionCoerced = ensureSemver(browser, givenVersion);
const latestVersion = getLatestVersionOfBrowser(browser);
const latestVersionCoerced = ensureSemver(browser, latestVersion);
if (givenVersionCoerced == null || latestVersionCoerced == null) {
throw new TypeError(`Could not detect the version of: '${givenVersion}' for browser: ${browser}`);
}
if (givenVersionCoerced.major > latestVersionCoerced.major ||
(givenVersionCoerced.major === latestVersionCoerced.major && givenVersionCoerced.minor > latestVersionCoerced.minor) ||
(givenVersionCoerced.major === latestVersionCoerced.major && givenVersionCoerced.minor === latestVersionCoerced.minor && givenVersionCoerced.patch > latestVersionCoerced.patch)) {
return latestVersion;
}
const closestMatch = getClosestMatchingBrowserVersion(browser, givenVersion, versions);
// Allow smaller, but not larger browser versions than the known ones
if (allowSmaller && lt(givenVersionCoerced, ensureSemver(browser, closestMatch), { loose: true })) {
return givenVersion;
}
return closestMatch;
}
/**
* Gets the known version of the given browser that is closest to the given version
*/
function getClosestMatchingBrowserVersion(browser, version, versions = getSortedBrowserVersions(browser)) {
const coerced = ensureSemver(browser, version);
if (browser === "op_mini" && version === "all")
return "all";
if (browser === "safari") {
if (version === "TP")
return "TP";
// If the given version is greater than or equal to the latest non-technical preview version of Safari, the closest match IS TP.
else if (gt(ensureSemver(browser, `${coerced.major}.${coerced.minor}`), ensureSemver(browser, versions.slice(-2)[0])))
return "TP";
}
let candidate = versions[0];
versions.forEach(currentVersion => {
const currentCoerced = ensureSemver(browser, currentVersion);
if (gte(coerced, currentCoerced)) {
candidate = currentVersion;
}
});
return candidate;
}
function getSortedBrowserVersionsWithLeadingVersion(browser, inputVersion) {
const versions = getSortedBrowserVersions(browser);
const [firstVersion] = versions;
if (firstVersion != null && inputVersion != null) {
const firstVersionSemver = ensureSemver(browser, firstVersion);
let nextInputVersion = inputVersion;
while (true) {
const nextInputSemver = ensureSemver(browser, nextInputVersion);
if (gt(firstVersionSemver, nextInputSemver)) {
versions.unshift(nextInputVersion);
nextInputVersion = String(nextInputSemver.major + 1);
}
else {
break;
}
}
}
return versions;
}
/**
* Gets all versions of the given browser, sorted
*/
function getSortedBrowserVersions(browser) {
// Generate the Browserslist query
const queryResult = Browserslist([`>= 0%`, `unreleased versions`]);
const versions = [];
// First, organize the different versions of the browsers inside the Map
queryResult.forEach(part => {
const [currentBrowser, version] = part.split(" ");
if (currentBrowser !== browser)
return;
const versionMatch = version.match(NORMALIZE_BROWSER_VERSION_REGEXP);
const normalizedVersion = versionMatch == null ? version : versionMatch[1];
versions.push(normalizedVersion);
});
return versions.sort(compareVersions);
}
/**
* Gets the latest version of the given browser
*/
function getLatestVersionOfBrowser(browser) {
const versions = getSortedBrowserVersions(browser);
return versions[versions.length - 1];
}
/**
* Gets the oldest (stable) version of the given browser
*/
function getOldestVersionOfBrowser(browser) {
const versions = getSortedBrowserVersions(browser);
return versions[0];
}
/**
* Gets the previous version of the given browser from the given version
*/
function getPreviousVersionOfBrowser(browser, version) {
const versions = getSortedBrowserVersions(browser);
const indexOfVersion = versions.indexOf(normalizeBrowserVersion(browser, version, versions));
// If the version isn't included, or if it is the first version of it, return undefined
if (indexOfVersion <= 0)
return undefined;
return versions[indexOfVersion - 1];
}
/**
* Gets the previous version of the given browser from the given version
*/
function getNextVersionOfBrowser(browser, version) {
const versions = getSortedBrowserVersions(browser);
const indexOfVersion = versions.indexOf(normalizeBrowserVersion(browser, version, versions));
// If the version isn't included, or if it is the first version of it, return undefined
if (indexOfVersion <= 0)
return undefined;
return versions[indexOfVersion + 1];
}
/**
* Caniuse has only a limited set of supported browsers.
* There are cases where there is simply no way to guess
* a browser based on a User Agent, and in these cases
* this can be used as a fallback.
* Chrome is the world's most used browser, and as an evergreen
* browser, it is commonly the newest version. But to be safe
* This fallback browser is placed a bit in the past
*/
const UNKNOWN_CANIUSE_BROWSER = {
browser: "chrome",
version: "80"
};
const ES5_FEATURES = [
"javascript.builtins.Object.create",
"javascript.builtins.Object.getPrototypeOf",
"javascript.builtins.Object.defineProperty",
"javascript.builtins.Object.defineProperties",
"javascript.builtins.Object.getOwnPropertyDescriptor",
"javascript.builtins.Object.getOwnPropertyNames",
"javascript.builtins.Object.keys",
"javascript.builtins.Object.preventExtensions",
"javascript.builtins.Object.isExtensible",
"javascript.builtins.Object.seal",
"javascript.builtins.Object.isSealed",
"javascript.builtins.Object.freeze",
"javascript.builtins.Object.isFrozen",
"javascript.builtins.Function.bind",
"javascript.builtins.String.trim",
"javascript.builtins.Array.isArray",
"javascript.builtins.Array.every",
"javascript.builtins.Array.filter",
"javascript.builtins.Array.forEach",
"javascript.builtins.Array.indexOf",
"javascript.builtins.Array.lastIndexOf",
"javascript.builtins.Array.map",
"javascript.builtins.Array.reduce",
"javascript.builtins.Array.some",
"javascript.builtins.JSON.parse",
"javascript.builtins.JSON.stringify",
"javascript.builtins.Date.now",
"javascript.builtins.Date.toISOString"
];
const ES2015_FEATURES = [
...ES5_FEATURES,
"javascript.classes",
"javascript.statements.const",
"javascript.statements.let",
"javascript.functions.arrow_functions",
"javascript.functions.rest_parameters",
"javascript.grammar.template_literals",
"javascript.operators.destructuring",
"javascript.operators.spread.spread_in_arrays",
"javascript.functions.default_parameters",
"javascript.builtins.RegExp.sticky",
"javascript.operators.object_initializer.shorthand_property_names",
"javascript.operators.object_initializer.computed_property_names",
"javascript.operators.object_initializer.shorthand_method_names"
];
const ES2016_FEATURES = [...ES2015_FEATURES, "javascript.operators.exponentiation", "javascript.builtins.Array.includes"];
const ES2017_FEATURES = [
...ES2016_FEATURES,
"javascript.builtins.AsyncFunction",
"javascript.builtins.Object.values",
"javascript.builtins.Object.entries",
"javascript.builtins.Object.getOwnPropertyDescriptors",
"javascript.builtins.String.padStart",
"javascript.builtins.String.padEnd"
];
const ES2018_FEATURES = [...ES2017_FEATURES, "javascript.operators.spread.spread_in_object_literals", "javascript.builtins.Promise.finally"];
const ES2019_FEATURES = [
...ES2018_FEATURES,
"javascript.builtins.Array.flat",
"javascript.builtins.Array.flatMap",
"javascript.builtins.Object.fromEntries",
"javascript.builtins.String.trimStart",
"javascript.builtins.String.trimEnd",
"javascript.builtins.JSON.json_superset",
"javascript.builtins.JSON.stringify.well_formed_stringify",
"javascript.builtins.Symbol.description",
"javascript.statements.try_catch.optional_catch_binding"
];
const ES2020_FEATURES = [...ES2019_FEATURES, "javascript.builtins.String.matchAll"];
const ES2021_FEATURES = [
...ES2020_FEATURES,
"javascript.operators.logical_or_assignment",
"javascript.operators.nullish_coalescing_assignment",
"javascript.operators.logical_and_assignment",
"javascript.builtins.String.replaceAll",
"javascript.grammar.numeric_separators",
"javascript.builtins.Promise.any"
];
const ES2022_FEATURES = [
...ES2021_FEATURES,
"javascript.builtins.Array.at",
"javascript.builtins.String.matchAll",
"javascript.classes.public_class_fields",
"javascript.classes.private_class_fields",
"javascript.classes.private_class_fields_in",
"javascript.classes.static_class_fields",
"javascript.operators.await.top_level",
"javascript.builtins.RegExp.hasIndices"
];
const ES2023_FEATURES = [
...ES2022_FEATURES,
"javascript.builtins.Array.findLast",
"javascript.builtins.Array.findLastIndex",
"javascript.grammar.hashbang_comments",
"javascript.builtins.WeakMap.symbol_as_keys",
"javascript.builtins.Array.toReversed",
"javascript.builtins.Array.toSorted",
"javascript.builtins.Array.toSpliced",
"javascript.builtins.Array.with"
];
/**
* Applies the given correction within the given version range
*/
function rangeCorrection(browser, supportKind, start, end) {
const versions = getSortedBrowserVersions(browser);
const corrections = [];
versions.forEach(version => {
let shouldSet = false;
if (start == null && end == null) {
shouldSet = true;
}
else if (start != null && end == null) {
if (version === "TP") {
shouldSet = true;
}
else if (version === "all") {
shouldSet = true;
}
else {
shouldSet = gte(coerceToString(browser, version), coerceToString(browser, start));
}
}
else if (start == null && end != null) {
if (version === "TP") {
shouldSet = end === "TP";
}
else if (version === "all") {
shouldSet = true;
}
else {
shouldSet = lte(coerceToString(browser, version), coerceToString(browser, end));
}
}
else if (start != null && end != null) {
if (version === "TP") {
shouldSet = end === "TP";
}
else if (version === "all") {
shouldSet = true;
}
else {
shouldSet = gte(coerceToString(browser, version), coerceToString(browser, start)) && lte(coerceToString(browser, version), coerceToString(browser, end));
}
}
if (shouldSet) {
corrections.push({
kind: supportKind,
version
});
}
});
return corrections;
}
var CaniuseSupportKind;
(function (CaniuseSupportKind) {
CaniuseSupportKind["AVAILABLE"] = "AVAILABLE";
CaniuseSupportKind["UNAVAILABLE"] = "UNAVAILABLE";
CaniuseSupportKind["PARTIAL_SUPPORT"] = "PARTIAL_SUPPORT";
CaniuseSupportKind["PREFIXED"] = "PREFIXED";
})(CaniuseSupportKind || (CaniuseSupportKind = {}));
const FIREFOX_MATCH = /Firefox\/([\d.]+)/i;
const IOS_REGEX_1 = /(iPhone)|(iPad)/i;
const IOS_REGEX_2 = /(iOS)\s*([\d._]+)/i;
const UNDERSCORED_VERSION_REGEX = /\d+_/;
const FBSV_IOS_VERSION_REGEX = /FBSV\/([\d.]+)/i;
const IOS_14_5_UA_1 = /(CFNetwork\/1237\s+Darwin\/20.4)/i;
const IOS_3_2_UA_1 = /(^Mobile\/7B334b)/i;
// Extend 'isbot' with more matches
isbot.extend(["bitdiscovery", "Dalvik/", "placid.app/v1", "WebsiteMetadataRetriever", "(compatible; aa/1.0)"]);
// These extension provide ua-parser-js with support for additional browsers
// such as Sogou Explorer
const PARSER_EXTENSIONS = {
engine: [[/(Chrome)\/([\d.]+)/i], ["blink", "version"]],
browser: [
[/(MetaSr)\s*([\d.]+)/i],
["Sogou Explorer", "version"],
[/(HeyTapBrowser)\/([\d.]+)/i],
["HeyTapBrowser", "version"],
[/(SamsungBrowser)\/CrossApp/i],
["Samsung Browser"],
[/(Nokia\d+\/[\d.]+.*Profile\/MIDP)/i],
["WAP"]
]
};
/**
* A class that wraps UAParser
*/
class UaParserWrapper {
constructor(userAgent) {
this.userAgent = userAgent;
this.parser = new UAParser(userAgent, PARSER_EXTENSIONS);
}
/**
* Gets the IUserAgentBrowser based on the UAParser
*/
getBrowser() {
return this.extendGetBrowserResult(this.parser.getBrowser());
}
/**
* Gets the IUserAgentOS based on the UAParser
*/
getOS() {
return this.extendGetOsResult(this.parser.getOS());
}
/**
* Gets the IUserAgentDevice based on the UAParser
*/
getDevice() {
return this.parser.getDevice();
}
/**
* Gets the IEngine based on the UAParser
*/
getEngine() {
return this.extendGetEngineResult(this.parser.getEngine());
}
/**
* Extends the result of calling 'getBrowser' on the UAParser and always takes bots into account
*/
extendGetBrowserResult(result) {
var _a, _b;
// Ensure that the EdgeHTML version is used
if (result.name === "Edge") {
const engine = this.parser.getEngine();
if (engine.name === "EdgeHTML") {
result.version = engine.version;
// noinspection JSDeprecatedSymbols
result.major = String((_b = (_a = coerce(engine.version)) === null || _a === void 0 ? void 0 : _a.major) !== null && _b !== void 0 ? _b : result.version);
}
}
// Check if it is a bot and match it if so
// Also treat Dalvik/ as a bot
if (result.name !== "Chrome Headless" && isbot(this.userAgent)) {
if (this.userAgent.includes("http://www.google.com/bot.htm") || this.userAgent.includes("http://www.google.com/adsbot.htm")) {
// As far as we know, the last reported update to Googlebot was the intent
// to keep it evergreen, but so far it seems 74 is the latest official version
result.name = "Chrome";
result.version = "74";
// noinspection JSDeprecatedSymbols
result.major = "74";
}
// Treat all other bots as IE 11
else {
result.name = "IE";
result.version = "11";
// noinspection JSDeprecatedSymbols
result.major = "11";
}
}
if (result["Sogou Explorer"] != null) {
result.name = "Sogou Explorer";
delete result["Sogou Explorer"];
}
else if (result.HeyTapBrowser != null) {
result.name = "HeyTapBrowser";
delete result.HeyTapBrowser;
}
else if (result["Samsung Browser"] != null) {
result.name = "Samsung Browser";
delete result["Samsung Browser"];
}
else if (result.WAP != null) {
result.name = "IE";
result.version = "8";
delete result.WAP;
}
return result;
}
/**
* Extends the result of calling 'getEngine'
*/
extendGetEngineResult(result) {
if (result.blink != null) {
result.name = "Blink";
delete result.blink;
}
// The User Agent may hold additional information, such as the equivalent Firefox version
if (result.name === "Goanna") {
const ffMatch = this.userAgent.match(FIREFOX_MATCH);
if (ffMatch != null) {
result.name = "Gecko";
result.version = ffMatch[1];
}
}
return result;
}
/**
* Extends the result of calling 'getOS'
*/
extendGetOsResult(result) {
if (result.version != null && UNDERSCORED_VERSION_REGEX.test(result.version)) {
result.version = result.version.replace(/_/g, ".");
}
if ((result.name == null || result.name === "iOS") && (IOS_REGEX_1.test(this.userAgent) || IOS_REGEX_2.test(this.userAgent))) {
result.name = "iOS";
if (result.version == null) {
// If it is the Facebook browser, the iOS version may be reported
// through its FBSV/{version} part
const fbsvMatch = this.userAgent.match(FBSV_IOS_VERSION_REGEX);
if (fbsvMatch != null) {
result.version = fbsvMatch[1].replace(/_/g, ".");
}
else {
const iosRegex2Match = this.userAgent.match(IOS_REGEX_2);
if (iosRegex2Match != null) {
result.version = iosRegex2Match[2].replace(/_/g, ".");
}
}
}
}
if ((result.name == null || result.name === "iOS") && IOS_14_5_UA_1.test(this.userAgent)) {
result.name = "iOS";
result.version = "14.5";
}
if ((result.name == null || result.name === "iOS") && IOS_3_2_UA_1.test(this.userAgent)) {
result.name = "iOS";
result.version = "3.2";
}
return result;
}
}
const require = createRequire(import.meta.url);
const compatData = require('@mdn/browser-compat-data');
/**
* A Cache between user agent names and generated Browserslists
*/
const userAgentToBrowserslistCache = new Map();
/**
* A Cache for retrieving browser support for some features
*/
const browserSupportForFeaturesCache = new Map();
/**
* A Cache between feature names and their CaniuseStats
*/
const featureToCaniuseStatsCache = new Map();
/**
* A Cache between user agents with any amount of features and whether or not they are supported by the user agent
*/
const userAgentWithFeaturesToSupportCache = new Map();
/**
* By and large, MDN has the best compat data, especially when looking into at which point older version of Android-based browsers
* received support for a feature. Therefore we generally prioritize MDN compat data and will attempt to rewrite common caniuse queries to
* their respective MDN feature names
*/
const CANIUSE_TO_MDN_FEATURE_MAP = {
pointer: "api.PointerEvent.PointerEvent",
shadowdomv1: "api.ShadowRoot",
"custom-elementsv1": "api.CustomElementRegistry",
template: "html.elements.template",
fetch: "api.fetch",
promises: "javascript.builtins.Promise",
"object-values": "javascript.builtins.Object.values",
mutationobserver: "api.MutationObserver",
"focusin-focusout-events": "api.Element.focusin_event",
"high-resolution-time": "api.Performance.now",
url: "api.URL",
urlsearchparams: "api.URLSearchParams",
"object-fit": "css.properties.object-fit",
"console-basic": "api.console.info",
"console-time": "api.console.time",
"atob-btoa": "api.atob",
blobbuilder: "api.Blob.Blob",
bloburls: "api.URL.createObjectURL",
requestidlecallback: "api.Window.requestIdleCallback",
requestanimationframe: "api.Window.requestAnimationFrame",
proxy: "javascript.builtins.Proxy"
};
/**
* A Map between features and browsers that has partial support for them but should be allowed anyway
* @type {Map<string, string[]>}
*/
const PARTIAL_SUPPORT_ALLOWANCES = new Map([
["shadowdomv1", "*"],
["custom-elementsv1", "*"],
["web-animation", "*"]
]);
const TYPED_ARRAY_BASE_DATA_CORRECTIONS_INPUT = {
/* eslint-disable @typescript-eslint/naming-convention */
android: rangeCorrection("android", CaniuseSupportKind.AVAILABLE, `4`),
chrome: rangeCorrection("and_chr", CaniuseSupportKind.AVAILABLE, `7`),
and_chr: rangeCorrection("and_chr", CaniuseSupportKind.AVAILABLE, `7`),
edge: rangeCorrection("edge", CaniuseSupportKind.AVAILABLE, "12"),
samsung: rangeCorrection("samsung", CaniuseSupportKind.AVAILABLE, `4`),
opera: rangeCorrection("opera", CaniuseSupportKind.AVAILABLE, `12`),
op_mob: rangeCorrection("op_mob", CaniuseSupportKind.AVAILABLE, `12`),
firefox: rangeCorrection("firefox", CaniuseSupportKind.AVAILABLE, `4`),
and_ff: rangeCorrection("and_ff", CaniuseSupportKind.AVAILABLE, `4`),
safari: rangeCorrection("safari", CaniuseSupportKind.AVAILABLE, `6`),
ios_saf: rangeCorrection("safari", CaniuseSupportKind.AVAILABLE, `5`),
ie: rangeCorrection("ie", CaniuseSupportKind.AVAILABLE, `11`),
op_mini: rangeCorrection("op_mini", CaniuseSupportKind.AVAILABLE, `all`),
bb: rangeCorrection("bb", CaniuseSupportKind.AVAILABLE, `10`),
and_uc: rangeCorrection("and_uc", CaniuseSupportKind.AVAILABLE, `11.8`),
and_qq: rangeCorrection("and_qq", CaniuseSupportKind.AVAILABLE, `1.2`),
baidu: rangeCorrection("baidu", CaniuseSupportKind.AVAILABLE, `7.12`)
/* eslint-enable @typescript-eslint/naming-convention */
};
const TYPED_ARRAY_ES2015_DATA_CORRECTIONS_INPUT = {
/* eslint-disable @typescript-eslint/naming-convention */
android: rangeCorrection("android", CaniuseSupportKind.AVAILABLE, `45`),
chrome: rangeCorrection("and_chr", CaniuseSupportKind.AVAILABLE, `45`),
and_chr: rangeCorrection("and_chr", CaniuseSupportKind.AVAILABLE, `45`),
edge: rangeCorrection("edge", CaniuseSupportKind.AVAILABLE, "12"),
samsung: rangeCorrection("samsung", CaniuseSupportKind.AVAILABLE, `5`),
opera: rangeCorrection("opera", CaniuseSupportKind.AVAILABLE, `32`),
op_mob: rangeCorrection("op_mob", CaniuseSupportKind.AVAILABLE, `32`),
firefox: rangeCorrection("firefox", CaniuseSupportKind.AVAILABLE, `38`),
and_ff: rangeCorrection("and_ff", CaniuseSupportKind.AVAILABLE, `38`),
safari: rangeCorrection("safari", CaniuseSupportKind.AVAILABLE, `10`),
ios_saf: rangeCorrection("safari", CaniuseSupportKind.AVAILABLE, `10`),
ie: rangeCorrection("ie", CaniuseSupportKind.AVAILABLE, `11`),
ie_mob: rangeCorrection("ie", CaniuseSupportKind.AVAILABLE, `11`)
/* eslint-enable @typescript-eslint/naming-convention */
};
const TYPED_ARRAY_ES2016_DATA_CORRECTIONS_INPUT = {
/* eslint-disable @typescript-eslint/naming-convention */
android: rangeCorrection("android", CaniuseSupportKind.AVAILABLE, `47`),
chrome: rangeCorrection("and_chr", CaniuseSupportKind.AVAILABLE, `47`),
and_chr: rangeCorrection("and_chr", CaniuseSupportKind.AVAILABLE, `47`),
edge: rangeCorrection("edge", CaniuseSupportKind.AVAILABLE, "14"),
samsung: rangeCorrection("samsung", CaniuseSupportKind.AVAILABLE, `5`),
opera: rangeCorrection("opera", CaniuseSupportKind.AVAILABLE, `34`),
op_mob: rangeCorrection("op_mob", CaniuseSupportKind.AVAILABLE, `34`),
firefox: rangeCorrection("firefox", CaniuseSupportKind.AVAILABLE, `43`),
and_ff: rangeCorrection("and_ff", CaniuseSupportKind.AVAILABLE, `43`),
safari: rangeCorrection("safari", CaniuseSupportKind.AVAILABLE, `10`),
ios_saf: rangeCorrection("safari", CaniuseSupportKind.AVAILABLE, `10`)
/* eslint-enable @typescript-eslint/naming-convention */
};
const TYPED_ARRAY_KEYS_VALUES_ENTRIES_ITERATOR_DATA_CORRECTIONS_INPUT = {
/* eslint-disable @typescript-eslint/naming-convention */
android: rangeCorrection("android", CaniuseSupportKind.AVAILABLE, `38`),
chrome: rangeCorrection("and_chr", CaniuseSupportKind.AVAILABLE, `38`),
and_chr: rangeCorrection("and_chr", CaniuseSupportKind.AVAILABLE, `38`),
edge: rangeCorrection("edge", CaniuseSupportKind.AVAILABLE, "12"),
samsung: rangeCorrection("samsung", CaniuseSupportKind.AVAILABLE, `5`),
opera: rangeCorrection("opera", CaniuseSupportKind.AVAILABLE, `26`),
op_mob: rangeCorrection("op_mob", CaniuseSupportKind.AVAILABLE, `26`),
firefox: rangeCorrection("firefox", CaniuseSupportKind.AVAILABLE, `37`),
and_ff: rangeCorrection("and_ff", CaniuseSupportKind.AVAILABLE, `37`),
safari: rangeCorrection("safari", CaniuseSupportKind.AVAILABLE, `10`),
ios_saf: rangeCorrection("safari", CaniuseSupportKind.AVAILABLE, `10`)
/* eslint-enable @typescript-eslint/naming-convention */
};
const TYPED_ARRAY_SPECIES_DATA_CORRECTIONS_INPUT = {
/* eslint-disable @typescript-eslint/naming-convention */
android: rangeCorrection("android", CaniuseSupportKind.AVAILABLE, `51`),
chrome: rangeCorrection("and_chr", CaniuseSupportKind.AVAILABLE, `51`),
and_chr: rangeCorrection("and_chr", CaniuseSupportKind.AVAILABLE, `51`),
edge: rangeCorrection("edge", CaniuseSupportKind.AVAILABLE, "13"),
samsung: rangeCorrection("samsung", CaniuseSupportKind.AVAILABLE, `5`),
opera: rangeCorrection("opera", CaniuseSupportKind.AVAILABLE, `38`),
op_mob: rangeCorrection("op_mob", CaniuseSupportKind.AVAILABLE, `38`),
firefox: rangeCorrection("firefox", CaniuseSupportKind.AVAILABLE, `48`),
and_ff: rangeCorrection("and_ff", CaniuseSupportKind.AVAILABLE, `48`),
safari: rangeCorrection("safari", CaniuseSupportKind.AVAILABLE, `10`),
ios_saf: rangeCorrection("safari", CaniuseSupportKind.AVAILABLE, `10`)
/* eslint-enable @typescript-eslint/naming-convention */
};
/**
* Not all Caniuse data is entirely correct. For some features, the data on https://kangax.github.io/compat-table/es6/
* is more correct. When a Browserslist is generated based on support for specific features, it is really important
* that it is correct, especially if the browserslist will be used as an input to tools such as @babel/preset-env.
* This table provides some corrections to the Caniuse data that makes it align better with actual availability
* @type {[string, CaniuseBrowserCorrection][]}
*/
const FEATURE_TO_BROWSER_DATA_CORRECTIONS_INPUT = [
/* eslint-disable @typescript-eslint/naming-convention */
[
"xhr2",
{
ie: [
{
// Caniuse reports that XMLHttpRequest support is partial in Internet Explorer 11, but it is in fact properly supported
kind: CaniuseSupportKind.AVAILABLE,
version: "11"
}
]
}
],
[
// Caniuse reports that Safari 12.1 and iOS Safari 12.2 has partial support for Web Animations,
// but they do not - They require enabling it as an experimental feature
"web-animation",
{
safari: rangeCorrection("safari", CaniuseSupportKind.UNAVAILABLE, `0`, "13.4"),
ios_saf: rangeCorrection("ios_saf", CaniuseSupportKind.UNAVAILABLE, `0`, "13.4")
}
],
[
"es6-class",
{
edge: [
{
// Caniuse reports that Microsoft Edge has been supporting classes since v12, but it was prefixed until v13
kind: CaniuseSupportKind.PREFIXED,
version: "12"
}
],
ios_saf: [
{
// Caniuse reports that iOS Safari has been supporting classes since v9, but the implementation was only partial
kind: CaniuseSupportKind.PARTIAL_SUPPORT,
version: "9"
},
{
// Caniuse reports that iOS Safari has been supporting classes since v9, but the implementation was only partial
kind: CaniuseSupportKind.PARTIAL_SUPPORT,
version: "9.2"
},
{
// Caniuse reports that iOS Safari has been supporting classes since v9, but the implementation was only partial
kind: CaniuseSupportKind.PARTIAL_SUPPORT,
version: "9.3"
}
],
safari: [
{
// Caniuse reports that Safari has been supporting classes since v9, but the implementation was only partial
kind: CaniuseSupportKind.PARTIAL_SUPPORT,
version: "9"
},
{
// Caniuse reports that Safari has been supporting classes since v9, but the implementation was only partial
kind: CaniuseSupportKind.PARTIAL_SUPPORT,
version: "9.1"
}
]
}
],
[
"api.Element.classList",
{
edge: [
{
// Caniuse reports that Microsoft Edge v15 has only partial support for class-list since it doesn't support SVG elements,
// but we don't want feature detections to return false for that browser
kind: CaniuseSupportKind.AVAILABLE,
version: "15"
}
],
ie: [
{
// Caniuse reports that IE 10 has only partial support for class-list since it doesn't support SVG elements,
// but we don't want feature detections to return false for that browser
kind: CaniuseSupportKind.AVAILABLE,
version: "10"
},
{
// Caniuse reports that IE 11 has only partial support for class-list since it doesn't support SVG elements,
// but we don't want feature detections to return false for that browser
kind: CaniuseSupportKind.AVAILABLE,
version: "11"
}
]
}
],
["javascript.builtins.TypedArray.from", TYPED_ARRAY_ES2015_DATA_CORRECTIONS_INPUT],
["javascript.builtins.TypedArray.of", TYPED_ARRAY_ES2015_DATA_CORRECTIONS_INPUT],
["javascript.builtins.TypedArray.subarray", TYPED_ARRAY_BASE_DATA_CORRECTIONS_INPUT],
["javascript.builtins.TypedArray.copyWithin", TYPED_ARRAY_ES2015_DATA_CORRECTIONS_INPUT],
["javascript.builtins.TypedArray.every", TYPED_ARRAY_ES2015_DATA_CORRECTIONS_INPUT],
["javascript.builtins.TypedArray.fill", TYPED_ARRAY_ES2015_DATA_CORRECTIONS_INPUT],
["javascript.builtins.TypedArray.filter", TYPED_ARRAY_ES2015_DATA_CORRECTIONS_INPUT],
["javascript.builtins.TypedArray.find", TYPED_ARRAY_ES2015_DATA_CORRECTIONS_INPUT],
["javascript.builtins.TypedArray.findIndex", TYPED_ARRAY_ES2015_DATA_CORRECTIONS_INPUT],
["javascript.builtins.TypedArray.forEach", TYPED_ARRAY_ES2015_DATA_CORRECTIONS_INPUT],
["javascript.builtins.TypedArray.indexOf", TYPED_ARRAY_ES2015_DATA_CORRECTIONS_INPUT],
["javascript.builtins.TypedArray.join", TYPED_ARRAY_ES2015_DATA_CORRECTIONS_INPUT],
["javascript.builtins.TypedArray.lastIndexOf", TYPED_ARRAY_ES2015_DATA_CORRECTIONS_INPUT],
["javascript.builtins.TypedArray.map", TYPED_ARRAY_ES2015_DATA_CORRECTIONS_INPUT],
["javascript.builtins.TypedArray.reduce", TYPED_ARRAY_ES2015_DATA_CORRECTIONS_INPUT],
["javascript.builtins.TypedArray.reduceRight", TYPED_ARRAY_ES2015_DATA_CORRECTIONS_INPUT],
["javascript.builtins.TypedArray.reverse", TYPED_ARRAY_ES2015_DATA_CORRECTIONS_INPUT],
["javascript.builtins.TypedArray.some", TYPED_ARRAY_ES2015_DATA_CORRECTIONS_INPUT],
["javascript.builtins.TypedArray.sort", TYPED_ARRAY_ES2015_DATA_CORRECTIONS_INPUT],
["javascript.builtins.TypedArray.toLocaleString", TYPED_ARRAY_ES2015_DATA_CORRECTIONS_INPUT],
["javascript.builtins.TypedArray.toString", TYPED_ARRAY_ES2015_DATA_CORRECTIONS_INPUT],
["javascript.builtins.TypedArray.slice", TYPED_ARRAY_ES2015_DATA_CORRECTIONS_INPUT],
["javascript.builtins.TypedArray.includes", TYPED_ARRAY_ES2016_DATA_CORRECTIONS_INPUT],
["javascript.builtins.TypedArray.keys", TYPED_ARRAY_KEYS_VALUES_ENTRIES_ITERATOR_DATA_CORRECTIONS_INPUT],
["javascript.builtins.TypedArray.values", TYPED_ARRAY_KEYS_VALUES_ENTRIES_ITERATOR_DATA_CORRECTIONS_INPUT],
["javascript.builtins.TypedArray.entries", TYPED_ARRAY_KEYS_VALUES_ENTRIES_ITERATOR_DATA_CORRECTIONS_INPUT],
["javascript.builtins.TypedArray.@@iterator", TYPED_ARRAY_KEYS_VALUES_ENTRIES_ITERATOR_DATA_CORRECTIONS_INPUT],
["javascript.builtins.TypedArray.@@species", TYPED_ARRAY_SPECIES_DATA_CORRECTIONS_INPUT],
["javascript.builtins.TypedArray", TYPED_ARRAY_BASE_DATA_CORRECTIONS_INPUT],
["javascript.builtins.Int8Array", TYPED_ARRAY_BASE_DATA_CORRECTIONS_INPUT],
["javascript.builtins.Int16Array", TYPED_ARRAY_BASE_DATA_CORRECTIONS_INPUT],
["javascript.builtins.Int32Array", TYPED_ARRAY_BASE_DATA_CORRECTIONS_INPUT],
["javascript.builtins.Float32Array", TYPED_ARRAY_BASE_DATA_CORRECTIONS_INPUT],
["javascript.builtins.Float64Array", TYPED_ARRAY_BASE_DATA_CORRECTIONS_INPUT],
["javascript.builtins.Uint8Array", TYPED_ARRAY_BASE_DATA_CORRECTIONS_INPUT],
["javascript.builtins.Uint8ClampedArray", TYPED_ARRAY_BASE_DATA_CORRECTIONS_INPUT],
["javascript.builtins.Uint16ClampedArray", TYPED_ARRAY_BASE_DATA_CORRECTIONS_INPUT],
["javascript.builtins.Uint32ClampedArray", TYPED_ARRAY_BASE_DATA_CORRECTIONS_INPUT],
[
"javascript.builtins.String.@@iterator",
{
android: rangeCorrection("chrome", CaniuseSupportKind.AVAILABLE, `38`),
chrome: rangeCorrection("chrome", CaniuseSupportKind.AVAILABLE, `38`),
and_chr: rangeCorrection("and_chr", CaniuseSupportKind.AVAILABLE, `38`),
edge: rangeCorrection("edge", CaniuseSupportKind.AVAILABLE, `12`),
opera: rangeCorrection("opera", CaniuseSupportKind.AVAILABLE, `25`),
op_mob: rangeCorrection("op_mob", CaniuseSupportKind.AVAILABLE, `25`),
firefox: rangeCorrection("firefox", CaniuseSupportKind.AVAILABLE, `36`),
and_ff: rangeCorrection("and_ff", CaniuseSupportKind.AVAILABLE, `36`),
safari: rangeCorrection("safari", CaniuseSupportKind.AVAILABLE, `9`),
ios_saf: rangeCorrection("ios_saf", CaniuseSupportKind.AVAILABLE, `9`),
samsung: rangeCorrection("samsung", CaniuseSupportKind.AVAILABLE, `3`)
}
],
[
"javascript.builtins.Symbol.asyncIterator",
{
android: rangeCorrection("android", CaniuseSupportKind.AVAILABLE, `63`),
chrome: rangeCorrection("chrome", CaniuseSupportKind.AVAILABLE, `63`),
and_chr: rangeCorrection("and_chr", CaniuseSupportKind.AVAILABLE, `63`),
opera: rangeCorrection("opera", CaniuseSupportKind.AVAILABLE, `50`),
op_mob: rangeCorrection("op_mob", CaniuseSupportKind.AVAILABLE, `50`),
firefox: rangeCorrection("firefox", CaniuseSupportKind.AVAILABLE, `57`),
and_ff: rangeCorrection("and_ff", CaniuseSupportKind.AVAILABLE, `57`),
safari: rangeCorrection("safari", CaniuseSupportKind.AVAILABLE, `11.1`),
ios_saf: rangeCorrection("ios_saf", CaniuseSupportKind.AVAILABLE, `11.1`)
}
],
[
"javascript.builtins.Array.@@species",
{
android: rangeCorrection("android", CaniuseSupportKind.AVAILABLE, `51`),
// MDN reports that it doesn't support Array.@@species, but it does and has done since Chrome v51
chrome: rangeCorrection("chrome", CaniuseSupportKind.AVAILABLE, `51`),
// MDN reports that it doesn't support Array.@@species, but it does and has done since Chrome for Android v51
and_chr: rangeCorrection("and_chr", CaniuseSupportKind.AVAILABLE, `51`),
// MDN reports that it doesn't support Array.@@species, but it does and has done since Edge v14
edge: rangeCorrection("edge", CaniuseSupportKind.AVAILABLE, `14`),
// MDN reports that it doesn't support Array.@@species, but it does and has done since Firefox v41
firefox: rangeCorrection("firefox", CaniuseSupportKind.AVAILABLE, `41`),
// MDN reports that it doesn't support Array.@@species, but it does and has done since Firefox for Android v41
and_ff: rangeCorrection("and_ff", CaniuseSupportKind.AVAILABLE, `41`),
// MDN reports that it doesn't support Array.@@species, but it does and has done since Opera v38
opera: rangeCorrection("opera", CaniuseSupportKind.AVAILABLE, `38`),
// MDN reports that it doesn't support Array.@@species, but it does and has done since Opera for Android v38
op_mob: rangeCorrection("op_mob", CaniuseSupportKind.AVAILABLE, `38`),
// MDN reports that it doesn't support Array.@@species, but it does and has done since Safari v10
safari: rangeCorrection("safari", CaniuseSupportKind.AVAILABLE, `10`),
// MDN reports that it doesn't support Array.@@species, but it does and has done since Safari for iOS v10
ios_saf: rangeCorrection("ios_saf", CaniuseSupportKind.AVAILABLE, `10`)
}
],
[
"javascript.builtins.Date.@@toPrimitive",
{
android: rangeCorrection("android", CaniuseSupportKind.AVAILABLE, `48`),
// MDN reports that it doesn't support Date.@@toPrimitive, but it does and has done since Chrome v48
chrome: rangeCorrection("chrome", CaniuseSupportKind.AVAILABLE, `48`),
// MDN reports that it doesn't support Date.@@toPrimitive, but it does and has done since Chrome for Android v48
and_chr: rangeCorrection("and_chr", CaniuseSupportKind.AVAILABLE, `48`),
// MDN reports that it doesn't support Date.@@toPrimitive, but it does and has done in all Edge versions
edge: rangeCorrection("edge", CaniuseSupportKind.AVAILABLE),
// MDN reports that it doesn't support Date.@@toPrimitive, but it does and has done since Firefox v44
firefox: rangeCorrection("firefox", CaniuseSupportKind.AVAILABLE, `44`),
// MDN reports that it doesn't support Date.@@toPrimitive, but it does and has done since Firefox for Android v44
and_ff: rangeCorrection("and_ff", CaniuseSupportKind.AVAILABLE, `44`),
// MDN reports that it doesn't support Date.@@toPrimitive, but it does and has done since Opera v35
opera: rangeCorrection("opera", CaniuseSupportKind.AVAILABLE, `35`),
// MDN reports that it doesn't support Date.@@toPrimitive, but it does and has done since Opera for Android v35
op_mob: rangeCorrection("op_mob", CaniuseSupportKind.AVAILABLE, `35`),
// MDN reports that it doesn't support Date.@@toPrimitive, but it does and has done since Safari v10
safari: rangeCorrection("safari", CaniuseSupportKind.AVAILABLE, `10`),
// MDN reports that it doesn't support Date.@@toPrimitive, but it does and has done since Safari for iOS v10
ios_saf: rangeCorrection("ios_saf", CaniuseSupportKind.AVAILABLE, `10`),
// MDN reports that it doesn't support the Date.@@toPrimitive method, but it does and has done for all Samsung Internet versions
samsung: rangeCorrection("samsung", CaniuseSupportKind.AVAILABLE)
}
],
[
"fetch",
{
edge: [
{
// Caniuse reports that Microsoft Edge has been supporting fetch since v14, but the implementation was quite unstable until v15
kind: CaniuseSupportKind.UNAVAILABLE,
version: "14"
}
]
}
],
[
"api.Window",
{
chrome: rangeCorrection("chrome", CaniuseSupportKind.UNAVAILABLE, `0`, `18`),
safari: rangeCorrection("safari", CaniuseSupportKind.UNAVAILABLE, `0`, `5.1`),
ie: rangeCorrection("ie", CaniuseSupportKind.UNAVAILABLE, `0`, `7`),
opera: rangeCorrection("safari", CaniuseSupportKind.UNAVAILABLE, `0`, `11.1`)
}
],
[
"javascript.builtins.String.matchAll",
{
samsung: rangeCorrection("samsung", CaniuseSupportKind.UNAVAILABLE, `0`, `9.4`)
}
],
[
"resizeobserver",
{
safari: rangeCorrection("safari", CaniuseSupportKind.UNAVAILABLE, `0`)
}
]
/* eslint-enable @typescript-eslint/naming-convention */
];
/**
* A Map between caniuse features and corrections to apply (see above)
* @type {Map<string, CaniuseBrowserCorrection>}
*/
const FEATURE_TO_BROWSER_DATA_CORRECTIONS_MAP = new Map(FEATURE_TO_BROWSER_DATA_CORRECTIONS_INPUT);
/**
* Returns the input query, but extended with the given options
*/
function extendQueryWith(query, extendWith) {
const normalizedExtendWith = Array.isArray(extendWith) ? extendWith : [extendWith];
return [...new Set([...query, ...normalizedExtendWith])];
}
/**
* Normalizes the given Browserslist
*/
function normalizeBrowserslist(browserslist) {
const result = Browserslist(browserslist);
// Caniuse only tracks the latest Browser version for Android-based browsers,
// so we'll need to add the relevant details back in after normalizing the Browserslist
// to make sure comparsions won't fail
const inputBrowserslist = Array.isArray(browserslist) ? browserslist : [browserslist];
for (const browser of ["and_ff", "and_chr", "and_uc", "and_qq", "baidu", "op_mini"]) {
const versions = getSortedBrowserVersions(browser);
for (const entry of inputBrowserslist) {
if (!entry.startsWith(browser))
continue;
const directMatch = entry.match(new RegExp(`${browser} (\\d+.*)`));
if (directMatch != null) {
const candidate = `${browser} ${directMatch[1]}`;
if (!result.includes(candidate)) {
result.push(candidate);
}
}
else {
const greaterThanOrEqualsMatch = entry.match(new RegExp(`${browser} >= (\\d+)`));
if (greaterThanOrEqualsMatch != null) {
let currentMajor = Number(greaterThanOrEqualsMatch[1]);
while (true) {
const candidate = `${browser} ${currentMajor}`;
if (!result.includes(candidate)) {
result.push(candidate);
currentMajor++;
if (Number(getClosestMatchingBrowserVersion(browser, String(currentMajor), versions)) <= currentMajor)
break;
}
else {
break;
}
}
}
}
}
}
return result.sort();
}
/**
* Returns the input query, but extended with 'unreleased versions'
*/
function extendQueryWithUnreleasedVersions(query, browsers) {
return extendQueryWith(query, Array.from(browsers).map(browser => `unreleased ${browser} versions`));
}
/**
* Generates a Browserslist based on browser support for the given features
*/
function browsersWithSupportForFeatures(...features) {
const { query, browsers } = browserSupportForFeaturesCommon(">=", ...features);
return extendQueryWithUnreleasedVersions(query, browsers);
}
/**
* Returns true if the given Browserslist supports the given EcmaVersion
*/
function browserslistSupportsEcmaVersion(browserslist, version) {
switch (version) {
case "es3":
// ES3 is the lowest possible target and will always be treated as supported
return true;
case "es5":
return browserslistSupportsFeatures(browserslist, ...ES5_FEATURES);
case "es2015":
return browserslistSupportsFeatures(browserslist, ...ES2015_FEATURES);
case "es2016":
return browserslistSupportsFeatures(browserslist, ...ES2016_FEATURES);
case "es2017":
return browserslistSupportsFeatures(browserslist, ...ES2017_FEATURES);
case "es2018":
return browserslistSupportsFeatures(browserslist, ...ES2018_FEATURES);
case "es2019":
return browserslistSupportsFeatures(browserslist, ...ES2019_FEATURES);
case "es2020":
return browserslistSupportsFeatures(browserslist, ...ES2020_FEATURES);
case "es2021":
return browserslistSupportsFeatures(browserslist, ...ES2021_FEATURES);
case "es2022":
return browserslistSupportsFeatures(browserslist, ...ES2022_FEATURES);
case "es2023":
return browserslistSupportsFeatures(browserslist, ...ES2023_FEATURES);
}
}
/**
* Returns the appropriate Ecma version for the given Browserslist
*/
function getAppropriateEcmaVersionForBrowserslist(browserslist) {
if (browserslistSupportsEcmaVersion(browserslist, "es2023"))
return "es2023";
if (browserslistSupportsEcmaVersion(browserslist, "es2022"))
return "es2022";
if (browserslistSupportsEcmaVersion(browserslist, "es2021"))
return "es2021";
if (browserslistSupportsEcmaVersion(browserslist, "es2020"))
return "es2020";
if (browserslistSupportsEcmaVersion(browserslist, "es2019"))
return "es2019";
if (browserslistSupportsEcmaVersion(browserslist, "es2018"))
return "es2018";
else if (browserslistSupportsEcmaVersion(browserslist, "es2017"))
return "es2017";
else if (browserslistSupportsEcmaVersion(browserslist, "es2016"))
return "es2016";
else if (browserslistSupportsEcmaVersion(browserslist, "es2015"))
return "es2015";
else if (browserslistSupportsEcmaVersion(browserslist, "es5"))
return "es5";
else
return "es3";
}
/**
* Generates a Browserslist based on browser support for the given ECMA version
*/
function browsersWithSupportForEcmaVersion(version) {
switch (version) {
case "es3":
return browsersWithoutSupportForFeatures(...ES5_FEATURES);
case "es5":
return browsersWithSupportForFeatures(...ES5_FEATURES);
case "es2015":
return browsersWithSupportForFeatures(...ES2015_FEATURES);
case "es2016":
return browsersWithSupportForFeatures(...ES2016_FEATURES);
case "es2017":
return browsersWithSupportForFeatures(...ES2017_FEATURES);
case "es2018":
return browsersWithSupportForFeatures(...ES2018_FEATURES);
case "es2019":
return browsersWithSupportForFeatures(...ES2019_FEATURES);
case "es2020":
return browsersWithSupportForFeatures(...ES2020_FEATURES);
case "es2021":
return browsersWithSupportForFeatures(...ES2021_FEATURES);
case "es2022":
return browsersWithSupportForFeatures(...ES2022_FEATURES);
case "es2023":
return browsersWithSupportForFeatures(...ES2023_FEATURES);
}
}
/**
* Returns true if the given browserslist support all of the given features
*/
function browserslistSupportsFeatures(browserslist, ...features) {
// First, generate an ideal browserslist that would target the given features exactly
const normalizedIdealBrowserslist = normalizeBrowserslist(browsersWithSupportForFeatures(...features));
// Now, normalize the input browserslist
const normalizedInputBrowserslist = normalizeBrowserslist(browserslist);
// Now, compare the two and see if they align. If they do, the input browserslist *does* support all of the given features.
// They align if all members of the input browserslist are included in the ideal browserslist
return normalizedInputBrowserslist.every(option => normalizedIdealBrowserslist.includes(option));
}
/**
* Generates a Browserslist based