UNPKG

7.53 kBJavaScriptView Raw
1var region = require('caniuse-lite/dist/unpacker/region').default
2var path = require('path')
3var fs = require('fs')
4
5var BrowserslistError = require('./error')
6
7var IS_SECTION = /^\s*\[(.+)\]\s*$/
8var CONFIG_PATTERN = /^browserslist-config-/
9var SCOPED_CONFIG__PATTERN = /@[^./]+\/browserslist-config(-|$)/
10var FORMAT = 'Browserslist config should be a string or an array ' +
11 'of strings with browser queries'
12
13var filenessCache = { }
14var configCache = { }
15
16function checkExtend (name) {
17 var use = ' Use `dangerousExtend` option to disable.'
18 if (!CONFIG_PATTERN.test(name) && !SCOPED_CONFIG__PATTERN.test(name)) {
19 throw new BrowserslistError(
20 'Browserslist config needs `browserslist-config-` prefix. ' + use)
21 }
22 if (name.indexOf('.') !== -1) {
23 throw new BrowserslistError(
24 '`.` not allowed in Browserslist config name. ' + use)
25 }
26 if (name.indexOf('node_modules') !== -1) {
27 throw new BrowserslistError(
28 '`node_modules` not allowed in Browserslist config.' + use)
29 }
30}
31
32function isFile (file) {
33 if (file in filenessCache) {
34 return filenessCache[file]
35 }
36 var result = fs.existsSync(file) && fs.statSync(file).isFile()
37 if (!process.env.BROWSERSLIST_DISABLE_CACHE) {
38 filenessCache[file] = result
39 }
40 return result
41}
42
43function eachParent (file, callback) {
44 var loc = path.resolve(file)
45 do {
46 var result = callback(loc)
47 if (typeof result !== 'undefined') return result
48 } while (loc !== (loc = path.dirname(loc)))
49 return undefined
50}
51
52function check (section) {
53 if (Array.isArray(section)) {
54 for (var i = 0; i < section.length; i++) {
55 if (typeof section[i] !== 'string') {
56 throw new BrowserslistError(FORMAT)
57 }
58 }
59 } else if (typeof section !== 'string') {
60 throw new BrowserslistError(FORMAT)
61 }
62}
63
64function pickEnv (config, opts) {
65 if (typeof config !== 'object') return config
66
67 var name
68 if (typeof opts.env === 'string') {
69 name = opts.env
70 } else if (process.env.BROWSERSLIST_ENV) {
71 name = process.env.BROWSERSLIST_ENV
72 } else if (process.env.NODE_ENV) {
73 name = process.env.NODE_ENV
74 } else {
75 name = 'production'
76 }
77
78 return config[name] || config.defaults
79}
80
81function parsePackage (file) {
82 var config = JSON.parse(fs.readFileSync(file))
83 if (config.browserlist && !config.browserslist) {
84 throw new BrowserslistError(
85 '`browserlist` key instead of `browserslist` in ' + file)
86 }
87 var list = config.browserslist
88 if (Array.isArray(list)) {
89 list = { defaults: list }
90 }
91
92 for (var i in list) {
93 check(list[i])
94 }
95
96 return list
97}
98
99module.exports = {
100 loadQueries: function loadQueries (context, name) {
101 if (!context.dangerousExtend) checkExtend(name)
102 // eslint-disable-next-line security/detect-non-literal-require
103 var queries = require(require.resolve(name, { paths: ['.'] }))
104 if (!Array.isArray(queries)) {
105 throw new BrowserslistError(
106 '`' + name + '` config exports not an array of queries')
107 }
108 return queries
109 },
110
111 getStat: function getStat (opts) {
112 var stats
113 if (opts.stats) {
114 stats = opts.stats
115 } else if (process.env.BROWSERSLIST_STATS) {
116 stats = process.env.BROWSERSLIST_STATS
117 } else if (opts.path && path.resolve && fs.existsSync) {
118 stats = eachParent(opts.path, function (dir) {
119 var file = path.join(dir, 'browserslist-stats.json')
120 return isFile(file) ? file : undefined
121 })
122 }
123
124 if (typeof stats === 'string') {
125 try {
126 stats = JSON.parse(fs.readFileSync(stats))
127 } catch (e) {
128 throw new BrowserslistError('Can\'t read ' + stats)
129 }
130 }
131
132 if (stats && 'dataByBrowser' in stats) {
133 stats = stats.dataByBrowser
134 }
135
136 return stats
137 },
138
139 loadConfig: function loadConfig (opts) {
140 if (process.env.BROWSERSLIST) {
141 return process.env.BROWSERSLIST
142 } else if (opts.config || process.env.BROWSERSLIST_CONFIG) {
143 var file = opts.config || process.env.BROWSERSLIST_CONFIG
144 if (path.basename(file) === 'package.json') {
145 return pickEnv(parsePackage(file), opts)
146 } else {
147 return pickEnv(module.exports.readConfig(file), opts)
148 }
149 } else if (opts.path) {
150 return pickEnv(module.exports.findConfig(opts.path), opts)
151 } else {
152 return undefined
153 }
154 },
155
156 loadCountry: function loadCountry (usage, country) {
157 var code = country.replace(/[^\w-]/g, '')
158 if (!usage[code]) {
159 // eslint-disable-next-line security/detect-non-literal-require
160 var compressed = require('caniuse-lite/data/regions/' + code + '.js')
161 var data = region(compressed)
162 usage[country] = { }
163 for (var i in data) {
164 for (var j in data[i]) {
165 usage[country][i + ' ' + j] = data[i][j]
166 }
167 }
168 }
169 },
170
171 parseConfig: function parseConfig (string) {
172 var result = { defaults: [] }
173 var sections = ['defaults']
174
175 string.toString()
176 .replace(/#[^\n]*/g, '')
177 .split(/\n/)
178 .map(function (line) {
179 return line.trim()
180 })
181 .filter(function (line) {
182 return line !== ''
183 })
184 .forEach(function (line) {
185 if (IS_SECTION.test(line)) {
186 sections = line.match(IS_SECTION)[1].trim().split(' ')
187 sections.forEach(function (section) {
188 if (result[section]) {
189 throw new BrowserslistError(
190 'Dublicate section ' + section + ' in Browserslist config')
191 }
192 result[section] = []
193 })
194 } else {
195 sections.forEach(function (section) {
196 result[section].push(line)
197 })
198 }
199 })
200
201 return result
202 },
203
204 readConfig: function readConfig (file) {
205 if (!isFile(file)) {
206 throw new BrowserslistError('Can\'t read ' + file + ' config')
207 }
208 return module.exports.parseConfig(fs.readFileSync(file))
209 },
210
211 findConfig: function findConfig (from) {
212 from = path.resolve(from)
213
214 var cacheKey = isFile(from) ? path.dirname(from) : from
215 if (cacheKey in configCache) {
216 return configCache[cacheKey]
217 }
218
219 var resolved = eachParent(from, function (dir) {
220 var config = path.join(dir, 'browserslist')
221 var pkg = path.join(dir, 'package.json')
222 var rc = path.join(dir, '.browserslistrc')
223
224 var pkgBrowserslist
225 if (isFile(pkg)) {
226 try {
227 pkgBrowserslist = parsePackage(pkg)
228 } catch (e) {
229 if (e.name === 'BrowserslistError') throw e
230 console.warn(
231 '[Browserslist] Could not parse ' + pkg + '. Ignoring it.')
232 }
233 }
234
235 if (isFile(config) && pkgBrowserslist) {
236 throw new BrowserslistError(
237 dir + ' contains both browserslist and package.json with browsers')
238 } else if (isFile(rc) && pkgBrowserslist) {
239 throw new BrowserslistError(
240 dir + ' contains both .browserslistrc and package.json with browsers')
241 } else if (isFile(config) && isFile(rc)) {
242 throw new BrowserslistError(
243 dir + ' contains both .browserslistrc and browserslist')
244 } else if (isFile(config)) {
245 return module.exports.readConfig(config)
246 } else if (isFile(rc)) {
247 return module.exports.readConfig(rc)
248 } else {
249 return pkgBrowserslist
250 }
251 })
252 if (!process.env.BROWSERSLIST_DISABLE_CACHE) {
253 configCache[cacheKey] = resolved
254 }
255 return resolved
256 },
257
258 clearCaches: function clearCaches () {
259 filenessCache = { }
260 configCache = { }
261 }
262}