feat(es/transforms/react): Improve development more (#2542)

swc_ecma_transforms_react:
 - `jsx_src`: Add column to `__source`.
 - `jsx`: Support `jsxDEV`.
 - `jsx`: Handle `__source` and `__self` specially.
This commit is contained in:
Donny/강동윤 2021-10-26 20:09:09 +09:00 committed by GitHub
parent b8933e3db9
commit 70f55833e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 209 additions and 71 deletions

2
Cargo.lock generated
View File

@ -2937,7 +2937,7 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_react"
version = "0.55.0"
version = "0.55.1"
dependencies = [
"ahash",
"base64 0.13.0",

View File

@ -7,7 +7,7 @@ include = ["Cargo.toml", "src/**/*.rs"]
license = "Apache-2.0/MIT"
name = "swc_ecma_transforms_react"
repository = "https://github.com/swc-project/swc.git"
version = "0.55.0"
version = "0.55.1"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View File

@ -18,7 +18,7 @@ use swc_ecma_ast::*;
use swc_ecma_parser::{Parser, StringInput, Syntax};
use swc_ecma_transforms_base::helper;
use swc_ecma_utils::{
drop_span, member_expr, prepend, private_ident, quote_ident, ExprFactory, HANDLER,
drop_span, member_expr, prepend, private_ident, quote_ident, undefined, ExprFactory, HANDLER,
};
use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith};
@ -198,16 +198,19 @@ where
import_source: options.import_source.into(),
import_jsx: None,
import_jsxs: None,
import_fragment: None,
import_create_element: None,
dev: None,
development: options.development,
import_fragment: None,
top_level_node: true,
pragma: parse_expr_for_jsx(&cm, "pragma", options.pragma, top_level_mark),
comments,
pragma_frag: parse_expr_for_jsx(&cm, "pragmaFrag", options.pragma_frag, top_level_mark),
use_builtins: options.use_builtins,
use_spread: options.use_spread,
throw_if_namespace: options.throw_if_namespace,
top_level_node: true,
})
}
@ -217,6 +220,8 @@ where
{
cm: Lrc<SourceMap>,
development: bool,
top_level_mark: Mark,
next: bool,
@ -233,6 +238,9 @@ where
import_fragment: Option<Ident>,
top_level_node: bool,
/// For automatic runtime.
dev: Option<JsxDevMode>,
pragma: Arc<Box<Expr>>,
comments: Option<C>,
pragma_frag: Arc<Box<Expr>>,
@ -241,6 +249,10 @@ where
throw_if_namespace: bool,
}
struct JsxDevMode {
import: Ident,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct JsxDirectives {
pub runtime: Option<Runtime>,
@ -326,6 +338,12 @@ impl<C> Jsx<C>
where
C: Comments,
{
fn dev_mode(&mut self) -> &mut JsxDevMode {
self.dev.get_or_insert_with(|| JsxDevMode {
import: private_ident!("_jsxDEV"),
})
}
fn jsx_frag_to_expr(&mut self, el: JSXFragment) -> Expr {
let span = el.span();
@ -337,14 +355,18 @@ where
match self.runtime {
Runtime::Automatic => {
let jsx = if use_jsxs {
self.import_jsxs
.get_or_insert_with(|| private_ident!("_jsxs"))
.clone()
let jsx = if self.development {
self.dev_mode().import.clone()
} else {
self.import_jsx
.get_or_insert_with(|| private_ident!("_jsx"))
.clone()
if use_jsxs {
self.import_jsxs
.get_or_insert_with(|| private_ident!("_jsxs"))
.clone()
} else {
self.import_jsx
.get_or_insert_with(|| private_ident!("_jsx"))
.clone()
}
};
let fragment = self
@ -443,14 +465,20 @@ where
self.import_create_element
.get_or_insert_with(|| private_ident!("_createElement"))
.clone()
} else if use_jsxs {
self.import_jsxs
.get_or_insert_with(|| private_ident!("_jsxs"))
.clone()
} else {
self.import_jsx
.get_or_insert_with(|| private_ident!("_jsx"))
.clone()
if self.development {
self.dev_mode().import.clone()
} else {
if use_jsxs {
self.import_jsxs
.get_or_insert_with(|| private_ident!("_jsxs"))
.clone()
} else {
self.import_jsx
.get_or_insert_with(|| private_ident!("_jsx"))
.clone()
}
}
};
let mut props_obj = ObjectLit {
@ -460,12 +488,36 @@ where
let mut key = None;
let mut source_attr = None;
let mut self_attr = None;
for attr in el.opening.attrs {
match attr {
JSXAttrOrSpread::JSXAttr(attr) => {
//
match attr.name {
JSXAttrName::Ident(i) => {
match &*i.sym {
"__source" => {
source_attr = attr
.value
.map(jsx_attr_value_to_expr)
.flatten()
.map(|expr| ExprOrSpread { expr, spread: None });
continue;
}
"__self" => {
self_attr = attr
.value
.map(jsx_attr_value_to_expr)
.flatten()
.map(|expr| ExprOrSpread { expr, spread: None });
continue;
}
_ => {}
}
//
if !use_create_element && i.sym == js_word!("key") {
key = attr
@ -555,6 +607,8 @@ where
}
}
let children_cnt = el.children.len();
let children = el
.children
.into_iter()
@ -587,13 +641,38 @@ where
self.top_level_node = top_level_node;
let mut args = once(name.as_arg())
.chain(once(props_obj.as_arg()))
.collect::<Vec<_>>();
if !self.development {
args.extend(key);
} else {
args.push(key.unwrap_or_else(|| ExprOrSpread {
spread: None,
expr: undefined(DUMMY_SP),
}));
args.push(ExprOrSpread {
spread: None,
expr: Box::new(Expr::Lit(Lit::Bool(Bool {
span: DUMMY_SP,
value: children_cnt > 1,
}))),
});
args.push(source_attr.unwrap_or_else(|| ExprOrSpread {
spread: None,
expr: undefined(DUMMY_SP),
}));
args.push(self_attr.unwrap_or_else(|| ExprOrSpread {
spread: None,
expr: Box::new(Expr::This(ThisExpr { span: DUMMY_SP })),
}));
}
Expr::Call(CallExpr {
span,
callee: jsx.as_callee(),
args: once(name.as_arg())
.chain(once(props_obj.as_arg()))
.chain(key)
.collect(),
args,
type_args: Default::default(),
})
}
@ -961,51 +1040,82 @@ where
);
}
let imports = self
.import_jsx
.take()
.map(|local| ImportNamedSpecifier {
span: DUMMY_SP,
local,
imported: Some(quote_ident!("jsx")),
is_type_only: false,
})
.into_iter()
.chain(self.import_jsxs.take().map(|local| ImportNamedSpecifier {
span: DUMMY_SP,
local,
imported: Some(quote_ident!("jsxs")),
is_type_only: false,
}))
.chain(
self.import_fragment
.take()
.map(|local| ImportNamedSpecifier {
span: DUMMY_SP,
local,
imported: Some(quote_ident!("Fragment")),
is_type_only: false,
}),
)
.map(ImportSpecifier::Named)
.collect::<Vec<_>>();
{
// Import for development mode
if !imports.is_empty() {
prepend(
&mut module.body,
ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
if let Some(dev) = self.dev.take() {
let specifier = ImportSpecifier::Named(ImportNamedSpecifier {
span: DUMMY_SP,
specifiers: imports,
src: Str {
local: dev.import,
imported: Some(quote_ident!("jsxDEV")),
is_type_only: false,
});
prepend(
&mut module.body,
ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
span: DUMMY_SP,
value: format!("{}/jsx-runtime", self.import_source).into(),
has_escape: false,
kind: Default::default(),
},
type_only: Default::default(),
asserts: Default::default(),
})),
);
specifiers: vec![specifier],
src: Str {
span: DUMMY_SP,
value: format!("{}/jsx-dev-runtime", self.import_source).into(),
has_escape: false,
kind: Default::default(),
},
type_only: Default::default(),
asserts: Default::default(),
})),
);
}
}
{
let imports = self
.import_jsx
.take()
.map(|local| ImportNamedSpecifier {
span: DUMMY_SP,
local,
imported: Some(quote_ident!("jsx")),
is_type_only: false,
})
.into_iter()
.chain(self.import_jsxs.take().map(|local| ImportNamedSpecifier {
span: DUMMY_SP,
local,
imported: Some(quote_ident!("jsxs")),
is_type_only: false,
}))
.chain(
self.import_fragment
.take()
.map(|local| ImportNamedSpecifier {
span: DUMMY_SP,
local,
imported: Some(quote_ident!("Fragment")),
is_type_only: false,
}),
)
.map(ImportSpecifier::Named)
.collect::<Vec<_>>();
if !imports.is_empty() {
prepend(
&mut module.body,
ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
span: DUMMY_SP,
specifiers: imports,
src: Str {
span: DUMMY_SP,
value: format!("{}/jsx-runtime", self.import_source).into(),
has_escape: false,
kind: Default::default(),
},
type_only: Default::default(),
asserts: Default::default(),
})),
);
}
}
}
}

View File

@ -36,10 +36,7 @@ impl VisitMut for JsxSrc {
return;
}
let file_lines = match self.cm.span_to_lines(e.span) {
Ok(v) => v,
_ => return,
};
let loc = self.cm.lookup_char_pos(e.span.lo);
e.attrs.push(JSXAttrOrSpread::JSXAttr(JSXAttr {
span: DUMMY_SP,
@ -54,7 +51,7 @@ impl VisitMut for JsxSrc {
key: PropName::Ident(quote_ident!("fileName")),
value: Box::new(Expr::Lit(Lit::Str(Str {
span: DUMMY_SP,
value: file_lines.file.name.to_string().into(),
value: loc.file.name.to_string().into(),
has_escape: false,
kind: Default::default(),
}))),
@ -63,7 +60,14 @@ impl VisitMut for JsxSrc {
key: PropName::Ident(quote_ident!("lineNumber")),
value: Box::new(Expr::Lit(Lit::Num(Number {
span: DUMMY_SP,
value: (file_lines.lines[0].line_index + 1) as _,
value: loc.line as _,
}))),
}))),
PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
key: PropName::Ident(quote_ident!("columnNumber")),
value: Box::new(Expr::Lit(Lit::Num(Number {
span: DUMMY_SP,
value: (loc.col.0 + 1) as _,
}))),
}))),
],

View File

@ -0,0 +1,14 @@
{
"jsc": {
"parser": {
"syntax": "ecmascript",
"jsx": true
},
"transform": {
"react": {
"runtime": "automatic",
"development": true
}
}
}
}

View File

@ -0,0 +1,3 @@
const foo = <div></div>

View File

@ -0,0 +1,7 @@
import { jsxDEV as _jsxDEV } from "react/jsx-dev-runtime";
var foo = /*#__PURE__*/ _jsxDEV("div", {
}, void 0, false, {
fileName: "$DIR/tests/fixture/next.js/jsx-dev/1/input/index.js",
lineNumber: 3,
columnNumber: 13
}, this);