@parcel/core
Version:
299 lines (263 loc) • 7 kB
JavaScript
// @flow strict-local
import type {
Environment as IEnvironment,
EnvironmentContext,
EnvironmentFeature,
Engines,
OutputFormat,
PackageName,
VersionMap,
SourceLocation,
SourceType,
TargetSourceMapOptions,
} from '@parcel/types';
import type {Environment as InternalEnvironment, ParcelOptions} from '../types';
import nullthrows from 'nullthrows';
import browserslist from 'browserslist';
import semver from 'semver';
import {fromInternalSourceLocation} from '../utils';
const inspect = Symbol.for('nodejs.util.inspect.custom');
export const BROWSER_ENVS: Set<string> = new Set<string>([
'browser',
'web-worker',
'service-worker',
'worklet',
'electron-renderer',
]);
const ELECTRON_ENVS = new Set(['electron-main', 'electron-renderer']);
const NODE_ENVS = new Set(['node', ...ELECTRON_ENVS]);
const WORKER_ENVS = new Set(['web-worker', 'service-worker']);
export const ISOLATED_ENVS: Set<string> = new Set([...WORKER_ENVS, 'worklet']);
const ALL_BROWSERS = [
'chrome',
'and_chr',
'edge',
'firefox',
'and_ff',
'safari',
'ios',
'samsung',
'opera',
'ie',
'op_mini',
'blackberry',
'op_mob',
'ie_mob',
'and_uc',
'and_qq',
'baidu',
'kaios',
];
// See require("caniuse-api").getSupport(<feature name>)
const supportData = {
esmodules: {
edge: '16',
firefox: '60',
chrome: '61',
safari: '10.1',
opera: '48',
ios: '10.3',
android: '76',
and_chr: '76',
and_ff: '68',
samsung: '8.2',
and_qq: '10.4',
op_mob: '64',
},
'dynamic-import': {
edge: '76',
firefox: '67',
chrome: '63',
safari: '11.1',
opera: '50',
ios: '11.3',
android: '63',
and_chr: '63',
and_ff: '67',
samsung: '8',
and_qq: '10.4',
op_mob: '64',
},
'worker-module': {
edge: '80',
chrome: '80',
opera: '67',
android: '81',
and_chr: '86',
},
'service-worker-module': {
// TODO: Safari 14.1??
},
'import-meta-url': {
edge: '79',
firefox: '62',
chrome: '64',
safari: '11.1',
opera: '51',
ios: '12',
android: '64',
and_chr: '64',
and_ff: '62',
samsung: '9.2',
and_qq: '10.4',
op_mob: '64',
},
'arrow-functions': {
chrome: '47',
opera: '34',
edge: '13',
firefox: '45',
safari: '10',
node: '6',
ios: '10',
samsung: '5',
electron: '0.36',
android: '50',
qq: '10.4',
baidu: '7.12',
kaios: '2.5',
and_chr: '50',
and_qq: '12.12',
op_mob: '64',
},
};
const internalEnvironmentToEnvironment: WeakMap<
InternalEnvironment,
Environment,
> = new WeakMap();
const _environmentToInternalEnvironment: WeakMap<
IEnvironment,
InternalEnvironment,
> = new WeakMap();
export function environmentToInternalEnvironment(
environment: IEnvironment,
): InternalEnvironment {
return nullthrows(_environmentToInternalEnvironment.get(environment));
}
export default class Environment implements IEnvironment {
#environment /*: InternalEnvironment */;
#options /*: ParcelOptions */;
constructor(env: InternalEnvironment, options: ParcelOptions): Environment {
let existing = internalEnvironmentToEnvironment.get(env);
if (existing != null) {
return existing;
}
this.#environment = env;
this.#options = options;
_environmentToInternalEnvironment.set(this, env);
internalEnvironmentToEnvironment.set(env, this);
return this;
}
get id(): string {
return this.#environment.id;
}
get context(): EnvironmentContext {
return this.#environment.context;
}
get engines(): Engines {
return this.#environment.engines;
}
get includeNodeModules():
| boolean
| Array<PackageName>
| {[PackageName]: boolean, ...} {
return this.#environment.includeNodeModules;
}
get outputFormat(): OutputFormat {
return this.#environment.outputFormat;
}
get sourceType(): SourceType {
return this.#environment.sourceType;
}
get isLibrary(): boolean {
return this.#environment.isLibrary;
}
get shouldOptimize(): boolean {
return this.#environment.shouldOptimize;
}
get shouldScopeHoist(): boolean {
return this.#environment.shouldScopeHoist;
}
get sourceMap(): ?TargetSourceMapOptions {
return this.#environment.sourceMap;
}
get loc(): ?SourceLocation {
return fromInternalSourceLocation(
this.#options.projectRoot,
this.#environment.loc,
);
}
// $FlowFixMe[unsupported-syntax]
[inspect](): string {
return `Env(${this.#environment.context})`;
}
isBrowser(): boolean {
return BROWSER_ENVS.has(this.#environment.context);
}
isNode(): boolean {
return NODE_ENVS.has(this.#environment.context);
}
isElectron(): boolean {
return ELECTRON_ENVS.has(this.#environment.context);
}
isIsolated(): boolean {
return ISOLATED_ENVS.has(this.#environment.context);
}
isWorker(): boolean {
return WORKER_ENVS.has(this.#environment.context);
}
isWorklet(): boolean {
return this.#environment.context === 'worklet';
}
matchesEngines(
minVersions: VersionMap,
defaultValue?: boolean = false,
): boolean {
// Determine if the environment matches some minimum version requirements.
// For browsers, we run a browserslist query with and without the minimum
// required browsers and compare the lists. For node, we just check semver.
if (this.isBrowser() && this.engines.browsers != null) {
let targetBrowsers = this.engines.browsers;
let browsers =
targetBrowsers != null && !Array.isArray(targetBrowsers)
? [targetBrowsers]
: targetBrowsers;
// If outputting esmodules, exclude browsers without support.
if (this.outputFormat === 'esmodule') {
browsers = [...browsers, ...getExcludedBrowsers(supportData.esmodules)];
}
let matchedBrowsers = browserslist(browsers);
if (matchedBrowsers.length === 0) {
return false;
}
let minBrowsers = getExcludedBrowsers(minVersions);
let withoutMinBrowsers = browserslist([...browsers, ...minBrowsers]);
return matchedBrowsers.length === withoutMinBrowsers.length;
} else if (this.isNode() && this.engines.node != null) {
return (
minVersions.node != null &&
!semver.intersects(`< ${minVersions.node}`, this.engines.node)
);
}
return defaultValue;
}
supports(feature: EnvironmentFeature, defaultValue?: boolean): boolean {
let engines = supportData[feature];
if (!engines) {
throw new Error('Unknown environment feature: ' + feature);
}
return this.matchesEngines(engines, defaultValue);
}
}
function getExcludedBrowsers(minVersions: VersionMap) {
let browsers = [];
for (let browser of ALL_BROWSERS) {
let version = minVersions[browser];
if (version) {
browsers.push(`not ${browser} < ${version}`);
} else {
browsers.push(`not ${browser} > 0`);
}
}
return browsers;
}