Outdated browser detection with Browserslist
Tags: ruby, node, browserdetection, showdev
Published: 2018-11-20 19:43:22

---
title: Outdated browser detection with Browserslist
published: true
description: Detecting outdated browser in one way on both backend and frontend
tags: #ruby #nodejs #browserdetection #showdev
cover_image: https://thepracticaldev.s3.amazonaws.com/i/ebqd2kjkuo4cmmjdq6sy.png
---

The standard way to configure target browsers with Node.js is [Browserslist](https://github.com/browserslist/browserslist). It is possible to add the following:

```json
{
  "browserslist": [
    "last 2 version",
    "not dead"
  ]
}
```

to the `package.json` or the `.browserslistrc` config file:

```
# Browsers that we support
last 2 version
not dead
```

Those two similar examples mean that the target browsers are two last version and the browser is not dead.


This config is used by many front-end tools, such as [Autoprefixer](https://github.com/postcss/autoprefixer), [Babel](https://github.com/babel/babel/tree/master/packages/babel-preset-env) and many others.

But in this article I am going to write about the [Browserslist Useragent](https://github.com/browserslist/browserslist-useragent) frontend tool for finding if a given user agent string satisfies a `Browserslist` browsers:

Install the `browserslist-useragent`:

```bash
npm install browserslist-useragent
```

and you can determine by `User-Agent` string if you browser matches:

```javascript
const { matchesUA } = require('browserslist-useragent')

matchesUA(userAgentString, options)

// with browserslist config inferred
matchesUA('Mozilla/5.0 (Windows NT 10.0; rv:54.0) Gecko/20100101 Firefox/54.0')
//returns boolean

// with explicit browserslist
matchesUA('Mozilla/5.0 (Windows NT 10.0; rv:54.0) Gecko/20100101 Firefox/54.0', { browsers: ['Firefox > 53']})
// returns true
```

Imaging we have the `.browserslistrc` config file like this:

```
last 2 versions
not IE 11
not ExplorerMobile 11
not last 1 OperaMini version
not OperaMobile 12.1
not dead
```

We can get the array of detailed browsers rules with the help of browserslist:

```javascript
const browserslist = require('browserslist')
const fs = require('fs')

fs.writeFileSync('./browsers.json', JSON.stringify(browserslist()))
```

For the sample above it will produce the json file with:

```json
[
  "and_chr 67",
  "and_ff 60",
  "and_qq 1.2",
  "and_uc 11.8",
  "android 67",
  "android 4.4.3-4.4.4",
  "baidu 7.12",
  "chrome 69",
  "chrome 68",
  "edge 17",
  "edge 16",
  "firefox 62",
  "firefox 61",
  "ios_saf 11.3-11.4",
  "ios_saf 11.0-11.2",
  "op_mob 46",
  "opera 55",
  "opera 54",
  "safari 11.1",
  "safari 11",
  "samsung 7.2",
  "samsung 6.2"
]
```

That is the way to determine the browsers matchings with Node.js.


Why do we need to check the browsers version on both sides: backend and frontend?
In the case of your modern javascript frontend would not be loaded on the old browser - we can still use the backend rendering to write some HTML notifying user about the issue:


![The outdated browser html block sample](https://thepracticaldev.s3.amazonaws.com/i/arfb7qw3ely8pq2j20k5.png)

❗This HTML block would work in any browser no matter how old it is.

And if your backend is written with Ruby - use can still use the port of the original tool to the Ruby - [browserslist-useragent gem](https://github.com/browserslist/browserslist-useragent-ruby). It works the same way its Node.js version - recognises the family and the version from the `User-Agent` header string and matches it with the `browserslist`-rules produced by the `Browserslists` tool.

# Single project

The usage is straightforward - it just needs you to generate the `browsers.json` file before.

```ruby

class ApplicationController
  def supported_browser?
    @browsers ||= JSON.parse(Rails.root.join("browsers.json").read)
    matcher = BrowserslistUseragent::Match.new(@browsers, request.user_agent)
    matcher.browser? && matcher.version?(allow_higher: true)
  end
  helper_method :supported_browser?
end
```
Then add this code to you Rails-application layout template:

```haml
- if !supported_browser?
  .div 
div( style: "position: fixed; bottom: 0; right: 0; padding: 8px 10px; background: #e9502f; color: white; width: 100%; z-index: 10; text-align: center;" )
    .div
      = t('unsupported_browser')
```
❗This old-fashioned style is deliberately chosen: 'style'-attributes will work mostly everywhere!

Here it is. It will work well for the Rails projects where all the frontend and backend live together as one [solid project](https://evilmartians.com/chronicles/evil-front-part-1).

## Separated frontend and backend projects

If you have separated projects for Ruby backend and Node.js frontend, you will prefer to get browsers.json over HTTP. You will need to do the following:
 - serve the `/browsers.json` path to render the browserslist output by putting it to the `public` folder:

```javascript
fs.writeFileSync(
  path.join(__dirname, 'public', 'browsers.json'),
  JSON.stringify(browserslist(undefined, { path: path.join(__dirname, '..') }))
)
```
 - get in over HTTP in the ruby-backend code:

```ruby
browsers = JSON.parse(Faraday.get('http://frontend-domain.local/browsers.json').body)
matcher = BrowserslistUseragent::Match.new(browsers, request.user_agent)
matcher.browser? && matcher.version?(allow_higher: true)
```

Or use the [faraday-http-cache](https://github.com/plataformatec/faraday-http-cache) to cache the results of the http request. It will force to make one request per the Rails application instance only:

```ruby 
# caches http response locally with etag
http_client = Faraday.new do |builder|
  builder.use Faraday::HttpCache, store: Rails.cache
  builder.adapter Faraday.default_adapter
end

browsers = JSON.parse(
  http_client.get('http://frontend-domain.local/browsers.json').body
)
...
```

That's it. This solution will use one `browserslist.rc` config in the frontend repository, which will automatically be shared over the backend.

More details abort the `browserslist_useragent` gem you will find [here](https://github.com/browserslist/browserslist-useragent-ruby).

Thanks for reading!