mirror of
https://github.com/swc-project/swc.git
synced 2024-11-23 17:54:15 +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]]
|
||||
name = "swc_ecma_transforms_react"
|
||||
version = "0.55.0"
|
||||
version = "0.55.1"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"base64 0.13.0",
|
||||
|
@ -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]
|
||||
|
@ -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(),
|
||||
})),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 _,
|
||||
}))),
|
||||
}))),
|
||||
],
|
||||
|
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