├── .gitignore ├── README.md ├── index.js ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | test.js -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # css-browser-support 2 | 3 | > Query for **CSS browser support data**, combined from caniuse and MDN, including version support started and global support percentages. 4 | 5 | ## Usage 6 | 7 | Install the package: 8 | 9 | ```bash 10 | npm i --save-dev css-browser-support 11 | ``` 12 | 13 | Then import it into your project: 14 | 15 | ```js 16 | const { cssBrowserSupport } = require("css-browser-support"); 17 | ``` 18 | 19 | And call it by passing a string or an array of strings containing the CSS features you'd like to query support: 20 | 21 | ```js 22 | cssBrowserSupport([ 23 | "aspect-ratio", 24 | "margin-inline", 25 | "border-radius", 26 | ":nth-last-child", 27 | "@layer", 28 | "gap", 29 | ]); 30 | ``` 31 | 32 | Returns an object that includes each browser for which support is available, example for `aspect-ratio`: 33 | 34 | ```js 35 | { 36 | 'aspect-ratio': { 37 | chrome: { 38 | sinceVersion: '88', 39 | flagged: true, 40 | globalSupport: 22.46, 41 | browserTitle: 'Chrome' 42 | }, 43 | chrome_android: { 44 | sinceVersion: '88', 45 | flagged: false, 46 | globalSupport: 41.34, 47 | browserTitle: 'Chrome Android' 48 | }, 49 | edge: { 50 | sinceVersion: '88', 51 | flagged: false, 52 | globalSupport: 3.88, 53 | browserTitle: 'Edge' 54 | }, 55 | // ... continued for all browsers 56 | globalSupport: 86.49 57 | } 58 | } 59 | ``` 60 | 61 | ## Supported CSS features 62 | 63 | The API is intended to work for passing features as you would write them in CSS. As such, a few things will not be available if they exist on MDN under an expanded name. For example, `>` would be available as `child`. 64 | 65 | Additionally, some features are nested and may be missed by the API. Exceptions are grid features (ex. `repeat()`), and color types (ex. `color-contrast()`) which have been explicitly included. 66 | 67 | Review the data from MDN: 68 | 69 | - [at-rules](https://github.com/mdn/browser-compat-data/tree/main/css/at-rules) 70 | - [properties](https://github.com/mdn/browser-compat-data/tree/main/css/properties) 71 | - [selectors](https://github.com/mdn/browser-compat-data/tree/main/css/selectors) 72 | - [types](https://github.com/mdn/browser-compat-data/tree/main/css/types) 73 | 74 | ### Special case: `gap` 75 | 76 | Since `gap` is a popular feature known to have been implemented for both flexbox and grid at different times, the API splits a request for `gap` to return support for both implementations. 77 | 78 | In your implementation, you'll want to check for an input of `gap` and then update to handle for the two returned keys of `gap - flexbox` and `gap - grid`. 79 | 80 | Example: 81 | 82 | ```js 83 | if (queries.includes("gap")) { 84 | queries.splice(queries.indexOf("gap"), 1); 85 | queries.push("gap - flexbox"); 86 | queries.push("gap - grid"); 87 | } 88 | ``` 89 | 90 | ## Implementing the data 91 | 92 | - if your implementation accepts properties with values, ex `margin-inline: auto`, you are responsible for removing values before passing the property to the API 93 | - due to the data returned from MDN, characters like `:` are stripped from selectors and pseudo-elements, and `@` is removed from at-rule, so for example `@layer` will be found in returned data as `layer` 94 | 95 | For an example on using this data, see my Eleventy plugin implementation: **@11tyrocks/eleventy-plugin-css-browser-support** 96 | 97 | - [npm](https://www.npmjs.com/package/@11tyrocks/eleventy-plugin-css-browser-support) 98 | - [GitHub repo](https://github.com/5t3ph/eleventy-plugin-css-browser-support) 99 | 100 | ## Browser list 101 | 102 | You can also import the full browser list as `BROWSERS`: 103 | 104 | ```js 105 | const { cssBrowserSupport, BROWSERS } = require("css-browser-support"); 106 | ``` 107 | 108 |
109 | View full browser list 110 | 111 | The list is as follows: 112 | 113 | ```js 114 | [ 115 | "chrome", 116 | "chrome_android", 117 | "edge", 118 | "firefox", 119 | "firefox_android", 120 | "ie", 121 | "opera", 122 | "safari", 123 | "safari_ios", 124 | "samsunginternet_android", 125 | ]; 126 | ``` 127 | 128 |
129 | 130 | ## Credits 131 | 132 | Two packages are being used to obtain support data: 133 | 134 | - [@mdn/browser-compat-data](https://www.npmjs.com/package/@mdn/browser-compat-data) 135 | - [caniuse-lite](https://www.npmjs.com/package/caniuse-lite) 136 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const bcd = require("@mdn/browser-compat-data"); 2 | const lite = require("caniuse-lite"); 3 | 4 | const BROWSERS = [ 5 | "chrome", 6 | "chrome_android", 7 | "edge", 8 | "firefox", 9 | "firefox_android", 10 | "ie", 11 | "opera", 12 | "safari", 13 | "safari_ios", 14 | "samsunginternet_android", 15 | ]; 16 | 17 | const CANIUSE_BROWSERS = { 18 | chrome: "chrome", 19 | chrome_android: "and_chr", 20 | edge: "edge", 21 | firefox: "firefox", 22 | firefox_android: "and_ff", 23 | ie: "ie", 24 | opera: "opera", 25 | safari: "safari", 26 | safari_ios: "ios_saf", 27 | samsunginternet_android: "samsung", 28 | }; 29 | 30 | const BROWSER_LABELS = { 31 | chrome: "Chrome", 32 | chrome_android: "Chrome Android", 33 | edge: "Edge", 34 | firefox: "Firefox", 35 | firefox_android: "Firefox for Android", 36 | ie: "Internet Explorer", 37 | opera: "Opera", 38 | safari: "Safari", 39 | safari_ios: "Safari on iOS", 40 | samsunginternet_android: "Samsung Internet", 41 | }; 42 | 43 | const cssBrowserSupport = (request) => { 44 | const report = {}; 45 | let list = Array.isArray(request) ? request : [request]; 46 | 47 | list = list.map((i) => i.trim()); 48 | 49 | if (list.includes("gap")) { 50 | list.splice(list.indexOf("gap"), 1); 51 | list.push("gap - flexbox"); 52 | list.push("gap - grid"); 53 | } 54 | 55 | for (let item of list) { 56 | item = item.trim().replace(/@|:|\(|\)*/g, ""); 57 | 58 | let itemType; 59 | if (item.includes("gap")) itemType = bcd.css.properties.gap; 60 | else if (item in bcd.css.properties) itemType = bcd.css.properties; 61 | else if (item in bcd.css.properties["grid-template-columns"]) 62 | itemType = bcd.css.properties["grid-template-columns"]; 63 | else if (item in bcd.css.selectors) itemType = bcd.css.selectors; 64 | else if (item in bcd.css.types) itemType = bcd.css.types; 65 | else if (item in bcd.css.types.color) itemType = bcd.css.types.color; 66 | else if (item in bcd.css["at-rules"]) itemType = bcd.css["at-rules"]; 67 | 68 | if (itemType) { 69 | let itemGlobalSupportAll = 0; 70 | let gapItem = false; 71 | 72 | if (item.includes("gap")) { 73 | gapItem = item; 74 | item = item.includes("flexbox") ? "flex_context" : "grid_context"; 75 | } 76 | 77 | const reportKey = gapItem ? gapItem : item; 78 | report[reportKey] = {}; 79 | 80 | BROWSERS.map((browser) => { 81 | let versionAddedProp; 82 | let flagged = false; 83 | 84 | const supportBrowser = itemType[item].__compat.support[browser]; 85 | 86 | if (Array.isArray(supportBrowser)) { 87 | // E.g. CSS property with prefixes 88 | if (supportBrowser[1].flags) { 89 | flagged = true; 90 | } 91 | versionAddedProp = supportBrowser[0].version_added; 92 | } else if (supportBrowser) { 93 | versionAddedProp = supportBrowser.version_added; 94 | 95 | if (supportBrowser.flags) { 96 | flagged = true; 97 | } 98 | } 99 | 100 | // Global usage 101 | let globalUsage = 0; 102 | if (versionAddedProp) { 103 | const caniuseBrowser = CANIUSE_BROWSERS[browser]; 104 | const browserVersions = lite.agents[caniuseBrowser].usage_global; 105 | 106 | for (let v in browserVersions) { 107 | if ( 108 | parseInt(v) >= versionAddedProp || 109 | v.includes(versionAddedProp) 110 | ) { 111 | globalUsage += browserVersions[v]; 112 | } 113 | } 114 | } 115 | 116 | itemGlobalSupportAll += globalUsage; 117 | 118 | report[reportKey][browser] = { 119 | sinceVersion: versionAddedProp, 120 | flagged, 121 | globalSupport: parseFloat(globalUsage.toFixed(2)), 122 | browserTitle: BROWSER_LABELS[browser], 123 | }; 124 | }); 125 | 126 | report[reportKey].globalSupport = parseFloat( 127 | itemGlobalSupportAll.toFixed(2) 128 | ); 129 | } 130 | } 131 | 132 | // Return false for empty report 133 | return Object.keys(report).length === 0 ? false : report; 134 | }; 135 | 136 | module.exports = { cssBrowserSupport, BROWSERS }; 137 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-browser-support", 3 | "version": "0.12.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "css-browser-support", 9 | "version": "0.12.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@mdn/browser-compat-data": "^5.6.4", 13 | "caniuse-lite": "^1.0.30001666" 14 | } 15 | }, 16 | "node_modules/@mdn/browser-compat-data": { 17 | "version": "5.6.4", 18 | "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-5.6.4.tgz", 19 | "integrity": "sha512-bOOF4GGzn0exmvNHpSWmTfOXB9beTpIFCm2KPY2UVoCdn1YVfr8heuHr1C++BYI9Tun8REgi5TNVdKbBs249CA==", 20 | "license": "CC0-1.0" 21 | }, 22 | "node_modules/caniuse-lite": { 23 | "version": "1.0.30001666", 24 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001666.tgz", 25 | "integrity": "sha512-gD14ICmoV5ZZM1OdzPWmpx+q4GyefaK06zi8hmfHV5xe4/2nOQX3+Dw5o+fSqOws2xVwL9j+anOPFwHzdEdV4g==", 26 | "funding": [ 27 | { 28 | "type": "opencollective", 29 | "url": "https://opencollective.com/browserslist" 30 | }, 31 | { 32 | "type": "tidelift", 33 | "url": "https://tidelift.com/funding/github/npm/caniuse-lite" 34 | }, 35 | { 36 | "type": "github", 37 | "url": "https://github.com/sponsors/ai" 38 | } 39 | ], 40 | "license": "CC-BY-4.0" 41 | } 42 | }, 43 | "dependencies": { 44 | "@mdn/browser-compat-data": { 45 | "version": "5.6.4", 46 | "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-5.6.4.tgz", 47 | "integrity": "sha512-bOOF4GGzn0exmvNHpSWmTfOXB9beTpIFCm2KPY2UVoCdn1YVfr8heuHr1C++BYI9Tun8REgi5TNVdKbBs249CA==" 48 | }, 49 | "caniuse-lite": { 50 | "version": "1.0.30001666", 51 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001666.tgz", 52 | "integrity": "sha512-gD14ICmoV5ZZM1OdzPWmpx+q4GyefaK06zi8hmfHV5xe4/2nOQX3+Dw5o+fSqOws2xVwL9j+anOPFwHzdEdV4g==" 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-browser-support", 3 | "version": "0.12.0", 4 | "description": "Query for CSS browser support data, combined from caniuse and MDN, including version support started and global support percentages.", 5 | "main": "index.js", 6 | "scripts": { 7 | "bump": "npm --no-git-tag-version version" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/5t3ph/css-browser-support.git" 12 | }, 13 | "keywords": [ 14 | "css", 15 | "browser-support", 16 | "browser-compatibility", 17 | "css-support", 18 | "caniuse", 19 | "caniuse-lite" 20 | ], 21 | "author": "5t3ph", 22 | "license": "ISC", 23 | "bugs": { 24 | "url": "https://github.com/5t3ph/css-browser-support/issues" 25 | }, 26 | "dependencies": { 27 | "@mdn/browser-compat-data": "^5.6.4", 28 | "caniuse-lite": "^1.0.30001666" 29 | } 30 | } 31 | --------------------------------------------------------------------------------