From 70f55833e9a19a62257806e79225bda896d90396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Donny/=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Tue, 26 Oct 2021 20:09:09 +0900 Subject: [PATCH] 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. --- Cargo.lock | 2 +- ecmascript/transforms/react/Cargo.toml | 2 +- ecmascript/transforms/react/src/jsx/mod.rs | 236 +++++++++++++----- .../transforms/react/src/jsx_src/mod.rs | 16 +- tests/fixture/next.js/jsx-dev/1/input/.swcrc | 14 ++ .../fixture/next.js/jsx-dev/1/input/index.js | 3 + .../fixture/next.js/jsx-dev/1/output/index.js | 7 + 7 files changed, 209 insertions(+), 71 deletions(-) create mode 100644 tests/fixture/next.js/jsx-dev/1/input/.swcrc create mode 100644 tests/fixture/next.js/jsx-dev/1/input/index.js create mode 100644 tests/fixture/next.js/jsx-dev/1/output/index.js diff --git a/Cargo.lock b/Cargo.lock index 51959b689791..cb9b748a87c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/ecmascript/transforms/react/Cargo.toml b/ecmascript/transforms/react/Cargo.toml index 90639d630f8c..5dea28f1b4c3 100644 --- a/ecmascript/transforms/react/Cargo.toml +++ b/ecmascript/transforms/react/Cargo.toml @@ -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] diff --git a/ecmascript/transforms/react/src/jsx/mod.rs b/ecmascript/transforms/react/src/jsx/mod.rs index bfe3326e0a5b..612544e3ecfc 100644 --- a/ecmascript/transforms/react/src/jsx/mod.rs +++ b/ecmascript/transforms/react/src/jsx/mod.rs @@ -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, + development: bool, + top_level_mark: Mark, next: bool, @@ -233,6 +238,9 @@ where import_fragment: Option, top_level_node: bool, + /// For automatic runtime. + dev: Option, + pragma: Arc>, comments: Option, pragma_frag: Arc>, @@ -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, @@ -326,6 +338,12 @@ impl Jsx 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::>(); + + 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 { + { + // Import for development mode + + if let Some(dev) = self.dev.take() { + let specifier = ImportSpecifier::Named(ImportNamedSpecifier { + span: DUMMY_SP, + local: dev.import, + imported: Some(quote_ident!("jsxDEV")), + is_type_only: false, + }); + + prepend( + &mut module.body, + ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl { span: DUMMY_SP, - local, - imported: Some(quote_ident!("Fragment")), - is_type_only: false, - }), - ) - .map(ImportSpecifier::Named) - .collect::>(); - - if !imports.is_empty() { - prepend( - &mut module.body, - ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl { + 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, - specifiers: imports, - src: Str { + 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::>(); + + if !imports.is_empty() { + 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: 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(), + })), + ); + } } } } diff --git a/ecmascript/transforms/react/src/jsx_src/mod.rs b/ecmascript/transforms/react/src/jsx_src/mod.rs index a4a3e65e26b1..3cb4cb79a989 100644 --- a/ecmascript/transforms/react/src/jsx_src/mod.rs +++ b/ecmascript/transforms/react/src/jsx_src/mod.rs @@ -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 _, }))), }))), ], diff --git a/tests/fixture/next.js/jsx-dev/1/input/.swcrc b/tests/fixture/next.js/jsx-dev/1/input/.swcrc new file mode 100644 index 000000000000..ccfe7177506c --- /dev/null +++ b/tests/fixture/next.js/jsx-dev/1/input/.swcrc @@ -0,0 +1,14 @@ +{ + "jsc": { + "parser": { + "syntax": "ecmascript", + "jsx": true + }, + "transform": { + "react": { + "runtime": "automatic", + "development": true + } + } + } +} \ No newline at end of file diff --git a/tests/fixture/next.js/jsx-dev/1/input/index.js b/tests/fixture/next.js/jsx-dev/1/input/index.js new file mode 100644 index 000000000000..49ce105c5b76 --- /dev/null +++ b/tests/fixture/next.js/jsx-dev/1/input/index.js @@ -0,0 +1,3 @@ + + +const foo =
\ No newline at end of file diff --git a/tests/fixture/next.js/jsx-dev/1/output/index.js b/tests/fixture/next.js/jsx-dev/1/output/index.js new file mode 100644 index 000000000000..8eadb89b0d9d --- /dev/null +++ b/tests/fixture/next.js/jsx-dev/1/output/index.js @@ -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);