Skip to content

Instantly share code, notes, and snippets.

@connorjclark
Last active December 5, 2021 23:23
Show Gist options
  • Save connorjclark/832ee48c82dc36d203e4a7710a470806 to your computer and use it in GitHub Desktop.
Save connorjclark/832ee48c82dc36d203e4a7710a470806 to your computer and use it in GitHub Desktop.
web platform latest stable features script
const browserCompat = require('@mdn/browser-compat-data');
const browsersToConsider = [
'chrome',
'edge',
'firefox',
'safari_ios',
'safari',
];
const releasesCache = {};
// Returns newest first.
function sortBrowserReleasesByReleaseDate(browser) {
if (releasesCache[browser]) return releasesCache[browser];
const releases = browserCompat.browsers[browser].releases;
const asArray = Object.entries(releases).map(entry => ({ version: entry[0], details: entry[1] }));
releasesCache[browser] = asArray.sort((a, b) => new Date(b.details.release_date) - new Date(a.details.release_date));
return asArray;
}
/**
* Stable.
* @param {Date} month
*/
function findBrowsersReleasedInMonth(month) {
const result = [];
for (const browser of Object.keys(browserCompat.browsers)) {
if (!browsersToConsider.includes(browser)) continue;
// To simplify, only consider the latest version from each browser.
const releaseEntry = sortBrowserReleasesByReleaseDate(browser).find(({ version, details }) => {
if (!details.release_date) return false;
const releaseDate = new Date(details.release_date);
return releaseDate.getYear() === month.getYear() && releaseDate.getMonth() === month.getMonth();
});
if (!releaseEntry) continue;
result.push({
browser,
version: releaseEntry.version,
});
}
return result;
}
/**
* @param {Date} month
*/
function findStableBrowsersInMonth(month) {
const result = [];
for (const browser of Object.keys(browserCompat.browsers)) {
if (!browsersToConsider.includes(browser)) continue;
const releaseEntries = sortBrowserReleasesByReleaseDate(browser).filter(({ details }) => {
if (!details.release_date) return false;
const releaseDate = new Date(details.release_date);
return month.getYear() > releaseDate.getYear() || (month.getYear() === releaseDate.getYear() && month.getMonth() > releaseDate.getMonth());
});
const releaseEntry = releaseEntries[0];
if (!releaseEntry) continue;
result.push({
browser,
version: releaseEntry.version,
});
}
return result;
}
function findBrowserVersionReleaseIndex(browser, version) {
const releases = sortBrowserReleasesByReleaseDate(browser);
return releases.findIndex(release => release.version === version);
}
const compatData = getCompatData();
function getCompatData() {
let compats = [];
function findCompats(obj, path = []) {
if (!(typeof obj === 'object' && obj)) return;
for (const [key, child] of Object.entries(obj)) {
if (child.__compat) {
compats.push({
id: [...path, key].join('.'),
compat: child.__compat,
});
} else {
findCompats(child, [...path, key]);
}
}
}
const { browser, ...rootCompatObj } = browserCompat;
findCompats(rootCompatObj);
return compats;
}
function getFeatureSetForBrowserVersion({ browser, version }) {
const features = [];
for (const feature of compatData) {
if (!feature.compat.support[browser]) continue;
const supportStatement = Array.isArray(feature.compat.support[browser]) ?
feature.compat.support[browser][0] :
feature.compat.support[browser];
if (!supportStatement.version_added) continue;
if (supportStatement.version_removed) continue;
if (supportStatement.flags) continue;
const isSupported = supportStatement.version_added === true ||
findBrowserVersionReleaseIndex(browser, version) <= findBrowserVersionReleaseIndex(browser, supportStatement.version_added);
if (isSupported) {
if (supportStatement.flags) console.log(feature.id, supportStatement);
features.push(feature.id);
}
}
browserCompat.javascript.operators.optional_chaining.__compat.support
return new Set(features);
}
function getFeatureSetForBrowserVersions(browserVerisons) {
return setIntersections(...browserVerisons.map(getFeatureSetForBrowserVersion));
}
function setIntersections(...sets) {
const result = new Set();
const [firstSet, ...rest] = sets;
for (const item of firstSet) {
if (rest.every(set => set.has(item))) {
result.add(item);
}
}
return result;
}
function setDifference(a, b) {
const result = new Set();
for (const item of a) {
if (b.has(item)) continue;
result.add(item);
}
for (const item of b) {
if (a.has(item)) continue;
result.add(item);
}
return result;
}
function yearInReview() {
for (let i = 1; i <= 12; i++) {
const lastMonth = new Date(i === 1 ? '2020/12/01' : `2021/${i - 1}/01`);
const thisMonth = new Date(`2021/${i}/01`);
const newReleases = findBrowsersReleasedInMonth(new Date(thisMonth));
const lastMonthStableBrowsers = findStableBrowsersInMonth(lastMonth);
const thisMonthStableBrowsers = findStableBrowsersInMonth(thisMonth);
const lastMonthFeatureSet = getFeatureSetForBrowserVersions(lastMonthStableBrowsers);
const thisMonthFeatureSet = getFeatureSetForBrowserVersions(thisMonthStableBrowsers);
const difference = setDifference(lastMonthFeatureSet, thisMonthFeatureSet);
console.log(`in ${thisMonth.toLocaleString('en-us', { month: 'short', year: 'numeric' })} these browsers were released:`, newReleases);
console.log(`and these features became stable across all major browsers:`, difference);
console.log('------------------------');
}
}
yearInReview();
// console.log(getFeatureSetForBrowserVersion({ browser: 'safari', version: '15.1' }).has('api.AudioWorkletNode'));
// console.log(browserCompat.api.AudioWorkletNode.__compat.support);
output (ran Nov 22 2021):
in Jan 2021 these browsers were released: [
{ browser: 'chrome', version: '88' },
{ browser: 'edge', version: '88' },
{ browser: 'firefox', version: '85' }
]
and these features became stable across all major browsers: Set(0) {}
------------------------
in Feb 2021 these browsers were released: [ { browser: 'firefox', version: '86' } ]
and these features became stable across all major browsers: Set(2) { 'css.selectors.is', 'css.selectors.where' }
------------------------
in Mar 2021 these browsers were released: [
{ browser: 'chrome', version: '89' },
{ browser: 'edge', version: '89' },
{ browser: 'firefox', version: '87' }
]
and these features became stable across all major browsers: Set(0) {}
------------------------
in Apr 2021 these browsers were released: [
{ browser: 'chrome', version: '90' },
{ browser: 'edge', version: '90' },
{ browser: 'firefox', version: '88' },
{ browser: 'safari', version: '14.1' },
{ browser: 'safari_ios', version: '14.5' }
]
and these features became stable across all major browsers: Set(1) { 'css.properties.text-decoration-thickness' }
------------------------
in May 2021 these browsers were released: [
{ browser: 'chrome', version: '91' },
{ browser: 'edge', version: '91' },
{ browser: 'firefox', version: '89' }
]
and these features became stable across all major browsers: Set(56) {
'api.AbstractRange',
'api.AudioContext',
'api.AudioParamMap',
'api.AudioWorklet',
'api.AudioWorkletGlobalScope',
'api.AudioWorkletNode',
'api.AudioWorkletProcessor',
'api.BaseAudioContext',
'api.ConstantSourceNode',
'api.IIRFilterNode',
'api.OfflineAudioContext',
'api.PannerNode',
'api.PerformancePaintTiming',
'api.StereoPannerNode',
'api.Worklet',
'api.WorkletGlobalScope',
'css.properties.border-block-color',
'css.properties.border-block-style',
'css.properties.border-block-width',
'css.properties.border-block',
'css.properties.border-inline-color',
'css.properties.border-inline-style',
'css.properties.border-inline-width',
'css.properties.border-inline',
'css.properties.column-gap.flex_context',
'css.properties.gap.flex_context',
'css.properties.inset-block-end',
'css.properties.inset-block-start',
'css.properties.inset-block',
'css.properties.inset-inline-end',
'css.properties.inset-inline-start',
'css.properties.inset-inline',
'css.properties.inset',
'css.properties.margin-block',
'css.properties.margin-inline',
'css.properties.padding-block',
'css.properties.padding-inline',
'css.properties.row-gap.flex_context',
'css.properties.scroll-margin-block-end',
'css.properties.scroll-margin-block-start',
'css.properties.scroll-margin-block',
'css.properties.scroll-margin-bottom',
'css.properties.scroll-margin-inline-end',
'css.properties.scroll-margin-inline-start',
'css.properties.scroll-margin-inline',
'css.properties.scroll-margin-left',
'css.properties.scroll-margin-right',
'css.properties.scroll-margin-top',
'css.properties.scroll-padding-bottom',
'css.properties.scroll-padding-left',
'css.properties.scroll-padding-right',
'css.properties.scroll-padding-top',
'css.properties.scroll-padding',
'css.selectors.file-selector-button',
'javascript.builtins.FinalizationRegistry',
'javascript.builtins.WeakRef'
}
------------------------
in Jun 2021 these browsers were released: []
and these features became stable across all major browsers: Set(0) {}
------------------------
in Jul 2021 these browsers were released: [
{ browser: 'chrome', version: '92' },
{ browser: 'edge', version: '92' },
{ browser: 'firefox', version: '90' }
]
and these features became stable across all major browsers: Set(0) {}
------------------------
in Aug 2021 these browsers were released: [
{ browser: 'chrome', version: '93' },
{ browser: 'firefox', version: '91' }
]
and these features became stable across all major browsers: Set(1) { 'css.properties.scroll-margin' }
------------------------
in Sep 2021 these browsers were released: [
{ browser: 'chrome', version: '94' },
{ browser: 'edge', version: '94' },
{ browser: 'firefox', version: '92' },
{ browser: 'safari', version: '15' },
{ browser: 'safari_ios', version: '15' }
]
and these features became stable across all major browsers: Set(2) { 'api.VisualViewport', 'css.properties.tab-size' }
------------------------
in Oct 2021 these browsers were released: [
{ browser: 'chrome', version: '95' },
{ browser: 'edge', version: '95' },
{ browser: 'firefox', version: '93' },
{ browser: 'safari', version: '15.1' },
{ browser: 'safari_ios', version: '15.1' }
]
and these features became stable across all major browsers: Set(113) {
'api.EXT_float_blend',
'api.FormDataEvent',
'api.ImageBitmapRenderingContext',
'api.MediaMetadata',
'api.MediaSession',
'api.SubmitEvent',
'api.WEBGL_color_buffer_float',
'api.WEBGL_draw_buffers',
'api.WebGL2RenderingContext',
'api.WebGLTransformFeedback',
'api.WebGLVertexArrayObject',
'api.createImageBitmap',
'css.properties.aspect-ratio',
'css.properties.border-end-end-radius',
'css.properties.border-end-start-radius',
'css.properties.border-start-end-radius',
'css.properties.border-start-start-radius',
'css.properties.scroll-padding-block-end',
'css.properties.scroll-padding-block-start',
'css.properties.scroll-padding-block',
'css.properties.scroll-padding-inline-end',
'css.properties.scroll-padding-inline-start',
'css.properties.scroll-padding-inline',
'javascript.builtins.BigInt64Array',
'javascript.builtins.BigUint64Array',
'webextensions.api.alarms.Alarm',
'webextensions.api.alarms.clear',
'webextensions.api.alarms.clearAll',
'webextensions.api.alarms.create',
'webextensions.api.alarms.get',
'webextensions.api.alarms.getAll',
'webextensions.api.alarms.onAlarm',
'webextensions.api.browserAction.ColorArray',
'webextensions.api.browserAction.ImageDataType',
'webextensions.api.browserAction.disable',
'webextensions.api.browserAction.enable',
'webextensions.api.browserAction.getBadgeBackgroundColor',
'webextensions.api.browserAction.getBadgeText',
'webextensions.api.browserAction.getPopup',
'webextensions.api.browserAction.getTitle',
'webextensions.api.browserAction.onClicked',
'webextensions.api.browserAction.setBadgeBackgroundColor',
'webextensions.api.browserAction.setBadgeText',
'webextensions.api.browserAction.setIcon',
'webextensions.api.browserAction.setPopup',
'webextensions.api.browserAction.setTitle',
'webextensions.api.commands.Command',
'webextensions.api.commands.getAll',
'webextensions.api.commands.onCommand',
'webextensions.api.cookies.Cookie',
'webextensions.api.cookies.CookieStore',
'webextensions.api.cookies.get',
'webextensions.api.cookies.getAll',
'webextensions.api.cookies.getAllCookieStores',
'webextensions.api.cookies.remove',
'webextensions.api.cookies.sameSiteStatus',
'webextensions.api.cookies.set',
'webextensions.api.extensionTypes.ImageDetails',
'webextensions.api.extensionTypes.ImageFormat',
'webextensions.api.extensionTypes.RunAt',
'webextensions.api.i18n.getAcceptLanguages',
'webextensions.api.i18n.getMessage',
'webextensions.api.i18n.getUILanguage',
'webextensions.api.pageAction.ImageDataType',
'webextensions.api.pageAction.getPopup',
'webextensions.api.pageAction.getTitle',
'webextensions.api.pageAction.onClicked',
'webextensions.api.pageAction.setIcon',
'webextensions.api.pageAction.setPopup',
'webextensions.api.pageAction.setTitle',
'webextensions.api.permissions.contains',
'webextensions.api.permissions.getAll',
'webextensions.api.permissions.onAdded',
'webextensions.api.permissions.onRemoved',
'webextensions.api.permissions.Permissions',
'webextensions.api.permissions.remove',
'webextensions.api.permissions.request',
'webextensions.api.runtime.MessageSender',
'webextensions.api.runtime.OnInstalledReason',
'webextensions.api.runtime.PlatformArch',
'webextensions.api.runtime.PlatformInfo',
'webextensions.api.runtime.PlatformOs',
'webextensions.api.runtime.Port',
'webextensions.api.runtime.connect',
'webextensions.api.runtime.connectNative',
'webextensions.api.runtime.getBackgroundPage',
'webextensions.api.runtime.getManifest',
'webextensions.api.runtime.getPlatformInfo',
'webextensions.api.runtime.getURL',
'webextensions.api.runtime.id',
'webextensions.api.runtime.lastError',
'webextensions.api.runtime.onConnect',
'webextensions.api.runtime.onInstalled',
'webextensions.api.runtime.onMessage',
'webextensions.api.runtime.onMessageExternal',
'webextensions.api.runtime.onStartup',
'webextensions.api.runtime.openOptionsPage',
'webextensions.api.runtime.reload',
'webextensions.api.runtime.sendMessage',
'webextensions.api.runtime.sendNativeMessage',
'webextensions.api.runtime.setUninstallURL',
'webextensions.api.storage.StorageArea',
'webextensions.api.storage.StorageChange',
'webextensions.api.storage.local',
'webextensions.api.storage.onChanged',
'webextensions.api.storage.sync',
'webextensions.api.webNavigation.getAllFrames',
'webextensions.api.webNavigation.getFrame',
'webextensions.api.webNavigation.onBeforeNavigate',
'webextensions.api.webNavigation.onCommitted',
'webextensions.api.webNavigation.onCompleted',
'webextensions.api.webNavigation.onDOMContentLoaded',
'webextensions.api.webNavigation.onErrorOccurred'
}
------------------------
in Nov 2021 these browsers were released: [
{ browser: 'chrome', version: '96' },
{ browser: 'firefox', version: '94' }
]
and these features became stable across all major browsers: Set(1) { 'api.PerformanceNavigationTiming' }
------------------------
in Dec 2021 these browsers were released: [ { browser: 'firefox', version: '95' } ]
and these features became stable across all major browsers: Set(1) { 'html.global_attributes.enterkeyhint' }
------------------------
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment