Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support browserslist #121

Closed
Gotterbild opened this issue May 19, 2020 · 9 comments
Closed

Support browserslist #121

Gotterbild opened this issue May 19, 2020 · 9 comments

Comments

@Gotterbild
Copy link

I think this lib will benefit a lot if It has support of industry standard way of detecting target JavaScript version, the browserslist. If you don't support tools like that I don't think your lib will draw enough attention.

Yes, esbuild can be used as intermediary step, but still I'll have to run Babel.

And you know, if project is small then compile speed is not the issue. So new projects don't have real reasons to prefer esbuild or esbuild-loader over webpack or rollup with Babel.

Compile speed is the issue for large codebases. But large codebases usually have to address wide audiences and need tooling to deliver code that will work everywhere. Their audience often includes people that use older browsers. Browserslist helps to address that a lot.

@evanw
Copy link
Owner

evanw commented May 21, 2020

It looks like that API combined with perhaps something like mdn-browser-compat-data could be useful. That would let you use browserslist to get a list of browsers and then you could use browser compatibility data to pick the corresponding value for the --target= language flag for esbuild.

I think something like this is better done as another package outside of esbuild core. That way people that don't need this don't have to pay the cost of it (the feature database is rather large).

@evanw
Copy link
Owner

evanw commented Jun 20, 2020

I was curious about this so I gave it a shot. Here's what I came up with:

Target Chrome Safari Firefox Edge
es2015 49+ 10.1+ 45+ 14+
es2016 52+ 10.1+ 52+ 14+
es2017 55+ 10.1+ 52+ 15+
es2018 60+ 11.1+ 55+ 79+
es2019 66+ 11.1+ 58+ 79+
es2020 80+ 13.1+ 72+ 80+

Given an esbuild language target (i.e. the esbuild --target=... flag), this table shows the minimum browser version needed to run that code. For example, if I want to support Chrome 54 then I need to pass --target=es2016 to esbuild. The top row of this table is essentially the minimum browser versions that esbuild supports.

Caveats:

  • The MDN data seems high quality but incomplete. I couldn't find data for async generators, for example. I'm not sure where to look to get that data. Perhaps there's some way to replace this with data from https://kangax.github.io/compat-table/?

  • This table doesn't account for browser bugs, which esbuild currently doesn't try to work around. See https://bugs.webkit.org/show_bug.cgi?id=171041 for an example.

  • Since esbuild's syntax transforms are currently incomplete, setting the target correctly doesn't mean esbuild will always be able to generate code that works in those browsers. The good news is attempting to use syntax that isn't yet supported for a given language target will stop the build with an error so you shouldn't be able to accidentally build code that won't work in a browser you support. But you may not be able to use certain syntax with esbuild for that language target.

Here is the code to generate this table (click to expand)
const mdn = require('mdn-browser-compat-data')

const features = {
  // Required features
  class: [mdn.javascript.statements.class.__compat, -Infinity],
  spread_in_arrays: [mdn.javascript.operators.spread.spread_in_arrays.__compat, -Infinity],
  arrow_functions: [mdn.javascript.functions.arrow_functions.__compat, -Infinity],
  default_parameters: [mdn.javascript.functions.default_parameters.__compat, -Infinity],
  method_definitions: [mdn.javascript.functions.method_definitions.__compat, -Infinity],
  rest_parameters: [mdn.javascript.functions.rest_parameters.__compat, -Infinity],
  get: [mdn.javascript.functions.get.__compat, -Infinity],
  set: [mdn.javascript.functions.set.__compat, -Infinity],
  yield: [mdn.javascript.operators.yield.__compat, -Infinity],
  yield_star: [mdn.javascript.operators.yield_star.__compat, -Infinity],
  template_literals: [mdn.javascript.grammar.template_literals.__compat, -Infinity],

  // Features that can be transformed
  optional_catch_binding: [mdn.javascript.statements.try_catch.optional_catch_binding.__compat, 2018],
  public_class_fields: [mdn.javascript.classes.public_class_fields.__compat, 2020],
  static_class_fields: [mdn.javascript.classes.static_class_fields.__compat, 2020],
  exponentiation: [mdn.javascript.operators.exponentiation.__compat, 2015],
  exponentiation_assignment: [mdn.javascript.operators.exponentiation_assignment.__compat, 2015],
  object_spread: [mdn.javascript.operators.spread.spread_in_object_literals.__compat, 2017],
  nullish_coalescing: [mdn.javascript.operators.nullish_coalescing.__compat, 2019],

  // Features that can't be transformed
  async_function: [mdn.javascript.statements.async_function.__compat, 2016],
  private_class_fields: [mdn.javascript.classes.private_class_fields.__compat, 2020],
}

function queryTargetForBrowsers(browsers) {
  let target = 2020 // Always need to transform unreleased Stage 3 features
  for (const feature in features) {
    const [__compat, lowerTarget] = features[feature]
    for (const browser in browsers) {
      const info = __compat.support[browser]
      const versionAdded = (info[0] || info).version_added
      const requestedVersion = browsers[browser]
      if (+requestedVersion < +versionAdded && lowerTarget < target)
        target = lowerTarget
    }
  }
  return target
}

function queryBrowsersForTarget(target) {
  const browsers = {}
  for (const browser of ['chrome', 'safari', 'firefox', 'edge'])
    for (const version of Object.keys(mdn.browsers[browser].releases).sort((a, b) => +a - +b))
      if (queryTargetForBrowsers({ [browser]: version }) >= target) {
        browsers[browser] = version
        break
      }
  return browsers
}

const table = {}
for (let target = 2015; target <= 2020; target++) {
  table[`es${target}`] = queryBrowsersForTarget(target)
}
console.table(table)

@evanw
Copy link
Owner

evanw commented Jul 8, 2020

I have an update on this. I just added the ability to add additional targets to the --target flag (not released yet). This should go most of the way toward solving this issue because you can call browserslist yourself and then pass the version numbers on to esbuild. Here's an example of what is now possible: --target=chrome58,firefox57,safari11,edge16.

@evanw
Copy link
Owner

evanw commented Jan 28, 2021

I'm going to close this since the target option is the intended way to configure this in esbuild. As I said above, actually including the browserslist database inside esbuild itself is outside the scope of the project and is best done in another package instead.

@evanw evanw closed this as completed Jan 28, 2021
@tommoor
Copy link

tommoor commented Apr 22, 2021

The nice thing about browserslist is that it allows for rolling targets like "last 3 chrome versions", this proves to be useful and means less maintenance of config in the long run

@nihalgonsalves
Copy link

I wrote a small plugin here: https://github.com/nihalgonsalves/esbuild-plugin-browserslist

@danny007in
Copy link

danny007in commented May 1, 2021

I have Creaetd a Simple library

getTarget.js ---> FIle

'use strict'

const browserslist = require('browserslist')

function getBuildTargets(targ) {
  const SUPPORTED_BUILD_TARGETS = targ !== 'default' ?
    targ :
    [
      'es',
      'chrome',
      'edge',
      'firefox',
      'ios',
      'node',
      'safari'
    ]

  const getEveryTar = browserslist().reverse()
  const sep = ' '
  const targets = []
  let singleTar = ''
  let i = 0

  for (const tar of getEveryTar) {
    for (const selTar of SUPPORTED_BUILD_TARGETS) {
      if (tar.startsWith(selTar + sep) && !singleTar.startsWith(selTar)) {
        i++
        singleTar = tar.replace(sep, '')
        targets[i] = singleTar
      }
    }
  }

  return targets.filter(Boolean)
}

module.exports.getTarget = targ => {
  return getBuildTargets(targ)
}

esbuild.config.js ---> File

'use strict'

const esbuild = require('esbuild')
const { getTarget } = require('./getTarget')

esbuild.build({
  entryPoints: ['build/ts/AdminLTE.ts'],
  bundle: true,
  sourcemap: true,
  target: getTarget(['es', 'chrome', 'edge', 'firefox', 'ios', 'safari']), // another option as `getTarget('default')`
  outfile: 'dist/js/adminlte.js'
}).catch(error => console.error(error))

@marcofugaro
Copy link

I've put getBuildTargets() function shared by @danny007in in an npm package you can install and made it so it can read your browserslist config from package.json as well.

https://github.com/marcofugaro/browserslist-to-esbuild

@lydell
Copy link

lydell commented May 27, 2022

Here’s a “poor man’s”/lightweight way to support ~3 years of old browsers that I’m experimenting with, that does not rely on browserslist. Sharing in case it helps someone.

// When this was written, the idea here was to support ~3 years of old browsers.
// If you don’t use the `target` option, esbuild will basically assume all the
// latest features are supported, and might even “upgrade” your code to newer
// syntax if it’s shorter as a form of minification. So it’s important to lock
// this down somewhat, so that we don’t update esbuild and accidentally make
// many customers unable to use the site.
const thisYear = new Date().getFullYear();
const maxYearsToSupport = 3;
// Safari for iOS 13 was released in September 2019. Since then, Apple has released
// a new major version in the middle of September every year. However, it’s
// reasonable that it takes a few months for people to upgrade, so we remove 1
// to not count the new version until January.
// https://en.wikipedia.org/wiki/Safari_version_history
const latestIosVersion = thisYear - 2019 + 13 - 1;
const TARGET = [
  // For JavaScript you can specify an ECMAScript standard. A new standard is
  // usually set during spring, and becomes official around June. Removing 1
  // gives a year for browsers to implement the new features (but some are
  // already supported before the specification comes out).
  `es${thisYear - maxYearsToSupport - 1}`,

  // For CSS one has to specify browser versions. Note that this affects JS too.
  // Safari has the slowest release cycle so it felt the easiest to use that.
  `ios${latestIosVersion - maxYearsToSupport}`,

  // If the assumptions made in the above calculations don’t hold in the future,
  // feel free to change this to whatever makes sense then.
];

esbuild.build({
  // ...
  target: TARGET,
})

mhsdesign added a commit to neos/neos-ui that referenced this issue Nov 24, 2022
process.env.NODE_ENV will be automatically set already based on minify
https://esbuild.github.io/api/#platform

and browserslist is ignored by esbuild evanw/esbuild#121
mhsdesign added a commit to neos/neos-ui that referenced this issue Nov 24, 2022
process.env.NODE_ENV will be automatically set already based on minify
https://esbuild.github.io/api/#platform

and browserslist is ignored by esbuild evanw/esbuild#121
markusguenther pushed a commit to neos/neos-ui that referenced this issue Nov 28, 2022
process.env.NODE_ENV will be automatically set already based on minify
https://esbuild.github.io/api/#platform

and browserslist is ignored by esbuild evanw/esbuild#121
markusguenther pushed a commit to neos/neos-ui that referenced this issue Nov 28, 2022
* Task: remove uneeded configs

process.env.NODE_ENV will be automatically set already based on minify
https://esbuild.github.io/api/#platform

and browserslist is ignored by esbuild evanw/esbuild#121

* Task: remove postcss-hexrgba and its few usages

* Task: remove unneded postcss import plugin as esbuild does it itself

* Task: remove postcss autoprefixer

* Revert "Revert "Task: remove unused babel.rc and jsconfig.json""

This reverts commit 3605abb.

* Task: Fix css linting

* Task: Fix js linting by using @typescript-eslint/parser instead of babel

* Task: Fix omit prop
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants