mirror of
https://github.com/swc-project/swc.git
synced 2024-10-04 04:07:18 +03:00
fix(es/minifier): Fix bugs (#2283)
swc_ecma_transforms_optimization: - `expr_simplifier`: Don't inject `0` needlessly. (#2257)
This commit is contained in:
parent
650e1494d4
commit
e8a1710a21
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -2647,10 +2647,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swc_ecma_minifier"
|
||||
version = "0.30.0"
|
||||
version = "0.30.1"
|
||||
dependencies = [
|
||||
"ansi_term 0.12.1",
|
||||
"anyhow",
|
||||
"backtrace",
|
||||
"indexmap",
|
||||
"once_cell",
|
||||
"pretty_assertions 0.6.1",
|
||||
@ -2847,7 +2848,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swc_ecma_transforms_optimization"
|
||||
version = "0.44.0"
|
||||
version = "0.44.1"
|
||||
dependencies = [
|
||||
"dashmap",
|
||||
"indexmap",
|
||||
@ -3498,6 +3499,7 @@ dependencies = [
|
||||
"swc",
|
||||
"swc_common",
|
||||
"swc_ecmascript",
|
||||
"tracing",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
]
|
||||
|
@ -7,12 +7,13 @@ include = ["Cargo.toml", "src/**/*.rs", "src/lists/*.json"]
|
||||
license = "Apache-2.0/MIT"
|
||||
name = "swc_ecma_minifier"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
version = "0.30.0"
|
||||
version = "0.30.1"
|
||||
|
||||
[features]
|
||||
debug = []
|
||||
debug = ["backtrace"]
|
||||
|
||||
[dependencies]
|
||||
backtrace = {version = "0.3.61", optional = true}
|
||||
indexmap = "1.7.0"
|
||||
once_cell = "1.5.2"
|
||||
pretty_assertions = {version = "0.6.1", optional = true}
|
||||
|
@ -6,7 +6,7 @@ set -eux
|
||||
./scripts/run.sh
|
||||
|
||||
export RUST_BACKTRACE=1
|
||||
export RUST_LOG=swc_ecma_minifier=trace
|
||||
export RUST_LOG=debug,swc_ecma_minifier=trace
|
||||
|
||||
UPDATE=1 cargo test -q --test compress projects__files --all-features || true
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
||||
#
|
||||
set -eux
|
||||
|
||||
export RUST_LOG=swc_ecma_minifier=trace
|
||||
export RUST_LOG=debug,swc_ecma_minifier=trace
|
||||
|
||||
# Run unit tests.
|
||||
cargo test --all-features --lib
|
||||
|
@ -36,6 +36,7 @@ use swc_ecma_transforms::{
|
||||
};
|
||||
use swc_ecma_utils::StmtLike;
|
||||
use swc_ecma_visit::{as_folder, noop_visit_mut_type, VisitMut, VisitMutWith, VisitWith};
|
||||
use tracing::{span, Level};
|
||||
|
||||
mod drop_console;
|
||||
mod hoist_decls;
|
||||
@ -234,6 +235,12 @@ where
|
||||
{
|
||||
self.data = Some(analyze(&*n, Some(self.marks)));
|
||||
|
||||
let _tracing = if cfg!(feature = "debug") {
|
||||
Some(span!(Level::ERROR, "compressor", "pass" = self.pass).entered())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if self.options.passes != 0 && self.options.passes + 1 <= self.pass {
|
||||
let done = dump(&*n);
|
||||
tracing::debug!("===== Done =====\n{}", done);
|
||||
@ -302,7 +309,7 @@ where
|
||||
|
||||
let start_time = now();
|
||||
|
||||
let mut visitor = pure_optimizer(&self.options, self.marks, self.mode);
|
||||
let mut visitor = pure_optimizer(&self.options, self.marks, self.mode, self.pass >= 20);
|
||||
n.apply(&mut visitor);
|
||||
self.changed |= visitor.changed();
|
||||
|
||||
@ -338,6 +345,7 @@ where
|
||||
self.data.as_ref().unwrap(),
|
||||
&mut self.optimizer_state,
|
||||
self.mode,
|
||||
self.pass >= 20,
|
||||
);
|
||||
n.apply(&mut visitor);
|
||||
self.changed |= visitor.changed();
|
||||
|
@ -92,7 +92,7 @@ where
|
||||
|
||||
match &p.key {
|
||||
PropName::Str(s) => {
|
||||
tracing::debug!(
|
||||
tracing::trace!(
|
||||
"hoist_props: Storing a varaible to inline \
|
||||
properties"
|
||||
);
|
||||
@ -100,7 +100,7 @@ where
|
||||
.insert((name.to_id(), s.value.clone()), value);
|
||||
}
|
||||
PropName::Ident(i) => {
|
||||
tracing::debug!(
|
||||
tracing::trace!(
|
||||
"hoist_props: Storing a varaible to inline \
|
||||
properties"
|
||||
);
|
||||
|
@ -448,6 +448,10 @@ where
|
||||
span: DUMMY_SP,
|
||||
expr: Box::new(cur),
|
||||
}))
|
||||
} else {
|
||||
if cfg!(feature = "debug") {
|
||||
tracing::debug!("if_return: Ignoring return value");
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
|
@ -218,7 +218,7 @@ where
|
||||
|
||||
let should_be_inlined = self.can_be_inlined_for_iife(arg);
|
||||
if should_be_inlined {
|
||||
tracing::debug!(
|
||||
tracing::trace!(
|
||||
"iife: Trying to inline argument ({}{:?})",
|
||||
param.id.sym,
|
||||
param.id.span.ctxt
|
||||
@ -226,7 +226,7 @@ where
|
||||
vars.insert(param.to_id(), arg.clone());
|
||||
}
|
||||
} else {
|
||||
tracing::debug!(
|
||||
tracing::trace!(
|
||||
"iife: Trying to inline argument ({}{:?}) (undefined)",
|
||||
param.id.sym,
|
||||
param.id.span.ctxt
|
||||
@ -245,11 +245,11 @@ where
|
||||
let mut optimizer = self.with_ctx(ctx);
|
||||
match find_body(callee) {
|
||||
Some(Either::Left(body)) => {
|
||||
tracing::debug!("inline: Inlining arguments");
|
||||
tracing::trace!("inline: Inlining arguments");
|
||||
optimizer.inline_vars_in_node(body, vars);
|
||||
}
|
||||
Some(Either::Right(body)) => {
|
||||
tracing::debug!("inline: Inlining arguments");
|
||||
tracing::trace!("inline: Inlining arguments");
|
||||
optimizer.inline_vars_in_node(body, vars);
|
||||
}
|
||||
_ => {}
|
||||
@ -261,7 +261,9 @@ where
|
||||
where
|
||||
N: VisitMutWith<Self>,
|
||||
{
|
||||
tracing::debug!("inline: inline_vars_in_node");
|
||||
if cfg!(feature = "debug") {
|
||||
tracing::trace!("inline: inline_vars_in_node");
|
||||
}
|
||||
let ctx = Ctx {
|
||||
inline_prevented: false,
|
||||
..self.ctx
|
||||
|
@ -21,6 +21,7 @@ use swc_ecma_utils::{
|
||||
Value,
|
||||
};
|
||||
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith, VisitWith};
|
||||
use tracing::{span, Level};
|
||||
use Value::Known;
|
||||
|
||||
mod arguments;
|
||||
@ -56,6 +57,7 @@ pub(super) fn optimizer<'a, M>(
|
||||
data: &'a ProgramData,
|
||||
state: &'a mut OptimizerState,
|
||||
mode: &'a M,
|
||||
debug_inifinite_loop: bool,
|
||||
) -> impl 'a + VisitMut + Repeated
|
||||
where
|
||||
M: Mode,
|
||||
@ -85,6 +87,7 @@ where
|
||||
done_ctxt,
|
||||
label: Default::default(),
|
||||
mode,
|
||||
debug_inifinite_loop,
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,6 +224,8 @@ struct Optimizer<'a, M> {
|
||||
label: Option<Id>,
|
||||
|
||||
mode: &'a M,
|
||||
|
||||
debug_inifinite_loop: bool,
|
||||
}
|
||||
|
||||
impl<M> Repeated for Optimizer<'_, M> {
|
||||
@ -667,6 +672,7 @@ where
|
||||
|
||||
/// Returns [None] if expression is side-effect-free.
|
||||
/// If an expression has a side effect, only side effects are returned.
|
||||
#[cfg_attr(feature = "debug", tracing::instrument(skip(self, e)))]
|
||||
fn ignore_return_value(&mut self, e: &mut Expr) -> Option<Expr> {
|
||||
self.optimize_bang_within_logical_ops(e, true);
|
||||
|
||||
@ -674,7 +680,9 @@ where
|
||||
|
||||
match e {
|
||||
Expr::Ident(..) | Expr::This(_) | Expr::Invalid(_) | Expr::Lit(..) => {
|
||||
tracing::debug!("ignore_return_value: Dropping unused expr");
|
||||
if cfg!(feature = "debug") {
|
||||
tracing::debug!("ignore_return_value: Dropping unused expr: {}", dump(&*e));
|
||||
}
|
||||
self.changed = true;
|
||||
return None;
|
||||
}
|
||||
@ -1586,7 +1594,8 @@ where
|
||||
span: DUMMY_SP,
|
||||
value: 0.0,
|
||||
})));
|
||||
tracing::trace!("injecting zero to preserve `this` in call");
|
||||
self.changed = true;
|
||||
tracing::debug!("injecting zero to preserve `this` in call");
|
||||
|
||||
*callee = Box::new(Expr::Seq(SeqExpr {
|
||||
span: callee.span(),
|
||||
@ -1819,7 +1828,12 @@ where
|
||||
_ => {}
|
||||
}
|
||||
let expr = self.ignore_return_value(&mut n.expr);
|
||||
n.expr = expr.map(Box::new).unwrap_or_else(|| undefined(DUMMY_SP));
|
||||
n.expr = expr.map(Box::new).unwrap_or_else(|| {
|
||||
if cfg!(feature = "debug") {
|
||||
tracing::debug!("visit_mut_expr_stmt: Dropped an expression statement");
|
||||
}
|
||||
undefined(DUMMY_SP)
|
||||
});
|
||||
} else {
|
||||
match &mut *n.expr {
|
||||
Expr::Seq(e) => {
|
||||
@ -2083,6 +2097,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "debug", tracing::instrument(skip(self, n)))]
|
||||
fn visit_mut_seq_expr(&mut self, n: &mut SeqExpr) {
|
||||
{
|
||||
let ctx = Ctx {
|
||||
@ -2124,7 +2139,7 @@ where
|
||||
|| !self.ctx.is_this_aware_callee
|
||||
|| !should_preserve_zero);
|
||||
|
||||
if can_remove {
|
||||
let ret = if can_remove {
|
||||
// If negate_iife is true, it's already handled by
|
||||
// visit_mut_children_with(self) above.
|
||||
if !self.options.negate_iife {
|
||||
@ -2134,7 +2149,9 @@ where
|
||||
self.ignore_return_value(&mut **expr).map(Box::new)
|
||||
} else {
|
||||
Some(expr.take())
|
||||
}
|
||||
};
|
||||
|
||||
ret
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
n.exprs = exprs;
|
||||
@ -2146,6 +2163,18 @@ where
|
||||
}
|
||||
|
||||
fn visit_mut_stmt(&mut self, s: &mut Stmt) {
|
||||
let _tracing = if cfg!(feature = "debug") && self.debug_inifinite_loop {
|
||||
let text = dump(&*s);
|
||||
|
||||
if text.lines().count() < 10 {
|
||||
Some(span!(Level::ERROR, "visit_mut_stmt", "start" = &*text).entered())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let ctx = Ctx {
|
||||
is_callee: false,
|
||||
is_delete_arg: false,
|
||||
@ -2158,6 +2187,14 @@ where
|
||||
};
|
||||
s.visit_mut_children_with(&mut *self.with_ctx(ctx));
|
||||
|
||||
if cfg!(feature = "debug") && self.debug_inifinite_loop {
|
||||
let text = dump(&*s);
|
||||
|
||||
if text.lines().count() < 10 {
|
||||
tracing::debug!("after: visit_mut_children_with: {}", text);
|
||||
}
|
||||
}
|
||||
|
||||
match s {
|
||||
Stmt::Expr(ExprStmt { expr, .. }) => {
|
||||
if is_pure_undefined(expr) {
|
||||
@ -2225,6 +2262,14 @@ where
|
||||
self.optimize_const_switches(s);
|
||||
|
||||
self.optimize_switches(s);
|
||||
|
||||
if cfg!(feature = "debug") && self.debug_inifinite_loop {
|
||||
let text = dump(&*s);
|
||||
|
||||
if text.lines().count() < 10 {
|
||||
tracing::debug!("after: visit_mut_stmt: {}", text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) {
|
||||
|
@ -1085,7 +1085,7 @@ where
|
||||
span: DUMMY_SP,
|
||||
value: 0.0,
|
||||
})));
|
||||
tracing::trace!("injecting zero to preserve `this` in call");
|
||||
tracing::debug!("injecting zero to preserve `this` in call");
|
||||
|
||||
*b_callee = Box::new(Expr::Seq(SeqExpr {
|
||||
span: b_callee.span(),
|
||||
|
@ -33,6 +33,7 @@ where
|
||||
self.drop_unused_vars(var.span, &mut var.name, Some(init));
|
||||
|
||||
if var.name.is_invalid() {
|
||||
tracing::debug!("unused: Removing an unused variable declarator");
|
||||
let side_effects = self.ignore_return_value(init);
|
||||
if let Some(e) = side_effects {
|
||||
if prepend {
|
||||
|
@ -1,11 +1,13 @@
|
||||
use self::ctx::Ctx;
|
||||
use crate::{
|
||||
marks::Marks, mode::Mode, option::CompressOptions, util::MoudleItemExt, MAX_PAR_DEPTH,
|
||||
debug::dump, marks::Marks, mode::Mode, option::CompressOptions, util::MoudleItemExt,
|
||||
MAX_PAR_DEPTH,
|
||||
};
|
||||
use rayon::prelude::*;
|
||||
use swc_common::{pass::Repeated, util::take::Take, DUMMY_SP};
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith, VisitWith};
|
||||
use tracing::{span, Level};
|
||||
|
||||
mod arrows;
|
||||
mod bools;
|
||||
@ -27,6 +29,7 @@ pub(crate) fn pure_optimizer<'a, M>(
|
||||
options: &'a CompressOptions,
|
||||
marks: Marks,
|
||||
mode: &'a M,
|
||||
debug_inifinite_loop: bool,
|
||||
) -> impl 'a + VisitMut + Repeated
|
||||
where
|
||||
M: Mode,
|
||||
@ -37,6 +40,7 @@ where
|
||||
ctx: Default::default(),
|
||||
changed: Default::default(),
|
||||
mode,
|
||||
debug_inifinite_loop,
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,6 +50,8 @@ struct Pure<'a, M> {
|
||||
ctx: Ctx,
|
||||
changed: bool,
|
||||
mode: &'a M,
|
||||
|
||||
debug_inifinite_loop: bool,
|
||||
}
|
||||
|
||||
impl<M> Repeated for Pure<'_, M> {
|
||||
@ -92,6 +98,7 @@ where
|
||||
ctx: self.ctx,
|
||||
changed: false,
|
||||
mode: self.mode,
|
||||
debug_inifinite_loop: self.debug_inifinite_loop,
|
||||
};
|
||||
node.visit_mut_with(&mut v);
|
||||
|
||||
@ -110,6 +117,7 @@ where
|
||||
},
|
||||
changed: false,
|
||||
mode: self.mode,
|
||||
debug_inifinite_loop: self.debug_inifinite_loop,
|
||||
};
|
||||
node.visit_mut_with(&mut v);
|
||||
|
||||
@ -407,6 +415,18 @@ where
|
||||
}
|
||||
|
||||
fn visit_mut_stmt(&mut self, s: &mut Stmt) {
|
||||
let _tracing = if cfg!(feature = "debug") && self.debug_inifinite_loop {
|
||||
let text = dump(&*s);
|
||||
|
||||
if text.lines().count() < 10 {
|
||||
Some(span!(Level::ERROR, "visit_mut_stmt", "start" = &*text).entered())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
{
|
||||
let ctx = Ctx {
|
||||
is_update_arg: false,
|
||||
@ -418,6 +438,14 @@ where
|
||||
s.visit_mut_children_with(&mut *self.with_ctx(ctx));
|
||||
}
|
||||
|
||||
if cfg!(feature = "debug") && self.debug_inifinite_loop {
|
||||
let text = dump(&*s);
|
||||
|
||||
if text.lines().count() < 10 {
|
||||
tracing::debug!("after: visit_mut_children_with: {}", text);
|
||||
}
|
||||
}
|
||||
|
||||
if self.options.drop_debugger {
|
||||
match s {
|
||||
Stmt::Debugger(..) => {
|
||||
@ -440,6 +468,14 @@ where
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if cfg!(feature = "debug") && self.debug_inifinite_loop {
|
||||
let text = dump(&*s);
|
||||
|
||||
if text.lines().count() < 10 {
|
||||
tracing::debug!("after: visit_mut_stmt: {}", text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_mut_stmts(&mut self, items: &mut Vec<Stmt>) {
|
||||
|
@ -229,6 +229,7 @@ impl Evaluator {
|
||||
&serde_json::from_str("{}").unwrap(),
|
||||
self.marks,
|
||||
&data,
|
||||
false,
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -496,10 +496,16 @@ fn value_to_expr(v: Value) -> Box<Expr> {
|
||||
span: DUMMY_SP,
|
||||
value,
|
||||
}))),
|
||||
Value::Number(v) => Box::new(Expr::Lit(Lit::Num(Number {
|
||||
span: DUMMY_SP,
|
||||
value: v.as_f64().unwrap(),
|
||||
}))),
|
||||
Value::Number(v) => {
|
||||
if cfg!(feature = "debug") {
|
||||
tracing::debug!("Creating a numeric literal from value");
|
||||
}
|
||||
|
||||
Box::new(Expr::Lit(Lit::Num(Number {
|
||||
span: DUMMY_SP,
|
||||
value: v.as_f64().unwrap(),
|
||||
})))
|
||||
}
|
||||
Value::String(v) => Box::new(Expr::Lit(Lit::Str(Str {
|
||||
span: DUMMY_SP,
|
||||
value: v.into(),
|
||||
|
@ -15,6 +15,9 @@ pub(crate) mod unit;
|
||||
|
||||
///
|
||||
pub(crate) fn make_number(span: Span, value: f64) -> Expr {
|
||||
if cfg!(feature = "debug") {
|
||||
tracing::debug!("Creating a numeric literal");
|
||||
}
|
||||
Expr::Lit(Lit::Num(Number { span, value }))
|
||||
}
|
||||
|
||||
@ -84,6 +87,9 @@ impl MoudleItemExt for ModuleItem {
|
||||
/// - `!0` for true
|
||||
/// - `!1` for false
|
||||
pub(crate) fn make_bool(span: Span, value: bool) -> Expr {
|
||||
if cfg!(feature = "debug") {
|
||||
tracing::debug!("Creating a boolean literal");
|
||||
}
|
||||
Expr::Unary(UnaryExpr {
|
||||
span,
|
||||
op: op!("!"),
|
||||
|
26725
ecmascript/minifier/tests/compress/fixture/issues/2257/full/input.js
Normal file
26725
ecmascript/minifier/tests/compress/fixture/issues/2257/full/input.js
Normal file
File diff suppressed because one or more lines are too long
17722
ecmascript/minifier/tests/compress/fixture/issues/2257/full/output.js
Normal file
17722
ecmascript/minifier/tests/compress/fixture/issues/2257/full/output.js
Normal file
File diff suppressed because one or more lines are too long
@ -6,7 +6,7 @@ edition = "2018"
|
||||
license = "Apache-2.0/MIT"
|
||||
name = "swc_ecma_transforms_optimization"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
version = "0.44.0"
|
||||
version = "0.44.1"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
|
@ -1177,20 +1177,30 @@ impl VisitMut for SimplifyExpr {
|
||||
expr.visit_mut_with(self);
|
||||
*e = expr;
|
||||
} else {
|
||||
match seq.exprs.get(0).map(|v| &**v) {
|
||||
Some(Expr::Lit(..) | Expr::Ident(..)) => {}
|
||||
_ => {
|
||||
seq.exprs.insert(
|
||||
0,
|
||||
Box::new(Expr::Lit(Lit::Num(Number {
|
||||
span: DUMMY_SP,
|
||||
value: 0.0,
|
||||
}))),
|
||||
);
|
||||
if let Some(
|
||||
Expr::Member(..)
|
||||
| Expr::Ident(Ident {
|
||||
sym: js_word!("eval"),
|
||||
..
|
||||
}),
|
||||
) = seq.exprs.last().map(|v| &**v)
|
||||
{
|
||||
match seq.exprs.get(0).map(|v| &**v) {
|
||||
Some(Expr::Lit(..) | Expr::Ident(..)) => {}
|
||||
_ => {
|
||||
tracing::debug!("Injecting `0` to preserve `this = undefined`");
|
||||
seq.exprs.insert(
|
||||
0,
|
||||
Box::new(Expr::Lit(Lit::Num(Number {
|
||||
span: DUMMY_SP,
|
||||
value: 0.0,
|
||||
}))),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
seq.visit_mut_with(self);
|
||||
seq.visit_mut_with(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
@ -1447,6 +1457,8 @@ impl VisitMut for SimplifyExpr {
|
||||
span: DUMMY_SP,
|
||||
value: 0.0,
|
||||
}))));
|
||||
|
||||
tracing::trace!("expr_simplifier: Preserving first zero");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1458,6 +1470,8 @@ impl VisitMut for SimplifyExpr {
|
||||
span: DUMMY_SP,
|
||||
value: 0.0,
|
||||
}))));
|
||||
|
||||
tracing::debug!("expr_simplifier: Injected first zero");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1487,10 +1501,7 @@ impl VisitMut for SimplifyExpr {
|
||||
|
||||
self.changed |= len != exprs.len();
|
||||
|
||||
*e = SeqExpr {
|
||||
exprs,
|
||||
span: e.span,
|
||||
}
|
||||
e.exprs = exprs;
|
||||
}
|
||||
|
||||
fn visit_mut_stmt(&mut self, s: &mut Stmt) {
|
||||
|
@ -21,5 +21,6 @@ serde_json = "1"
|
||||
swc = {path = "../"}
|
||||
swc_common = {path = "../common"}
|
||||
swc_ecmascript = {path = "../ecmascript"}
|
||||
tracing = {version = "0.1.28", features = ["release_max_level_off"]}
|
||||
wasm-bindgen = {version = "0.2", features = ["serde-serialize"]}
|
||||
wasm-bindgen-futures = "0.4.8"
|
||||
|
Loading…
Reference in New Issue
Block a user