browserslistTargetHandler.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  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 =
  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. 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.includes("-")
  80. ? parsedVersion.split("-")[0].split(".")
  81. : parsedVersion.split(".");
  82. if (typeof requiredVersion === "number") {
  83. return Number(parsedMajor) >= requiredVersion;
  84. }
  85. return requiredVersion[0] === Number(parsedMajor)
  86. ? Number(parserMinor) >= requiredVersion[1]
  87. : Number(parsedMajor) > requiredVersion[0];
  88. });
  89. const anyNode = browsers.some(b => b.startsWith("node "));
  90. const anyBrowser = browsers.some(b => /^(?!node)/.test(b));
  91. const browserProperty = !anyBrowser ? false : anyNode ? null : true;
  92. const nodeProperty = !anyNode ? false : anyBrowser ? null : true;
  93. // Internet Explorer Mobile, Blackberry browser and Opera Mini are very old browsers, they do not support new features
  94. const es6DynamicImport = rawChecker({
  95. /* eslint-disable camelcase */
  96. chrome: 63,
  97. and_chr: 63,
  98. edge: 79,
  99. firefox: 67,
  100. and_ff: 67,
  101. // ie: Not supported
  102. opera: 50,
  103. op_mob: 46,
  104. safari: [11, 1],
  105. ios_saf: [11, 3],
  106. samsung: [8, 2],
  107. android: 63,
  108. and_qq: [10, 4],
  109. baidu: [13, 18],
  110. and_uc: [15, 5],
  111. kaios: [3, 0],
  112. node: [12, 17]
  113. /* eslint-enable camelcase */
  114. });
  115. return {
  116. /* eslint-disable camelcase */
  117. const: rawChecker({
  118. chrome: 49,
  119. and_chr: 49,
  120. edge: 12,
  121. // Prior to Firefox 13, <code>const</code> is implemented, but re-assignment is not failing.
  122. // Prior to Firefox 46, a <code>TypeError</code> was thrown on redeclaration instead of a <code>SyntaxError</code>.
  123. firefox: 36,
  124. and_ff: 36,
  125. // Not supported in for-in and for-of loops
  126. // ie: Not supported
  127. opera: 36,
  128. op_mob: 36,
  129. safari: [10, 0],
  130. ios_saf: [10, 0],
  131. // Before 5.0 supported correctly in strict mode, otherwise supported without block scope
  132. samsung: [5, 0],
  133. android: 37,
  134. and_qq: [10, 4],
  135. // Supported correctly in strict mode, otherwise supported without block scope
  136. baidu: [13, 18],
  137. and_uc: [12, 12],
  138. kaios: [2, 5],
  139. node: [6, 0]
  140. }),
  141. arrowFunction: rawChecker({
  142. chrome: 45,
  143. and_chr: 45,
  144. edge: 12,
  145. // 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.
  146. // 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.
  147. firefox: 39,
  148. and_ff: 39,
  149. // ie: Not supported,
  150. opera: 32,
  151. op_mob: 32,
  152. safari: 10,
  153. ios_saf: 10,
  154. samsung: [5, 0],
  155. android: 45,
  156. and_qq: [10, 4],
  157. baidu: [7, 12],
  158. and_uc: [12, 12],
  159. kaios: [2, 5],
  160. node: [6, 0]
  161. }),
  162. forOf: rawChecker({
  163. chrome: 38,
  164. and_chr: 38,
  165. edge: 12,
  166. // Prior to Firefox 51, using the for...of loop construct with the const keyword threw a SyntaxError ("missing = in const declaration").
  167. firefox: 51,
  168. and_ff: 51,
  169. // ie: Not supported,
  170. opera: 25,
  171. op_mob: 25,
  172. safari: 7,
  173. ios_saf: 7,
  174. samsung: [3, 0],
  175. android: 38,
  176. // and_qq: Unknown support
  177. // baidu: Unknown support
  178. // and_uc: Unknown support
  179. kaios: [3, 0],
  180. node: [0, 12]
  181. }),
  182. destructuring: rawChecker({
  183. chrome: 49,
  184. and_chr: 49,
  185. edge: 14,
  186. firefox: 41,
  187. and_ff: 41,
  188. // ie: Not supported,
  189. opera: 36,
  190. op_mob: 36,
  191. safari: 8,
  192. ios_saf: 8,
  193. samsung: [5, 0],
  194. android: 49,
  195. // and_qq: Unknown support
  196. // baidu: Unknown support
  197. // and_uc: Unknown support
  198. kaios: [2, 5],
  199. node: [6, 0]
  200. }),
  201. bigIntLiteral: rawChecker({
  202. chrome: 67,
  203. and_chr: 67,
  204. edge: 79,
  205. firefox: 68,
  206. and_ff: 68,
  207. // ie: Not supported,
  208. opera: 54,
  209. op_mob: 48,
  210. safari: 14,
  211. ios_saf: 14,
  212. samsung: [9, 2],
  213. android: 67,
  214. and_qq: [13, 1],
  215. baidu: [13, 18],
  216. and_uc: [15, 5],
  217. kaios: [3, 0],
  218. node: [10, 4]
  219. }),
  220. // Support syntax `import` and `export` and no limitations and bugs on Node.js
  221. // Not include `export * as namespace`
  222. module: rawChecker({
  223. chrome: 61,
  224. and_chr: 61,
  225. edge: 16,
  226. firefox: 60,
  227. and_ff: 60,
  228. // ie: Not supported,
  229. opera: 48,
  230. op_mob: 45,
  231. safari: [10, 1],
  232. ios_saf: [10, 3],
  233. samsung: [8, 0],
  234. android: 61,
  235. and_qq: [10, 4],
  236. baidu: [13, 18],
  237. and_uc: [15, 5],
  238. kaios: [3, 0],
  239. node: [12, 17]
  240. }),
  241. dynamicImport: es6DynamicImport,
  242. dynamicImportInWorker: es6DynamicImport && !anyNode,
  243. // browserslist does not have info about globalThis
  244. // so this is based on mdn-browser-compat-data
  245. globalThis: rawChecker({
  246. chrome: 71,
  247. and_chr: 71,
  248. edge: 79,
  249. firefox: 65,
  250. and_ff: 65,
  251. // ie: Not supported,
  252. opera: 58,
  253. op_mob: 50,
  254. safari: [12, 1],
  255. ios_saf: [12, 2],
  256. samsung: [10, 1],
  257. android: 71,
  258. // and_qq: Unknown support
  259. // baidu: Unknown support
  260. // and_uc: Unknown support
  261. kaios: [3, 0],
  262. node: 12
  263. }),
  264. optionalChaining: rawChecker({
  265. chrome: 80,
  266. and_chr: 80,
  267. edge: 80,
  268. firefox: 74,
  269. and_ff: 79,
  270. // ie: Not supported,
  271. opera: 67,
  272. op_mob: 64,
  273. safari: [13, 1],
  274. ios_saf: [13, 4],
  275. samsung: 13,
  276. android: 80,
  277. // and_qq: Not supported
  278. // baidu: Not supported
  279. // and_uc: Not supported
  280. kaios: [3, 0],
  281. node: 14
  282. }),
  283. templateLiteral: rawChecker({
  284. chrome: 41,
  285. and_chr: 41,
  286. edge: 13,
  287. firefox: 34,
  288. and_ff: 34,
  289. // ie: Not supported,
  290. opera: 29,
  291. op_mob: 64,
  292. safari: [9, 1],
  293. ios_saf: 9,
  294. samsung: 4,
  295. android: 41,
  296. and_qq: [10, 4],
  297. baidu: [7, 12],
  298. and_uc: [12, 12],
  299. kaios: [2, 5],
  300. node: 4
  301. }),
  302. asyncFunction: rawChecker({
  303. chrome: 55,
  304. and_chr: 55,
  305. edge: 15,
  306. firefox: 52,
  307. and_ff: 52,
  308. // ie: Not supported,
  309. opera: 42,
  310. op_mob: 42,
  311. safari: 11,
  312. ios_saf: 11,
  313. samsung: [6, 2],
  314. android: 55,
  315. and_qq: [13, 1],
  316. baidu: [13, 18],
  317. and_uc: [15, 5],
  318. kaios: 3,
  319. node: [7, 6]
  320. }),
  321. /* eslint-enable camelcase */
  322. browser: browserProperty,
  323. electron: false,
  324. node: nodeProperty,
  325. nwjs: false,
  326. web: browserProperty,
  327. webworker: false,
  328. document: browserProperty,
  329. fetchWasm: browserProperty,
  330. global: nodeProperty,
  331. importScripts: false,
  332. importScriptsInWorker: true,
  333. nodeBuiltins: nodeProperty,
  334. nodePrefixForCoreModules:
  335. nodeProperty &&
  336. !browsers.some(b => b.startsWith("node 15")) &&
  337. rawChecker({
  338. node: [14, 18]
  339. }),
  340. require: nodeProperty
  341. };
  342. };
  343. module.exports = {
  344. resolve,
  345. load
  346. };