loading...
Cover image for Outdated browser detection with Browserslist
Amplifr.com

Outdated browser detection with Browserslist

dsalahutdinov profile image Salahutdinov Dmitry Updated on ・4 min read

The standard way to configure target browsers with Node.js is Browserslist. It is possible to add the following:

{
  "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, Babel and many others.

But in this article I am going to write about the Browserslist Useragent frontend tool for finding if a given user agent string satisfies a Browserslist browsers:

Install the browserslist-useragent:

npm install browserslist-useragent

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

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:

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:

[
  "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

❗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. 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.


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:

- 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.

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:
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:
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 to cache the results of the http request. It will force to make one request per the Rails application instance only:

# 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.

Thanks for reading!

Posted on by:

dsalahutdinov profile

Salahutdinov Dmitry

@dsalahutdinov

Lead backend developer (Ruby) at Amplifr, testing fan, simplicity propagandist Family man, nature-lover, mathematician, red car driver

Amplifr.com

The easiest way to publish and analyze content on social media for media companies, e-commerce and small businesses.

Discussion

markdown guide
 

FYI "browsers" is mispelled as "browers" in 3 places.

 
 

Unsupporting old browsers is very reasonable for the services with saas-service with modern complicated UI.

 

appreciated, we don't want outdated browsers anywhere.