`);
16 | const selected = true;
17 | let id = "my-h1";
18 | let link;
19 | const template = (()=>{
20 | const _el$ = _tmpl$(), _el$2 = _el$.firstChild, _el$3 = _el$2.firstChild, _ref$ = link;
21 | _$spread(_el$, _$mergeProps(results, {
22 | classList: {
23 | selected: unknown
24 | },
25 | style: {
26 | color
27 | }
28 | }), false, true);
29 | _$spread(_el$2, _$mergeProps(results, {
30 | foo: "",
31 | disabled: true,
32 | get title () {
33 | return welcoming();
34 | },
35 | get style () {
36 | return {
37 | "background-color": color(),
38 | "margin-right": "40px"
39 | };
40 | },
41 | get classList () {
42 | return {
43 | dynamic: dynamic(),
44 | selected
45 | };
46 | }
47 | }), false, true);
48 | typeof _ref$ === "function" ? _$use(_ref$, _el$3) : link = _el$3;
49 | _$classList(_el$3, {
50 | "ccc ddd": true
51 | });
52 | return _el$;
53 | })();
54 | const template2 = (()=>{
55 | const _el$4 = _tmpl$2(), _el$5 = _el$4.firstChild, _el$6 = _el$5.nextSibling, _el$7 = _el$6.firstChild, _el$8 = _el$6.nextSibling;
56 | _$spread(_el$4, _$mergeProps(()=>getProps("test")), false, true);
57 | _el$5.textContent = rowId;
58 | _el$8.innerHTML = "
";
59 | _$effect(()=>_el$7.data = row.label);
60 | return _el$4;
61 | })();
62 | const template3 = (()=>{
63 | const _el$9 = _tmpl$3();
64 | _$setAttribute(_el$9, "id", state.id);
65 | state.color != null ? _el$9.style.setProperty("background-color", state.color) : _el$9.style.removeProperty("background-color");
66 | _el$9.textContent = state.content;
67 | _$effect(()=>_$setAttribute(_el$9, "name", state.name));
68 | return _el$9;
69 | })();
70 | const template4 = (()=>{
71 | const _el$10 = _tmpl$4();
72 | _$classList(_el$10, {
73 | "ccc:ddd": true
74 | });
75 | _$effect(()=>_$className(_el$10, `hi ${state.class || ""}`));
76 | return _el$10;
77 | })();
78 | const template5 = _tmpl$5();
79 | const template6 = (()=>{
80 | const _el$12 = _tmpl$4();
81 | _el$12.textContent = "Hi";
82 | _$effect((_$p)=>_$style(_el$12, someStyle(), _$p));
83 | return _el$12;
84 | })();
85 | let undefVar;
86 | const template7 = (()=>{
87 | const _el$13 = _tmpl$4();
88 | _el$13.classList.toggle("other-class", !!undefVar);
89 | _el$13.classList.toggle("other-class2", !!undefVar);
90 | _$effect((_p$)=>{
91 | const _v$ = {
92 | "background-color": color(),
93 | "margin-right": "40px",
94 | ...props.style
95 | }, _v$2 = props.top, _v$3 = !!props.active;
96 | _p$._v$ = _$style(_el$13, _v$, _p$._v$);
97 | _v$2 !== _p$._v$2 && ((_p$._v$2 = _v$2) != null ? _el$13.style.setProperty("padding-top", _v$2) : _el$13.style.removeProperty("padding-top"));
98 | _v$3 !== _p$._v$3 && _el$13.classList.toggle("my-class", _p$._v$3 = _v$3);
99 | return _p$;
100 | }, {
101 | _v$: undefined,
102 | _v$2: undefined,
103 | _v$3: undefined
104 | });
105 | return _el$13;
106 | })();
107 | let refTarget;
108 | const template8 = (()=>{
109 | const _el$14 = _tmpl$4(), _ref$2 = refTarget;
110 | typeof _ref$2 === "function" ? _$use(_ref$2, _el$14) : refTarget = _el$14;
111 | return _el$14;
112 | })();
113 | const template9 = (()=>{
114 | const _el$15 = _tmpl$4();
115 | _$use((e)=>console.log(e), _el$15);
116 | return _el$15;
117 | })();
118 | const template10 = (()=>{
119 | const _el$16 = _tmpl$4(), _ref$3 = refFactory();
120 | typeof _ref$3 === "function" && _$use(_ref$3, _el$16);
121 | return _el$16;
122 | })();
123 | const template11 = (()=>{
124 | const _el$17 = _tmpl$4();
125 | _$use(zero, _el$17, ()=>0);
126 | _$use(another, _el$17, ()=>thing);
127 | _$use(something, _el$17, ()=>true);
128 | return _el$17;
129 | })();
130 | const template12 = (()=>{
131 | const _el$18 = _tmpl$4();
132 | _el$18.htmlFor = thing;
133 | return _el$18;
134 | })();
135 | const template13 = (()=>{
136 | const _el$19 = _tmpl$6();
137 | _el$19.checked = true;
138 | return _el$19;
139 | })();
140 | const template14 = (()=>{
141 | const _el$20 = _tmpl$6();
142 | _$effect(()=>_el$20.checked = state.visible);
143 | return _el$20;
144 | })();
145 | const template15 = _tmpl$7();
146 | const template16 = _tmpl$8();
147 | const template17 = (()=>{
148 | const _el$23 = _tmpl$9();
149 | _$addEventListener(_el$23, "click", increment, true);
150 | return _el$23;
151 | })();
152 | const template18 = (()=>{
153 | const _el$24 = _tmpl$4();
154 | _$spread(_el$24, _$mergeProps(()=>({
155 | get [key()] () {
156 | return props.value;
157 | }
158 | })), false, false);
159 | return _el$24;
160 | })();
161 | const template19 = _tmpl$10();
162 | const template20 = (()=>{
163 | const _el$26 = _tmpl$11(), _el$27 = _el$26.firstChild, _el$28 = _el$27.nextSibling;
164 | _$addEventListener(_el$27, "input", doSomething, true);
165 | _$addEventListener(_el$28, "input", doSomethingElse, true);
166 | _el$28.readOnly = value;
167 | _$effect((_p$)=>{
168 | const _v$4 = min(), _v$5 = max(), _v$6 = min(), _v$7 = max();
169 | _v$4 !== _p$._v$4 && _$setAttribute(_el$27, "min", _p$._v$4 = _v$4);
170 | _v$5 !== _p$._v$5 && _$setAttribute(_el$27, "max", _p$._v$5 = _v$5);
171 | _v$6 !== _p$._v$6 && _$setAttribute(_el$28, "min", _p$._v$6 = _v$6);
172 | _v$7 !== _p$._v$7 && _$setAttribute(_el$28, "max", _p$._v$7 = _v$7);
173 | return _p$;
174 | }, {
175 | _v$4: undefined,
176 | _v$5: undefined,
177 | _v$6: undefined,
178 | _v$7: undefined
179 | });
180 | _$effect(()=>_el$27.value = s());
181 | _$effect(()=>_el$28.checked = s2());
182 | return _el$26;
183 | })();
184 | const template21 = (()=>{
185 | const _el$29 = _tmpl$4();
186 | _$effect((_$p)=>_$style(_el$29, {
187 | a: "static",
188 | ...rest
189 | }, _$p));
190 | return _el$29;
191 | })();
192 | const template22 = _tmpl$12();
193 | const template23 = (()=>{
194 | const _el$31 = _tmpl$4();
195 | _$insert(_el$31, ()=>"t" in test && "true");
196 | _$effect(()=>_el$31.disabled = "t" in test);
197 | return _el$31;
198 | })();
199 | const template24 = (()=>{
200 | const _el$32 = _tmpl$13();
201 | _$spread(_el$32, _$mergeProps(props, {
202 | something: ""
203 | }), false, false);
204 | return _el$32;
205 | })();
206 | const template25 = (()=>{
207 | const _el$33 = _tmpl$14(), _el$34 = _el$33.firstChild;
208 | _$insert(_el$33, ()=>props.children, _el$34);
209 | _$spread(_el$34, _$mergeProps(props, {
210 | something: ""
211 | }), false, false);
212 | return _el$33;
213 | })();
214 | const template26 = (()=>{
215 | const _el$35 = _tmpl$15();
216 | _$setAttribute(_el$35, "middle", middle);
217 | _$spread(_el$35, spread, false, true);
218 | return _el$35;
219 | })();
220 | const template27 = (()=>{
221 | const _el$36 = _tmpl$15();
222 | _$spread(_el$36, _$mergeProps(first, {
223 | middle: middle
224 | }, second), false, true);
225 | return _el$36;
226 | })();
227 | const template28 = (()=>{
228 | const _el$37 = _tmpl$16(), _el$38 = _el$37.firstChild, _el$39 = _el$38.firstChild, _el$40 = _el$38.nextSibling, _el$41 = _el$40.nextSibling;
229 | _$spread(_el$37, _$mergeProps(api), false, true);
230 | _$spread(_el$38, _$mergeProps(api), false, true);
231 | _$insert(_el$38, ()=>api() ? "checked" : "unchecked", null);
232 | _$spread(_el$40, _$mergeProps(api), false, false);
233 | _$spread(_el$41, _$mergeProps(api), false, false);
234 | return _el$37;
235 | })();
236 | const template29 = (()=>{
237 | const _el$42 = _tmpl$4();
238 | _$setAttribute(_el$42, "attribute", !!someValue);
239 | _$insert(_el$42, !!someValue);
240 | return _el$42;
241 | })();
242 | const template30 = _tmpl$17();
243 | const template31 = (()=>{
244 | const _el$44 = _tmpl$4();
245 | _$effect(()=>getStore.itemProperties.color != null ? _el$44.style.setProperty("background-color", getStore.itemProperties.color) : _el$44.style.removeProperty("background-color"));
246 | return _el$44;
247 | })();
248 | const template32 = (()=>{
249 | const _el$45 = _tmpl$4();
250 | _el$45.style.removeProperty("background-color");
251 | return _el$45;
252 | })();
253 | _$delegateEvents([
254 | "click",
255 | "input"
256 | ]);
257 |
--------------------------------------------------------------------------------
/src/shared/transform.rs:
--------------------------------------------------------------------------------
1 | use super::structs::TemplateInstantiation;
2 | use crate::shared::utils::{escape_backticks, escape_html, trim_whitespace};
3 | pub use crate::shared::{
4 | structs::TransformVisitor,
5 | utils::{get_tag_name, is_component},
6 | };
7 | use rustc_hash::{FxHashMap, FxHashSet};
8 | use std::collections::HashSet;
9 | use swc_core::{
10 | common::{DUMMY_SP, comments::Comments},
11 | ecma::{
12 | ast::*,
13 | utils::private_ident,
14 | visit::{Visit, VisitMut, VisitMutWith, VisitWith},
15 | },
16 | };
17 |
18 | #[derive(Default)]
19 | pub struct VarBindingCollector {
20 | pub const_var_bindings: FxHashMap
>,
21 | pub function_bindings: FxHashSet,
22 | }
23 |
24 | impl VarBindingCollector {
25 | pub fn new() -> Self {
26 | Self::default()
27 | }
28 |
29 | fn collect_pat(&mut self, pat: &Pat, init: Option) {
30 | match pat {
31 | Pat::Ident(id) => {
32 | self.const_var_bindings.insert(id.to_id(), init);
33 | }
34 | Pat::Array(a) => {
35 | for p in a.elems.iter().flatten() {
36 | self.collect_pat(p, None);
37 | }
38 | }
39 | Pat::Rest(rest) => self.collect_pat(&rest.arg, None),
40 | _ => {}
41 | };
42 | }
43 | }
44 |
45 | impl Visit for VarBindingCollector {
46 | fn visit_import_decl(&mut self, import_dect: &ImportDecl) {
47 | for spec in &import_dect.specifiers {
48 | match spec {
49 | ImportSpecifier::Named(s) => self.const_var_bindings.insert(s.local.to_id(), None),
50 | ImportSpecifier::Default(s) => {
51 | self.const_var_bindings.insert(s.local.to_id(), None)
52 | }
53 | ImportSpecifier::Namespace(s) => {
54 | self.const_var_bindings.insert(s.local.to_id(), None)
55 | }
56 | };
57 | }
58 | }
59 |
60 | fn visit_var_decl(&mut self, n: &VarDecl) {
61 | if n.kind == VarDeclKind::Const {
62 | for decl in &n.decls {
63 | self.collect_pat(&decl.name, decl.init.clone().map(|v| *v));
64 | }
65 | }
66 | n.visit_children_with(self);
67 | }
68 |
69 | fn visit_fn_decl(&mut self, f: &FnDecl) {
70 | self.function_bindings.insert(f.ident.to_id());
71 | }
72 | }
73 |
74 | pub struct ThisBlockVisitor {
75 | this_id: Option,
76 | has_jsx: bool,
77 | }
78 |
79 | impl ThisBlockVisitor {
80 | pub fn new() -> Self {
81 | Self {
82 | this_id: None,
83 | has_jsx: false,
84 | }
85 | }
86 | }
87 |
88 | impl VisitMut for ThisBlockVisitor {
89 | fn visit_mut_block_stmt(&mut self, block: &mut BlockStmt) {
90 | let mut id = self.this_id.clone();
91 | self.has_jsx = false;
92 | block.visit_mut_children_with(self);
93 | std::mem::swap(&mut self.this_id, &mut id);
94 | if let Some(id) = id {
95 | block.stmts.insert(
96 | 0,
97 | Stmt::Decl(Decl::Var(Box::new(VarDecl {
98 | span: DUMMY_SP,
99 | kind: VarDeclKind::Const,
100 | declare: false,
101 | decls: vec![VarDeclarator {
102 | span: DUMMY_SP,
103 | name: Pat::Ident(id.into()),
104 | init: Some(Box::new(Expr::This(ThisExpr { span: DUMMY_SP }))),
105 | definite: false,
106 | }],
107 | ..Default::default()
108 | }))),
109 | )
110 | }
111 | }
112 | fn visit_mut_jsx_element(&mut self, el: &mut JSXElement) {
113 | self.has_jsx = true;
114 | el.visit_mut_children_with(self);
115 | }
116 | fn visit_mut_jsx_fragment(&mut self, el: &mut JSXFragment) {
117 | self.has_jsx = true;
118 | el.visit_mut_children_with(self);
119 | }
120 | fn visit_mut_expr(&mut self, n: &mut Expr) {
121 | if let Expr::This(_) = n {
122 | if !self.has_jsx {
123 | return;
124 | }
125 | if self.this_id.is_none() {
126 | self.this_id = Some(private_ident!("_self$"));
127 | }
128 | *n = Expr::Ident(self.this_id.clone().unwrap());
129 | } else {
130 | n.visit_mut_children_with(self);
131 | }
132 | }
133 | }
134 |
135 | #[derive(Default)]
136 | pub struct TransformInfo {
137 | pub top_level: bool,
138 | pub skip_id: bool,
139 | pub component_child: bool,
140 | pub last_element: bool,
141 | pub fragment_child: bool,
142 | pub to_be_closed: Option>,
143 | pub do_not_escape: bool,
144 | }
145 |
146 | impl TransformVisitor
147 | where
148 | C: Comments,
149 | {
150 | pub fn transform_jsx(&mut self, node: &JSXElementChild) -> Expr {
151 | let info = match node {
152 | JSXElementChild::JSXFragment(_) => Default::default(),
153 | _ => TransformInfo {
154 | top_level: true,
155 | last_element: true,
156 | ..Default::default()
157 | },
158 | };
159 | let result = self.transform_node(node, &info);
160 | self.create_template(&mut result.unwrap(), false)
161 | }
162 |
163 | pub fn transform_node(
164 | &mut self,
165 | node: &JSXElementChild,
166 | info: &TransformInfo,
167 | ) -> Option {
168 | if let JSXElementChild::JSXElement(node) = node {
169 | return Some(self.transform_element(node, info));
170 | } else if let JSXElementChild::JSXFragment(node) = node {
171 | let mut results = TemplateInstantiation::default();
172 | self.transform_fragment_children(&node.children, &mut results);
173 | return Some(results);
174 | } else if let JSXElementChild::JSXText(node) = node {
175 | let text =
176 | trim_whitespace(&html_escape::encode_text(&node.raw).replace('\u{a0}', " "));
177 | if text.is_empty() {
178 | return None;
179 | }
180 | let mut results = TemplateInstantiation {
181 | template: escape_backticks(&text),
182 | text: true,
183 | ..TemplateInstantiation::default()
184 | };
185 | if !info.skip_id {
186 | results.id = Some(self.generate_uid_identifier("el$"));
187 | }
188 | return Some(results);
189 | } else if let Some(static_value) = self.get_static_expression(node) {
190 | let text = if info.do_not_escape {
191 | static_value
192 | } else {
193 | escape_html(&static_value, false)
194 | };
195 | if text.is_empty() {
196 | return None;
197 | }
198 | let mut results = TemplateInstantiation {
199 | template: escape_backticks(&text),
200 | text: true,
201 | ..TemplateInstantiation::default()
202 | };
203 | if !info.skip_id {
204 | results.id = Some(self.generate_uid_identifier("el$"));
205 | }
206 | return Some(results);
207 | } else if let JSXElementChild::JSXExprContainer(JSXExprContainer { expr, span }) = node {
208 | match expr {
209 | JSXExpr::JSXEmptyExpr(_) => {
210 | return None;
211 | }
212 | JSXExpr::Expr(exp) => {
213 | if !self.is_dynamic(
214 | exp,
215 | Some(*span),
216 | true,
217 | info.component_child,
218 | true,
219 | !info.component_child,
220 | ) {
221 | return Some(TemplateInstantiation {
222 | exprs: vec![*exp.clone()],
223 | ..Default::default()
224 | });
225 | }
226 | let mut expr = vec![];
227 | if self.config.wrap_conditionals
228 | && self.config.generate != "ssr"
229 | && (matches!(**exp, Expr::Bin(_)) || matches!(**exp, Expr::Cond(_)))
230 | {
231 | let result =
232 | self.transform_condition(*exp.clone(), info.component_child, false);
233 | match result {
234 | (Some(stmt0), ex1) => {
235 | expr = vec![Expr::Call(CallExpr {
236 | span: DUMMY_SP,
237 | callee: Callee::Expr(Box::new(Expr::Arrow(ArrowExpr {
238 | span: DUMMY_SP,
239 | params: vec![],
240 | body: Box::new(BlockStmtOrExpr::BlockStmt(BlockStmt {
241 | span: DUMMY_SP,
242 | stmts: vec![
243 | stmt0,
244 | Stmt::Return(ReturnStmt {
245 | span: DUMMY_SP,
246 | arg: Some(Box::new(ex1)),
247 | }),
248 | ],
249 | ..Default::default()
250 | })),
251 | ..Default::default()
252 | }))),
253 | ..Default::default()
254 | })];
255 | }
256 | (None, ex0) => expr = vec![ex0],
257 | }
258 | } else {
259 | let mut flag = false;
260 | if !info.component_child
261 | && (self.config.generate != "ssr" || info.fragment_child)
262 | && let Expr::Call(CallExpr {
263 | callee: Callee::Expr(ref ex),
264 | ref args,
265 | ..
266 | }) = **exp
267 | && !matches!(**ex, Expr::Member(_))
268 | && args.is_empty()
269 | {
270 | flag = true;
271 | expr = vec![*ex.clone()];
272 | }
273 | if !flag {
274 | expr = vec![Expr::Arrow(ArrowExpr {
275 | span: DUMMY_SP,
276 | params: vec![],
277 | body: Box::new(BlockStmtOrExpr::Expr(exp.clone())),
278 | ..Default::default()
279 | })];
280 | }
281 | }
282 | return Some(TemplateInstantiation {
283 | exprs: expr,
284 | dynamic: true,
285 | ..Default::default()
286 | });
287 | }
288 | }
289 | } else if let JSXElementChild::JSXSpreadChild(JSXSpreadChild { expr, .. }) = node {
290 | if !self.is_dynamic(expr, None, true, false, true, !info.component_child) {
291 | return Some(TemplateInstantiation {
292 | exprs: vec![*expr.clone()],
293 | ..Default::default()
294 | });
295 | }
296 | return Some(TemplateInstantiation {
297 | exprs: vec![Expr::Arrow(ArrowExpr {
298 | span: DUMMY_SP,
299 | params: vec![],
300 | body: Box::new(BlockStmtOrExpr::Expr(expr.clone())),
301 | ..Default::default()
302 | })],
303 | dynamic: true,
304 | ..Default::default()
305 | });
306 | }
307 | None
308 | }
309 |
310 | pub fn transform_jsx_expr(&mut self, node: &mut JSXElement) -> Expr {
311 | let mut results = self.transform_element(
312 | node,
313 | &TransformInfo {
314 | top_level: true,
315 | ..Default::default()
316 | },
317 | );
318 | self.create_template(&mut results, false)
319 | }
320 |
321 | pub fn transform_jsx_element(&mut self, node: &JSXElement) -> TemplateInstantiation {
322 | self.transform_element(node, &Default::default())
323 | }
324 |
325 | pub fn transform_element(
326 | &mut self,
327 | node: &JSXElement,
328 | info: &TransformInfo,
329 | ) -> TemplateInstantiation {
330 | let tag_name = get_tag_name(node);
331 | if is_component(&tag_name) {
332 | return self.transform_component(node);
333 | }
334 | self.transform_element_dom(node, info)
335 | }
336 | }
337 |
--------------------------------------------------------------------------------
/src/dom/template.rs:
--------------------------------------------------------------------------------
1 | use super::element::AttrOptions;
2 | use crate::{
3 | TransformVisitor,
4 | shared::structs::{DynamicAttr, TemplateConstruction, TemplateInstantiation},
5 | };
6 | use swc_core::{
7 | common::{DUMMY_SP, Span, comments::Comments},
8 | ecma::{
9 | ast::*,
10 | utils::{prepend_stmt, quote_ident},
11 | },
12 | };
13 |
14 | impl TransformVisitor
15 | where
16 | C: Comments,
17 | {
18 | pub fn create_template(&mut self, result: &mut TemplateInstantiation, wrap: bool) -> Expr {
19 | if let Some(id) = result.id.clone() {
20 | self.register_template(result);
21 | if result.exprs.is_empty()
22 | && result.dynamics.is_empty()
23 | && result.post_exprs.is_empty()
24 | && result.declarations.len() == 1
25 | {
26 | return *result.declarations[0].init.clone().unwrap();
27 | } else {
28 | return Expr::Call(CallExpr {
29 | span: DUMMY_SP,
30 | callee: Callee::Expr(Box::new(Expr::Arrow(ArrowExpr {
31 | span: DUMMY_SP,
32 | params: vec![],
33 | body: Box::new(BlockStmtOrExpr::BlockStmt(BlockStmt {
34 | span: DUMMY_SP,
35 | stmts: [Stmt::Decl(Decl::Var(Box::new(VarDecl {
36 | span: DUMMY_SP,
37 | kind: VarDeclKind::Const,
38 | declare: false,
39 | decls: result.declarations.clone(),
40 | ..Default::default()
41 | })))]
42 | .into_iter()
43 | .chain(result.exprs.clone().into_iter().map(|x| {
44 | Stmt::Expr(ExprStmt {
45 | span: DUMMY_SP,
46 | expr: Box::new(x),
47 | })
48 | }))
49 | .chain(
50 | self.wrap_dynamics(&mut result.dynamics)
51 | .unwrap_or_default()
52 | .into_iter()
53 | .map(|x| {
54 | Stmt::Expr(ExprStmt {
55 | span: DUMMY_SP,
56 | expr: Box::new(x),
57 | })
58 | }),
59 | )
60 | .chain(result.post_exprs.clone().into_iter().map(|x| {
61 | Stmt::Expr(ExprStmt {
62 | span: DUMMY_SP,
63 | expr: Box::new(x),
64 | })
65 | }))
66 | .chain([Stmt::Return(ReturnStmt {
67 | span: DUMMY_SP,
68 | arg: Some(Box::new(Expr::Ident(id))),
69 | })])
70 | .collect(),
71 | ..Default::default()
72 | })),
73 | ..Default::default()
74 | }))),
75 | ..Default::default()
76 | });
77 | }
78 | }
79 |
80 | if wrap && result.dynamic && !self.config.memo_wrapper.is_empty() {
81 | return Expr::Call(CallExpr {
82 | span: DUMMY_SP,
83 | callee: Callee::Expr(Box::new(Expr::Ident(
84 | self.register_import_method(&self.config.memo_wrapper.clone()),
85 | ))),
86 | args: vec![result.exprs[0].clone().into()],
87 | ..Default::default()
88 | });
89 | }
90 |
91 | result.exprs[0].clone()
92 | }
93 |
94 | pub fn append_templates(&mut self, module: &mut Module) {
95 | if self.templates.is_empty() {
96 | return;
97 | }
98 | let templ = self.register_import_method("template");
99 | prepend_stmt(
100 | &mut module.body,
101 | ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl {
102 | span: DUMMY_SP,
103 | kind: VarDeclKind::Const,
104 | declare: false,
105 | decls: self
106 | .templates
107 | .drain(..)
108 | .map(|template| {
109 | let span = Span::dummy_with_cmt();
110 | self.comments.add_pure_comment(span.lo);
111 | let mut args = vec![ExprOrSpread {
112 | spread: None,
113 | expr: Box::new(
114 | Tpl {
115 | span: DUMMY_SP,
116 | exprs: vec![],
117 | quasis: vec![TplElement {
118 | span: DUMMY_SP,
119 | tail: true,
120 | cooked: None,
121 | raw: template.template.into(),
122 | }],
123 | }
124 | .into(),
125 | ),
126 | }];
127 | if template.is_svg || template.is_ce {
128 | args.push(ExprOrSpread {
129 | spread: None,
130 | expr: Box::new(Expr::Lit(template.is_ce.into())),
131 | });
132 | args.push(ExprOrSpread {
133 | spread: None,
134 | expr: Box::new(Expr::Lit(template.is_svg.into())),
135 | });
136 | }
137 | VarDeclarator {
138 | span: DUMMY_SP,
139 | name: template.id.into(),
140 | init: Some(Box::new(Expr::Call(CallExpr {
141 | span,
142 | callee: Callee::Expr(Box::new(Expr::Ident(templ.clone()))),
143 | args,
144 | ..Default::default()
145 | }))),
146 | definite: false,
147 | }
148 | })
149 | .collect(),
150 | ..Default::default()
151 | })))),
152 | )
153 | }
154 |
155 | pub fn register_template(&mut self, results: &mut TemplateInstantiation) {
156 | let decl: VarDeclarator;
157 |
158 | if !results.template.is_empty() {
159 | let template_id: Ident;
160 | if !results.skip_template {
161 | let template_def = self
162 | .templates
163 | .iter()
164 | .find(|t| t.template == results.template);
165 | if let Some(template_def) = template_def {
166 | template_id = template_def.id.clone();
167 | } else {
168 | template_id = self.generate_uid_identifier("tmpl$");
169 | self.templates.push(TemplateConstruction {
170 | id: template_id.clone(),
171 | template: results.template.clone(),
172 | is_svg: results.is_svg,
173 | is_ce: results.has_custom_element,
174 | });
175 | }
176 |
177 | decl = VarDeclarator {
178 | span: DUMMY_SP,
179 | name: Pat::Ident(results.id.clone().unwrap().into()),
180 | init: Some(Box::new(Expr::Call(CallExpr {
181 | span: DUMMY_SP,
182 | callee: Callee::Expr(Box::new(Expr::Ident(template_id))),
183 | ..Default::default()
184 | }))),
185 | definite: false,
186 | };
187 |
188 | results.declarations.insert(0, decl);
189 | }
190 | }
191 | }
192 |
193 | fn wrap_dynamics(&mut self, dynamics: &mut Vec) -> Option> {
194 | if dynamics.is_empty() {
195 | return None;
196 | }
197 |
198 | let effect_wrapper_id = self.register_import_method(&self.config.effect_wrapper.clone());
199 |
200 | if dynamics.len() == 1 {
201 | let prev_value = if dynamics[0].key == "classList" || dynamics[0].key == "style" {
202 | Some(Ident::new_no_ctxt("_$p".into(), Default::default()))
203 | } else {
204 | None
205 | };
206 |
207 | if dynamics[0].key.starts_with("class:")
208 | && !matches!(dynamics[0].value, Expr::Lit(Lit::Bool(_)))
209 | && !dynamics[0].value.is_unary()
210 | {
211 | dynamics[0].value = Expr::Unary(UnaryExpr {
212 | span: Default::default(),
213 | op: UnaryOp::Bang,
214 | arg: Box::new(Expr::Unary(UnaryExpr {
215 | span: Default::default(),
216 | op: UnaryOp::Bang,
217 | arg: Box::new(dynamics[0].value.clone()),
218 | })),
219 | });
220 | }
221 |
222 | return Some(vec![Expr::Call(CallExpr {
223 | span: Default::default(),
224 | callee: Callee::Expr(Box::new(Expr::Ident(effect_wrapper_id))),
225 | args: vec![ExprOrSpread {
226 | spread: None,
227 | expr: Box::new(Expr::Arrow(ArrowExpr {
228 | span: Default::default(),
229 | params: prev_value
230 | .clone()
231 | .map(|v| {
232 | vec![Pat::Ident(BindingIdent {
233 | id: v,
234 | type_ann: None,
235 | })]
236 | })
237 | .unwrap_or_default(),
238 | body: Box::new(BlockStmtOrExpr::Expr(Box::new(self.set_attr(
239 | &dynamics[0].elem,
240 | &dynamics[0].key,
241 | &dynamics[0].value,
242 | &AttrOptions {
243 | is_svg: dynamics[0].is_svg,
244 | is_ce: dynamics[0].is_ce,
245 | dynamic: true,
246 | prev_id: prev_value.map(Expr::Ident),
247 | tag_name: dynamics[0].tag_name.clone(),
248 | },
249 | )))),
250 | ..Default::default()
251 | })),
252 | }],
253 | ..Default::default()
254 | })]);
255 | }
256 |
257 | let mut decls = vec![];
258 | let mut statements = vec![];
259 | let mut identifiers = vec![];
260 | let prev_id = Ident::new_no_ctxt("_p$".into(), DUMMY_SP);
261 |
262 | for dynamic in dynamics {
263 | let identifier = self.generate_uid_identifier("v$");
264 | if dynamic.key.starts_with("class:")
265 | && !matches!(dynamic.value, Expr::Lit(Lit::Bool(_)))
266 | && !dynamic.value.is_unary()
267 | {
268 | dynamic.value = Expr::Unary(UnaryExpr {
269 | span: Default::default(),
270 | op: UnaryOp::Bang,
271 | arg: Box::new(Expr::Unary(UnaryExpr {
272 | span: Default::default(),
273 | op: UnaryOp::Bang,
274 | arg: Box::new(dynamic.value.clone()),
275 | })),
276 | });
277 | }
278 | identifiers.push(identifier.clone());
279 | decls.push(VarDeclarator {
280 | span: Default::default(),
281 | name: Pat::Ident(BindingIdent {
282 | id: identifier.clone(),
283 | type_ann: None,
284 | }),
285 | init: Some(Box::new(dynamic.value.clone())),
286 | definite: false,
287 | });
288 |
289 | if dynamic.key == "classList" || dynamic.key == "style" {
290 | let prev = Expr::Member(MemberExpr {
291 | span: Default::default(),
292 | obj: Box::new(Expr::Ident(prev_id.clone())),
293 | prop: MemberProp::Ident(identifier.clone().into()),
294 | });
295 | statements.push(Stmt::Expr(ExprStmt {
296 | span: Default::default(),
297 | expr: Box::new(Expr::Assign(AssignExpr {
298 | span: Default::default(),
299 | left: AssignTarget::Simple(SimpleAssignTarget::Paren(ParenExpr {
300 | span: DUMMY_SP,
301 | expr: Box::new(prev.clone()),
302 | })),
303 | op: AssignOp::Assign,
304 | right: Box::new(self.set_attr(
305 | &dynamic.elem,
306 | &dynamic.key,
307 | &Expr::Ident(identifier),
308 | &AttrOptions {
309 | is_svg: dynamic.is_svg,
310 | is_ce: dynamic.is_ce,
311 | tag_name: dynamic.tag_name.clone(),
312 | dynamic: true,
313 | prev_id: Some(prev),
314 | },
315 | )),
316 | })),
317 | }));
318 | } else {
319 | let prev = if dynamic.key.starts_with("style:") {
320 | Expr::Ident(identifier.clone())
321 | } else {
322 | Expr::Ident(quote_ident!("undefined").into())
323 | };
324 | statements.push(Stmt::Expr(ExprStmt {
325 | span: Default::default(),
326 | expr: Box::new(Expr::Bin(BinExpr {
327 | span: Default::default(),
328 | left: Box::new(Expr::Bin(BinExpr {
329 | span: Default::default(),
330 | left: Box::new(Expr::Ident(identifier.clone())),
331 | op: BinaryOp::NotEqEq,
332 | right: Box::new(Expr::Member(MemberExpr {
333 | span: Default::default(),
334 | obj: Box::new(Expr::Ident(prev_id.clone())),
335 | prop: MemberProp::Ident(identifier.clone().into()),
336 | })),
337 | })),
338 | op: BinaryOp::LogicalAnd,
339 | right: Box::new(self.set_attr(
340 | &dynamic.elem,
341 | &dynamic.key,
342 | &Expr::Assign(AssignExpr {
343 | span: Default::default(),
344 | left: AssignTarget::Simple(SimpleAssignTarget::Paren(ParenExpr {
345 | span: DUMMY_SP,
346 | expr: Box::new(Expr::Member(MemberExpr {
347 | span: DUMMY_SP,
348 | obj: Box::new(Expr::Ident(prev_id.clone())),
349 | prop: MemberProp::Ident(identifier.clone().into()),
350 | })),
351 | })),
352 | op: AssignOp::Assign,
353 | right: Box::new(Expr::Ident(identifier)),
354 | }),
355 | &AttrOptions {
356 | is_svg: dynamic.is_svg,
357 | is_ce: dynamic.is_ce,
358 | tag_name: "".to_string(),
359 | dynamic: true,
360 | prev_id: Some(prev),
361 | },
362 | )),
363 | })),
364 | }));
365 | }
366 | }
367 |
368 | Some(vec![Expr::Call(CallExpr {
369 | span: Default::default(),
370 | callee: Callee::Expr(Box::new(Expr::Ident(effect_wrapper_id))),
371 | args: vec![
372 | ExprOrSpread {
373 | spread: None,
374 | expr: Box::new(Expr::Arrow(ArrowExpr {
375 | span: Default::default(),
376 | params: vec![Pat::Ident(BindingIdent {
377 | id: prev_id.clone(),
378 | type_ann: None,
379 | })],
380 | body: Box::new(BlockStmtOrExpr::BlockStmt(BlockStmt {
381 | span: Default::default(),
382 | stmts: [Stmt::Decl(Decl::Var(Box::new(VarDecl {
383 | span: Default::default(),
384 | kind: VarDeclKind::Const,
385 | declare: false,
386 | decls,
387 | ..Default::default()
388 | })))]
389 | .into_iter()
390 | .chain(statements)
391 | .chain(
392 | [Stmt::Return(ReturnStmt {
393 | span: Default::default(),
394 | arg: Some(Box::new(Expr::Ident(prev_id))),
395 | })]
396 | .into_iter(),
397 | )
398 | .collect(),
399 | ..Default::default()
400 | })),
401 | ..Default::default()
402 | })),
403 | },
404 | ExprOrSpread {
405 | spread: None,
406 | expr: Box::new(Expr::Object(ObjectLit {
407 | span: Default::default(),
408 | props: identifiers
409 | .into_iter()
410 | .map(|id| {
411 | PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
412 | key: PropName::Ident(id.into()),
413 | value: Box::new(Expr::Ident(quote_ident!("undefined").into())),
414 | })))
415 | })
416 | .collect(),
417 | })),
418 | },
419 | ],
420 | ..Default::default()
421 | })])
422 | }
423 | }
424 |
--------------------------------------------------------------------------------
/src/shared/utils.rs:
--------------------------------------------------------------------------------
1 | use super::structs::TemplateInstantiation;
2 | use crate::TransformVisitor;
3 | use convert_case::{Case, Converter};
4 | use once_cell::sync::Lazy;
5 | use regex::Regex;
6 | use std::collections::HashSet;
7 | use swc_core::{
8 | atoms::Atom,
9 | common::{BytePos, DUMMY_SP, Span, comments::Comments, iter::IdentifyLast},
10 | ecma::{
11 | ast::*,
12 | minifier::eval::EvalResult,
13 | utils::{prepend_stmt, private_ident},
14 | visit::{Visit, VisitWith},
15 | },
16 | };
17 |
18 | pub static RESERVED_NAME_SPACES: Lazy> =
19 | Lazy::new(|| HashSet::from(["class", "on", "oncapture", "style", "use", "prop", "attr"]));
20 |
21 | static NON_SPREAD_NAME_SPACES: Lazy> =
22 | Lazy::new(|| HashSet::from(["class", "style", "use", "prop", "attr"]));
23 |
24 | pub fn is_component(tag_name: &str) -> bool {
25 | let first_char = tag_name.chars().next().unwrap();
26 | let first_char_lower = first_char.to_lowercase().to_string();
27 | let has_dot = tag_name.contains('.');
28 | let has_non_alpha = !first_char.is_alphabetic();
29 | first_char_lower != first_char.to_string() || has_dot || has_non_alpha
30 | }
31 |
32 | pub fn get_tag_name(element: &JSXElement) -> String {
33 | let jsx_name = &element.opening.name;
34 | match jsx_name {
35 | JSXElementName::Ident(ident) => ident.sym.to_string(),
36 | JSXElementName::JSXMemberExpr(member) => {
37 | let mut name = member.prop.sym.to_string();
38 | let mut obj = &member.obj;
39 | let o = loop {
40 | if let JSXObject::JSXMemberExpr(member) = obj {
41 | name = format!("{}.{}", member.prop.sym, name);
42 | obj = &member.obj;
43 | } else if let JSXObject::Ident(id) = obj {
44 | break id.sym.to_string();
45 | }
46 | };
47 | format!("{o}.{name}")
48 | }
49 | JSXElementName::JSXNamespacedName(name) => {
50 | format!("{}:{}", name.ns.sym, name.name.sym)
51 | }
52 | }
53 | }
54 |
55 | impl TransformVisitor
56 | where
57 | C: Comments,
58 | {
59 | pub fn register_import_method(&mut self, name: &str) -> Ident {
60 | self.imports
61 | .entry(name.to_string())
62 | .or_insert_with(|| private_ident!(format!("_${}", name)))
63 | .clone()
64 | }
65 |
66 | pub fn insert_imports(&mut self, module: &mut Module) {
67 | let mut entries = self.imports.drain().collect::>();
68 | entries.sort_by(|(a, _), (b, _)| a.cmp(b));
69 | for (name, val) in entries {
70 | prepend_stmt(
71 | &mut module.body,
72 | ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
73 | specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
74 | local: val,
75 | imported: Some(ModuleExportName::Ident(Ident::new_no_ctxt(
76 | name.into(),
77 | DUMMY_SP,
78 | ))),
79 | span: DUMMY_SP,
80 | is_type_only: false,
81 | })],
82 | src: Box::new(Str {
83 | span: DUMMY_SP,
84 | value: self.config.module_name.clone().into(),
85 | raw: None,
86 | }),
87 | span: DUMMY_SP,
88 | type_only: false,
89 | with: None,
90 | phase: ImportPhase::default(),
91 | })),
92 | );
93 | }
94 | }
95 |
96 | pub fn insert_events(&mut self, module: &mut Module) {
97 | if !self.events.is_empty() {
98 | let mut elems: Vec<_> = self.events.drain().collect();
99 | elems.sort();
100 | let elems = elems
101 | .into_iter()
102 | .map(|v| {
103 | Some(ExprOrSpread {
104 | spread: None,
105 | expr: Box::new(Expr::Lit(Lit::Str(v.into()))),
106 | })
107 | })
108 | .collect();
109 | module.body.push(ModuleItem::Stmt(Stmt::Expr(ExprStmt {
110 | span: DUMMY_SP,
111 | expr: Box::new(Expr::Call(CallExpr {
112 | span: DUMMY_SP,
113 | callee: Callee::Expr(Box::new(Expr::Ident(
114 | self.register_import_method("delegateEvents"),
115 | ))),
116 | args: vec![ExprOrSpread {
117 | spread: None,
118 | expr: Box::new(Expr::Array(ArrayLit {
119 | span: DUMMY_SP,
120 | elems,
121 | })),
122 | }],
123 | ..Default::default()
124 | })),
125 | })))
126 | }
127 | }
128 |
129 | pub fn transform_condition(
130 | &mut self,
131 | mut node: Expr,
132 | inline: bool,
133 | deep: bool,
134 | ) -> (Option, Expr) {
135 | let memo_wrapper = self.config.memo_wrapper.clone();
136 | let memo = self.register_import_method(&memo_wrapper);
137 | let mut d_test = false;
138 | let mut cond = Expr::Invalid(Invalid { span: DUMMY_SP });
139 | let mut id = Expr::Invalid(Invalid { span: DUMMY_SP });
140 | match &mut node {
141 | Expr::Cond(expr) => {
142 | if self.is_dynamic(&expr.cons, None, false, true, true, false)
143 | || self.is_dynamic(&expr.alt, None, false, true, true, false)
144 | {
145 | d_test = self.is_dynamic(&expr.test, None, true, false, true, false);
146 | if d_test {
147 | cond = *expr.test.clone();
148 | if !is_binary_expression(&cond) {
149 | cond = Expr::Unary(UnaryExpr {
150 | span: DUMMY_SP,
151 | op: UnaryOp::Bang,
152 | arg: Box::new(Expr::Unary(UnaryExpr {
153 | span: DUMMY_SP,
154 | op: UnaryOp::Bang,
155 | arg: Box::new(cond),
156 | })),
157 | })
158 | }
159 | id = if inline {
160 | Expr::Call(CallExpr {
161 | span: DUMMY_SP,
162 | callee: Callee::Expr(Box::new(Expr::Ident(memo.clone()))),
163 | args: vec![ExprOrSpread {
164 | spread: None,
165 | expr: Box::new(Expr::Arrow(ArrowExpr {
166 | span: DUMMY_SP,
167 | params: vec![],
168 | body: Box::new(BlockStmtOrExpr::Expr(Box::new(
169 | cond.clone(),
170 | ))),
171 | ..Default::default()
172 | })),
173 | }],
174 | ..Default::default()
175 | })
176 | } else {
177 | Expr::Ident(self.generate_uid_identifier("_c$"))
178 | };
179 |
180 | expr.test = Box::new(Expr::Call(CallExpr {
181 | span: DUMMY_SP,
182 | callee: Callee::Expr(Box::new(id.clone())),
183 | ..Default::default()
184 | }));
185 |
186 | if matches!(*expr.cons, Expr::Cond(_)) || is_logical_expression(&expr.cons)
187 | {
188 | let (_, e) = self.transform_condition(*expr.cons.clone(), inline, true);
189 | expr.cons = Box::new(e);
190 | }
191 |
192 | match &mut *expr.cons {
193 | Expr::Paren(ParenExpr { expr: ex, .. })
194 | if (matches!(**ex, Expr::Cond(_))
195 | || is_logical_expression(&*ex)) =>
196 | {
197 | let (_, e) = self.transform_condition(*ex.clone(), inline, true);
198 | **ex = e;
199 | }
200 | _ => {}
201 | }
202 |
203 | if matches!(*expr.alt, Expr::Cond(_)) || is_logical_expression(&expr.alt) {
204 | let (_, e) = self.transform_condition(*expr.alt.clone(), inline, true);
205 | expr.alt = Box::new(e);
206 | }
207 |
208 | match &mut *expr.alt {
209 | Expr::Paren(ParenExpr { expr: ex, .. })
210 | if (matches!(**ex, Expr::Cond(_))
211 | || is_logical_expression(&*ex)) =>
212 | {
213 | let (_, e) = self.transform_condition(*ex.clone(), inline, true);
214 | **ex = e;
215 | }
216 | _ => {}
217 | }
218 | }
219 | }
220 | }
221 | Expr::Bin(expr) if is_logical_op(expr) => {
222 | let mut next_path = expr;
223 | loop {
224 | if next_path.op == BinaryOp::LogicalAnd {
225 | self.transform_condition_left_logical(
226 | next_path,
227 | &mut d_test,
228 | &mut cond,
229 | &mut id,
230 | inline,
231 | &memo,
232 | );
233 | break;
234 | }
235 |
236 | if let Expr::Paren(ParenExpr { expr, .. }) = &*next_path.left {
237 | *next_path.left = *expr.clone();
238 | }
239 | if let Expr::Bin(ref mut left) = *next_path.left {
240 | if !is_logical_op(left) {
241 | self.transform_condition_left_logical(
242 | left,
243 | &mut d_test,
244 | &mut cond,
245 | &mut id,
246 | inline,
247 | &memo,
248 | );
249 | break;
250 | }
251 | next_path = left;
252 | } else {
253 | self.transform_condition_left_logical(
254 | next_path,
255 | &mut d_test,
256 | &mut cond,
257 | &mut id,
258 | inline,
259 | &memo,
260 | );
261 | break;
262 | }
263 | }
264 | }
265 | _ => {}
266 | }
267 | if d_test
268 | && !inline
269 | && let Expr::Ident(ref ident) = id
270 | {
271 | let init_id_var = if memo_wrapper.is_empty() {
272 | Expr::Arrow(ArrowExpr {
273 | span: DUMMY_SP,
274 | params: vec![],
275 | body: Box::new(BlockStmtOrExpr::Expr(Box::new(cond))),
276 | ..Default::default()
277 | })
278 | } else {
279 | Expr::Call(CallExpr {
280 | span: DUMMY_SP,
281 | callee: Callee::Expr(Box::new(Expr::Ident(memo))),
282 | args: vec![ExprOrSpread {
283 | spread: None,
284 | expr: Box::new(Expr::Arrow(ArrowExpr {
285 | span: DUMMY_SP,
286 | params: vec![],
287 | body: Box::new(BlockStmtOrExpr::Expr(Box::new(cond))),
288 | ..Default::default()
289 | })),
290 | }],
291 | ..Default::default()
292 | })
293 | };
294 | let stmt1 = Stmt::Decl(Decl::Var(Box::new(VarDecl {
295 | span: DUMMY_SP,
296 | kind: VarDeclKind::Const,
297 | declare: false,
298 | decls: vec![VarDeclarator {
299 | span: DUMMY_SP,
300 | name: Pat::Ident(BindingIdent {
301 | id: ident.clone(),
302 | type_ann: None,
303 | }),
304 | init: Some(Box::new(init_id_var)),
305 | definite: false,
306 | }],
307 | ..Default::default()
308 | })));
309 | let expr2 = Expr::Arrow(ArrowExpr {
310 | span: DUMMY_SP,
311 | params: vec![],
312 | body: Box::new(BlockStmtOrExpr::Expr(Box::new(node))),
313 | ..Default::default()
314 | });
315 | return if deep {
316 | (
317 | None,
318 | Expr::Call(CallExpr {
319 | span: DUMMY_SP,
320 | callee: Callee::Expr(Box::new(Expr::Arrow(ArrowExpr {
321 | span: DUMMY_SP,
322 | params: vec![],
323 | body: Box::new(BlockStmtOrExpr::BlockStmt(BlockStmt {
324 | span: DUMMY_SP,
325 | stmts: vec![
326 | stmt1,
327 | Stmt::Return(ReturnStmt {
328 | span: DUMMY_SP,
329 | arg: Some(Box::new(expr2)),
330 | }),
331 | ],
332 | ..Default::default()
333 | })),
334 | ..Default::default()
335 | }))),
336 | ..Default::default()
337 | }),
338 | )
339 | } else {
340 | (Some(stmt1), expr2)
341 | };
342 | }
343 |
344 | if deep {
345 | (None, node)
346 | } else {
347 | (
348 | None,
349 | Expr::Arrow(ArrowExpr {
350 | span: DUMMY_SP,
351 | params: vec![],
352 | body: Box::new(BlockStmtOrExpr::Expr(Box::new(node))),
353 | ..Default::default()
354 | }),
355 | )
356 | }
357 | }
358 |
359 | fn transform_condition_left_logical(
360 | &mut self,
361 | next_path: &mut BinExpr,
362 | d_test: &mut bool,
363 | cond: &mut Expr,
364 | id: &mut Expr,
365 | inline: bool,
366 | memo: &Ident,
367 | ) {
368 | if next_path.op == BinaryOp::LogicalAnd
369 | && self.is_dynamic(&next_path.right, None, false, true, true, false)
370 | {
371 | *d_test = self.is_dynamic(&next_path.left.clone(), None, true, false, true, false);
372 | }
373 | if *d_test {
374 | *cond = *next_path.left.clone();
375 | if !is_binary_expression(cond) {
376 | *cond = Expr::Unary(UnaryExpr {
377 | span: DUMMY_SP,
378 | op: UnaryOp::Bang,
379 | arg: Box::new(Expr::Unary(UnaryExpr {
380 | span: DUMMY_SP,
381 | op: UnaryOp::Bang,
382 | arg: Box::new(cond.clone()),
383 | })),
384 | });
385 | }
386 | *id = if inline {
387 | Expr::Call(CallExpr {
388 | span: DUMMY_SP,
389 | callee: Callee::Expr(Box::new(Expr::Ident(memo.clone()))),
390 | args: vec![ExprOrSpread {
391 | spread: None,
392 | expr: Box::new(Expr::Arrow(ArrowExpr {
393 | span: DUMMY_SP,
394 | params: vec![],
395 | body: Box::new(BlockStmtOrExpr::Expr(Box::new(cond.clone()))),
396 | ..Default::default()
397 | })),
398 | }],
399 | ..Default::default()
400 | })
401 | } else {
402 | Expr::Ident(self.generate_uid_identifier("_c$"))
403 | };
404 | next_path.left = Box::new(Expr::Call(CallExpr {
405 | span: DUMMY_SP,
406 | callee: Callee::Expr(Box::new(id.clone())),
407 | ..Default::default()
408 | }));
409 | }
410 | }
411 |
412 | pub fn get_static_expression(&mut self, child: &JSXElementChild) -> Option {
413 | match child {
414 | JSXElementChild::JSXExprContainer(JSXExprContainer {
415 | expr: JSXExpr::Expr(expr),
416 | ..
417 | }) => match **expr {
418 | Expr::Lit(ref lit) => Some(lit_to_string(lit)),
419 | Expr::Seq(_) => None,
420 | _ => match self.evaluator.as_mut().unwrap().eval(expr) {
421 | Some(EvalResult::Lit(lit)) => Some(lit_to_string(&lit)),
422 | _ => None,
423 | },
424 | },
425 | _ => None,
426 | }
427 | }
428 |
429 | pub fn is_dynamic(
430 | &self,
431 | expr: &Expr,
432 | span: Option,
433 | check_member: bool,
434 | check_tags: bool,
435 | check_call_expression: bool,
436 | _native: bool,
437 | ) -> bool {
438 | if matches!(expr, Expr::Fn(_) | Expr::Arrow(_)) {
439 | return false;
440 | }
441 |
442 | if let Some(span) = span {
443 | let pos = span.lo + BytePos(1);
444 | if let Some(mut cmts) = self.comments.take_trailing(pos)
445 | && cmts[0].text.to_string().trim() == self.config.static_marker
446 | {
447 | cmts.remove(0);
448 | self.comments.add_trailing_comments(pos, cmts);
449 | return false;
450 | }
451 | }
452 |
453 | if match expr {
454 | Expr::Call(_) => check_call_expression,
455 | Expr::Member(_) => check_member,
456 | Expr::OptChain(_) => check_member,
457 | Expr::Bin(BinExpr {
458 | op: BinaryOp::In, ..
459 | }) => check_member,
460 | Expr::JSXElement(_) => check_tags,
461 | Expr::JSXFragment(_) => check_tags,
462 | _ => false,
463 | } {
464 | return true;
465 | }
466 |
467 | let mut dyn_visitor = DynamicVisitor {
468 | _transform_visitor: self,
469 | check_member,
470 | check_tags,
471 | check_call_expression,
472 | // native,
473 | dynamic: false,
474 | is_stop: false,
475 | };
476 | expr.visit_with(&mut dyn_visitor);
477 | dyn_visitor.dynamic
478 | }
479 | }
480 |
481 | struct DynamicVisitor<'a, C>
482 | where
483 | C: Comments,
484 | {
485 | _transform_visitor: &'a TransformVisitor,
486 | check_member: bool,
487 | check_tags: bool,
488 | check_call_expression: bool,
489 | // native: bool,
490 | dynamic: bool,
491 | is_stop: bool,
492 | }
493 |
494 | impl Visit for DynamicVisitor<'_, C>
495 | where
496 | C: Comments,
497 | {
498 | fn visit_method_prop(&mut self, _n: &MethodProp) {
499 | // self.dynamic = self.transform_visitor.is_dynamic(&n.function, None, self.check_member, self.check_tags, self.check_call_expression, self.native);
500 | self.dynamic = false;
501 | }
502 | fn visit_function(&mut self, _: &Function) {}
503 | fn visit_call_expr(&mut self, c: &CallExpr) {
504 | if self.is_stop {
505 | return;
506 | }
507 | if self.check_call_expression {
508 | self.dynamic = true;
509 | self.is_stop = true;
510 | } else {
511 | c.visit_children_with(self);
512 | }
513 | }
514 | fn visit_opt_call(&mut self, c: &OptCall) {
515 | if self.is_stop {
516 | return;
517 | }
518 | if self.check_call_expression {
519 | self.dynamic = true;
520 | self.is_stop = true;
521 | } else {
522 | c.visit_children_with(self);
523 | }
524 | }
525 | fn visit_member_expr(&mut self, e: &MemberExpr) {
526 | if self.is_stop {
527 | return;
528 | }
529 | if self.check_member {
530 | self.dynamic = true;
531 | self.is_stop = true;
532 | } else {
533 | e.visit_children_with(self);
534 | }
535 | }
536 | fn visit_opt_chain_expr(&mut self, e: &OptChainExpr) {
537 | if self.is_stop {
538 | return;
539 | }
540 | if self.check_member {
541 | self.dynamic = true;
542 | self.is_stop = true;
543 | } else {
544 | e.visit_children_with(self);
545 | }
546 | }
547 | fn visit_spread_element(&mut self, s: &SpreadElement) {
548 | if self.is_stop {
549 | return;
550 | }
551 | if self.check_member {
552 | self.dynamic = true;
553 | self.is_stop = true;
554 | } else {
555 | s.visit_children_with(self);
556 | }
557 | }
558 | fn visit_bin_expr(&mut self, bin_expr: &BinExpr) {
559 | if self.is_stop {
560 | return;
561 | }
562 | if self.check_member && bin_expr.op == BinaryOp::In {
563 | self.dynamic = true;
564 | self.is_stop = true;
565 | } else {
566 | bin_expr.visit_children_with(self);
567 | }
568 | }
569 | fn visit_jsx_element(&mut self, _: &JSXElement) {
570 | if self.is_stop {
571 | return;
572 | }
573 | if self.check_tags {
574 | self.dynamic = true;
575 | self.is_stop = true;
576 | }
577 | }
578 | fn visit_jsx_fragment(&mut self, _: &JSXFragment) {
579 | if self.is_stop {
580 | return;
581 | }
582 | if self.check_tags {
583 | self.dynamic = true;
584 | self.is_stop = true;
585 | }
586 | }
587 | }
588 |
589 | pub fn filter_children(c: &JSXElementChild) -> bool {
590 | match c {
591 | JSXElementChild::JSXText(t) => {
592 | let regex = Regex::new(r"^[\r\n]\s*$").unwrap();
593 | !regex.is_match(&t.raw)
594 | }
595 | JSXElementChild::JSXExprContainer(JSXExprContainer {
596 | expr: JSXExpr::JSXEmptyExpr(_),
597 | ..
598 | }) => false,
599 | _ => true,
600 | }
601 | }
602 |
603 | pub fn convert_jsx_identifier(attr_name: &JSXAttrName) -> (PropName, String) {
604 | let name = match &attr_name {
605 | JSXAttrName::Ident(ident) => ident.sym.to_string(),
606 | JSXAttrName::JSXNamespacedName(name) => {
607 | format!("{}:{}", name.ns.sym, name.name.sym)
608 | }
609 | };
610 | match Ident::verify_symbol(&name) {
611 | Ok(_) => (
612 | PropName::Ident(IdentName::new(name.clone().into(), DUMMY_SP)),
613 | name,
614 | ),
615 | Err(_) => (
616 | PropName::Str(Str {
617 | span: DUMMY_SP,
618 | value: name.clone().into(),
619 | raw: None,
620 | }),
621 | name,
622 | ),
623 | }
624 | }
625 |
626 | pub fn check_length(children: &Vec<&JSXElementChild>) -> bool {
627 | let mut i = 0;
628 | for child in children {
629 | if !matches!(
630 | child,
631 | JSXElementChild::JSXExprContainer(JSXExprContainer {
632 | expr: JSXExpr::JSXEmptyExpr(_),
633 | ..
634 | })
635 | ) {
636 | if let JSXElementChild::JSXText(t) = child {
637 | if !Regex::new(r"^\s*$").unwrap().is_match(&t.raw)
638 | || Regex::new(r"^ *$").unwrap().is_match(&t.raw)
639 | {
640 | i += 1;
641 | }
642 | } else {
643 | i += 1;
644 | }
645 | }
646 | }
647 | i > 1
648 | }
649 |
650 | pub fn trim_whitespace(text: &str) -> String {
651 | let mut text = text.replace('\r', "");
652 | if text.contains('\n') {
653 | let start_space_regex = Regex::new(r"^\s*").unwrap();
654 | let space_regex = Regex::new(r"^\s*$").unwrap();
655 | text = text
656 | .split('\n')
657 | .enumerate()
658 | .map(|(i, t)| {
659 | if i > 0 {
660 | start_space_regex.replace_all(t, "").to_string()
661 | } else {
662 | String::from(t)
663 | }
664 | })
665 | .filter(|s| !space_regex.is_match(s))
666 | .reduce(|cur, nxt| format!("{cur} {nxt}"))
667 | .unwrap_or("".to_owned());
668 | }
669 | Regex::new(r"\s+")
670 | .unwrap()
671 | .replace_all(&text, " ")
672 | .to_string()
673 | }
674 |
675 | pub fn to_property_name(name: &str) -> String {
676 | let conv = Converter::new().from_case(Case::Kebab).to_case(Case::Camel);
677 | conv.convert(name.to_lowercase())
678 | }
679 |
680 | pub fn wrapped_by_text(list: &[TemplateInstantiation], start_index: usize) -> bool {
681 | let mut index = start_index;
682 | let mut wrapped = false;
683 | while index > 0 {
684 | index -= 1;
685 | let node = &list[index];
686 | if node.text {
687 | wrapped = true;
688 | break;
689 | }
690 |
691 | if node.id.is_some() {
692 | return false;
693 | }
694 | }
695 | if !wrapped {
696 | return false;
697 | }
698 | index = start_index;
699 | while index < list.len() {
700 | let node = &list[index];
701 | if node.text {
702 | return true;
703 | }
704 | if node.id.is_some() {
705 | return false;
706 | }
707 | index += 1;
708 | }
709 |
710 | false
711 | }
712 |
713 | pub fn escape_backticks(value: &str) -> String {
714 | Regex::new(r"`")
715 | .unwrap()
716 | .replace_all(value, r"\`")
717 | .to_string()
718 | }
719 |
720 | pub fn escape_html(s: &str, attr: bool) -> String {
721 | let delim = if attr { "\"" } else { "<" };
722 | let esc_delim = if attr { """ } else { "<" };
723 | let mut i_delim = s.find(delim).map_or(-1, |i| i as i32);
724 | let mut i_amp = s.find('&').map_or(-1, |i| i as i32);
725 |
726 | if i_delim < 0 && i_amp < 0 {
727 | return s.to_string();
728 | }
729 |
730 | let mut left = 0;
731 | let mut out = String::from("");
732 |
733 | while i_delim >= 0 && i_amp >= 0 {
734 | if i_delim < i_amp {
735 | if left < i_delim {
736 | out += &s[left as usize..i_delim as usize];
737 | }
738 | out += esc_delim;
739 | left = i_delim + 1;
740 | i_delim = s[left as usize..]
741 | .find(delim)
742 | .map_or(-1, |i| i as i32 + left);
743 | } else {
744 | if left < i_amp {
745 | out += &s[left as usize..i_amp as usize];
746 | }
747 | out += "&";
748 | left = i_amp + 1;
749 | i_amp = s[left as usize..].find('&').map_or(-1, |i| i as i32 + left);
750 | }
751 | }
752 |
753 | if i_delim >= 0 {
754 | loop {
755 | if left < i_delim {
756 | out += &s[left as usize..i_delim as usize];
757 | }
758 | out += esc_delim;
759 | left = i_delim + 1;
760 | i_delim = s[left as usize..]
761 | .find(delim)
762 | .map_or(-1, |i| i as i32 + left);
763 | if i_delim < 0 {
764 | break;
765 | }
766 | }
767 | } else {
768 | while i_amp >= 0 {
769 | if left < i_amp {
770 | out += &s[left as usize..i_amp as usize];
771 | }
772 | out += "&";
773 | left = i_amp + 1;
774 | i_amp = s[left as usize..].find('&').map_or(-1, |i| i as i32 + left);
775 | }
776 | }
777 |
778 | if left < s.len() as i32 {
779 | out += &s[left as usize..];
780 | }
781 | out
782 | }
783 |
784 | pub fn can_native_spread(key: &str, check_name_spaces: bool) -> bool {
785 | if check_name_spaces
786 | && key.contains(':')
787 | && NON_SPREAD_NAME_SPACES.contains(key.split(':').next().unwrap())
788 | {
789 | false
790 | } else {
791 | key != "ref"
792 | }
793 | }
794 |
795 | pub fn is_static_expr(expr: &Expr) -> bool {
796 | if let Expr::Object(ObjectLit { props, .. }) = expr {
797 | for prop in props {
798 | match prop {
799 | PropOrSpread::Spread(_) => return false,
800 | PropOrSpread::Prop(p) => match **p {
801 | Prop::KeyValue(ref kv) => {
802 | if !is_static_expr(&kv.value) {
803 | return false;
804 | }
805 | }
806 | _ => return false,
807 | },
808 | }
809 | }
810 | true
811 | } else {
812 | matches!(expr, Expr::Lit(_))
813 | }
814 | }
815 |
816 | pub fn lit_to_string(lit: &Lit) -> String {
817 | match lit {
818 | Lit::Str(value) => value.value.to_string(),
819 | Lit::Bool(value) => value.value.to_string(),
820 | Lit::Null(_) => "null".to_string(),
821 | Lit::Num(value) => value.value.to_string(),
822 | Lit::BigInt(value) => value.value.to_string(),
823 | Lit::Regex(value) => value.exp.to_string(),
824 | Lit::JSXText(value) => value.raw.to_string(),
825 | }
826 | }
827 |
828 | pub fn is_l_val(expr: &Expr) -> bool {
829 | matches!(
830 | expr,
831 | Expr::Ident(_)
832 | | Expr::Member(_)
833 | | Expr::Assign(_)
834 | | Expr::Array(_)
835 | | Expr::Object(_)
836 | | Expr::TsAs(_)
837 | | Expr::TsSatisfies(_)
838 | | Expr::TsTypeAssertion(_)
839 | | Expr::TsNonNull(_)
840 | )
841 | }
842 |
843 | pub fn is_logical_op(b: &BinExpr) -> bool {
844 | b.op == BinaryOp::LogicalOr
845 | || b.op == BinaryOp::LogicalAnd
846 | || b.op == BinaryOp::NullishCoalescing
847 | }
848 |
849 | pub fn is_logical_expression(expr: &Expr) -> bool {
850 | if let Expr::Bin(b) = expr
851 | && is_logical_op(b)
852 | {
853 | return true;
854 | }
855 | false
856 | }
857 |
858 | pub fn is_binary_expression(expr: &Expr) -> bool {
859 | if let Expr::Bin(b) = expr
860 | && !is_logical_op(b)
861 | {
862 | return true;
863 | }
864 | false
865 | }
866 |
867 | pub fn jsx_text_to_str(t: &Atom) -> String {
868 | let mut buf = String::new();
869 | let replaced = t.replace('\t', " ");
870 |
871 | for (is_last, (i, line)) in replaced.lines().enumerate().identify_last() {
872 | if line.is_empty() {
873 | continue;
874 | }
875 | let line = if i != 0 {
876 | line.trim_start_matches(' ')
877 | } else {
878 | line
879 | };
880 | let line = if is_last {
881 | line
882 | } else {
883 | line.trim_end_matches(' ')
884 | };
885 | if line.is_empty() {
886 | continue;
887 | }
888 | if i != 0 && !buf.is_empty() {
889 | buf.push(' ')
890 | }
891 | buf.push_str(line);
892 | }
893 |
894 | buf
895 | }
896 |
--------------------------------------------------------------------------------
/src/shared/component.rs:
--------------------------------------------------------------------------------
1 | use super::{
2 | structs::TemplateInstantiation,
3 | transform::TransformInfo,
4 | utils::{convert_jsx_identifier, filter_children, jsx_text_to_str},
5 | };
6 | use crate::{TransformVisitor, shared::utils::is_l_val};
7 | use swc_core::{
8 | common::{DUMMY_SP, comments::Comments},
9 | ecma::{ast::*, utils::quote_ident},
10 | };
11 |
12 | impl TransformVisitor
13 | where
14 | C: Comments,
15 | {
16 | pub fn transform_component(&mut self, node: &JSXElement) -> TemplateInstantiation {
17 | let mut exprs: Vec = vec![];
18 | let mut tag_id = get_component_identifier(&node.opening.name);
19 | let mut props = vec![];
20 | let mut running_objects = vec![];
21 | let mut dynamic_spread = false;
22 | let has_children = !node.children.is_empty();
23 |
24 | if let Expr::Ident(id) = &tag_id
25 | && self.config.built_ins.iter().any(|v| v.as_str() == &id.sym)
26 | && id.ctxt.as_u32() == 1
27 | {
28 | tag_id = Expr::Ident(self.register_import_method(&id.sym));
29 | }
30 |
31 | for attribute in &node.opening.attrs {
32 | match attribute {
33 | JSXAttrOrSpread::SpreadElement(node) => {
34 | if !running_objects.is_empty() {
35 | props.push(
36 | ObjectLit {
37 | span: DUMMY_SP,
38 | props: running_objects
39 | .into_iter()
40 | .map(|prop| PropOrSpread::Prop(Box::new(prop)))
41 | .collect(),
42 | }
43 | .into(),
44 | );
45 | running_objects = vec![];
46 | }
47 |
48 | let expr = if self.is_dynamic(&node.expr, None, true, false, true, false) {
49 | dynamic_spread = true;
50 | match *node.expr.clone() {
51 | Expr::Call(CallExpr {
52 | callee: Callee::Expr(callee_expr),
53 | args,
54 | ..
55 | }) if args.is_empty()
56 | && !matches!(*callee_expr, Expr::Call(_) | Expr::Member(_)) =>
57 | {
58 | *callee_expr.clone()
59 | }
60 | expr => ArrowExpr {
61 | span: DUMMY_SP,
62 | params: vec![],
63 | body: Box::new(BlockStmtOrExpr::Expr(Box::new(expr))),
64 | ..Default::default()
65 | }
66 | .into(),
67 | }
68 | } else {
69 | *node.expr.clone()
70 | };
71 | props.push(expr);
72 | }
73 | JSXAttrOrSpread::JSXAttr(attr) => {
74 | let (id, key) = convert_jsx_identifier(&attr.name);
75 |
76 | if has_children && key == "children" {
77 | continue;
78 | }
79 |
80 | match attr.value.clone() {
81 | Some(JSXAttrValue::JSXExprContainer(JSXExprContainer {
82 | expr: JSXExpr::Expr(expr),
83 | span,
84 | })) => {
85 | if key == "ref" {
86 | let expr = {
87 | let mut expr = *expr;
88 | loop {
89 | match expr {
90 | Expr::TsNonNull(non_null_expr) => {
91 | expr = *non_null_expr.expr
92 | }
93 | Expr::TsAs(as_expr) => expr = *as_expr.expr,
94 | Expr::TsSatisfies(satisfies_expr) => {
95 | expr = *satisfies_expr.expr
96 | }
97 | _ => break,
98 | }
99 | }
100 | expr
101 | };
102 | let is_function = if let Expr::Ident(ref id) = expr {
103 | self.binding_collector
104 | .const_var_bindings
105 | .contains_key(&id.to_id())
106 | } else {
107 | false
108 | };
109 | if !is_function && is_l_val(&expr) {
110 | let ref_identifier = self.generate_uid_identifier("_ref$");
111 | running_objects.push(Prop::Method(MethodProp {
112 | key: PropName::Ident(quote_ident!("ref")),
113 | function: Box::new(Function {
114 | params: vec![Param {
115 | span: DUMMY_SP,
116 | decorators: vec![],
117 | pat: Pat::Ident(quote_ident!("r$").into()),
118 | }],
119 | decorators: vec![],
120 | span: DUMMY_SP,
121 | body: Some(BlockStmt {
122 | span: DUMMY_SP,
123 | stmts: vec![
124 | Stmt::Decl(Decl::Var(Box::new(VarDecl {
125 | span: DUMMY_SP,
126 | kind: VarDeclKind::Const,
127 | declare: false,
128 | decls: vec![VarDeclarator {
129 | definite: false,
130 | span: DUMMY_SP,
131 | name: Pat::Ident(
132 | ref_identifier.clone().into(),
133 | ),
134 | init: Some(Box::new(expr.clone())),
135 | }],
136 | ..Default::default()
137 | }))),
138 | Stmt::Expr(ExprStmt {
139 | span: DUMMY_SP,
140 | expr: Box::new(Expr::Cond(CondExpr {
141 | span: DUMMY_SP,
142 | test: Box::new(Expr::Bin(BinExpr {
143 | span: DUMMY_SP,
144 | op: BinaryOp::EqEqEq,
145 | left: Box::new(Expr::Unary(
146 | UnaryExpr {
147 | span: DUMMY_SP,
148 | op: UnaryOp::TypeOf,
149 | arg: Box::new(Expr::Ident(
150 | ref_identifier.clone(),
151 | )),
152 | },
153 | )),
154 | right: Box::new(Expr::Lit(
155 | Lit::Str("function".into()),
156 | )),
157 | })),
158 | cons: Box::new(Expr::Call(CallExpr {
159 | span: DUMMY_SP,
160 | callee: Callee::Expr(Box::new(
161 | Expr::Ident(
162 | ref_identifier.clone(),
163 | ),
164 | )),
165 | args: vec![ExprOrSpread {
166 | spread: None,
167 | expr: Box::new(Expr::Ident(
168 | quote_ident!("r$").into(),
169 | )),
170 | }],
171 | ..Default::default()
172 | })),
173 | alt: Box::new(Expr::Assign(
174 | AssignExpr {
175 | span: DUMMY_SP,
176 | op: AssignOp::Assign,
177 | left: AssignTarget::Simple(
178 | SimpleAssignTarget::Paren(
179 | ParenExpr {
180 | span: DUMMY_SP,
181 | expr: Box::new(
182 | expr,
183 | ),
184 | },
185 | ),
186 | ),
187 | right: Box::new(Expr::Ident(
188 | quote_ident!("r$").into(),
189 | )),
190 | },
191 | )),
192 | })),
193 | }),
194 | ],
195 | ..Default::default()
196 | }),
197 | ..Default::default()
198 | }),
199 | }));
200 | } else if is_function
201 | || matches!(expr, Expr::Fn(_) | Expr::Arrow(_))
202 | {
203 | running_objects.push(Prop::KeyValue(KeyValueProp {
204 | key: PropName::Ident(quote_ident!("ref")),
205 | value: Box::new(expr),
206 | }))
207 | } else if matches!(expr, Expr::Call(_)) {
208 | let ref_identifier = self.generate_uid_identifier("_ref$");
209 | running_objects.push(Prop::Method(MethodProp {
210 | key: PropName::Ident(quote_ident!("ref")),
211 | function: Box::new(Function {
212 | params: vec![Param {
213 | span: DUMMY_SP,
214 | decorators: vec![],
215 | pat: Pat::Ident(quote_ident!("r$").into()),
216 | }],
217 | decorators: vec![],
218 | span: DUMMY_SP,
219 | body: Some(BlockStmt {
220 | span: DUMMY_SP,
221 | stmts: vec![
222 | Stmt::Decl(Decl::Var(Box::new(VarDecl {
223 | span: DUMMY_SP,
224 | kind: VarDeclKind::Const,
225 | declare: false,
226 | decls: vec![VarDeclarator {
227 | definite: false,
228 | span: DUMMY_SP,
229 | name: Pat::Ident(
230 | ref_identifier.clone().into(),
231 | ),
232 | init: Some(Box::new(expr)),
233 | }],
234 | ..Default::default()
235 | }))),
236 | Stmt::Expr(ExprStmt {
237 | span: DUMMY_SP,
238 | expr: Box::new(Expr::Bin(BinExpr {
239 | span: DUMMY_SP,
240 | op: BinaryOp::LogicalAnd,
241 | left: Box::new(Expr::Bin(BinExpr {
242 | span: DUMMY_SP,
243 | op: BinaryOp::EqEqEq,
244 | left: Box::new(Expr::Unary(
245 | UnaryExpr {
246 | span: DUMMY_SP,
247 | op: UnaryOp::TypeOf,
248 | arg: Box::new(Expr::Ident(
249 | ref_identifier.clone(),
250 | )),
251 | },
252 | )),
253 | right: Box::new(Expr::Lit(
254 | Lit::Str("function".into()),
255 | )),
256 | })),
257 | right: Box::new(Expr::Call(CallExpr {
258 | span: DUMMY_SP,
259 | callee: Callee::Expr(Box::new(
260 | Expr::Ident(
261 | ref_identifier.clone(),
262 | ),
263 | )),
264 | args: vec![ExprOrSpread {
265 | spread: None,
266 | expr: Box::new(Expr::Ident(
267 | quote_ident!("r$").into(),
268 | )),
269 | }],
270 | ..Default::default()
271 | })),
272 | })),
273 | }),
274 | ],
275 | ..Default::default()
276 | }),
277 | ..Default::default()
278 | }),
279 | }));
280 | }
281 | } else if self.is_dynamic(&expr, Some(span), true, true, true, false) {
282 | let mut exp;
283 | if self.config.wrap_conditionals
284 | && (matches!(*expr, Expr::Bin(_))
285 | || matches!(*expr, Expr::Cond(_)))
286 | {
287 | (_, exp) = self.transform_condition(*expr.clone(), true, false);
288 | if let Expr::Arrow(ArrowExpr { body, .. }) = exp {
289 | match *body {
290 | BlockStmtOrExpr::Expr(ex) => exp = *ex,
291 | BlockStmtOrExpr::BlockStmt(_) => panic!(),
292 | }
293 | } else {
294 | panic!()
295 | }
296 | } else {
297 | exp = *expr;
298 | }
299 |
300 | running_objects.push(
301 | GetterProp {
302 | span: DUMMY_SP,
303 | key: id,
304 | type_ann: None,
305 | body: Some(BlockStmt {
306 | span: DUMMY_SP,
307 | stmts: vec![Stmt::Return(ReturnStmt {
308 | span: DUMMY_SP,
309 | arg: Some(Box::new(exp)),
310 | })],
311 | ..Default::default()
312 | }),
313 | }
314 | .into(),
315 | );
316 | } else {
317 | running_objects.push(Prop::KeyValue(KeyValueProp {
318 | key: id,
319 | value: expr,
320 | }));
321 | }
322 | }
323 | Some(JSXAttrValue::Lit(lit)) => {
324 | let lit = match lit {
325 | Lit::Str(s) => {
326 | Lit::Str(html_escape::decode_html_entities(&s.value).into())
327 | }
328 | _ => lit,
329 | };
330 | running_objects.push(Prop::KeyValue(KeyValueProp {
331 | key: id,
332 | value: lit.into(),
333 | }));
334 | }
335 | Some(JSXAttrValue::JSXElement(el)) => {
336 | running_objects.push(Prop::KeyValue(KeyValueProp {
337 | key: id,
338 | value: Box::new(Expr::JSXElement(el)),
339 | }));
340 | }
341 | Some(JSXAttrValue::JSXFragment(frag)) => {
342 | running_objects.push(Prop::KeyValue(KeyValueProp {
343 | key: id,
344 | value: Box::new(Expr::JSXFragment(frag)),
345 | }));
346 | }
347 | None
348 | | Some(JSXAttrValue::JSXExprContainer(JSXExprContainer {
349 | expr: JSXExpr::JSXEmptyExpr(_),
350 | ..
351 | })) => running_objects.push(Prop::KeyValue(KeyValueProp {
352 | key: id,
353 | value: Lit::Bool(Bool {
354 | span: DUMMY_SP,
355 | value: true,
356 | })
357 | .into(),
358 | })),
359 | };
360 | }
361 | }
362 | }
363 |
364 | let child_result = self.transform_component_children(&node.children);
365 |
366 | match child_result {
367 | Some((expr, true)) => {
368 | running_objects.push(
369 | GetterProp {
370 | span: DUMMY_SP,
371 | key: quote_ident!("children").into(),
372 | body: {
373 | let body = match &expr {
374 | Expr::Call(CallExpr { args, .. }) => {
375 | if let Some(ExprOrSpread { expr, .. }) = args.first() {
376 | if let Expr::Fn(fun) = &**expr {
377 | fun.function.body.clone()
378 | } else if let Expr::Arrow(arrow) = &**expr {
379 | match *arrow.body.clone() {
380 | BlockStmtOrExpr::BlockStmt(b) => Some(b),
381 | BlockStmtOrExpr::Expr(ex) => Some(BlockStmt {
382 | span: DUMMY_SP,
383 | stmts: vec![Stmt::Return(ReturnStmt {
384 | span: DUMMY_SP,
385 | arg: Some(ex),
386 | })],
387 | ..Default::default()
388 | }),
389 | }
390 | } else {
391 | None
392 | }
393 | } else {
394 | None
395 | }
396 | }
397 | Expr::Fn(fun) => fun.function.body.clone(),
398 | Expr::Arrow(arrow) => Some(match *arrow.body.clone() {
399 | BlockStmtOrExpr::BlockStmt(block) => block,
400 | BlockStmtOrExpr::Expr(expr) => BlockStmt {
401 | span: DUMMY_SP,
402 | stmts: vec![Stmt::Return(ReturnStmt {
403 | span: DUMMY_SP,
404 | arg: Some(expr),
405 | })],
406 | ..Default::default()
407 | },
408 | }),
409 | _ => None,
410 | };
411 |
412 | Some(body.unwrap_or(BlockStmt {
413 | span: DUMMY_SP,
414 | stmts: vec![Stmt::Return(ReturnStmt {
415 | span: DUMMY_SP,
416 | arg: Some(Box::new(expr)),
417 | })],
418 | ..Default::default()
419 | }))
420 | },
421 | type_ann: None,
422 | }
423 | .into(),
424 | );
425 | }
426 | Some((expr, false)) => {
427 | running_objects.push(
428 | KeyValueProp {
429 | key: quote_ident!("children").into(),
430 | value: Box::new(expr),
431 | }
432 | .into(),
433 | );
434 | }
435 | None => (),
436 | }
437 |
438 | if !running_objects.is_empty() || props.is_empty() {
439 | props.push(
440 | ObjectLit {
441 | span: DUMMY_SP,
442 | props: running_objects.into_iter().map(|p| p.into()).collect(),
443 | }
444 | .into(),
445 | )
446 | }
447 |
448 | if props.len() > 1 || dynamic_spread {
449 | props = vec![Expr::Call(CallExpr {
450 | span: DUMMY_SP,
451 | callee: Callee::Expr(self.register_import_method("mergeProps").into()),
452 | args: props.into_iter().map(|p| p.into()).collect(),
453 | ..Default::default()
454 | })];
455 | }
456 |
457 | let component_args = vec![tag_id, props.remove(0)];
458 |
459 | exprs.push(
460 | CallExpr {
461 | span: DUMMY_SP,
462 | callee: Callee::Expr(self.register_import_method("createComponent").into()),
463 | args: component_args
464 | .into_iter()
465 | .map(|v| ExprOrSpread {
466 | spread: None,
467 | expr: Box::new(v),
468 | })
469 | .collect(),
470 | ..Default::default()
471 | }
472 | .into(),
473 | );
474 |
475 | TemplateInstantiation {
476 | exprs: if exprs.len() > 1 {
477 | let ret = exprs.pop();
478 | let mut stmts: Vec = exprs
479 | .into_iter()
480 | .map(|expr| {
481 | Stmt::Expr(ExprStmt {
482 | span: DUMMY_SP,
483 | expr: Box::new(expr),
484 | })
485 | })
486 | .collect();
487 | stmts.push(Stmt::Return(ReturnStmt {
488 | span: DUMMY_SP,
489 | arg: ret.map(Box::new),
490 | }));
491 |
492 | vec![
493 | CallExpr {
494 | span: DUMMY_SP,
495 | callee: Callee::Expr(
496 | ArrowExpr {
497 | span: DUMMY_SP,
498 | params: vec![],
499 | body: Box::new(
500 | BlockStmt {
501 | span: DUMMY_SP,
502 | stmts,
503 | ..Default::default()
504 | }
505 | .into(),
506 | ),
507 | ..Default::default()
508 | }
509 | .into(),
510 | ),
511 | ..Default::default()
512 | }
513 | .into(),
514 | ]
515 | } else {
516 | exprs
517 | },
518 | component: true,
519 | ..Default::default()
520 | }
521 | }
522 |
523 | fn transform_component_children(
524 | &mut self,
525 | children: &[JSXElementChild],
526 | ) -> Option<(Expr, bool)> {
527 | let filtered_children = children
528 | .iter()
529 | .filter(|child| filter_children(child))
530 | .collect::>();
531 | if filtered_children.is_empty() {
532 | return None;
533 | }
534 |
535 | let mut dynamic = false;
536 | let mut path_nodes = vec![];
537 | let is_filtered_children_plural = filtered_children.len() > 1;
538 |
539 | let transformed_children: Vec =
540 | filtered_children.iter().fold(vec![], |mut memo, node| {
541 | match node {
542 | JSXElementChild::JSXText(child) => {
543 | let value = jsx_text_to_str(&child.value);
544 | if !value.is_empty() {
545 | path_nodes.push(node);
546 | memo.push(Lit::Str(value.into()).into());
547 | }
548 | }
549 | node => {
550 | let child = self.transform_node(
551 | node,
552 | &TransformInfo {
553 | top_level: true,
554 | component_child: true,
555 | last_element: true,
556 | ..Default::default()
557 | },
558 | );
559 | if let Some(mut child) = child {
560 | dynamic = dynamic || child.dynamic;
561 |
562 | if self.config.generate == "ssr"
563 | && is_filtered_children_plural
564 | && child.dynamic
565 | && let Some(Expr::Arrow(ArrowExpr { body, .. })) =
566 | child.exprs.first()
567 | && let BlockStmtOrExpr::Expr(expr) = body.as_ref()
568 | {
569 | child.exprs.insert(0, *expr.clone());
570 | }
571 |
572 | path_nodes.push(node);
573 | memo.push(
574 | self.create_template(&mut child, is_filtered_children_plural),
575 | );
576 | }
577 | }
578 | };
579 | memo
580 | });
581 |
582 | if transformed_children.len() == 1 {
583 | let first_children = transformed_children.into_iter().next().unwrap();
584 |
585 | if !path_nodes.is_empty()
586 | && !matches!(
587 | path_nodes[0],
588 | JSXElementChild::JSXExprContainer(_)
589 | | JSXElementChild::JSXSpreadChild(_)
590 | | JSXElementChild::JSXText(_)
591 | )
592 | {
593 | let expr = match &first_children {
594 | Expr::Call(CallExpr {
595 | callee: Callee::Expr(callee_expr),
596 | args,
597 | ..
598 | }) if args.is_empty() => match *callee_expr.clone() {
599 | Expr::Ident(_) => None,
600 | expr => Some(expr),
601 | },
602 | _ => None,
603 | }
604 | .unwrap_or(
605 | ArrowExpr {
606 | span: DUMMY_SP,
607 | params: vec![],
608 | body: Box::new(BlockStmtOrExpr::Expr(Box::new(first_children))),
609 | ..Default::default()
610 | }
611 | .into(),
612 | );
613 |
614 | Some((expr, true))
615 | } else {
616 | Some((first_children, dynamic))
617 | }
618 | } else {
619 | Some((
620 | ArrowExpr {
621 | span: DUMMY_SP,
622 | params: vec![],
623 | body: Box::new(BlockStmtOrExpr::Expr(
624 | ArrayLit {
625 | span: DUMMY_SP,
626 | elems: transformed_children
627 | .into_iter()
628 | .map(|expr| Some(expr.into()))
629 | .collect(),
630 | }
631 | .into(),
632 | )),
633 | ..Default::default()
634 | }
635 | .into(),
636 | true,
637 | ))
638 | }
639 | }
640 | }
641 |
642 | fn get_component_identifier(node: &JSXElementName) -> Expr {
643 | match node {
644 | JSXElementName::Ident(ident) => match Ident::verify_symbol(&ident.sym) {
645 | Ok(_) => Expr::Ident(ident.clone()),
646 | Err(_) => Expr::Lit(Lit::Str(ident.sym.to_string().into())),
647 | },
648 | JSXElementName::JSXMemberExpr(member) => {
649 | let prop = get_component_identifier(&JSXElementName::Ident(member.prop.clone().into()));
650 | Expr::Member(MemberExpr {
651 | span: DUMMY_SP,
652 | obj: Box::new(get_component_identifier(&match &member.obj {
653 | JSXObject::Ident(id) => JSXElementName::Ident(id.clone()),
654 | JSXObject::JSXMemberExpr(member) => {
655 | JSXElementName::JSXMemberExpr(*member.clone())
656 | }
657 | })),
658 | prop: match prop {
659 | Expr::Ident(id) => MemberProp::Ident(id.into()),
660 | _ => MemberProp::Computed(ComputedPropName {
661 | span: DUMMY_SP,
662 | expr: Box::new(prop),
663 | }),
664 | },
665 | })
666 | }
667 | JSXElementName::JSXNamespacedName(_) => panic!("Can't handle this"),
668 | }
669 | }
670 |
--------------------------------------------------------------------------------