mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
feat(transformer): support logical-assignment-operators plugin (#4890)
part of #4754 The implementation copy from the original implementation which removed in https://github.com/oxc-project/oxc/pull/2865.
This commit is contained in:
parent
ab1d08ccfb
commit
0d7912217a
8 changed files with 373 additions and 7 deletions
|
|
@ -0,0 +1,271 @@
|
|||
use std::cell::Cell;
|
||||
|
||||
use oxc_allocator::{CloneIn, Vec};
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_semantic::{ReferenceFlag, SymbolFlags};
|
||||
use oxc_span::SPAN;
|
||||
use oxc_syntax::operator::{AssignmentOperator, LogicalOperator};
|
||||
use oxc_traverse::TraverseCtx;
|
||||
|
||||
use crate::context::Ctx;
|
||||
|
||||
/// ES2021: Logical Assignment Operators
|
||||
///
|
||||
/// References:
|
||||
/// * <https://babel.dev/docs/babel-plugin-transform-logical-assignment-operators>
|
||||
/// * <https://github.com/babel/babel/blob/main/packages/babel-plugin-transform-logical-assignment-operators>
|
||||
pub struct LogicalAssignmentOperators<'a> {
|
||||
_ctx: Ctx<'a>,
|
||||
var_declarations: std::vec::Vec<Vec<'a, VariableDeclarator<'a>>>,
|
||||
}
|
||||
|
||||
impl<'a> LogicalAssignmentOperators<'a> {
|
||||
pub fn new(ctx: Ctx<'a>) -> Self {
|
||||
Self { _ctx: ctx, var_declarations: vec![] }
|
||||
}
|
||||
|
||||
fn clone_identifier_reference(
|
||||
ident: &IdentifierReference<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> IdentifierReference<'a> {
|
||||
let reference = ctx.symbols().get_reference(ident.reference_id.get().unwrap());
|
||||
let symbol_id = reference.symbol_id();
|
||||
let flag = reference.flag();
|
||||
ctx.create_reference_id(ident.span, ident.name.clone(), symbol_id, *flag)
|
||||
}
|
||||
|
||||
pub fn maybe_generate_memoised(
|
||||
&mut self,
|
||||
expr: &Expression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) -> Option<IdentifierReference<'a>> {
|
||||
let name = match expr {
|
||||
Expression::Super(_) | Expression::ThisExpression(_) => return None,
|
||||
Expression::Identifier(ident) => ident.name.clone(),
|
||||
Expression::StringLiteral(str) => str.value.clone(),
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let symbol_id =
|
||||
ctx.generate_uid_in_current_scope(name.as_str(), SymbolFlags::FunctionScopedVariable);
|
||||
let symbol_name = ctx.ast.atom(ctx.symbols().get_name(symbol_id));
|
||||
|
||||
// var _name;
|
||||
let binding_identifier = BindingIdentifier {
|
||||
span: SPAN,
|
||||
name: symbol_name.clone(),
|
||||
symbol_id: Cell::new(Some(symbol_id)),
|
||||
};
|
||||
let kind = VariableDeclarationKind::Var;
|
||||
let id = ctx.ast.binding_pattern_kind_from_binding_identifier(binding_identifier);
|
||||
let id = ctx.ast.binding_pattern(id, None::<TSTypeAnnotation>, false);
|
||||
self.var_declarations
|
||||
.last_mut()
|
||||
.unwrap()
|
||||
.push(ctx.ast.variable_declarator(SPAN, kind, id, None, false));
|
||||
|
||||
// _name = name
|
||||
Some(ctx.create_reference_id(SPAN, symbol_name, Some(symbol_id), ReferenceFlag::Write))
|
||||
}
|
||||
|
||||
pub fn transform_statements(
|
||||
&mut self,
|
||||
_statements: &mut Vec<'a, Statement<'a>>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
self.var_declarations.push(ctx.ast.vec());
|
||||
}
|
||||
|
||||
pub fn transform_statements_on_exit(
|
||||
&mut self,
|
||||
statements: &mut Vec<'a, Statement<'a>>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
if let Some(declarations) = self.var_declarations.pop() {
|
||||
if declarations.is_empty() {
|
||||
return;
|
||||
}
|
||||
let variable = ctx.ast.alloc_variable_declaration(
|
||||
SPAN,
|
||||
VariableDeclarationKind::Var,
|
||||
declarations,
|
||||
false,
|
||||
);
|
||||
statements.insert(0, Statement::VariableDeclaration(variable));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transform_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::AssignmentExpression(assignment_expr) = expr else { return };
|
||||
|
||||
// `&&=` `||=` `??=`
|
||||
let operator = match assignment_expr.operator {
|
||||
AssignmentOperator::LogicalAnd => LogicalOperator::And,
|
||||
AssignmentOperator::LogicalOr => LogicalOperator::Or,
|
||||
AssignmentOperator::LogicalNullish => LogicalOperator::Coalesce,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
// `a &&= c` -> `a && (a = c);`
|
||||
// ^ ^ assign_target
|
||||
// ^ left_expr
|
||||
|
||||
let left_expr: Expression<'a>;
|
||||
let assign_target: AssignmentTarget;
|
||||
|
||||
// TODO: refactor this block, add tests, cover private identifier
|
||||
match &mut assignment_expr.left {
|
||||
AssignmentTarget::AssignmentTargetIdentifier(ident) => {
|
||||
left_expr = ctx.ast.expression_from_identifier_reference(
|
||||
Self::clone_identifier_reference(ident, ctx),
|
||||
);
|
||||
assign_target = AssignmentTarget::from(
|
||||
ctx.ast.simple_assignment_target_from_identifier_reference(
|
||||
Self::clone_identifier_reference(ident, ctx),
|
||||
),
|
||||
);
|
||||
}
|
||||
left @ match_member_expression!(AssignmentTarget) => {
|
||||
let member_expr = left.to_member_expression_mut();
|
||||
let op = AssignmentOperator::Assign;
|
||||
|
||||
// `a.b &&= c` -> `var _a; (_a = a).b && (_a.b = c)`
|
||||
match member_expr {
|
||||
MemberExpression::StaticMemberExpression(static_expr) => {
|
||||
if let Some(ident) = self.maybe_generate_memoised(&static_expr.object, ctx)
|
||||
{
|
||||
// (_o = o).a
|
||||
let right = ctx.ast.move_expression(&mut static_expr.object);
|
||||
let target = AssignmentTarget::from(
|
||||
ctx.ast.simple_assignment_target_from_identifier_reference(
|
||||
Self::clone_identifier_reference(&ident, ctx),
|
||||
),
|
||||
);
|
||||
let object = ctx.ast.expression_assignment(SPAN, op, target, right);
|
||||
left_expr = Expression::from(ctx.ast.member_expression_static(
|
||||
SPAN,
|
||||
object,
|
||||
static_expr.property.clone_in(ctx.ast.allocator),
|
||||
false,
|
||||
));
|
||||
|
||||
// (_o.a = 1)
|
||||
let assign_expr = ctx.ast.member_expression_static(
|
||||
SPAN,
|
||||
ctx.ast.expression_from_identifier_reference(ident),
|
||||
static_expr.property.clone_in(ctx.ast.allocator),
|
||||
false,
|
||||
);
|
||||
assign_target = AssignmentTarget::from(
|
||||
ctx.ast.simple_assignment_target_member_expression(assign_expr),
|
||||
);
|
||||
} else {
|
||||
left_expr = Expression::from(MemberExpression::StaticMemberExpression(
|
||||
static_expr.clone_in(ctx.ast.allocator),
|
||||
));
|
||||
assign_target = AssignmentTarget::from(
|
||||
ctx.ast.simple_assignment_target_member_expression(
|
||||
member_expr.clone_in(ctx.ast.allocator),
|
||||
),
|
||||
);
|
||||
};
|
||||
}
|
||||
// `a[b.y] &&= c;` ->
|
||||
// `var _a, _b$y; (_a = a)[_b$y = b.y] && (_a[_b$y] = c);`
|
||||
MemberExpression::ComputedMemberExpression(computed_expr) => {
|
||||
if let Some(ident) =
|
||||
self.maybe_generate_memoised(&computed_expr.object, ctx)
|
||||
{
|
||||
// (_o = object)
|
||||
let right = ctx.ast.move_expression(&mut computed_expr.object);
|
||||
let target = AssignmentTarget::from(
|
||||
ctx.ast.simple_assignment_target_from_identifier_reference(
|
||||
Self::clone_identifier_reference(&ident, ctx),
|
||||
),
|
||||
);
|
||||
let object = ctx.ast.expression_assignment(SPAN, op, target, right);
|
||||
|
||||
let mut expression =
|
||||
ctx.ast.move_expression(&mut computed_expr.expression);
|
||||
|
||||
// _b = expression
|
||||
let property = self.maybe_generate_memoised(&expression, ctx);
|
||||
|
||||
if let Some(ref property) = property {
|
||||
let left = AssignmentTarget::from(
|
||||
ctx.ast.simple_assignment_target_from_identifier_reference(
|
||||
Self::clone_identifier_reference(property, ctx),
|
||||
),
|
||||
);
|
||||
expression =
|
||||
ctx.ast.expression_assignment(SPAN, op, left, expression);
|
||||
}
|
||||
|
||||
// _o[_b]
|
||||
assign_target =
|
||||
AssignmentTarget::from(ctx.ast.member_expression_computed(
|
||||
SPAN,
|
||||
ctx.ast.expression_from_identifier_reference(
|
||||
Self::clone_identifier_reference(&ident, ctx),
|
||||
),
|
||||
property.map_or_else(
|
||||
|| expression.clone_in(ctx.ast.allocator),
|
||||
|ident| ctx.ast.expression_from_identifier_reference(ident),
|
||||
),
|
||||
false,
|
||||
));
|
||||
|
||||
left_expr = Expression::from(
|
||||
ctx.ast.member_expression_computed(SPAN, object, expression, false),
|
||||
);
|
||||
} else {
|
||||
let property_ident =
|
||||
self.maybe_generate_memoised(&computed_expr.expression, ctx);
|
||||
|
||||
let mut expr = computed_expr.clone_in(ctx.ast.allocator);
|
||||
if let Some(property_ident) = &property_ident {
|
||||
let left = AssignmentTarget::from(
|
||||
ctx.ast.simple_assignment_target_from_identifier_reference(
|
||||
property_ident.clone(),
|
||||
),
|
||||
);
|
||||
let right = computed_expr.expression.clone_in(ctx.ast.allocator);
|
||||
expr.expression =
|
||||
ctx.ast.expression_assignment(SPAN, op, left, right);
|
||||
}
|
||||
left_expr =
|
||||
Expression::from(MemberExpression::ComputedMemberExpression(expr));
|
||||
|
||||
let mut expr = computed_expr.clone_in(ctx.ast.allocator);
|
||||
if let Some(property_ident) = property_ident {
|
||||
expr.expression =
|
||||
ctx.ast.expression_from_identifier_reference(property_ident);
|
||||
}
|
||||
assign_target = AssignmentTarget::from(
|
||||
ctx.ast.simple_assignment_target_member_expression(
|
||||
MemberExpression::ComputedMemberExpression(expr),
|
||||
),
|
||||
);
|
||||
};
|
||||
}
|
||||
MemberExpression::PrivateFieldExpression(_) => return,
|
||||
}
|
||||
}
|
||||
// All other are TypeScript syntax.
|
||||
|
||||
// It is a Syntax Error if AssignmentTargetType of LeftHandSideExpression is not simple.
|
||||
// So safe to return here.
|
||||
_ => return,
|
||||
};
|
||||
|
||||
let assign_op = AssignmentOperator::Assign;
|
||||
let right = ctx.ast.move_expression(&mut assignment_expr.right);
|
||||
let right = ctx.ast.expression_assignment(SPAN, assign_op, assign_target, right);
|
||||
|
||||
let logical_expr = ctx.ast.expression_logical(SPAN, left_expr, operator, right);
|
||||
|
||||
*expr = logical_expr;
|
||||
}
|
||||
}
|
||||
56
crates/oxc_transformer/src/es2021/mod.rs
Normal file
56
crates/oxc_transformer/src/es2021/mod.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
mod logical_assignment_operators;
|
||||
mod options;
|
||||
|
||||
pub use logical_assignment_operators::LogicalAssignmentOperators;
|
||||
pub use options::ES2021Options;
|
||||
use oxc_allocator::Vec;
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_traverse::TraverseCtx;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::context::Ctx;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct ES2021<'a> {
|
||||
ctx: Ctx<'a>,
|
||||
options: ES2021Options,
|
||||
|
||||
// Plugins
|
||||
logical_assignment_operators: LogicalAssignmentOperators<'a>,
|
||||
}
|
||||
|
||||
impl<'a> ES2021<'a> {
|
||||
pub fn new(options: ES2021Options, ctx: Ctx<'a>) -> Self {
|
||||
Self {
|
||||
logical_assignment_operators: LogicalAssignmentOperators::new(Rc::clone(&ctx)),
|
||||
ctx,
|
||||
options,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transform_statements(
|
||||
&mut self,
|
||||
statements: &mut Vec<'a, Statement<'a>>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
if self.options.logical_assignment_operators {
|
||||
self.logical_assignment_operators.transform_statements(statements, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transform_statements_on_exit(
|
||||
&mut self,
|
||||
statements: &mut Vec<'a, Statement<'a>>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
if self.options.logical_assignment_operators {
|
||||
self.logical_assignment_operators.transform_statements_on_exit(statements, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transform_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
if self.options.logical_assignment_operators {
|
||||
self.logical_assignment_operators.transform_expression(expr, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
16
crates/oxc_transformer/src/es2021/options.rs
Normal file
16
crates/oxc_transformer/src/es2021/options.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize)]
|
||||
#[serde(default, rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct ES2021Options {
|
||||
#[serde(skip)]
|
||||
pub logical_assignment_operators: bool,
|
||||
}
|
||||
|
||||
impl ES2021Options {
|
||||
#[must_use]
|
||||
pub fn with_logical_assignment_operators(mut self, enable: bool) -> Self {
|
||||
self.logical_assignment_operators = enable;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ mod es2015;
|
|||
mod es2016;
|
||||
mod es2019;
|
||||
mod es2020;
|
||||
mod es2021;
|
||||
mod react;
|
||||
mod typescript;
|
||||
|
||||
|
|
@ -31,6 +32,7 @@ use std::{path::Path, rc::Rc};
|
|||
use es2016::ES2016;
|
||||
use es2019::ES2019;
|
||||
use es2020::ES2020;
|
||||
use es2021::ES2021;
|
||||
use oxc_allocator::{Allocator, Vec};
|
||||
use oxc_ast::{ast::*, AstBuilder, Trivias};
|
||||
use oxc_diagnostics::OxcDiagnostic;
|
||||
|
|
@ -64,6 +66,7 @@ pub struct Transformer<'a> {
|
|||
// NOTE: all callbacks must run in order.
|
||||
x0_typescript: TypeScript<'a>,
|
||||
x1_react: React<'a>,
|
||||
x2_es2021: ES2021<'a>,
|
||||
x2_es2020: ES2020<'a>,
|
||||
x2_es2019: ES2019<'a>,
|
||||
x2_es2016: ES2016<'a>,
|
||||
|
|
@ -91,6 +94,7 @@ impl<'a> Transformer<'a> {
|
|||
ctx: Rc::clone(&ctx),
|
||||
x0_typescript: TypeScript::new(options.typescript, Rc::clone(&ctx)),
|
||||
x1_react: React::new(options.react, Rc::clone(&ctx)),
|
||||
x2_es2021: ES2021::new(options.es2021, Rc::clone(&ctx)),
|
||||
x2_es2020: ES2020::new(options.es2020, Rc::clone(&ctx)),
|
||||
x2_es2019: ES2019::new(options.es2019, Rc::clone(&ctx)),
|
||||
x2_es2016: ES2016::new(options.es2016, Rc::clone(&ctx)),
|
||||
|
|
@ -172,6 +176,7 @@ impl<'a> Traverse<'a> for Transformer<'a> {
|
|||
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
self.x0_typescript.transform_expression(expr);
|
||||
self.x1_react.transform_expression(expr, ctx);
|
||||
self.x2_es2021.transform_expression(expr, ctx);
|
||||
self.x2_es2020.transform_expression(expr, ctx);
|
||||
self.x2_es2016.transform_expression(expr, ctx);
|
||||
self.x3_es2015.transform_expression(expr);
|
||||
|
|
@ -260,6 +265,7 @@ impl<'a> Traverse<'a> for Transformer<'a> {
|
|||
|
||||
fn enter_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
|
||||
self.x0_typescript.transform_statements(stmts);
|
||||
self.x2_es2021.transform_statements(stmts, ctx);
|
||||
self.x2_es2020.transform_statements(stmts, ctx);
|
||||
self.x2_es2016.transform_statements(stmts, ctx);
|
||||
self.x3_es2015.enter_statements(stmts);
|
||||
|
|
@ -267,6 +273,7 @@ impl<'a> Traverse<'a> for Transformer<'a> {
|
|||
|
||||
fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
|
||||
self.x0_typescript.transform_statements_on_exit(stmts, ctx);
|
||||
self.x2_es2021.transform_statements_on_exit(stmts, ctx);
|
||||
self.x2_es2020.transform_statements_on_exit(stmts, ctx);
|
||||
self.x2_es2016.transform_statements_on_exit(stmts, ctx);
|
||||
self.x3_es2015.exit_statements(stmts);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use crate::{
|
|||
es2016::ES2016Options,
|
||||
es2019::ES2019Options,
|
||||
es2020::ES2020Options,
|
||||
es2021::ES2021Options,
|
||||
options::babel::BabelOptions,
|
||||
react::ReactOptions,
|
||||
typescript::TypeScriptOptions,
|
||||
|
|
@ -43,6 +44,8 @@ pub struct TransformOptions {
|
|||
pub es2019: ES2019Options,
|
||||
|
||||
pub es2020: ES2020Options,
|
||||
|
||||
pub es2021: ES2021Options,
|
||||
}
|
||||
|
||||
impl TransformOptions {
|
||||
|
|
@ -128,6 +131,11 @@ impl TransformOptions {
|
|||
enable_plugin(plugin_name, options, &env_options, &targets).is_some()
|
||||
});
|
||||
|
||||
let es2021 = ES2021Options::default().with_logical_assignment_operators({
|
||||
let plugin_name = "transform-logical-assignment-operators";
|
||||
enable_plugin(plugin_name, options, &env_options, &targets).is_some()
|
||||
});
|
||||
|
||||
let typescript = {
|
||||
let plugin_name = "transform-typescript";
|
||||
from_value::<TypeScriptOptions>(get_plugin_options(plugin_name, options))
|
||||
|
|
@ -162,6 +170,7 @@ impl TransformOptions {
|
|||
es2016,
|
||||
es2019,
|
||||
es2020,
|
||||
es2021,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
commit: 12619ffe
|
||||
|
||||
Passed: 462/947
|
||||
Passed: 466/953
|
||||
|
||||
# All Passed:
|
||||
* babel-plugin-transform-optional-catch-binding
|
||||
|
|
@ -434,6 +434,10 @@ Passed: 462/947
|
|||
* shipped-proposals/new-class-features-chrome-94/input.js
|
||||
* shipped-proposals/new-class-features-firefox-70/input.js
|
||||
|
||||
# babel-plugin-transform-logical-assignment-operators (4/6)
|
||||
* logical-assignment/general-semantics/input.js
|
||||
* logical-assignment/null-coalescing/input.js
|
||||
|
||||
# babel-plugin-transform-nullish-coalescing-operator (2/12)
|
||||
* assumption-noDocumentAll/transform/input.js
|
||||
* assumption-noDocumentAll/transform-in-default-destructuring/input.js
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
commit: 12619ffe
|
||||
|
||||
Passed: 13/19
|
||||
Passed: 16/23
|
||||
|
||||
# All Passed:
|
||||
* babel-plugin-transform-nullish-coalescing-operator
|
||||
|
|
@ -15,6 +15,9 @@ Passed: 13/19
|
|||
* sanity/check-es2015-constants/exec.js
|
||||
* sanity/regex-dot-all/exec.js
|
||||
|
||||
# babel-plugin-transform-logical-assignment-operators (3/4)
|
||||
* logical-assignment/null-coalescing/exec.js
|
||||
|
||||
# babel-plugin-transform-react-jsx-source (0/2)
|
||||
* react-source/basic-sample/exec.js
|
||||
* react-source/with-source/exec.js
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ pub(crate) const PLUGINS: &[&str] = &[
|
|||
// "babel-plugin-transform-private-methods",
|
||||
// "babel-plugin-transform-private-property-in-object",
|
||||
// // [Syntax] "babel-plugin-transform-syntax-top-level-await",
|
||||
// // ES2021
|
||||
// "babel-plugin-transform-logical-assignment-operators",
|
||||
// ES2021
|
||||
"babel-plugin-transform-logical-assignment-operators",
|
||||
// "babel-plugin-transform-numeric-separator",
|
||||
// // ES2020
|
||||
// ES2020
|
||||
// "babel-plugin-transform-export-namespace-from",
|
||||
// "babel-plugin-transform-dynamic-import",
|
||||
"babel-plugin-transform-nullish-coalescing-operator",
|
||||
|
|
@ -19,7 +19,7 @@ pub(crate) const PLUGINS: &[&str] = &[
|
|||
// // [Syntax] "babel-plugin-transform-syntax-bigint",
|
||||
// // [Syntax] "babel-plugin-transform-syntax-dynamic-import",
|
||||
// // [Syntax] "babel-plugin-transform-syntax-import-meta",
|
||||
// // ES2019
|
||||
// ES2019
|
||||
"babel-plugin-transform-optional-catch-binding",
|
||||
// "babel-plugin-transform-json-strings",
|
||||
// // ES2018
|
||||
|
|
@ -32,7 +32,7 @@ pub(crate) const PLUGINS: &[&str] = &[
|
|||
// "babel-plugin-transform-async-to-generator",
|
||||
// ES2016
|
||||
"babel-plugin-transform-exponentiation-operator",
|
||||
// // ES2015
|
||||
// ES2015
|
||||
"babel-plugin-transform-arrow-functions",
|
||||
// "babel-plugin-transform-function-name",
|
||||
// "babel-plugin-transform-shorthand-properties",
|
||||
|
|
|
|||
Loading…
Reference in a new issue