diff options
Diffstat (limited to 'node_modules/browserslist/index.js')
| -rw-r--r-- | node_modules/browserslist/index.js | 648 |
1 files changed, 648 insertions, 0 deletions
diff --git a/node_modules/browserslist/index.js b/node_modules/browserslist/index.js new file mode 100644 index 00000000..40623b03 --- /dev/null +++ b/node_modules/browserslist/index.js @@ -0,0 +1,648 @@ +var path = require('path'); +var e2c = require('electron-to-chromium/versions'); +var fs = require('fs'); + +var caniuse = require('caniuse-db/data.json').agents; + +function normalize(versions) { + return versions.filter(function (version) { + return typeof version === 'string'; + }); +} + +var FLOAT_RANGE = /^\d+(\.\d+)?(-\d+(\.\d+)?)*$/; +var IS_SECTION = /^\s*\[(.+)\]\s*$/; + +function uniq(array) { + var filtered = []; + for ( var i = 0; i < array.length; i++ ) { + if ( filtered.indexOf(array[i]) === -1 ) filtered.push(array[i]); + } + return filtered; +} + +function BrowserslistError(message) { + this.name = 'BrowserslistError'; + this.message = message || ''; + this.browserslist = true; + if ( Error.captureStackTrace ) { + Error.captureStackTrace(this, BrowserslistError); + } +} +BrowserslistError.prototype = Error.prototype; + +// Helpers + +function error(name) { + throw new BrowserslistError(name); +} + +function fillUsage(result, name, data) { + for ( var i in data ) { + result[name + ' ' + i] = data[i]; + } +} + +function isFile(file) { + return fs.existsSync(file) && fs.statSync(file).isFile(); +} + +function eachParent(file, callback) { + if ( !fs.readFileSync || !fs.existsSync || !fs.statSync ) { + /* istanbul ignore next */ + return undefined; + } + + if ( file === false ) return undefined; + if ( typeof file === 'undefined' ) file = '.'; + var loc = path.resolve(file); + do { + var result = callback(loc); + if (typeof result !== 'undefined') return result; + } while (loc !== (loc = path.dirname(loc))); + return undefined; +} + +function getStat(opts) { + if ( opts.stats ) { + return opts.stats; + } else if ( process.env.BROWSERSLIST_STATS ) { + return process.env.BROWSERSLIST_STATS; + } else { + return eachParent(opts.path, function (dir) { + var file = path.join(dir, 'browserslist-stats.json'); + if ( isFile(file) ) { + return file; + } + }); + } +} + +function parsePackage(file) { + var config = JSON.parse(fs.readFileSync(file)).browserslist; + if ( typeof config === 'object' && config.length ) { + config = { defaults: config }; + } + return config; +} + +function pickEnv(config, opts) { + if ( typeof config !== 'object' ) return config; + + var env; + if ( typeof opts.env === 'string' ) { + env = opts.env; + } else if ( typeof process.env.BROWSERSLIST_ENV === 'string' ) { + env = process.env.BROWSERSLIST_ENV; + } else if ( typeof process.env.NODE_ENV === 'string' ) { + env = process.env.NODE_ENV; + } else { + env = 'development'; + } + + return config[env] || config.defaults; +} + +function generateFilter(sign, version) { + version = parseFloat(version); + if ( sign === '>' ) { + return function (v) { + return parseFloat(v) > version; + }; + } else if ( sign === '>=' ) { + return function (v) { + return parseFloat(v) >= version; + }; + } else if ( sign === '<' ) { + return function (v) { + return parseFloat(v) < version; + }; + } else if ( sign === '<=' ) { + return function (v) { + return parseFloat(v) <= version; + }; + } +} + +function compareStrings(a, b) { + if (a < b) return -1; + if (a > b) return +1; + return 0; +} + +/** + * Return array of browsers by selection queries. + * + * @param {string[]} queries Browser queries. + * @param {object} opts Options. + * @param {string} [opts.path="."] Path to processed file. + * It will be used to find config files. + * @param {string} [opts.env="development"] Processing environment. + * It will be used to take right + * queries from config file. + * @param {string} [opts.config] Path to config file with queries. + * @param {object} [opts.stats] Custom browser usage statistics + * for "> 1% in my stats" query. + * @return {string[]} Array with browser names in Can I Use. + * + * @example + * browserslist('IE >= 10, IE 8') //=> ['ie 11', 'ie 10', 'ie 8'] + */ +var browserslist = function (queries, opts) { + if ( typeof opts === 'undefined' ) opts = { }; + + if ( typeof queries === 'undefined' || queries === null ) { + if ( process.env.BROWSERSLIST ) { + queries = process.env.BROWSERSLIST; + } else if ( opts.config || process.env.BROWSERSLIST_CONFIG ) { + var file = opts.config || process.env.BROWSERSLIST_CONFIG; + queries = pickEnv(browserslist.readConfig(file), opts); + } else { + queries = pickEnv(browserslist.findConfig(opts.path), opts); + } + } + + if ( typeof queries === 'undefined' || queries === null ) { + queries = browserslist.defaults; + } + + if ( typeof queries === 'string' ) { + queries = queries.split(/,\s*/); + } + + var context = { }; + + var stats = getStat(opts); + if ( stats ) { + if ( typeof stats === 'string' ) { + try { + stats = JSON.parse(fs.readFileSync(stats)); + } catch (e) { + error('Can\'t read ' + stats); + } + } + if ( 'dataByBrowser' in stats ) { + stats = stats.dataByBrowser; + } + + context.customUsage = { }; + for ( var browser in stats ) { + fillUsage(context.customUsage, browser, stats[browser]); + } + } + + var result = []; + + queries.forEach(function (selection) { + if ( selection.trim() === '' ) return; + + var exclude = selection.indexOf('not ') === 0; + if ( exclude ) selection = selection.slice(4); + + for ( var i in browserslist.queries ) { + var type = browserslist.queries[i]; + var match = selection.match(type.regexp); + if ( match ) { + var args = [context].concat(match.slice(1)); + var array = type.select.apply(browserslist, args); + if ( exclude ) { + array = array.concat(array.map(function (j) { + return j.replace(/\s\d+/, ' 0'); + })); + result = result.filter(function (j) { + return array.indexOf(j) === -1; + }); + } else { + result = result.concat(array); + } + return; + } + } + + error('Unknown browser query `' + selection + '`'); + }); + result = result.map(function (i) { + var parts = i.split(' '); + var name = parts[0]; + var version = parts[1]; + if ( version === '0' ) { + return name + ' ' + browserslist.byName(name).versions[0]; + } else { + return i; + } + }).sort(function (name1, name2) { + name1 = name1.split(' '); + name2 = name2.split(' '); + if ( name1[0] === name2[0] ) { + if ( FLOAT_RANGE.test(name1[1]) && FLOAT_RANGE.test(name2[1]) ) { + return parseFloat(name2[1]) - parseFloat(name1[1]); + } else { + return compareStrings(name2[1], name1[1]); + } + } else { + return compareStrings(name1[0], name2[0]); + } + }); + + return uniq(result); +}; + +var normalizeVersion = function (data, version) { + if ( data.versions.indexOf(version) !== -1 ) { + return version; + } else if ( browserslist.versionAliases[data.name][version] ) { + return browserslist.versionAliases[data.name][version]; + } else if ( data.versions.length === 1 ) { + return data.versions[0]; + } +}; + +var loadCountryStatistics = function (country) { + if ( !browserslist.usage[country] ) { + var usage = { }; + var data = require( + 'caniuse-db/region-usage-json/' + country + '.json'); + for ( var i in data.data ) { + fillUsage(usage, i, data.data[i]); + } + browserslist.usage[country] = usage; + } +}; + +// Will be filled by Can I Use data below +browserslist.data = { }; +browserslist.usage = { + global: { }, + custom: null +}; + +// Default browsers query +browserslist.defaults = [ + '> 1%', + 'last 2 versions', + 'Firefox ESR' +]; + +// What browsers will be used in `last n version` query +browserslist.major = [ + 'safari', 'opera', 'ios_saf', 'ie_mob', 'ie', 'edge', 'firefox', 'chrome' +]; + +// Browser names aliases +browserslist.aliases = { + fx: 'firefox', + ff: 'firefox', + ios: 'ios_saf', + explorer: 'ie', + blackberry: 'bb', + explorermobile: 'ie_mob', + operamini: 'op_mini', + operamobile: 'op_mob', + chromeandroid: 'and_chr', + firefoxandroid: 'and_ff', + ucandroid: 'and_uc' +}; + +// Aliases to work with joined versions like `ios_saf 7.0-7.1` +browserslist.versionAliases = { }; + +// Get browser data by alias or case insensitive name +browserslist.byName = function (name) { + name = name.toLowerCase(); + name = browserslist.aliases[name] || name; + return browserslist.data[name]; +}; + +// Get browser data by alias or case insensitive name and throw error +// on unknown browser +browserslist.checkName = function (name) { + var data = browserslist.byName(name); + if ( !data ) error('Unknown browser ' + name); + return data; +}; + +// Read and parse config +browserslist.readConfig = function (file) { + if ( !fs.existsSync(file) || !fs.statSync(file).isFile() ) { + error('Can\'t read ' + file + ' config'); + } + return browserslist.parseConfig(fs.readFileSync(file)); +}; + +// Find config, read file and parse it +browserslist.findConfig = function (from) { + return eachParent(from, function (dir) { + var config = path.join(dir, 'browserslist'); + var pkg = path.join(dir, 'package.json'); + + var pkgBrowserslist; + if ( isFile(pkg) ) { + try { + pkgBrowserslist = parsePackage(pkg); + } catch (e) { + console.warn( + '[Browserslist] Could not parse ' + pkg + '. ' + + 'Ignoring it.'); + } + } + + if ( isFile(config) && pkgBrowserslist ) { + error( + dir + ' contains both browserslist ' + + 'and package.json with browsers'); + } else if ( isFile(config) ) { + return browserslist.readConfig(config); + } else if ( pkgBrowserslist ) { + return pkgBrowserslist; + } + }); +}; + +/** + * Return browsers market coverage. + * + * @param {string[]} browsers Browsers names in Can I Use. + * @param {string} [country="global"] Which country statistics should be used. + * + * @return {number} Total market coverage for all selected browsers. + * + * @example + * browserslist.coverage(browserslist('> 1% in US'), 'US') //=> 83.1 + */ +browserslist.coverage = function (browsers, country) { + if ( country && country !== 'global') { + country = country.toUpperCase(); + loadCountryStatistics(country); + } else { + country = 'global'; + } + + return browsers.reduce(function (all, i) { + var usage = browserslist.usage[country][i]; + if ( usage === undefined ) { + usage = browserslist.usage[country][i.replace(/ [\d.]+$/, ' 0')]; + } + return all + (usage || 0); + }, 0); +}; + +// Return array of queries from config content +browserslist.parseConfig = function (string) { + var result = { defaults: [] }; + var section = 'defaults'; + + string.toString() + .replace(/#[^\n]*/g, '') + .split(/\n/) + .map(function (line) { + return line.trim(); + }) + .filter(function (line) { + return line !== ''; + }) + .forEach(function (line) { + if ( IS_SECTION.test(line) ) { + section = line.match(IS_SECTION)[1].trim(); + result[section] = result[section] || []; + } else { + result[section].push(line); + } + }); + + return result; +}; + +browserslist.queries = { + + lastVersions: { + regexp: /^last\s+(\d+)\s+versions?$/i, + select: function (context, versions) { + var selected = []; + browserslist.major.forEach(function (name) { + var data = browserslist.byName(name); + if ( !data ) return; + var array = data.released.slice(-versions); + + array = array.map(function (v) { + return data.name + ' ' + v; + }); + selected = selected.concat(array); + }); + return selected; + } + }, + + lastByBrowser: { + regexp: /^last\s+(\d+)\s+(\w+)\s+versions?$/i, + select: function (context, versions, name) { + var data = browserslist.checkName(name); + return data.released.slice(-versions).map(function (v) { + return data.name + ' ' + v; + }); + } + }, + + globalStatistics: { + regexp: /^>\s*(\d*\.?\d+)%$/, + select: function (context, popularity) { + popularity = parseFloat(popularity); + var result = []; + + for ( var version in browserslist.usage.global ) { + if ( browserslist.usage.global[version] > popularity ) { + result.push(version); + } + } + + return result; + } + }, + + customStatistics: { + regexp: /^>\s*(\d*\.?\d+)%\s+in\s+my\s+stats$/, + select: function (context, popularity) { + popularity = parseFloat(popularity); + var result = []; + + if ( !context.customUsage ) { + error('Custom usage statistics was not provided'); + } + + for ( var version in context.customUsage ) { + if ( context.customUsage[version] > popularity ) { + result.push(version); + } + } + + return result; + } + }, + + countryStatistics: { + regexp: /^>\s*(\d*\.?\d+)%\s+in\s+(\w\w)$/, + select: function (context, popularity, country) { + popularity = parseFloat(popularity); + country = country.toUpperCase(); + var result = []; + + loadCountryStatistics(country); + var usage = browserslist.usage[country]; + + for ( var version in usage ) { + if ( usage[version] > popularity ) { + result.push(version); + } + } + + return result; + } + }, + + electronRange: { + regexp: /^electron\s+([\d\.]+)\s*-\s*([\d\.]+)$/i, + select: function (context, from, to) { + if ( !e2c[from] ) error('Unknown version ' + from + ' of electron'); + if ( !e2c[to] ) error('Unknown version ' + to + ' of electron'); + + from = parseFloat(from); + to = parseFloat(to); + + return Object.keys(e2c).filter(function (i) { + var parsed = parseFloat(i); + return parsed >= from && parsed <= to; + }).map(function (i) { + return 'chrome ' + e2c[i]; + }); + } + }, + + range: { + regexp: /^(\w+)\s+([\d\.]+)\s*-\s*([\d\.]+)$/i, + select: function (context, name, from, to) { + var data = browserslist.checkName(name); + from = parseFloat(normalizeVersion(data, from) || from); + to = parseFloat(normalizeVersion(data, to) || to); + + var filter = function (v) { + var parsed = parseFloat(v); + return parsed >= from && parsed <= to; + }; + + return data.released.filter(filter).map(function (v) { + return data.name + ' ' + v; + }); + } + }, + + electronVersions: { + regexp: /^electron\s*(>=?|<=?)\s*([\d\.]+)$/i, + select: function (context, sign, version) { + return Object.keys(e2c) + .filter(generateFilter(sign, version)) + .map(function (i) { + return 'chrome ' + e2c[i]; + }); + } + }, + + versions: { + regexp: /^(\w+)\s*(>=?|<=?)\s*([\d\.]+)$/, + select: function (context, name, sign, version) { + var data = browserslist.checkName(name); + var alias = normalizeVersion(data, version); + if ( alias ) { + version = alias; + } + return data.released + .filter(generateFilter(sign, version)) + .map(function (v) { + return data.name + ' ' + v; + }); + } + }, + + esr: { + regexp: /^(firefox|ff|fx)\s+esr$/i, + select: function () { + return ['firefox 52']; + } + }, + + opMini: { + regexp: /(operamini|op_mini)\s+all/i, + select: function () { + return ['op_mini all']; + } + }, + + electron: { + regexp: /^electron\s+([\d\.]+)$/i, + select: function (context, version) { + var chrome = e2c[version]; + if ( !chrome ) error('Unknown version ' + version + ' of electron'); + return ['chrome ' + chrome]; + } + }, + + direct: { + regexp: /^(\w+)\s+(tp|[\d\.]+)$/i, + select: function (context, name, version) { + if ( /tp/i.test(version) ) version = 'TP'; + var data = browserslist.checkName(name); + var alias = normalizeVersion(data, version); + if ( alias ) { + version = alias; + } else { + if ( version.indexOf('.') === -1 ) { + alias = version + '.0'; + } else if ( /\.0$/.test(version) ) { + alias = version.replace(/\.0$/, ''); + } + alias = normalizeVersion(data, alias); + if ( alias ) { + version = alias; + } else { + error('Unknown version ' + version + ' of ' + name); + } + } + return [data.name + ' ' + version]; + } + }, + + defaults: { + regexp: /^defaults$/i, + select: function () { + return browserslist(browserslist.defaults); + } + } + +}; + +// Get and convert Can I Use data + +(function () { + for ( var name in caniuse ) { + var browser = caniuse[name]; + browserslist.data[name] = { + name: name, + versions: normalize(caniuse[name].versions), + released: normalize(caniuse[name].versions.slice(0, -3)) + }; + fillUsage(browserslist.usage.global, name, browser.usage_global); + + browserslist.versionAliases[name] = { }; + for ( var i = 0; i < browser.versions.length; i++ ) { + var full = browser.versions[i]; + if (!full) continue; + + if ( full.indexOf('-') !== -1 ) { + var interval = full.split('-'); + for ( var j = 0; j < interval.length; j++ ) { + browserslist.versionAliases[name][interval[j]] = full; + } + } + } + } +}()); + +module.exports = browserslist; |
