browserslistTargetHandler.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Sergey Melyukov @smelukov
  4. */
  5. "use strict";
  6. const browserslist = require("browserslist");
  7. const path = require("path");
  8. /** @typedef {import("./target").ApiTargetProperties} ApiTargetProperties */
  9. /** @typedef {import("./target").EcmaTargetProperties} EcmaTargetProperties */
  10. /** @typedef {import("./target").PlatformTargetProperties} PlatformTargetProperties */
  11. // [[C:]/path/to/config][:env]
  12. const inputRx = /^(?:((?:[A-Z]:)?[/\\].*?))?(?::(.+?))?$/i;
  13. /**
  14. * @typedef {Object} BrowserslistHandlerConfig
  15. * @property {string=} configPath
  16. * @property {string=} env
  17. * @property {string=} query
  18. */
  19. /**
  20. * @param {string | null | undefined} input input string
  21. * @param {string} context the context directory
  22. * @returns {BrowserslistHandlerConfig} config
  23. */
  24. const parse = (input, context) => {
  25. if (!input) {
  26. return {};
  27. }
  28. if (path.isAbsolute(input)) {
  29. const [, configPath, env] = inputRx.exec(input) || [];
  30. return { configPath, env };
  31. }
  32. const config = browserslist.findConfig(context);
  33. if (config && Object.keys(config).includes(input)) {
  34. return { env: input };
  35. }
  36. return { query: input };
  37. };
  38. /**
  39. * @param {string | null | undefined} input input string
  40. * @param {string} context the context directory
  41. * @returns {string[] | undefined} selected browsers
  42. */
  43. const load = (input, context) => {
  44. const { configPath, env, query } = parse(input, context);
  45. // if a query is specified, then use it, else
  46. // if a path to a config is specified then load it, else
  47. // find a nearest config
  48. const config = query
  49. ? query
  50. : configPath
  51. ? browserslist.loadConfig({
  52. config: configPath,
  53. env
  54. })
  55. : browserslist.loadConfig({ path: context, env });
  56. if (!config) return;
  57. return browserslist(config);
  58. };
  59. /**
  60. * @param {string[]} browsers supported browsers list
  61. * @returns {EcmaTargetProperties & PlatformTargetProperties & ApiTargetProperties} target properties
  62. */
  63. const resolve = browsers => {
  64. /**
  65. * Checks all against a version number
  66. * @param {Record<string, number | [number, number]>} versions first supported version
  67. * @returns {boolean} true if supports
  68. */
  69. const rawChecker = versions => {
  70. return browsers.every(v => {
  71. const [name, parsedVersion] = v.split(" ");
  72. if (!name) return false;
  73. const requiredVersion = versions[name];
  74. if (!requiredVersion) return false;
  75. const [parsedMajor, parserMinor] =
  76. // safari TP supports all features for normal safari
  77. parsedVersion === "TP"
  78. ? [Infinity, Infinity]
  79. : parsedVersion.split(".");
  80. if (typeof requiredVersion === "number") {
  81. return +parsedMajor >= requiredVersion;
  82. }
  83. return requiredVersion[0] === +parsedMajor
  84. ? +parserMinor >= requiredVersion[1]
  85. : +parsedMajor > requiredVersion[0];
  86. });
  87. };
  88. const anyNode = browsers.some(b => /^node /.test(b));
  89. const anyBrowser = browsers.some(b => /^(?!node)/.test(b));
  90. const browserProperty = !anyBrowser ? false : anyNode ? null : true;
  91. const nodeProperty = !anyNode ? false : anyBrowser ? null : true;
  92. // Internet Explorer Mobile, Blackberry browser and Opera Mini are very old browsers, they do not support new features
  93. const es6DynamicImport = rawChecker({
  94. chrome: 63,
  95. and_chr: 63,
  96. edge: 79,
  97. firefox: 67,
  98. and_ff: 67,
  99. // ie: Not supported
  100. opera: 50,
  101. op_mob: 46,
  102. safari: [11, 1],
  103. ios_saf: [11, 3],
  104. samsung: [8, 2],
  105. android: 63,
  106. and_qq: [10, 4],
  107. // baidu: Not supported
  108. // and_uc: Not supported
  109. kaios: [3, 0],
  110. node: [12, 17]
  111. });
  112. return {
  113. const: rawChecker({
  114. chrome: 49,
  115. and_chr: 49,
  116. edge: 12,
  117. // Prior to Firefox 13, <code>const</code> is implemented, but re-assignment is not failing.
  118. // Prior to Firefox 46, a <code>TypeError</code> was thrown on redeclaration instead of a <code>SyntaxError</code>.
  119. firefox: 36,
  120. and_ff: 36,
  121. // Not supported in for-in and for-of loops
  122. // ie: Not supported
  123. opera: 36,
  124. op_mob: 36,
  125. safari: [10, 0],
  126. ios_saf: [10, 0],
  127. // Before 5.0 supported correctly in strict mode, otherwise supported without block scope
  128. samsung: [5, 0],
  129. android: 37,
  130. and_qq: [10, 4],
  131. // Supported correctly in strict mode, otherwise supported without block scope
  132. // baidu: Not supported
  133. and_uc: [12, 12],
  134. kaios: [2, 5],
  135. node: [6, 0]
  136. }),
  137. arrowFunction: rawChecker({
  138. chrome: 45,
  139. and_chr: 45,
  140. edge: 12,
  141. // The initial implementation of arrow functions in Firefox made them automatically strict. This has been changed as of Firefox 24. The use of <code>'use strict';</code> is now required.
  142. // Prior to Firefox 39, a line terminator (<code>\\n</code>) was incorrectly allowed after arrow function arguments. This has been fixed to conform to the ES2015 specification and code like <code>() \\n => {}</code> will now throw a <code>SyntaxError</code> in this and later versions.
  143. firefox: 39,
  144. and_ff: 39,
  145. // ie: Not supported,
  146. opera: 32,
  147. op_mob: 32,
  148. safari: 10,
  149. ios_saf: 10,
  150. samsung: [5, 0],
  151. android: 45,
  152. and_qq: [10, 4],
  153. baidu: [7, 12],
  154. and_uc: [12, 12],
  155. kaios: [2, 5],
  156. node: [6, 0]
  157. }),
  158. forOf: rawChecker({
  159. chrome: 38,
  160. and_chr: 38,
  161. edge: 12,
  162. // Prior to Firefox 51, using the for...of loop construct with the const keyword threw a SyntaxError ("missing = in const declaration").
  163. firefox: 51,
  164. and_ff: 51,
  165. // ie: Not supported,
  166. opera: 25,
  167. op_mob: 25,
  168. safari: 7,
  169. ios_saf: 7,
  170. samsung: [3, 0],
  171. android: 38,
  172. // and_qq: Unknown support
  173. // baidu: Unknown support
  174. // and_uc: Unknown support
  175. kaios: [3, 0],
  176. node: [0, 12]
  177. }),
  178. destructuring: rawChecker({
  179. chrome: 49,
  180. and_chr: 49,
  181. edge: 14,
  182. firefox: 41,
  183. and_ff: 41,
  184. // ie: Not supported,
  185. opera: 36,
  186. op_mob: 36,
  187. safari: 8,
  188. ios_saf: 8,
  189. samsung: [5, 0],
  190. android: 49,
  191. // and_qq: Unknown support
  192. // baidu: Unknown support
  193. // and_uc: Unknown support
  194. kaios: [2, 5],
  195. node: [6, 0]
  196. }),
  197. bigIntLiteral: rawChecker({
  198. chrome: 67,
  199. and_chr: 67,
  200. edge: 79,
  201. firefox: 68,
  202. and_ff: 68,
  203. // ie: Not supported,
  204. opera: 54,
  205. op_mob: 48,
  206. safari: 14,
  207. ios_saf: 14,
  208. samsung: [9, 2],
  209. android: 67,
  210. // and_qq: Not supported
  211. // baidu: Not supported
  212. // and_uc: Not supported
  213. kaios: [3, 0],
  214. node: [10, 4]
  215. }),
  216. // Support syntax `import` and `export` and no limitations and bugs on Node.js
  217. // Not include `export * as namespace`
  218. module: rawChecker({
  219. chrome: 61,
  220. and_chr: 61,
  221. edge: 16,
  222. firefox: 60,
  223. and_ff: 60,
  224. // ie: Not supported,
  225. opera: 48,
  226. op_mob: 45,
  227. safari: [10, 1],
  228. ios_saf: [10, 3],
  229. samsung: [8, 0],
  230. android: 61,
  231. and_qq: [10, 4],
  232. // baidu: Not supported
  233. // and_uc: Not supported
  234. kaios: [3, 0],
  235. node: [12, 17]
  236. }),
  237. dynamicImport: es6DynamicImport,
  238. dynamicImportInWorker: es6DynamicImport && !anyNode,
  239. // browserslist does not have info about globalThis
  240. // so this is based on mdn-browser-compat-data
  241. globalThis: rawChecker({
  242. chrome: 71,
  243. and_chr: 71,
  244. edge: 79,
  245. firefox: 65,
  246. and_ff: 65,
  247. // ie: Not supported,
  248. opera: 58,
  249. op_mob: 50,
  250. safari: [12, 1],
  251. ios_saf: [12, 2],
  252. samsung: [10, 1],
  253. android: 71,
  254. // and_qq: Unknown support
  255. // baidu: Unknown support
  256. // and_uc: Unknown support
  257. kaios: [3, 0],
  258. node: 12
  259. }),
  260. optionalChaining: rawChecker({
  261. chrome: 80,
  262. and_chr: 80,
  263. edge: 80,
  264. firefox: 74,
  265. and_ff: 79,
  266. // ie: Not supported,
  267. opera: 67,
  268. op_mob: 64,
  269. safari: [13, 1],
  270. ios_saf: [13, 4],
  271. samsung: 13,
  272. android: 80,
  273. // and_qq: Not supported
  274. // baidu: Not supported
  275. // and_uc: Not supported
  276. kaios: [3, 0],
  277. node: 14
  278. }),
  279. templateLiteral: rawChecker({
  280. chrome: 41,
  281. and_chr: 41,
  282. edge: 13,
  283. firefox: 34,
  284. and_ff: 34,
  285. // ie: Not supported,
  286. opera: 29,
  287. op_mob: 64,
  288. safari: [9, 1],
  289. ios_saf: 9,
  290. samsung: 4,
  291. android: 41,
  292. and_qq: [10, 4],
  293. baidu: [7, 12],
  294. and_uc: [12, 12],
  295. kaios: [2, 5],
  296. node: 4
  297. }),
  298. browser: browserProperty,
  299. electron: false,
  300. node: nodeProperty,
  301. nwjs: false,
  302. web: browserProperty,
  303. webworker: false,
  304. document: browserProperty,
  305. fetchWasm: browserProperty,
  306. global: nodeProperty,
  307. importScripts: false,
  308. importScriptsInWorker: true,
  309. nodeBuiltins: nodeProperty,
  310. require: nodeProperty
  311. };
  312. };
  313. module.exports = {
  314. resolve,
  315. load
  316. };