webpack/lib/config/browserslistTargetHandler.js

172 lines
4.8 KiB
JavaScript
Raw Normal View History

2020-09-24 19:33:09 +08:00
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Sergey Melyukov @smelukov
*/
2020-09-24 19:42:27 +08:00
"use strict";
2020-09-24 19:33:09 +08:00
const browserslist = require("browserslist");
2020-09-24 20:46:28 +08:00
const path = require("path");
2020-09-24 19:33:09 +08:00
/** @typedef {import("./target").ApiTargetProperties} ApiTargetProperties */
/** @typedef {import("./target").EcmaTargetProperties} EcmaTargetProperties */
/** @typedef {import("./target").PlatformTargetProperties} PlatformTargetProperties */
2020-09-25 04:10:33 +08:00
// [[C:]/path/to/config][:env]
2020-09-25 04:44:41 +08:00
const inputRx = /^(?:((?:[A-Z]:)?[/\\].*?))?(?::(.+?))?$/i;
2020-09-24 19:33:09 +08:00
/**
* @typedef {Object} BrowserslistHandlerConfig
* @property {string} [configPath]
* @property {string} [env]
* @property {string} [query]
*/
/**
2020-09-28 16:05:29 +08:00
* @param {BrowserslistHandlerConfig} handlerConfig config
* @returns {EcmaTargetProperties & PlatformTargetProperties & ApiTargetProperties} target properties
2020-09-24 19:33:09 +08:00
*/
const resolve = handlerConfig => {
2020-09-28 16:05:29 +08:00
const { configPath, env, query } = handlerConfig;
2020-09-24 19:33:09 +08:00
// if a query is specified, then use it, else
// if a path to a config is specified then load it, else
// find a nearest config
const config = query
? query
: configPath
? browserslist.loadConfig({
2020-09-24 19:33:09 +08:00
config: configPath,
env
})
: browserslist.loadConfig({ path: process.cwd(), env });
2020-09-24 19:33:09 +08:00
const browsers = browserslist(config);
2020-09-24 19:33:09 +08:00
return resolveESFeatures(browsers);
2020-09-24 19:33:09 +08:00
};
/**
* @param {string} input input string
2020-09-28 16:05:29 +08:00
* @returns {BrowserslistHandlerConfig} config
2020-09-24 19:33:09 +08:00
*/
const parse = input => {
if (!input) {
2020-09-28 16:05:29 +08:00
return {};
2020-09-24 19:33:09 +08:00
}
2020-09-24 20:41:54 +08:00
if (path.isAbsolute(input)) {
const [, configPath, env] = inputRx.exec(input) || [];
return { configPath, env };
}
const config = browserslist.findConfig(process.cwd());
2020-09-25 04:10:33 +08:00
if (config && Object.keys(config).includes(input)) {
return { env: input };
2020-09-24 20:41:54 +08:00
}
2020-09-24 19:33:09 +08:00
2020-09-25 04:10:33 +08:00
return { query: input };
2020-09-24 19:33:09 +08:00
};
/**
* @param {string[]} browsers supported browsers list
* @returns {EcmaTargetProperties & PlatformTargetProperties & ApiTargetProperties} target properties
2020-09-24 19:33:09 +08:00
*/
const resolveESFeatures = browsers => {
2020-09-24 19:33:09 +08:00
/**
* Checks only browser against the browserslist feature query
2020-09-24 19:33:09 +08:00
* @param {string} feature an ES feature to test
* @returns {boolean} true if supports
*/
const browserslistChecker = feature => {
2020-09-24 19:33:09 +08:00
const supportsFeature = browserslist(`supports ${feature}`);
return browsers.every(v => /^node /.test(v) || supportsFeature.includes(v));
};
/**
* Checks only node.js version against a version
* @param {number} major major version
* @param {number} minor minor version
* @returns {boolean} true if supports
*/
const nodeChecker = (major, minor = 0) => {
return browsers.every(v => {
const match = /^node (\d+)(?:\.\d+)?/.exec(v);
if (!match) return true;
const [, v1, v2] = match;
return major === +v1 ? +v2 >= minor : +v1 > major;
});
};
/**
* Checks all against a version number
* @param {Record<string, number | [number, number]} versions first supported version
* @returns {boolean} true if supports
*/
const rawChecker = versions => {
return browsers.every(v => {
const match = /^([^ ]+) (\d+)(?:\.\d+)?/.exec(v);
if (!match) return false;
const [, name, major, minor] = match;
const version = versions[name];
if (!version) return false;
if (typeof version === "number") return +major >= version;
return version[0] === +major ? +minor >= version[1] : +major > version[0];
});
};
const anyNode = browsers.some(b => /^node /.test(b));
const anyBrowser = browsers.some(b => /^(?!node)/.test(b));
const browserProperty = !anyBrowser ? false : anyNode ? null : true;
const nodeProperty = !anyNode ? false : anyBrowser ? null : true;
const es6 = browserslistChecker("es6");
const es6DynamicImport = browserslistChecker("es6-module-dynamic-import");
const node6 = nodeChecker(6);
return {
const: es6 && node6,
arrowFunction: es6 && node6,
forOf: es6 && nodeChecker(5),
destructuring: es6 && node6,
bigIntLiteral: browserslistChecker("bigint") && nodeChecker(10, 4),
module: browserslistChecker("es6-module") && nodeChecker(12, 17),
dynamicImport: es6DynamicImport && nodeChecker(10, 17),
dynamicImportInWorker: es6DynamicImport && nodeChecker(Infinity),
// browserslist does not have info about globalThis
// so this is based on mdn-browser-compat-data
globalThis: rawChecker({
chrome: 71,
chrome_android: 71,
edge: 79,
firefox: 65,
firefox_android: 65,
// ie: Not supported,
nodejs: 12,
opera: 58,
opera_android: 50,
safari: [12, 1],
safari_ios: [12, 2],
// cspell:word samsunginternet
samsunginternet_android: [10, 0],
webview_android: 71
}),
browser: browserProperty,
electron: false,
node: nodeProperty,
nwjs: false,
web: browserProperty,
webworker: false,
document: browserProperty,
fetchWasm: browserProperty,
global: nodeProperty,
importScripts: false,
importScriptsInWorker: true,
nodeBuiltins: nodeProperty,
require: nodeProperty
2020-09-24 19:33:09 +08:00
};
};
module.exports = {
resolve,
parse
};