browserslist 4.3.4 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,19 @@
1
1
  # Change Log
2
2
  This project adheres to [Semantic Versioning](http://semver.org/).
3
3
 
4
+ ## 4.4
5
+ * Added `and` and `or` keywords to combine queries (by Jon Ege Ronnenberg).
6
+
7
+ ## 4.3.7
8
+ * Fix fraction years support in `last 1.5 years` (by Clément P).
9
+ * Fix version-less browser support.
10
+
11
+ ## 4.3.6
12
+ * Fix version-less browser support in custom statistics (by Alex Walter).
13
+
14
+ ## 4.3.5
15
+ * Fix `not` query for wrong Can I Use data.
16
+
4
17
  ## 4.3.4
5
18
  * Allow to update `node-releases` without new Browserslist releases.
6
19
 
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Browserslist [![Cult Of Martians][cult-img]][cult]
2
2
 
3
3
  <img width="120" height="120" alt="Browserslist logo by Anton Lovchikov"
4
- src="http://browserslist.github.io/browserslist/logo.svg" align="right">
4
+ src="https://browserslist.github.io/browserslist/logo.svg" align="right">
5
5
 
6
6
  The config to share target browsers and Node.js versions between different
7
7
  front-end tools. It is used in:
@@ -41,16 +41,16 @@ not dead
41
41
 
42
42
  Developers set versions list in queries like `last 2 version`
43
43
  to be free from updating versions manually.
44
- Browserslist will use [Can I Use] data for this queries.
44
+ Browserslist will use [Can I Use] data for this queries.
45
45
 
46
46
  Browserslist will take queries from tool option,
47
47
  `browserslist` config, `.browserslistrc` config,
48
- `browserslist` section in `package.json` or environment variables.
48
+ `browserslist` section in `package.json` or environment variables.
49
49
 
50
50
  You can test Browserslist queries in [online demo].
51
51
 
52
- [cult-img]: http://cultofmartians.com/assets/badges/badge.svg
53
- [cult]: http://cultofmartians.com/done.html
52
+ [cult-img]: https://cultofmartians.com/assets/badges/badge.svg
53
+ [cult]: https://cultofmartians.com/done.html
54
54
 
55
55
  <a href="https://evilmartians.com/?utm_source=browserslist">
56
56
  <img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg"
@@ -59,13 +59,13 @@ You can test Browserslist queries in [online demo].
59
59
 
60
60
  [stylelint-no-unsupported-browser-features]: https://github.com/ismay/stylelint-no-unsupported-browser-features
61
61
  [eslint-plugin-compat]: https://github.com/amilajack/eslint-plugin-compat
62
- [Browserslist Example]: https://github.com/browserslist/browserslist-example
62
+ [Browserslist Example]: https://github.com/browserslist/browserslist-example
63
63
  [postcss-preset-env]: https://github.com/jonathantneal/postcss-preset-env
64
64
  [postcss-normalize]: https://github.com/jonathantneal/postcss-normalize
65
65
  [Autoprefixer]: https://github.com/postcss/autoprefixer
66
- [online demo]: http://browserl.ist/
67
- [Can I Use]: http://caniuse.com/
68
- [Babel]: https://github.com/babel/babel/tree/master/packages/babel-preset-env
66
+ [online demo]: https://browserl.ist/
67
+ [Can I Use]: https://caniuse.com/
68
+ [Babel]: https://github.com/babel/babel/tree/master/packages/babel-preset-env
69
69
 
70
70
  ## Tools
71
71
 
@@ -102,18 +102,43 @@ from one of this sources:
102
102
  `> 0.5%, last 2 versions, Firefox ESR, not dead`.
103
103
 
104
104
 
105
+ ### Query Composition
106
+
107
+ An `or` combiner can use the keyword `or` as well as `,`.
108
+ `last 1 version or > 1%` is equal to `last 1 version, > 1%`.
109
+
110
+ `and` query combinations are also supported to perform an
111
+ intersection of the previous query: `last 1 version and > 1%`.
112
+
113
+ There is 3 different ways to combine queries as depicted below. First you start
114
+ with a single query and then we combine the queries to get our final list.
115
+
116
+ Obviously you can *not* start with a `not` combiner, since the is no left-hand
117
+ side query to combine it with.
118
+
119
+ | Query combiner type | Illustration | Example |
120
+ | ------------------- | :----------: | ------- |
121
+ |`or`/ `,` combiner <br> (union) | ![Union of queries](img/union.svg) | `'> .5% or last 2 versions'` <br> `'> .5%, last 2 versions'` |
122
+ | `and` combiner <br> (intersection) | ![intersection of queries](img/intersection.svg) | `'> .5% and last 2 versions'` |
123
+ | `not` combiner <br> (relative complement) | ![Relative complement of queries](img/complement.svg) | `'> .5% and not last 2 versions'` <br> `'> .5% or not last 2 versions'` <br> `'> .5%, not last 2 versions'` |
124
+
125
+ _A quick way to test your query is to do `npx browserslist '> 0.5%, not IE 11'`
126
+ in your terminal._
127
+
128
+
105
129
  ### Best Practices
106
130
 
107
131
  * Select browsers directly (`last 2 Chrome versions`) only if you are making
108
- a web app for a kiosk with one browser. There are a lot of browsers
132
+ a web app for a kiosk with one browser. There are a lot of browsers
109
133
  on the market. If you are making general web app you should respect
110
134
  browsers diversity.
111
135
  * If you want to change the default set of browsers we recommend to combine
112
- `last 1 version`, `not dead` with `> 0.2%` (or `> 1% in US`,
136
+ `last 1 version`, `not dead` with `> 0.2%` (or `> 1% in US`,
113
137
  `> 1% in my stats`). `last n versions` adds too many dead browsers
114
- and does not add popular old versions. `> 0.2%` make popular browsers
115
- even more popular, so we will have a monopoly and stagnation,
116
- as we had with Internet Explorer 6.
138
+ and does not add popular old versions. Choosing a percentage above `0.2%`
139
+ will in the long run make popular browsers even more popular. We might run
140
+ into a monopoly and stagnation situation, as we had with Internet Explorer 6.
141
+ Please use this setting with caution.
117
142
  * Don’t remove browsers just because you don’t know them. Opera Mini has
118
143
  100 million users in Africa and it is more popular in the global market
119
144
  than Microsoft Edge. Chinese QQ Browsers has more market share than Firefox
@@ -193,18 +218,15 @@ samsung 5
193
218
  Browserslist works with separated versions of browsers.
194
219
  You should avoid queries like `Firefox > 0`.
195
220
 
196
- Multiple criteria are combined as a boolean `OR`. A browser version must match
197
- at least one of the criteria to be selected.
198
-
199
221
  All queries are based on the [Can I Use] support table,
200
222
  e.g. `last 3 iOS versions` might select `8.4, 9.2, 9.3` (mixed major and minor),
201
223
  whereas `last 3 Chrome versions` might select `50, 49, 48` (major only).
202
224
 
203
225
  [`caniuse-lite/data/regions`]: https://github.com/ben-eb/caniuse-lite/tree/master/data/regions
204
- [two-letter country code]: http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements
226
+ [two-letter country code]: https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements
205
227
  [custom usage data]: #custom-usage-data
206
228
  [still maintained]: https://github.com/nodejs/Release
207
- [Can I Use]: http://caniuse.com/
229
+ [Can I Use]: https://caniuse.com/
208
230
 
209
231
 
210
232
  ### Browsers
@@ -424,7 +446,7 @@ while also querying against global or regional data.
424
446
  For example, the query `> 1% in my stats, > 5% in US, 10%` is permitted.
425
447
 
426
448
  [`browserslist-ga`]: https://github.com/browserslist/browserslist-ga
427
- [Can I Use]: http://caniuse.com/
449
+ [Can I Use]: https://caniuse.com/
428
450
 
429
451
 
430
452
  ## JS API
package/index.js CHANGED
@@ -8,6 +8,13 @@ var BrowserslistError = require('./error')
8
8
  var env = require('./node') // Will load browser.js in webpack
9
9
 
10
10
  var FLOAT_RANGE = /^\d+(\.\d+)?(-\d+(\.\d+)?)*$/
11
+ var YEAR = 365.259641 * 24 * 60 * 60 * 1000
12
+
13
+ // Enum values MUST be powers of 2, so combination are safe
14
+ /** @constant {number} */
15
+ var QUERY_OR = 1
16
+ /** @constant {number} */
17
+ var QUERY_AND = 2
11
18
 
12
19
  function isVersionsMatch (versionA, versionB) {
13
20
  return (versionA + '.').indexOf(versionB + '.') === 0
@@ -103,6 +110,7 @@ function normalizeVersion (data, version) {
103
110
  }
104
111
 
105
112
  function filterByYear (since) {
113
+ since = since / 1000
106
114
  return Object.keys(agents).reduce(function (selected, name) {
107
115
  var data = byName(name)
108
116
  if (!data) return selected
@@ -132,10 +140,23 @@ function unknownQuery (query) {
132
140
  )
133
141
  }
134
142
 
143
+ /**
144
+ * Resolves queries into a browser list.
145
+ * @param {string|string[]} queries Queries to combine.
146
+ * Either an array of queries or a long string of queries.
147
+ * @param {object} [context] Optional arguments to
148
+ * the select function in `queries`.
149
+ * @returns {string[]} A list of browsers
150
+ */
135
151
  function resolve (queries, context) {
136
- return queries.reduce(function (result, selection, index) {
137
- selection = selection.trim()
138
- if (selection === '') return result
152
+ if (Array.isArray(queries)) {
153
+ queries = flatten(queries.map(parse))
154
+ } else {
155
+ queries = parse(queries)
156
+ }
157
+
158
+ return queries.reduce(function (result, query, index) {
159
+ var selection = query.queryString
139
160
 
140
161
  var isExclude = selection.indexOf('not ') === 0
141
162
  if (isExclude) {
@@ -152,16 +173,44 @@ function resolve (queries, context) {
152
173
  var match = selection.match(type.regexp)
153
174
  if (match) {
154
175
  var args = [context].concat(match.slice(1))
155
- var array = type.select.apply(browserslist, args)
156
- if (isExclude) {
157
- array = array.concat(array.map(function (j) {
158
- return j.replace(/\s\S+/, ' 0')
159
- }))
160
- return result.filter(function (j) {
161
- return array.indexOf(j) === -1
162
- })
176
+ var array = type.select.apply(browserslist, args).map(function (j) {
177
+ var parts = j.split(' ')
178
+ if (parts[1] === '0') {
179
+ return parts[0] + ' ' + byName(parts[0]).versions[0]
180
+ } else {
181
+ return j
182
+ }
183
+ })
184
+
185
+ switch (query.type) {
186
+ case QUERY_AND:
187
+ if (isExclude) {
188
+ return result.filter(function (j) {
189
+ // remove result items that are in array
190
+ // (the relative complement of array in result)
191
+ return array.indexOf(j) === -1
192
+ })
193
+ } else {
194
+ return result.filter(function (j) {
195
+ // remove result items not in array
196
+ // (intersect of result and array)
197
+ return array.indexOf(j) !== -1
198
+ })
199
+ }
200
+ case QUERY_OR:
201
+ default:
202
+ if (isExclude) {
203
+ var filter = { }
204
+ array.forEach(function (j) {
205
+ filter[j] = true
206
+ })
207
+ return result.filter(function (j) {
208
+ return !filter[j]
209
+ })
210
+ }
211
+ // union of result and array
212
+ return result.concat(array)
163
213
  }
164
- return result.concat(array)
165
214
  }
166
215
  }
167
216
 
@@ -186,7 +235,7 @@ function resolve (queries, context) {
186
235
  * version in direct query.
187
236
  * @param {boolean} [opts.dangerousExtend] Disable security checks
188
237
  * for extend query.
189
- * @return {string[]} Array with browser names in Can I Use.
238
+ * @returns {string[]} Array with browser names in Can I Use.
190
239
  *
191
240
  * @example
192
241
  * browserslist('IE >= 10, IE 8') //=> ['ie 11', 'ie 10', 'ie 8']
@@ -207,13 +256,9 @@ function browserslist (queries, opts) {
207
256
  }
208
257
  }
209
258
 
210
- if (typeof queries === 'string') {
211
- queries = queries.split(/,\s*/)
212
- }
213
-
214
- if (!Array.isArray(queries)) {
259
+ if (!(typeof queries === 'string' || Array.isArray(queries))) {
215
260
  throw new BrowserslistError(
216
- 'Browser queries must be an array. Got ' + typeof queries + '.')
261
+ 'Browser queries must be an array or string. Got ' + typeof queries + '.')
217
262
  }
218
263
 
219
264
  var context = {
@@ -230,16 +275,7 @@ function browserslist (queries, opts) {
230
275
  }
231
276
  }
232
277
 
233
- var result = resolve(queries, context).map(function (i) {
234
- var parts = i.split(' ')
235
- var name = parts[0]
236
- var version = parts[1]
237
- if (version === '0') {
238
- return name + ' ' + byName(name).versions[0]
239
- } else {
240
- return i
241
- }
242
- }).sort(function (name1, name2) {
278
+ var result = resolve(queries, context).sort(function (name1, name2) {
243
279
  name1 = name1.split(' ')
244
280
  name2 = name2.split(' ')
245
281
  if (name1[0] === name2[0]) {
@@ -256,6 +292,74 @@ function browserslist (queries, opts) {
256
292
  return uniq(result)
257
293
  }
258
294
 
295
+ /**
296
+ * @typedef {object} BrowserslistQuery
297
+ * @property {number} type A type constant like QUERY_OR @see QUERY_OR.
298
+ * @property {string} queryString A query like "not ie < 11".
299
+ */
300
+
301
+ /**
302
+ * Parse a browserslist string query
303
+ * @param {string} queries One or more queries as a string
304
+ * @returns {BrowserslistQuery[]} An array of BrowserslistQuery
305
+ */
306
+ function parse (queries) {
307
+ var qs = []
308
+
309
+ do {
310
+ queries = doMatch(queries, qs)
311
+ } while (queries)
312
+
313
+ return qs
314
+ }
315
+
316
+ /**
317
+ * Find query matches in a string. This function is meant to be called
318
+ * repeatedly with the returned query string until there is no more matches.
319
+ * @param {string} string A string with one or more queries.
320
+ * @param {BrowserslistQuery[]} qs Out parameter,
321
+ * will be filled with `BrowserslistQuery`.
322
+ * @returns {string} The rest of the query string minus the matched part.
323
+ */
324
+ function doMatch (string, qs) {
325
+ var or = /^(?:,\s*|\s+OR\s+)(.*)/i
326
+ var and = /^\s+AND\s+(.*)/i
327
+
328
+ return find(
329
+ string,
330
+ function (parsed, n, max) {
331
+ if (and.test(parsed)) {
332
+ qs.unshift({ type: QUERY_AND, queryString: parsed.match(and)[1] })
333
+ return true
334
+ } else if (or.test(parsed)) {
335
+ qs.unshift({ type: QUERY_OR, queryString: parsed.match(or)[1] })
336
+ return true
337
+ } else if (n === max) {
338
+ qs.unshift({ type: QUERY_OR, queryString: parsed.trim() })
339
+ return true
340
+ }
341
+ return false
342
+ }
343
+ )
344
+ }
345
+
346
+ function find (string, predicate) {
347
+ for (var n = 1, max = string.length; n <= max; n++) {
348
+ var parsed = string.substr(-n, n)
349
+ if (predicate(parsed, n, max)) {
350
+ return string.replace(parsed, '')
351
+ }
352
+ }
353
+ return ''
354
+ }
355
+
356
+ function flatten (array) {
357
+ if (!Array.isArray(array)) return [array]
358
+ return array.reduce(function (a, b) {
359
+ return a.concat(flatten(b))
360
+ }, [])
361
+ }
362
+
259
363
  // Will be filled by Can I Use data below
260
364
  browserslist.data = { }
261
365
  browserslist.usage = {
@@ -444,12 +548,9 @@ var QUERIES = [
444
548
  }
445
549
  },
446
550
  {
447
- regexp: /^last\s+(\d+)\s+years?$/i,
551
+ regexp: /^last\s+(\d*.?\d+)\s+years?$/i,
448
552
  select: function (context, years) {
449
- var date = new Date()
450
- var since = date.setFullYear(date.getFullYear() - years) / 1000
451
-
452
- return filterByYear(since)
553
+ return filterByYear(Date.now() - YEAR * years)
453
554
  }
454
555
  },
455
556
  {
@@ -458,9 +559,7 @@ var QUERIES = [
458
559
  year = parseInt(year)
459
560
  month = parseInt(month || '01') - 1
460
561
  date = parseInt(date || '01')
461
- var since = Date.UTC(year, month, date, 0, 0, 0) / 1000
462
-
463
- return filterByYear(since)
562
+ return filterByYear(Date.UTC(year, month, date, 0, 0, 0))
464
563
  }
465
564
  },
466
565
  {
package/node.js CHANGED
@@ -156,7 +156,7 @@ module.exports = {
156
156
  if (versions.length === 1 && data[i] && data[i].versions.length === 1) {
157
157
  var normal = Object.keys(data[i].versions)[0]
158
158
  normalized[i] = { }
159
- normalized[i][normal] = stats[i]
159
+ normalized[i][normal] = stats[i][versions[0]]
160
160
  } else {
161
161
  normalized[i] = stats[i]
162
162
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "browserslist",
3
- "version": "4.3.4",
3
+ "version": "4.4.0",
4
4
  "description": "Share target browsers between different front-end tools, like Autoprefixer, Stylelint and babel-env-preset",
5
5
  "keywords": [
6
6
  "caniuse",
@@ -11,13 +11,18 @@
11
11
  "license": "MIT",
12
12
  "repository": "browserslist/browserslist",
13
13
  "dependencies": {
14
- "caniuse-lite": "^1.0.30000899",
15
- "electron-to-chromium": "^1.3.82",
16
- "node-releases": "^1.0.1"
14
+ "caniuse-lite": "^1.0.30000928",
15
+ "electron-to-chromium": "^1.3.100",
16
+ "node-releases": "^1.1.3"
17
17
  },
18
18
  "bin": "./cli.js",
19
19
  "browser": {
20
20
  "./node.js": "./browser.js",
21
21
  "path": false
22
+ },
23
+ "husky": {
24
+ "hooks": {
25
+ "pre-commit": "lint-staged"
26
+ }
22
27
  }
23
28
  }