target.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const memoize = require("../util/memoize");
  7. const getBrowserslistTargetHandler = memoize(() =>
  8. require("./browserslistTargetHandler")
  9. );
  10. /**
  11. * Gets default target.
  12. * @param {string} context the context directory
  13. * @returns {string} default target
  14. */
  15. const getDefaultTarget = (context) => {
  16. const browsers = getBrowserslistTargetHandler().load(undefined, context);
  17. return browsers ? "browserslist" : "web";
  18. };
  19. /**
  20. * Defines the platform target properties type used by this module.
  21. * @typedef {object} PlatformTargetProperties
  22. * @property {boolean | null=} web web platform, importing of http(s) and std: is available
  23. * @property {boolean | null=} browser browser platform, running in a normal web browser
  24. * @property {boolean | null=} webworker (Web)Worker platform, running in a web/shared/service worker
  25. * @property {boolean | null=} node node platform, require of node built-in modules is available
  26. * @property {boolean | null=} nwjs nwjs platform, require of legacy nw.gui is available
  27. * @property {boolean | null=} electron electron platform, require of some electron built-in modules is available
  28. */
  29. /**
  30. * Defines the electron context target properties type used by this module.
  31. * @typedef {object} ElectronContextTargetProperties
  32. * @property {boolean | null} electronMain in main context
  33. * @property {boolean | null} electronPreload in preload context
  34. * @property {boolean | null} electronRenderer in renderer context with node integration
  35. */
  36. /**
  37. * Defines the api target properties type used by this module.
  38. * @typedef {object} ApiTargetProperties
  39. * @property {boolean | null} require has require function available
  40. * @property {boolean | null} nodeBuiltins has node.js built-in modules available
  41. * @property {boolean | null} nodePrefixForCoreModules node.js allows to use `node:` prefix for core modules
  42. * @property {boolean | null} importMetaDirnameAndFilename node.js allows to use `import.meta.dirname` and `import.meta.filename`
  43. * @property {boolean | null} document has document available (allows script tags)
  44. * @property {boolean | null} importScripts has importScripts available
  45. * @property {boolean | null} importScriptsInWorker has importScripts available when creating a worker
  46. * @property {boolean | null} fetchWasm has fetch function available for WebAssembly
  47. * @property {boolean | null} global has global variable available
  48. */
  49. /**
  50. * Defines the ecma target properties type used by this module.
  51. * @typedef {object} EcmaTargetProperties
  52. * @property {boolean | null} globalThis has globalThis variable available
  53. * @property {boolean | null} bigIntLiteral big int literal syntax is available
  54. * @property {boolean | null} const const and let variable declarations are available
  55. * @property {boolean | null} methodShorthand object method shorthand is available
  56. * @property {boolean | null} arrowFunction arrow functions are available
  57. * @property {boolean | null} forOf for of iteration is available
  58. * @property {boolean | null} destructuring destructuring is available
  59. * @property {boolean | null} dynamicImport async import() is available
  60. * @property {boolean | null} dynamicImportInWorker async import() is available when creating a worker
  61. * @property {boolean | null} module ESM syntax is available (when in module)
  62. * @property {boolean | null} optionalChaining optional chaining is available
  63. * @property {boolean | null} templateLiteral template literal is available
  64. * @property {boolean | null} asyncFunction async functions and await are available
  65. */
  66. /**
  67. * Defines the shared type used by this module.
  68. * @template T
  69. * @typedef {{ [P in keyof T]?: never }} Never<T>
  70. */
  71. /**
  72. * Defines the shared type used by this module.
  73. * @template A
  74. * @template B
  75. * @typedef {(A & Never<B>) | (Never<A> & B) | (A & B)} Mix<A, B>
  76. */
  77. /** @typedef {Mix<Mix<PlatformTargetProperties, ElectronContextTargetProperties>, Mix<ApiTargetProperties, EcmaTargetProperties>>} TargetProperties */
  78. /**
  79. * Returns check if version is greater or equal.
  80. * @param {string} major major version
  81. * @param {string | undefined} minor minor version
  82. * @returns {(vMajor: number, vMinor?: number) => boolean | undefined} check if version is greater or equal
  83. */
  84. const versionDependent = (major, minor) => {
  85. if (!major) {
  86. return () => /** @type {undefined} */ (undefined);
  87. }
  88. /** @type {number} */
  89. const nMajor = Number(major);
  90. /** @type {number} */
  91. const nMinor = minor ? Number(minor) : 0;
  92. return (vMajor, vMinor = 0) =>
  93. nMajor > vMajor || (nMajor === vMajor && nMinor >= vMinor);
  94. };
  95. /** @type {[string, string, RegExp, (...args: string[]) => Partial<TargetProperties>][]} */
  96. const TARGETS = [
  97. [
  98. "browserslist / browserslist:env / browserslist:query / browserslist:path-to-config / browserslist:path-to-config:env",
  99. "Resolve features from browserslist. Will resolve browserslist config automatically. Only browser or node queries are supported (electron is not supported). Examples: 'browserslist:modern' to use 'modern' environment from browserslist config",
  100. /^browserslist(?::(.+))?$/,
  101. (rest, context) => {
  102. const browserslistTargetHandler = getBrowserslistTargetHandler();
  103. const browsers = browserslistTargetHandler.load(
  104. rest ? rest.trim() : null,
  105. context
  106. );
  107. if (!browsers) {
  108. throw new Error(`No browserslist config found to handle the 'browserslist' target.
  109. See https://github.com/browserslist/browserslist#queries for possible ways to provide a config.
  110. The recommended way is to add a 'browserslist' key to your package.json and list supported browsers (resp. node.js versions).
  111. You can also more options via the 'target' option: 'browserslist' / 'browserslist:env' / 'browserslist:query' / 'browserslist:path-to-config' / 'browserslist:path-to-config:env'`);
  112. }
  113. return browserslistTargetHandler.resolve(browsers);
  114. }
  115. ],
  116. [
  117. "web",
  118. "Web browser.",
  119. /^web$/,
  120. () => ({
  121. node: false,
  122. web: true,
  123. webworker: null,
  124. browser: true,
  125. electron: false,
  126. nwjs: false,
  127. document: true,
  128. importScriptsInWorker: true,
  129. fetchWasm: true,
  130. nodeBuiltins: false,
  131. importScripts: false,
  132. require: false,
  133. global: false
  134. })
  135. ],
  136. [
  137. "webworker",
  138. "Web Worker, SharedWorker or Service Worker.",
  139. /^webworker$/,
  140. () => ({
  141. node: false,
  142. web: true,
  143. webworker: true,
  144. browser: true,
  145. electron: false,
  146. nwjs: false,
  147. importScripts: true,
  148. importScriptsInWorker: true,
  149. fetchWasm: true,
  150. nodeBuiltins: false,
  151. require: false,
  152. document: false,
  153. global: false
  154. })
  155. ],
  156. [
  157. "[async-]node[X[.Y]]",
  158. "Node.js in version X.Y. The 'async-' prefix will load chunks asynchronously via 'fs' and 'vm' instead of 'require()'. Examples: node14.5, async-node10.",
  159. /^(async-)?node((\d+)(?:\.(\d+))?)?$/,
  160. (asyncFlag, _, major, minor) => {
  161. const v = versionDependent(major, minor);
  162. // see https://node.green/
  163. return {
  164. node: true,
  165. web: false,
  166. webworker: false,
  167. browser: false,
  168. electron: false,
  169. nwjs: false,
  170. require: !asyncFlag,
  171. nodeBuiltins: true,
  172. // v16.0.0, v14.18.0
  173. nodePrefixForCoreModules: Number(major) < 15 ? v(14, 18) : v(16),
  174. // Added in: v21.2.0, v20.11.0, but Node.js will output experimental warning, we don't want it
  175. // v24.0.0, v22.16.0 - This property is no longer experimental.
  176. importMetaDirnameAndFilename: v(22, 16),
  177. global: true,
  178. document: false,
  179. fetchWasm: false,
  180. importScripts: false,
  181. importScriptsInWorker: false,
  182. globalThis: v(12),
  183. const: v(6),
  184. templateLiteral: v(4),
  185. optionalChaining: v(14),
  186. methodShorthand: v(4),
  187. arrowFunction: v(6),
  188. asyncFunction: v(7, 6),
  189. forOf: v(5),
  190. destructuring: v(6),
  191. bigIntLiteral: v(10, 4),
  192. dynamicImport: v(12, 17),
  193. dynamicImportInWorker: v(12, 17),
  194. module: v(12, 17)
  195. };
  196. }
  197. ],
  198. [
  199. "electron[X[.Y]]-main/preload/renderer",
  200. "Electron in version X.Y. Script is running in main, preload resp. renderer context.",
  201. /^electron((\d+)(?:\.(\d+))?)?-(main|preload|renderer)$/,
  202. (_, major, minor, context) => {
  203. const v = versionDependent(major, minor);
  204. // see https://node.green/ + https://github.com/electron/releases
  205. return {
  206. node: true,
  207. web: context !== "main",
  208. webworker: false,
  209. browser: false,
  210. electron: true,
  211. nwjs: false,
  212. electronMain: context === "main",
  213. electronPreload: context === "preload",
  214. electronRenderer: context === "renderer",
  215. global: true,
  216. nodeBuiltins: true,
  217. // 15.0.0 - Node.js v16.5
  218. // 14.0.0 - Mode.js v14.17, but prefixes only since v14.18
  219. nodePrefixForCoreModules: v(15),
  220. // 37.0.0 - Node.js v22.16
  221. importMetaDirnameAndFilename: v(37),
  222. require: true,
  223. document: context === "renderer",
  224. fetchWasm: context === "renderer",
  225. importScripts: false,
  226. importScriptsInWorker: true,
  227. globalThis: v(5),
  228. const: v(1, 1),
  229. templateLiteral: v(1, 1),
  230. optionalChaining: v(8),
  231. methodShorthand: v(1, 1),
  232. arrowFunction: v(1, 1),
  233. asyncFunction: v(1, 7),
  234. forOf: v(0, 36),
  235. destructuring: v(1, 1),
  236. bigIntLiteral: v(4),
  237. dynamicImport: v(11),
  238. dynamicImportInWorker: v(11),
  239. module: v(11)
  240. };
  241. }
  242. ],
  243. [
  244. "nwjs[X[.Y]] / node-webkit[X[.Y]]",
  245. "NW.js in version X.Y.",
  246. /^(?:nwjs|node-webkit)((\d+)(?:\.(\d+))?)?$/,
  247. (_, major, minor) => {
  248. const v = versionDependent(major, minor);
  249. // see https://node.green/ + https://github.com/nwjs/nw.js/blob/nw48/CHANGELOG.md
  250. return {
  251. node: true,
  252. web: true,
  253. webworker: null,
  254. browser: false,
  255. electron: false,
  256. nwjs: true,
  257. global: true,
  258. nodeBuiltins: true,
  259. document: false,
  260. importScriptsInWorker: false,
  261. fetchWasm: false,
  262. importScripts: false,
  263. require: false,
  264. globalThis: v(0, 43),
  265. const: v(0, 15),
  266. templateLiteral: v(0, 13),
  267. optionalChaining: v(0, 44),
  268. methodShorthand: v(0, 15),
  269. arrowFunction: v(0, 15),
  270. asyncFunction: v(0, 21),
  271. forOf: v(0, 13),
  272. destructuring: v(0, 15),
  273. bigIntLiteral: v(0, 32),
  274. dynamicImport: v(0, 43),
  275. dynamicImportInWorker: v(0, 44),
  276. module: v(0, 43)
  277. };
  278. }
  279. ],
  280. [
  281. "esX",
  282. "EcmaScript in this version. Examples: es2020, es5.",
  283. /^es(\d+)$/,
  284. (version) => {
  285. let v = Number(version);
  286. if (v < 1000) v += 2009;
  287. return {
  288. const: v >= 2015,
  289. templateLiteral: v >= 2015,
  290. optionalChaining: v >= 2020,
  291. methodShorthand: v >= 2015,
  292. arrowFunction: v >= 2015,
  293. forOf: v >= 2015,
  294. destructuring: v >= 2015,
  295. module: v >= 2015,
  296. asyncFunction: v >= 2017,
  297. globalThis: v >= 2020,
  298. bigIntLiteral: v >= 2020,
  299. dynamicImport: v >= 2020,
  300. dynamicImportInWorker: v >= 2020
  301. };
  302. }
  303. ]
  304. ];
  305. /**
  306. * Gets target properties.
  307. * @param {string} target the target
  308. * @param {string} context the context directory
  309. * @returns {TargetProperties} target properties
  310. */
  311. const getTargetProperties = (target, context) => {
  312. for (const [, , regExp, handler] of TARGETS) {
  313. const match = regExp.exec(target);
  314. if (match) {
  315. const [, ...args] = match;
  316. const result = handler(...args, context);
  317. if (result) return /** @type {TargetProperties} */ (result);
  318. }
  319. }
  320. throw new Error(
  321. `Unknown target '${target}'. The following targets are supported:\n${TARGETS.map(
  322. ([name, description]) => `* ${name}: ${description}`
  323. ).join("\n")}`
  324. );
  325. };
  326. /**
  327. * Merges target properties.
  328. * @param {TargetProperties[]} targetProperties array of target properties
  329. * @returns {TargetProperties} merged target properties
  330. */
  331. const mergeTargetProperties = (targetProperties) => {
  332. /** @type {Set<keyof TargetProperties>} */
  333. const keys = new Set();
  334. for (const tp of targetProperties) {
  335. for (const key of Object.keys(tp)) {
  336. keys.add(/** @type {keyof TargetProperties} */ (key));
  337. }
  338. }
  339. /** @type {TargetProperties} */
  340. const result = {};
  341. for (const key of keys) {
  342. let hasTrue = false;
  343. let hasFalse = false;
  344. for (const tp of targetProperties) {
  345. const value = tp[key];
  346. switch (value) {
  347. case true:
  348. hasTrue = true;
  349. break;
  350. case false:
  351. hasFalse = true;
  352. break;
  353. }
  354. }
  355. if (hasTrue || hasFalse) {
  356. /** @type {TargetProperties} */
  357. (result)[key] = hasFalse && hasTrue ? null : Boolean(hasTrue);
  358. }
  359. }
  360. return result;
  361. };
  362. /**
  363. * Gets targets properties.
  364. * @param {string[]} targets the targets
  365. * @param {string} context the context directory
  366. * @returns {TargetProperties} target properties
  367. */
  368. const getTargetsProperties = (targets, context) =>
  369. mergeTargetProperties(targets.map((t) => getTargetProperties(t, context)));
  370. module.exports.getDefaultTarget = getDefaultTarget;
  371. module.exports.getTargetProperties = getTargetProperties;
  372. module.exports.getTargetsProperties = getTargetsProperties;