573 lines
20 KiB
TypeScript
573 lines
20 KiB
TypeScript
// Run "make compat-table" to run this code
|
|
// Run "make update-compat-table" to update the data sources
|
|
|
|
import child_process = require('child_process')
|
|
import fs = require('fs')
|
|
import path = require('path')
|
|
import { generateTableForJS } from './js_table'
|
|
import { generateTableForCSS } from './css_table'
|
|
import * as caniuse from './caniuse'
|
|
import * as mdn from './mdn'
|
|
|
|
export type Engine = keyof typeof engines
|
|
export const engines = {
|
|
Chrome: true,
|
|
Deno: true,
|
|
Edge: true,
|
|
ES: true,
|
|
Firefox: true,
|
|
Hermes: true,
|
|
IE: true,
|
|
IOS: true,
|
|
Node: true,
|
|
Opera: true,
|
|
Rhino: true,
|
|
Safari: true,
|
|
}
|
|
|
|
export type JSFeature = keyof typeof jsFeatures
|
|
export const jsFeatures = {
|
|
ArbitraryModuleNamespaceNames: true,
|
|
ArraySpread: true,
|
|
Arrow: true,
|
|
AsyncAwait: true,
|
|
AsyncGenerator: true,
|
|
Bigint: true,
|
|
Class: true,
|
|
ClassField: true,
|
|
ClassPrivateAccessor: true,
|
|
ClassPrivateBrandCheck: true,
|
|
ClassPrivateField: true,
|
|
ClassPrivateMethod: true,
|
|
ClassPrivateStaticAccessor: true,
|
|
ClassPrivateStaticField: true,
|
|
ClassPrivateStaticMethod: true,
|
|
ClassStaticBlocks: true,
|
|
ClassStaticField: true,
|
|
ConstAndLet: true,
|
|
Decorators: true,
|
|
DefaultArgument: true,
|
|
Destructuring: true,
|
|
DynamicImport: true,
|
|
ExponentOperator: true,
|
|
ExportStarAs: true,
|
|
ForAwait: true,
|
|
ForOf: true,
|
|
FromBase64: true,
|
|
FunctionNameConfigurable: true,
|
|
FunctionOrClassPropertyAccess: true,
|
|
Generator: true,
|
|
Hashbang: true,
|
|
ImportAssertions: true,
|
|
ImportAttributes: true,
|
|
ImportDefer: true,
|
|
ImportMeta: true,
|
|
ImportSource: true,
|
|
InlineScript: true,
|
|
LogicalAssignment: true,
|
|
NestedRestBinding: true,
|
|
NewTarget: true,
|
|
NodeColonPrefixImport: true,
|
|
NodeColonPrefixRequire: true,
|
|
NullishCoalescing: true,
|
|
ObjectAccessors: true,
|
|
ObjectExtensions: true,
|
|
ObjectRestSpread: true,
|
|
OptionalCatchBinding: true,
|
|
OptionalChain: true,
|
|
RegexpDotAllFlag: true,
|
|
RegexpLookbehindAssertions: true,
|
|
RegexpMatchIndices: true,
|
|
RegexpNamedCaptureGroups: true,
|
|
RegexpSetNotation: true,
|
|
RegexpStickyAndUnicodeFlags: true,
|
|
RegexpUnicodePropertyEscapes: true,
|
|
RestArgument: true,
|
|
TemplateLiteral: true,
|
|
TopLevelAwait: true,
|
|
TypeofExoticObjectIsObject: true,
|
|
UnicodeEscapes: true,
|
|
Using: true,
|
|
}
|
|
|
|
export type CSSFeature = keyof typeof cssFeatures
|
|
export const cssFeatures = {
|
|
ColorFunctions: true,
|
|
GradientDoublePosition: true,
|
|
GradientInterpolation: true,
|
|
GradientMidpoints: true,
|
|
HexRGBA: true,
|
|
HWB: true,
|
|
InlineStyle: true,
|
|
InsetProperty: true,
|
|
IsPseudoClass: true,
|
|
MediaRange: true,
|
|
Modern_RGB_HSL: true,
|
|
Nesting: true,
|
|
RebeccaPurple: true,
|
|
}
|
|
|
|
export type CSSProperty = keyof typeof cssProperties
|
|
export const cssProperties = {
|
|
DAppearance: true,
|
|
DBackdropFilter: true,
|
|
DBackgroundClip: true,
|
|
DBoxDecorationBreak: true,
|
|
DClipPath: true,
|
|
DFontKerning: true,
|
|
DHeight: true,
|
|
DHyphens: true,
|
|
DInitialLetter: true,
|
|
DMask: true,
|
|
DMaskComposite: true,
|
|
DMaskImage: true,
|
|
DMaskOrigin: true,
|
|
DMaskPosition: true,
|
|
DMaskRepeat: true,
|
|
DMaskSize: true,
|
|
DMaxHeight: true,
|
|
DMaxWidth: true,
|
|
DMinHeight: true,
|
|
DMinWidth: true,
|
|
DPosition: true,
|
|
DPrintColorAdjust: true,
|
|
DTabSize: true,
|
|
DTextDecorationColor: true,
|
|
DTextDecorationLine: true,
|
|
DTextDecorationSkip: true,
|
|
DTextEmphasisColor: true,
|
|
DTextEmphasisPosition: true,
|
|
DTextEmphasisStyle: true,
|
|
DTextOrientation: true,
|
|
DTextSizeAdjust: true,
|
|
DUserSelect: true,
|
|
DWidth: true,
|
|
}
|
|
|
|
export interface Support {
|
|
force?: boolean
|
|
passed?: number
|
|
failed?: Set<string>
|
|
}
|
|
|
|
export interface VersionRange {
|
|
start: number[]
|
|
end?: number[]
|
|
}
|
|
|
|
export interface PrefixData {
|
|
engine: Engine
|
|
prefix: string
|
|
withoutPrefix?: number[]
|
|
}
|
|
|
|
export type SupportMap<F extends string> = Record<F, Partial<Record<Engine, Record<string, Support>>>>
|
|
export type VersionRangeMap<F extends string> = Partial<Record<F, Partial<Record<Engine, VersionRange[]>>>>
|
|
export type WhyNotMap<F extends string> = Partial<Record<F, Partial<Record<Engine, string[]>>>>
|
|
export type CSSPrefixMap = Partial<Record<CSSProperty, PrefixData[]>>
|
|
|
|
const compareVersions = (a: number[], b: number[]): number => {
|
|
let diff = a[0] - b[0]
|
|
if (!diff) {
|
|
diff = (a[1] || 0) - (b[1] || 0)
|
|
if (!diff) {
|
|
diff = (a[2] || 0) - (b[2] || 0)
|
|
}
|
|
}
|
|
return diff
|
|
}
|
|
|
|
const mergeSupportMaps = <F extends string>(to: SupportMap<F>, from: SupportMap<F>): void => {
|
|
for (const feature in from) {
|
|
const fromEngines = from[feature as F]
|
|
const toEngines = to[feature as F] || (to[feature as F] = {})
|
|
|
|
for (const engine in fromEngines) {
|
|
const fromVersions = fromEngines[engine as Engine]
|
|
const toVersions = toEngines[engine as Engine] || (toEngines[engine as Engine] = {})
|
|
|
|
for (const version in fromVersions) {
|
|
if (version in toVersions) {
|
|
throw new Error(`Merge conflict with feature=${feature} engine=${engine} version=${version}`)
|
|
}
|
|
|
|
toVersions[version] = fromVersions[version]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const mergePrefixMaps = (to: CSSPrefixMap, from: CSSPrefixMap): void => {
|
|
for (const property in from) {
|
|
if (property in to) {
|
|
throw new Error(`Merge conflict with property=${property}`)
|
|
}
|
|
to[property as CSSProperty] = from[property as CSSProperty]
|
|
}
|
|
}
|
|
|
|
const supportMapToVersionRanges = <F extends string>(supportMap: SupportMap<F>): [VersionRangeMap<F>, WhyNotMap<F>] => {
|
|
const versionRangeMap: VersionRangeMap<F> = {}
|
|
const whyNotMap: WhyNotMap<F> = {}
|
|
|
|
for (const feature in supportMap) {
|
|
const engines = supportMap[feature as F]
|
|
const featureMap: Partial<Record<Engine, VersionRange[]>> = {}
|
|
const whyNotByEngine: Partial<Record<Engine, string[]>> = {}
|
|
|
|
// Compute the maximum number of tests that any one engine has passed
|
|
let maxPassed = 0
|
|
for (const engine in engines) {
|
|
const versions = engines[engine as Engine]
|
|
for (const version in versions) {
|
|
const { passed } = versions[version]
|
|
if (passed && passed > maxPassed) maxPassed = passed
|
|
}
|
|
}
|
|
|
|
for (const engine in engines) {
|
|
const versions = engines[engine as Engine]
|
|
const sortedVersions: { version: number[], supported: boolean, failed?: Set<string> }[] = []
|
|
|
|
for (const version in versions) {
|
|
const { force, passed, failed } = versions[version]
|
|
const parsed = version.split('.').map(x => +x)
|
|
sortedVersions.push({
|
|
version: parsed,
|
|
supported: force !== void 0 ? force :
|
|
// If no test failed but less than the maximum number of tests passed,
|
|
// that means we have partial data (some tests have never been run for
|
|
// those versions). This happens for really old browser versions that
|
|
// people can't even run anymore. We conservatively consider this
|
|
// feature to be unsupported if not all tests were run, since it could
|
|
// be dangerous to assume otherwise.
|
|
!failed && passed === maxPassed,
|
|
failed,
|
|
})
|
|
}
|
|
|
|
sortedVersions.sort((a, b) => compareVersions(a.version, b.version))
|
|
|
|
if (sortedVersions.length) {
|
|
const last = sortedVersions[sortedVersions.length - 1]
|
|
if (last.failed) whyNotByEngine[engine as Engine] = [...last.failed].sort()
|
|
}
|
|
|
|
const versionRanges: VersionRange[] = []
|
|
let i = 0
|
|
|
|
while (i < sortedVersions.length) {
|
|
const { version, supported } = sortedVersions[i++]
|
|
if (supported) {
|
|
while (i < sortedVersions.length && sortedVersions[i].supported) {
|
|
i++
|
|
}
|
|
const range: VersionRange = { start: version }
|
|
if (i < sortedVersions.length) range.end = sortedVersions[i].version
|
|
versionRanges.push(range)
|
|
}
|
|
}
|
|
|
|
// The target is typically used to mean "make sure it works in this
|
|
// version and later". So we just take the last version range here.
|
|
//
|
|
// However, we make an exception for node since people sometimes use
|
|
// the target to build for the version of node that they currently
|
|
// have. Node has a discontiguous version range for the support of
|
|
// several features that people want to use.
|
|
if (versionRanges.length && engine as Engine !== 'Node') {
|
|
if (versionRanges[versionRanges.length - 1].end) {
|
|
// We say this engine doesn't support this feature at all if
|
|
// the feature is broken in the latest version of this engine.
|
|
// This sometimes happens when engines deliberately decide to
|
|
// not implement a feature of the language (e.g. Hermes).
|
|
continue
|
|
}
|
|
|
|
// Otherwise, only consider this feature to be supported for the
|
|
// last version range (i.e. the earliest version for which all
|
|
// later versions support this feature). Delete all version ranges
|
|
// before the last version range.
|
|
versionRanges.splice(0, versionRanges.length - 1)
|
|
}
|
|
|
|
if (versionRanges.length) {
|
|
featureMap[engine as Engine] = versionRanges
|
|
}
|
|
}
|
|
|
|
versionRangeMap[feature as F] = featureMap
|
|
whyNotMap[feature as F] = whyNotByEngine
|
|
}
|
|
|
|
return [versionRangeMap, whyNotMap]
|
|
}
|
|
|
|
const installGitHubDependencies = (): void => {
|
|
const updateCommits = process.argv.includes('--update')
|
|
const jsonPath = path.join(__dirname, 'package.json')
|
|
const jsonData = JSON.parse(fs.readFileSync(jsonPath, 'utf8'))
|
|
|
|
Object.keys(jsonData.githubDependencies).forEach(repo => {
|
|
const fullPath = path.join(__dirname, 'repos', repo)
|
|
if (!fs.existsSync(fullPath)) {
|
|
fs.mkdirSync(fullPath, { recursive: true })
|
|
child_process.execFileSync('git', ['clone', '-b', 'gh-pages', `https://github.com/${repo}.git`, fullPath], { cwd: fullPath, stdio: 'inherit' })
|
|
}
|
|
|
|
child_process.execFileSync('git', ['fetch'], { cwd: fullPath, stdio: 'inherit' })
|
|
|
|
if (updateCommits) {
|
|
child_process.execFileSync('git', ['reset', '--hard', '--quiet', 'origin/gh-pages'], { cwd: fullPath, stdio: 'inherit' })
|
|
const commit = child_process.execFileSync('git', ['rev-parse', 'HEAD'], { cwd: fullPath }).toString().trim()
|
|
jsonData.githubDependencies[repo] = commit
|
|
} else {
|
|
const commit = jsonData.githubDependencies[repo]
|
|
child_process.execFileSync('git', ['reset', '--hard', '--quiet', commit], { cwd: fullPath, stdio: 'inherit' })
|
|
}
|
|
})
|
|
|
|
if (updateCommits) {
|
|
fs.writeFileSync(jsonPath, JSON.stringify(jsonData, null, 2) + '\n')
|
|
}
|
|
}
|
|
|
|
// These dependencies are not published on npm and are instead pulled from GitHub (but pinned to a specific commit)
|
|
installGitHubDependencies()
|
|
|
|
import('./compat-table').then(compatTable => {
|
|
const js: SupportMap<JSFeature> = {} as SupportMap<JSFeature>
|
|
for (const feature in jsFeatures) js[feature as JSFeature] = {}
|
|
|
|
mergeSupportMaps(js, compatTable.js)
|
|
mergeSupportMaps(js, caniuse.js)
|
|
mergeSupportMaps(js, mdn.js)
|
|
|
|
// ES5 features
|
|
js.ObjectAccessors.ES = { 5: { force: true } }
|
|
js.ObjectAccessors.Node = { '0.4': { force: true } } // "node-compat-table" doesn't appear to cover ES5 features...
|
|
|
|
// ES6/ES2015 features
|
|
js.ArraySpread.ES = { 2015: { force: true } }
|
|
js.Arrow.ES = { 2015: { force: true } }
|
|
js.Class.ES = { 2015: { force: true } }
|
|
js.ConstAndLet.ES = { 2015: { force: true } }
|
|
js.DefaultArgument.ES = { 2015: { force: true } }
|
|
js.Destructuring.ES = { 2015: { force: true } }
|
|
js.DynamicImport.ES = { 2015: { force: true } }
|
|
js.ForOf.ES = { 2015: { force: true } }
|
|
js.FunctionNameConfigurable.ES = { 2015: { force: true } }
|
|
js.Generator.ES = { 2015: { force: true } }
|
|
js.NewTarget.ES = { 2015: { force: true } }
|
|
js.ObjectExtensions.ES = { 2015: { force: true } }
|
|
js.RegexpStickyAndUnicodeFlags.ES = { 2015: { force: true } }
|
|
js.RestArgument.ES = { 2015: { force: true } }
|
|
js.TemplateLiteral.ES = { 2015: { force: true } }
|
|
js.UnicodeEscapes.ES = { 2015: { force: true } }
|
|
|
|
// ES2016 features
|
|
js.ExponentOperator.ES = { 2016: { force: true } }
|
|
js.NestedRestBinding.ES = { 2016: { force: true } }
|
|
|
|
// ES2017 features
|
|
js.AsyncAwait.ES = { 2017: { force: true } }
|
|
|
|
// ES2018 features
|
|
js.AsyncGenerator.ES = { 2018: { force: true } }
|
|
js.ForAwait.ES = { 2018: { force: true } }
|
|
js.ObjectRestSpread.ES = { 2018: { force: true } }
|
|
js.RegexpDotAllFlag.ES = { 2018: { force: true } }
|
|
js.RegexpLookbehindAssertions.ES = { 2018: { force: true } }
|
|
js.RegexpNamedCaptureGroups.ES = { 2018: { force: true } }
|
|
js.RegexpUnicodePropertyEscapes.ES = { 2018: { force: true } }
|
|
|
|
// ES2019 features
|
|
js.OptionalCatchBinding.ES = { 2019: { force: true } }
|
|
|
|
// ES2020 features
|
|
js.Bigint.ES = { 2020: { force: true } }
|
|
js.ExportStarAs.ES = { 2020: { force: true } }
|
|
js.ImportMeta.ES = { 2020: { force: true } }
|
|
js.NullishCoalescing.ES = { 2020: { force: true } }
|
|
js.OptionalChain.ES = { 2020: { force: true } }
|
|
js.TypeofExoticObjectIsObject.ES = { 2020: { force: true } } // https://github.com/tc39/ecma262/pull/1441
|
|
|
|
// ES2021 features
|
|
js.LogicalAssignment.ES = { 2021: { force: true } }
|
|
|
|
// ES2022 features
|
|
js.ClassField.ES = { 2022: { force: true } }
|
|
js.ClassPrivateAccessor.ES = { 2022: { force: true } }
|
|
js.ClassPrivateBrandCheck.ES = { 2022: { force: true } }
|
|
js.ClassPrivateField.ES = { 2022: { force: true } }
|
|
js.ClassPrivateMethod.ES = { 2022: { force: true } }
|
|
js.ClassPrivateStaticAccessor.ES = { 2022: { force: true } }
|
|
js.ClassPrivateStaticField.ES = { 2022: { force: true } }
|
|
js.ClassPrivateStaticMethod.ES = { 2022: { force: true } }
|
|
js.ClassStaticBlocks.ES = { 2022: { force: true } }
|
|
js.ClassStaticField.ES = { 2022: { force: true } }
|
|
js.TopLevelAwait.ES = { 2022: { force: true } }
|
|
js.ArbitraryModuleNamespaceNames.ES = { 2022: { force: true } }
|
|
js.RegexpMatchIndices.ES = { 2022: { force: true } }
|
|
|
|
// ES2023 features
|
|
js.Hashbang.ES = { 2023: { force: true } }
|
|
|
|
// ES2024 features
|
|
js.RegexpSetNotation.ES = { 2024: { force: true } }
|
|
|
|
// This is a problem specific to Internet Explorer. See https://github.com/tc39/ecma262/issues/1440
|
|
for (const engine in engines) {
|
|
if (engine as Engine !== 'ES' && engine as Engine !== 'IE') {
|
|
js.TypeofExoticObjectIsObject[engine as Engine] = { 0: { force: true } }
|
|
}
|
|
}
|
|
|
|
// This is a problem specific to JavaScriptCore. Some examples of when the
|
|
// problematic case happens (checked in Safari 12.1):
|
|
//
|
|
// ❱ x(function(y=-1){}.z=2)
|
|
// SyntaxError: Left hand side of operator '=' must be a reference.
|
|
//
|
|
// ❱ x(class{f(y=-1){}}.z=2)
|
|
// SyntaxError: Left hand side of operator '=' must be a reference.
|
|
//
|
|
// Some examples of cases that aren't problematic (checked in Safari 12.1):
|
|
//
|
|
// // Adding parentheses makes it ok
|
|
// x((function(y=-1){}).z=2)
|
|
// x((class{f(y=-1){}}).z=2)
|
|
//
|
|
// // Not using a unary operator in the default argument makes it ok
|
|
// x(function(y=1){}.z=2)
|
|
// x(class{f(y=1){}}.z=2)
|
|
//
|
|
// // Methods in object literals are not affected
|
|
// x({f(y=-1){}}.z=2)
|
|
//
|
|
// We don't attempt to reverse-engineer the specific conditions that cause JSC
|
|
// to exhibit the bug. Instead we just always wrap function and class literals
|
|
// when they are nested inside of a property access. This workaround is overly
|
|
// conservative but is the same thing that UglifyJS does to handle this case.
|
|
//
|
|
// See https://github.com/mishoo/UglifyJS/pull/2056 and https://github.com/evanw/esbuild/issues/3072
|
|
for (const engine in engines) {
|
|
if (engine as Engine !== 'Safari') {
|
|
js.FunctionOrClassPropertyAccess[engine as Engine] = { 0: { force: true } }
|
|
} else {
|
|
// These bugs are known to be fixed in Safari 16.3+
|
|
js.FunctionOrClassPropertyAccess.Safari = { '16.3': { force: true } }
|
|
}
|
|
}
|
|
|
|
// This is a special case. Node added support for it to both v12.20+ and v13.2+
|
|
// so the range is inconveniently discontiguous. Sources:
|
|
//
|
|
// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
|
|
// - https://github.com/nodejs/node/pull/35950
|
|
// - https://github.com/nodejs/node/pull/31974
|
|
//
|
|
js.DynamicImport.Node = {
|
|
'12.20': { force: true },
|
|
'13': { force: false },
|
|
'13.2': { force: true },
|
|
}
|
|
|
|
// Manually copied from https://nodejs.org/api/esm.html#node-imports
|
|
js.NodeColonPrefixImport.Node = {
|
|
'12.20': { force: true },
|
|
'13': { force: false },
|
|
'14.13.1': { force: true },
|
|
}
|
|
js.NodeColonPrefixRequire.Node = {
|
|
'14.18': { force: true },
|
|
'15': { force: false },
|
|
'16': { force: true },
|
|
}
|
|
js.NodeColonPrefixImport.ES = { 0: { force: true } }
|
|
js.NodeColonPrefixRequire.ES = { 0: { force: true } }
|
|
|
|
// Arbitrary Module Namespace Names
|
|
{
|
|
// From https://github.com/tc39/ecma262/pull/2154#issuecomment-825201030
|
|
js.ArbitraryModuleNamespaceNames.Chrome = { 90: { force: true } }
|
|
js.ArbitraryModuleNamespaceNames.Node = { 16: { force: true } }
|
|
|
|
// From https://bugzilla.mozilla.org/show_bug.cgi?id=1670044
|
|
js.ArbitraryModuleNamespaceNames.Firefox = { 87: { force: true } }
|
|
|
|
// From https://developer.apple.com/documentation/safari-release-notes/safari-14_1-release-notes
|
|
js.ArbitraryModuleNamespaceNames.Safari = { '14.1': { force: true } }
|
|
js.ArbitraryModuleNamespaceNames.IOS = { '14.5': { force: true } }
|
|
}
|
|
|
|
// Import assertions (note: these were removed from the JavaScript specification and never standardized)
|
|
{
|
|
js.ImportAssertions.Node = {
|
|
// From https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V16.md#16.14.0
|
|
'16.14': { force: true },
|
|
|
|
// Manually tested using a binary from https://nodejs.org/en/download/prebuilt-binaries
|
|
'22': { force: false },
|
|
}
|
|
|
|
// MDN data is wrong here: https://bugs.webkit.org/show_bug.cgi?id=251600
|
|
delete js.ImportAssertions.IOS
|
|
delete js.ImportAssertions.Safari
|
|
}
|
|
|
|
// Import attributes (the replacement for import assertions)
|
|
{
|
|
// Manually tested using binaries from https://nodejs.org/en/download/prebuilt-binaries
|
|
js.ImportAttributes.Node = {
|
|
'18.20': { force: true },
|
|
'19': { force: false },
|
|
'20.10': { force: true },
|
|
}
|
|
}
|
|
|
|
// MDN data is wrong here: https://www.chromestatus.com/feature/6482797915013120
|
|
js.ClassStaticBlocks.Chrome = { 91: { force: true } }
|
|
|
|
// WebKit has now been marked as not supporting top-level await because it
|
|
// turns out they have a pretty severe bug: Importing a module that uses
|
|
// top-level await twice causes the second import to fail. For more info see:
|
|
// https://bugs.webkit.org/show_bug.cgi?id=242740
|
|
//
|
|
// However, we're going to override this to say it's still supported because:
|
|
// - It always fails loudly when this happens (the import fails)
|
|
// - People would otherwise be blocked from using this feature at all in
|
|
// WebKit-based browsers
|
|
// - From what I understand, most use of top-level await is in the entry-point
|
|
// module, where this isn't an issue
|
|
// - When you bundle your code, nested modules become entry points so this
|
|
// issue also wouldn't come up
|
|
// - The top-level await implementation in esbuild isn't even correct for
|
|
// nested modules as they become linearized in the bundle (like Rollup, but
|
|
// not like Webpack)
|
|
js.TopLevelAwait.IOS = { 15: { force: true } }
|
|
js.TopLevelAwait.Safari = { 15: { force: true } }
|
|
|
|
// MDN data is incomplete here: https://github.com/mdn/browser-compat-data/issues/28228
|
|
js.FromBase64.Node = { 25: { force: true } }
|
|
|
|
const [jsVersionRanges, jsWhyNot] = supportMapToVersionRanges(js)
|
|
generateTableForJS(jsVersionRanges, jsWhyNot)
|
|
})
|
|
|
|
const css: SupportMap<CSSFeature> = {} as SupportMap<CSSFeature>
|
|
const cssPrefix: CSSPrefixMap = {}
|
|
for (const feature in cssFeatures) css[feature as CSSFeature] = {}
|
|
|
|
mergeSupportMaps(css, caniuse.css)
|
|
mergeSupportMaps(css, mdn.css)
|
|
mergePrefixMaps(cssPrefix, caniuse.cssPrefix)
|
|
mergePrefixMaps(cssPrefix, mdn.cssPrefix)
|
|
|
|
// MDN data is wrong here, Firefox still had gradient interpolation rendering
|
|
// bugs until version 137: https://bugzilla.mozilla.org/show_bug.cgi?id=1904106
|
|
css.GradientInterpolation.Firefox = { 137: { force: true } }
|
|
|
|
const [cssVersionRanges] = supportMapToVersionRanges(css)
|
|
generateTableForCSS(cssVersionRanges, cssPrefix)
|