browserslist 4.3.7 → 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,9 @@
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
+
4
7
  ## 4.3.7
5
8
  * Fix fraction years support in `last 1.5 years` (by Clément P).
6
9
  * Fix version-less browser support.
package/README.md CHANGED
@@ -41,11 +41,11 @@ 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
 
@@ -102,14 +102,38 @@ 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
138
  and does not add popular old versions. Choosing a percentage above `0.2%`
115
139
  will in the long run make popular browsers even more popular. We might run
@@ -194,9 +218,6 @@ samsung 5
194
218
  Browserslist works with separated versions of browsers.
195
219
  You should avoid queries like `Firefox > 0`.
196
220
 
197
- Multiple criteria are combined as a boolean `OR`. A browser version must match
198
- at least one of the criteria to be selected.
199
-
200
221
  All queries are based on the [Can I Use] support table,
201
222
  e.g. `last 3 iOS versions` might select `8.4, 9.2, 9.3` (mixed major and minor),
202
223
  whereas `last 3 Chrome versions` might select `50, 49, 48` (major only).
package/index.js CHANGED
@@ -10,6 +10,12 @@ var env = require('./node') // Will load browser.js in webpack
10
10
  var FLOAT_RANGE = /^\d+(\.\d+)?(-\d+(\.\d+)?)*$/
11
11
  var YEAR = 365.259641 * 24 * 60 * 60 * 1000
12
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
18
+
13
19
  function isVersionsMatch (versionA, versionB) {
14
20
  return (versionA + '.').indexOf(versionB + '.') === 0
15
21
  }
@@ -134,10 +140,23 @@ function unknownQuery (query) {
134
140
  )
135
141
  }
136
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
+ */
137
151
  function resolve (queries, context) {
138
- return queries.reduce(function (result, selection, index) {
139
- selection = selection.trim()
140
- 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
141
160
 
142
161
  var isExclude = selection.indexOf('not ') === 0
143
162
  if (isExclude) {
@@ -162,19 +181,36 @@ function resolve (queries, context) {
162
181
  return j
163
182
  }
164
183
  })
165
- if (isExclude) {
166
- var filter = { }
167
- var browsers = { }
168
- array.forEach(function (j) {
169
- filter[j] = true
170
- var browser = j.replace(/\s\S+$/, '')
171
- browsers[browser] = true
172
- })
173
- return result.filter(function (j) {
174
- return !filter[j]
175
- })
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)
176
213
  }
177
- return result.concat(array)
178
214
  }
179
215
  }
180
216
 
@@ -199,7 +235,7 @@ function resolve (queries, context) {
199
235
  * version in direct query.
200
236
  * @param {boolean} [opts.dangerousExtend] Disable security checks
201
237
  * for extend query.
202
- * @return {string[]} Array with browser names in Can I Use.
238
+ * @returns {string[]} Array with browser names in Can I Use.
203
239
  *
204
240
  * @example
205
241
  * browserslist('IE >= 10, IE 8') //=> ['ie 11', 'ie 10', 'ie 8']
@@ -220,13 +256,9 @@ function browserslist (queries, opts) {
220
256
  }
221
257
  }
222
258
 
223
- if (typeof queries === 'string') {
224
- queries = queries.split(/,\s*/)
225
- }
226
-
227
- if (!Array.isArray(queries)) {
259
+ if (!(typeof queries === 'string' || Array.isArray(queries))) {
228
260
  throw new BrowserslistError(
229
- 'Browser queries must be an array. Got ' + typeof queries + '.')
261
+ 'Browser queries must be an array or string. Got ' + typeof queries + '.')
230
262
  }
231
263
 
232
264
  var context = {
@@ -260,6 +292,74 @@ function browserslist (queries, opts) {
260
292
  return uniq(result)
261
293
  }
262
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
+
263
363
  // Will be filled by Can I Use data below
264
364
  browserslist.data = { }
265
365
  browserslist.usage = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "browserslist",
3
- "version": "4.3.7",
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,8 +11,8 @@
11
11
  "license": "MIT",
12
12
  "repository": "browserslist/browserslist",
13
13
  "dependencies": {
14
- "caniuse-lite": "^1.0.30000925",
15
- "electron-to-chromium": "^1.3.96",
14
+ "caniuse-lite": "^1.0.30000928",
15
+ "electron-to-chromium": "^1.3.100",
16
16
  "node-releases": "^1.1.3"
17
17
  },
18
18
  "bin": "./cli.js",