mirror of
https://github.com/swc-project/swc.git
synced 2024-11-24 02:06:08 +03:00
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:
parent
b8933e3db9
commit
70f55833e9
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -2937,7 +2937,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "swc_ecma_transforms_react"
|
name = "swc_ecma_transforms_react"
|
||||||
version = "0.55.0"
|
version = "0.55.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
"base64 0.13.0",
|
"base64 0.13.0",
|
||||||
|
@ -7,7 +7,7 @@ include = ["Cargo.toml", "src/**/*.rs"]
|
|||||||
license = "Apache-2.0/MIT"
|
license = "Apache-2.0/MIT"
|
||||||
name = "swc_ecma_transforms_react"
|
name = "swc_ecma_transforms_react"
|
||||||
repository = "https://github.com/swc-project/swc.git"
|
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
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -18,7 +18,7 @@ use swc_ecma_ast::*;
|
|||||||
use swc_ecma_parser::{Parser, StringInput, Syntax};
|
use swc_ecma_parser::{Parser, StringInput, Syntax};
|
||||||
use swc_ecma_transforms_base::helper;
|
use swc_ecma_transforms_base::helper;
|
||||||
use swc_ecma_utils::{
|
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};
|
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_source: options.import_source.into(),
|
||||||
import_jsx: None,
|
import_jsx: None,
|
||||||
import_jsxs: None,
|
import_jsxs: None,
|
||||||
import_fragment: None,
|
|
||||||
import_create_element: 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),
|
pragma: parse_expr_for_jsx(&cm, "pragma", options.pragma, top_level_mark),
|
||||||
comments,
|
comments,
|
||||||
pragma_frag: parse_expr_for_jsx(&cm, "pragmaFrag", options.pragma_frag, top_level_mark),
|
pragma_frag: parse_expr_for_jsx(&cm, "pragmaFrag", options.pragma_frag, top_level_mark),
|
||||||
use_builtins: options.use_builtins,
|
use_builtins: options.use_builtins,
|
||||||
use_spread: options.use_spread,
|
use_spread: options.use_spread,
|
||||||
throw_if_namespace: options.throw_if_namespace,
|
throw_if_namespace: options.throw_if_namespace,
|
||||||
top_level_node: true,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,6 +220,8 @@ where
|
|||||||
{
|
{
|
||||||
cm: Lrc<SourceMap>,
|
cm: Lrc<SourceMap>,
|
||||||
|
|
||||||
|
development: bool,
|
||||||
|
|
||||||
top_level_mark: Mark,
|
top_level_mark: Mark,
|
||||||
|
|
||||||
next: bool,
|
next: bool,
|
||||||
@ -233,6 +238,9 @@ where
|
|||||||
import_fragment: Option<Ident>,
|
import_fragment: Option<Ident>,
|
||||||
top_level_node: bool,
|
top_level_node: bool,
|
||||||
|
|
||||||
|
/// For automatic runtime.
|
||||||
|
dev: Option<JsxDevMode>,
|
||||||
|
|
||||||
pragma: Arc<Box<Expr>>,
|
pragma: Arc<Box<Expr>>,
|
||||||
comments: Option<C>,
|
comments: Option<C>,
|
||||||
pragma_frag: Arc<Box<Expr>>,
|
pragma_frag: Arc<Box<Expr>>,
|
||||||
@ -241,6 +249,10 @@ where
|
|||||||
throw_if_namespace: bool,
|
throw_if_namespace: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct JsxDevMode {
|
||||||
|
import: Ident,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||||
pub struct JsxDirectives {
|
pub struct JsxDirectives {
|
||||||
pub runtime: Option<Runtime>,
|
pub runtime: Option<Runtime>,
|
||||||
@ -326,6 +338,12 @@ impl<C> Jsx<C>
|
|||||||
where
|
where
|
||||||
C: Comments,
|
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 {
|
fn jsx_frag_to_expr(&mut self, el: JSXFragment) -> Expr {
|
||||||
let span = el.span();
|
let span = el.span();
|
||||||
|
|
||||||
@ -337,14 +355,18 @@ where
|
|||||||
|
|
||||||
match self.runtime {
|
match self.runtime {
|
||||||
Runtime::Automatic => {
|
Runtime::Automatic => {
|
||||||
let jsx = if use_jsxs {
|
let jsx = if self.development {
|
||||||
self.import_jsxs
|
self.dev_mode().import.clone()
|
||||||
.get_or_insert_with(|| private_ident!("_jsxs"))
|
|
||||||
.clone()
|
|
||||||
} else {
|
} else {
|
||||||
self.import_jsx
|
if use_jsxs {
|
||||||
.get_or_insert_with(|| private_ident!("_jsx"))
|
self.import_jsxs
|
||||||
.clone()
|
.get_or_insert_with(|| private_ident!("_jsxs"))
|
||||||
|
.clone()
|
||||||
|
} else {
|
||||||
|
self.import_jsx
|
||||||
|
.get_or_insert_with(|| private_ident!("_jsx"))
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let fragment = self
|
let fragment = self
|
||||||
@ -443,14 +465,20 @@ where
|
|||||||
self.import_create_element
|
self.import_create_element
|
||||||
.get_or_insert_with(|| private_ident!("_createElement"))
|
.get_or_insert_with(|| private_ident!("_createElement"))
|
||||||
.clone()
|
.clone()
|
||||||
} else if use_jsxs {
|
|
||||||
self.import_jsxs
|
|
||||||
.get_or_insert_with(|| private_ident!("_jsxs"))
|
|
||||||
.clone()
|
|
||||||
} else {
|
} else {
|
||||||
self.import_jsx
|
if self.development {
|
||||||
.get_or_insert_with(|| private_ident!("_jsx"))
|
self.dev_mode().import.clone()
|
||||||
.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 {
|
let mut props_obj = ObjectLit {
|
||||||
@ -460,12 +488,36 @@ where
|
|||||||
|
|
||||||
let mut key = None;
|
let mut key = None;
|
||||||
|
|
||||||
|
let mut source_attr = None;
|
||||||
|
let mut self_attr = None;
|
||||||
|
|
||||||
for attr in el.opening.attrs {
|
for attr in el.opening.attrs {
|
||||||
match attr {
|
match attr {
|
||||||
JSXAttrOrSpread::JSXAttr(attr) => {
|
JSXAttrOrSpread::JSXAttr(attr) => {
|
||||||
//
|
//
|
||||||
match attr.name {
|
match attr.name {
|
||||||
JSXAttrName::Ident(i) => {
|
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") {
|
if !use_create_element && i.sym == js_word!("key") {
|
||||||
key = attr
|
key = attr
|
||||||
@ -555,6 +607,8 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let children_cnt = el.children.len();
|
||||||
|
|
||||||
let children = el
|
let children = el
|
||||||
.children
|
.children
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -587,13 +641,38 @@ where
|
|||||||
|
|
||||||
self.top_level_node = top_level_node;
|
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 {
|
Expr::Call(CallExpr {
|
||||||
span,
|
span,
|
||||||
callee: jsx.as_callee(),
|
callee: jsx.as_callee(),
|
||||||
args: once(name.as_arg())
|
args,
|
||||||
.chain(once(props_obj.as_arg()))
|
|
||||||
.chain(key)
|
|
||||||
.collect(),
|
|
||||||
type_args: Default::default(),
|
type_args: Default::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -961,51 +1040,82 @@ where
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let imports = self
|
{
|
||||||
.import_jsx
|
// Import for development mode
|
||||||
.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() {
|
if let Some(dev) = self.dev.take() {
|
||||||
prepend(
|
let specifier = ImportSpecifier::Named(ImportNamedSpecifier {
|
||||||
&mut module.body,
|
|
||||||
ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
|
|
||||||
span: DUMMY_SP,
|
span: DUMMY_SP,
|
||||||
specifiers: imports,
|
local: dev.import,
|
||||||
src: Str {
|
imported: Some(quote_ident!("jsxDEV")),
|
||||||
|
is_type_only: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
prepend(
|
||||||
|
&mut module.body,
|
||||||
|
ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
|
||||||
span: DUMMY_SP,
|
span: DUMMY_SP,
|
||||||
value: format!("{}/jsx-runtime", self.import_source).into(),
|
specifiers: vec![specifier],
|
||||||
has_escape: false,
|
src: Str {
|
||||||
kind: Default::default(),
|
span: DUMMY_SP,
|
||||||
},
|
value: format!("{}/jsx-dev-runtime", self.import_source).into(),
|
||||||
type_only: Default::default(),
|
has_escape: false,
|
||||||
asserts: Default::default(),
|
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(),
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,10 +36,7 @@ impl VisitMut for JsxSrc {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let file_lines = match self.cm.span_to_lines(e.span) {
|
let loc = self.cm.lookup_char_pos(e.span.lo);
|
||||||
Ok(v) => v,
|
|
||||||
_ => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
e.attrs.push(JSXAttrOrSpread::JSXAttr(JSXAttr {
|
e.attrs.push(JSXAttrOrSpread::JSXAttr(JSXAttr {
|
||||||
span: DUMMY_SP,
|
span: DUMMY_SP,
|
||||||
@ -54,7 +51,7 @@ impl VisitMut for JsxSrc {
|
|||||||
key: PropName::Ident(quote_ident!("fileName")),
|
key: PropName::Ident(quote_ident!("fileName")),
|
||||||
value: Box::new(Expr::Lit(Lit::Str(Str {
|
value: Box::new(Expr::Lit(Lit::Str(Str {
|
||||||
span: DUMMY_SP,
|
span: DUMMY_SP,
|
||||||
value: file_lines.file.name.to_string().into(),
|
value: loc.file.name.to_string().into(),
|
||||||
has_escape: false,
|
has_escape: false,
|
||||||
kind: Default::default(),
|
kind: Default::default(),
|
||||||
}))),
|
}))),
|
||||||
@ -63,7 +60,14 @@ impl VisitMut for JsxSrc {
|
|||||||
key: PropName::Ident(quote_ident!("lineNumber")),
|
key: PropName::Ident(quote_ident!("lineNumber")),
|
||||||
value: Box::new(Expr::Lit(Lit::Num(Number {
|
value: Box::new(Expr::Lit(Lit::Num(Number {
|
||||||
span: DUMMY_SP,
|
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 _,
|
||||||
}))),
|
}))),
|
||||||
}))),
|
}))),
|
||||||
],
|
],
|
||||||
|
14
tests/fixture/next.js/jsx-dev/1/input/.swcrc
Normal file
14
tests/fixture/next.js/jsx-dev/1/input/.swcrc
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"jsc": {
|
||||||
|
"parser": {
|
||||||
|
"syntax": "ecmascript",
|
||||||
|
"jsx": true
|
||||||
|
},
|
||||||
|
"transform": {
|
||||||
|
"react": {
|
||||||
|
"runtime": "automatic",
|
||||||
|
"development": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
tests/fixture/next.js/jsx-dev/1/input/index.js
Normal file
3
tests/fixture/next.js/jsx-dev/1/input/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
|
const foo = <div></div>
|
7
tests/fixture/next.js/jsx-dev/1/output/index.js
Normal file
7
tests/fixture/next.js/jsx-dev/1/output/index.js
Normal 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);
|
Loading…
Reference in New Issue
Block a user