feat(es/preset_env): Use browserslist-rs (#2845)

swc_ecma_preset_env:
 - Fix `default_path`.
 - Use `browserslist-rs` for query. (Closes #2781)
This commit is contained in:
Donny/강동윤 2021-11-23 19:07:16 +09:00 committed by GitHub
parent e99c4d26ae
commit 2c099bfd2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 125 additions and 136 deletions

63
Cargo.lock generated
View File

@ -256,6 +256,24 @@ dependencies = [
"generic-array",
]
[[package]]
name = "browserslist-rs"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa428c5a7369965603314e5fbfa5ae1755159bce2c35880d80ea84a866213b20"
dependencies = [
"chrono",
"itertools",
"js-sys",
"once_cell",
"regex",
"serde",
"serde-wasm-bindgen",
"serde_json",
"thiserror",
"wasm-bindgen",
]
[[package]]
name = "build_const"
version = "0.2.2"
@ -298,10 +316,12 @@ version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [
"js-sys",
"libc",
"num-integer",
"num-traits",
"time",
"wasm-bindgen",
"winapi",
]
@ -944,6 +964,15 @@ dependencies = [
"syn",
]
[[package]]
name = "itertools"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "0.4.8"
@ -2193,6 +2222,18 @@ dependencies = [
"serde_derive",
]
[[package]]
name = "serde-wasm-bindgen"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "618365e8e586c22123d692b72a7d791d5ee697817b65a218cdf12a98870af0f7"
dependencies = [
"fnv",
"js-sys",
"serde",
"wasm-bindgen",
]
[[package]]
name = "serde_derive"
version = "1.0.130"
@ -2829,6 +2870,8 @@ name = "swc_ecma_preset_env"
version = "0.66.0"
dependencies = [
"ahash",
"anyhow",
"browserslist-rs",
"dashmap",
"indexmap",
"once_cell",
@ -3390,6 +3433,26 @@ dependencies = [
"syn",
]
[[package]]
name = "thiserror"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thread_local"
version = "1.1.3"

View File

@ -11,6 +11,8 @@ version = "0.66.0"
[dependencies]
ahash = "0.7.4"
anyhow = "1"
browserslist-rs = "=0.2.0"
dashmap = "4.0.2"
indexmap = "1.6.2"
once_cell = "1.2.0"

View File

@ -1,8 +0,0 @@
{
"devDependencies": {
"browserslist": ">=4.16.5"
},
"browserslist": [
"defaults"
]
}

View File

@ -2,14 +2,12 @@
#![recursion_limit = "256"]
pub use self::{transform_data::Feature, version::Version};
use anyhow::{Context, Error};
use dashmap::DashMap;
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use serde::Deserialize;
use st_map::StaticMap;
use std::{
path::{Path, PathBuf},
process::Command,
};
use std::path::PathBuf;
use swc_atoms::{js_word, JsWord};
use swc_common::{
chain,
@ -38,8 +36,7 @@ where
C: Comments,
{
let loose = c.loose;
let targets: Versions =
targets_to_versions(c.targets, &c.path).expect("failed to parse targets");
let targets: Versions = targets_to_versions(c.targets).expect("failed to parse targets");
let is_any_target = targets.is_any_target();
let (include, included_modules) = FeatureOrModule::split(c.include);
@ -425,12 +422,12 @@ pub enum Mode {
pub type Versions = BrowserData<Option<Version>>;
impl BrowserData<Option<Version>> {
pub fn is_any_target(&self) -> bool {
pub(crate) fn is_any_target(&self) -> bool {
self.iter().all(|(_, v)| v.is_none())
}
pub fn parse_versions<'a>(lines: impl Iterator<Item = &'a str>) -> Result<Self, &'a str> {
fn remap(key: &str) -> String {
pub(crate) fn parse_versions(distribs: Vec<browserslist::Distrib>) -> Result<Self, Error> {
fn remap(key: &str) -> &str {
match key {
"and_chr" => "chrome".into(),
"and_ff" => "firefox".into(),
@ -441,13 +438,11 @@ impl BrowserData<Option<Version>> {
}
}
let browsers = lines.map(|v| {
let mut v = v.split(' ');
(remap(v.next().unwrap()), v.next().unwrap().to_string())
});
let mut data: Versions = BrowserData::default();
for (browser, version) in browsers {
for dist in distribs {
let browser = dist.name();
let browser = remap(browser);
let version = dist.version();
match &*browser {
"and_qq" | "and_uc" | "baidu" | "bb" | "kaios" | "op_mini" => continue,
@ -527,7 +522,11 @@ fn default_targets() -> Option<Targets> {
}
fn default_path() -> PathBuf {
std::env::current_dir().unwrap()
if cfg!(target_arch = "wasm32") {
Default::default()
} else {
std::env::current_dir().unwrap()
}
}
#[derive(Debug, Clone, Deserialize, FromVariant)]
@ -583,72 +582,69 @@ pub enum Query {
Multiple(Vec<String>),
}
type QueryResult = Result<Versions, ()>;
type QueryResult = Result<Versions, Error>;
impl Query {
fn exec(&self, path: &Path) -> QueryResult {
fn query<T>(s: &[T], path: &Path) -> QueryResult
fn exec(&self) -> QueryResult {
fn query<T>(s: &[T]) -> QueryResult
where
T: AsRef<str> + Serialize,
T: AsRef<str>,
{
let output = {
let output = Command::new("node")
.current_dir(path)
.arg("-e")
.arg(include_str!("query.js"))
.arg(serde_json::to_string(&s).expect("failed to serialize with serde"))
.output()
.expect("failed to collect output");
if !output.status.success() {
println!("query.js: Status {:?}", output.status,);
println!(
"{}\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
return Err(());
}
let distribs = browserslist::resolve(
s,
&browserslist::Opts {
mobile_to_desktop: false,
ignore_unknown_versions: true,
},
)
.with_context(|| {
format!(
"failed to resolve browserslist query: {:?}",
s.iter().map(|v| v.as_ref()).collect::<Vec<_>>()
)
})?;
output.stdout
};
let browsers: Vec<String> =
serde_json::from_slice(&output).expect("failed to read browser data output");
let versions = BrowserData::parse_versions(browsers.iter().map(|s| &**s))
.expect("failed to parse browser version");
let versions =
BrowserData::parse_versions(distribs).expect("failed to parse browser version");
Ok(versions)
}
static CACHE: Lazy<DashMap<Query, QueryResult, ahash::RandomState>> =
static CACHE: Lazy<DashMap<Query, Versions, ahash::RandomState>> =
Lazy::new(Default::default);
if let Some(v) = CACHE.get(self) {
return match &*v {
Ok(v) => Ok(*v),
Err(err) => Err(*err),
};
return Ok(v.clone());
}
let result = match *self {
Query::Single(ref s) => query(&[s], path),
Query::Multiple(ref s) => query(&s, path),
};
Query::Single(ref s) => {
if s == "" {
query(&["defaults"])
} else {
query(&[s])
}
}
Query::Multiple(ref s) => query(&s),
}
.context("failed to execute query")?;
CACHE.insert(self.clone(), result);
result
Ok(result)
}
}
fn targets_to_versions(v: Option<Targets>, path: &Path) -> Result<Versions, ()> {
fn targets_to_versions(v: Option<Targets>) -> Result<Versions, Error> {
match v {
None => Ok(Default::default()),
Some(Targets::Versions(v)) => Ok(v),
Some(Targets::Query(q)) => q.exec(path),
Some(Targets::Query(q)) => q
.exec()
.context("failed to convert target query to version data"),
Some(Targets::HashMap(mut map)) => {
let q = map.remove("browsers").map(|q| match q {
QueryOrVersion::Query(q) => q.exec(path).expect("failed to run query"),
QueryOrVersion::Query(q) => q.exec().expect("failed to run query"),
_ => unreachable!(),
});
@ -676,8 +672,7 @@ mod tests {
#[test]
fn test_empty() {
let path = std::env::current_dir().unwrap();
let res = Query::Single("".into()).exec(&path).unwrap();
let res = Query::Single("".into()).exec().unwrap();
assert!(
!res.is_any_target(),
"empty query should return non-empty result"

View File

@ -1,27 +0,0 @@
"use strict";
var browserslist;
try{
browserslist = require('browserslist');
} catch (e){
console.error('swc: You have to install `browserslist` to use `env`');
process.exit(1);
}
var target = JSON.parse(process.argv[1]);
target = target.browsers ? target.browsers : target;
target = Array.isArray(target) ? target : typeof target === 'string' ? [target] : Object.keys(target).map(function (k) {
return k + " " + target[k];
});
target = target.filter(function (v) {
return !v.startsWith('esmodules') && !!v;
}); // console.log('Target: ', target);
var browsers = browserslist(target && target.length ? target : undefined, {
mobileToDesktop: true
});
browsers = browsers.filter(function (v) {
return !v.includes("TP");
});
console.log(JSON.stringify(browsers));

View File

@ -1,13 +1,3 @@
import "core-js/modules/es6.array.sort";
import "core-js/modules/es7.array.flat-map";
import "core-js/modules/es7.object.define-getter";
import "core-js/modules/es7.object.define-setter";
import "core-js/modules/es7.object.lookup-getter";
import "core-js/modules/es7.object.lookup-setter";
import "core-js/modules/es7.promise.finally";
import "core-js/modules/es7.string.trim-left";
import "core-js/modules/es7.string.trim-right";
import "core-js/modules/es7.symbol.async-iterator";
import "core-js/modules/web.dom.iterable";
import "core-js/modules/web.immediate";
import "core-js/modules/web.timers";

View File

@ -1,28 +1,2 @@
import "core-js/modules/es.array.flat";
import "core-js/modules/es.array.flat-map";
import "core-js/modules/es.array.iterator";
import "core-js/modules/es.array.reduce";
import "core-js/modules/es.array.reduce-right";
import "core-js/modules/es.array.sort";
import "core-js/modules/es.array.unscopables.flat";
import "core-js/modules/es.array.unscopables.flat-map";
import "core-js/modules/es.math.hypot";
import "core-js/modules/es.object.define-getter";
import "core-js/modules/es.object.define-setter";
import "core-js/modules/es.object.from-entries";
import "core-js/modules/es.object.lookup-getter";
import "core-js/modules/es.object.lookup-setter";
import "core-js/modules/es.promise";
import "core-js/modules/es.promise.finally";
import "core-js/modules/es.string.replace";
import "core-js/modules/es.string.trim-end";
import "core-js/modules/es.string.trim-start";
import "core-js/modules/es.symbol.async-iterator";
import "core-js/modules/es.symbol.description";
import "core-js/modules/web.dom-collections.iterator";
import "core-js/modules/web.immediate";
import "core-js/modules/web.queue-microtask";
import "core-js/modules/web.url";
import "core-js/modules/web.url-search-params";
import "core-js/modules/web.url.to-json";
1 ** 2;

View File

@ -43,6 +43,7 @@
"Deser",
"dont",
"DWORD",
"ecma",
"ecmascript",
"eddyb",
"elems",
@ -125,5 +126,7 @@
"unimpl",
"untrusted"
],
"flagWords": ["actally"]
}
"flagWords": [
"actally"
]
}

View File

@ -49,9 +49,7 @@
"dependencies": {
"@node-rs/helper": "^1.0.0"
},
"optionalDependencies": {
"browserslist": "^4.16.6"
},
"optionalDependencies": {},
"types": "./index.d.ts",
"scripts": {
"prepare": "husky install && git config feature.manyFiles true",
@ -78,7 +76,6 @@
"@types/node": "^14.14.41",
"axios": "^0.21.1",
"babel-plugin-transform-node-env-inline": "^0.4.3",
"browserslist": "^4.16.6",
"class-validator": "^0.13.1",
"core-js": "^2.6.11",
"cross-env": "^7.0.3",