Skip to content

Commit

Permalink
feat: add support for styled components
Browse files Browse the repository at this point in the history
  • Loading branch information
arlyon committed Dec 1, 2022
1 parent d1469d6 commit 744d420
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 40 deletions.
18 changes: 18 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ Now get hacking!

## Usage

### The `tw` tag

You can interact with stailwc in two ways. The first is through
the `tw` JSW attribute, and the second is via the `tw` template
tag.
Expand All @@ -91,3 +93,19 @@ export const ColorButton = () => {
);
};
```

### Styled components

You can also create styled components using the `tw` template tag.

```tsx
import _styled from "@emotion/styled";

// currently needed due to a swc bug
// ===
_styled;
// ===

export const StyledButton = tw.button`p-1 m-4 text-green bg-white hover:(bg-gray text-yellow md:text-red) border-3`;
export const ShadowButton = tw(StyledButton)`shadow-lg`;
```
162 changes: 122 additions & 40 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ use swc_core::{
},
ecma::{
ast::{
ArrayLit, Expr, ExprOrSpread, Ident, ImportDecl, JSXAttr, JSXAttrName, JSXAttrOrSpread,
JSXAttrValue, JSXElementName, JSXExpr, JSXExprContainer, JSXOpeningElement, Lit,
Module, ModuleDecl, ModuleItem, ObjectLit, Program, Str, TaggedTpl, Tpl, TplElement,
ArrayLit, CallExpr, Callee, Expr, ExprOrSpread, Ident, ImportDecl, JSXAttr,
JSXAttrName, JSXAttrOrSpread, JSXAttrValue, JSXElementName, JSXExpr, JSXExprContainer,
JSXOpeningElement, Lit, MemberExpr, MemberProp, Module, ModuleDecl, ModuleItem,
ObjectLit, Program, Str, TaggedTpl, Tpl, TplElement,
},
atoms::Atom,
visit::{as_folder, FoldWith, VisitMut, VisitMutWith},
Expand All @@ -40,12 +41,21 @@ pub struct AppConfig<'a> {
pub strict: bool,
}

enum TplTransform {
/// tw`...`
Style(ObjectLit),
/// tw.div`...`
Component(Ident, ObjectLit),
/// tw(Component)`...`
ComponentCustom(Vec<ExprOrSpread>, ObjectLit),
}

#[derive(Default)]
pub struct TransformVisitor<'a> {
config: TailwindConfig<'a>,
strict: bool,
tw_attr: Option<(Span, ObjectLit)>,
tw_tpl: Option<ObjectLit>,
tw_tpl: Option<TplTransform>,
tw_style_imported: bool,
}

Expand Down Expand Up @@ -268,49 +278,79 @@ impl<'a> VisitMut for TransformVisitor<'a> {
* convert it to an emotion object.
*/
fn visit_mut_tagged_tpl(&mut self, n: &mut TaggedTpl) {
let _sym = match &n.tag {
box Expr::Ident(Ident { sym, .. }) if sym == "tw" => "tw",
_ => {
n.visit_mut_children_with(self);
return;
}
};
let extract_literal = || {
let (text, span) = match n.tpl.quasis.as_slice() {
[TplElement { raw, span, .. }] => (raw, span),
_ => {
HANDLER.with(|h| {
h.span_err(n.span, "variables inside template tags are not supported")
});
return None;
}
};

let (text, span) = match n.tpl.quasis.as_slice() {
[TplElement { raw, span, .. }] => (raw, span),
_ => {
HANDLER.with(|h| {
h.span_bug_no_panic(n.span, "unknown tw template, please file an issue")
});
return;
}
};
let d = match Directive::parse(LocatedSpan::new_extra(text, *span)) {
Ok((_, d)) => d,
Err(e) => {
HANDLER.with(|h| {
self.report(h, *span, "invalid syntax")
.note(&e.to_string())
.emit()
});
return None;
}
};

let d = match Directive::parse(LocatedSpan::new_extra(text, *span)) {
Ok((_, d)) => d,
Err(e) => {
HANDLER.with(|h| {
self.report(h, *span, "invalid syntax")
.note(&e.to_string())
.emit()
});
return;
match d.to_literal(*span, &self.config) {
Ok(lit) => Some(lit),
Err(e) => {
HANDLER.with(|h| {
self.report(h, *span, &e.to_string())
.note("when evaluating plugin")
.emit()
});
return None;
}
}
};

let lit = match d.to_literal(*span, &self.config) {
Ok(lit) => lit,
Err(e) => {
HANDLER.with(|h| {
self.report(h, *span, &e.to_string())
.note("when evaluating plugin")
.emit()
});
let transform = match &n.tag {
box Expr::Ident(Ident { sym, .. }) if sym == "tw" => {
if let Some(lit) = extract_literal() {
TplTransform::Style(lit)
} else {
return;
}
}
box Expr::Member(MemberExpr {
obj: box Expr::Ident(Ident { sym, .. }),
prop: MemberProp::Ident(ident),
..
}) if sym == "tw" => {
if let Some(lit) = extract_literal() {
TplTransform::Component(ident.to_owned(), lit)
} else {
return;
}
}
box Expr::Call(CallExpr {
callee: Callee::Expr(box Expr::Ident(Ident { sym, .. })),
args,
..
}) if sym == "tw" => {
if let Some(lit) = extract_literal() {
TplTransform::ComponentCustom(args.to_owned(), lit)
} else {
return;
}
}
_ => {
n.visit_mut_children_with(self);
return;
}
};

if self.tw_tpl.replace(lit).is_some() {
if self.tw_tpl.replace(transform).is_some() {
HANDLER.with(|h| {
h.span_bug_no_panic(
n.span,
Expand All @@ -326,8 +366,50 @@ impl<'a> VisitMut for TransformVisitor<'a> {
*/
fn visit_mut_expr(&mut self, n: &mut Expr) {
n.visit_mut_children_with(self);
if let Some(objlit) = self.tw_tpl.take() {
*n = Expr::Object(objlit);
match self.tw_tpl.take() {
Some(TplTransform::Style(lit)) => {
*n = Expr::Object(lit);
}
Some(TplTransform::Component(ident, lit)) => {
*n = Expr::Call(CallExpr {
span: DUMMY_SP,
callee: Callee::Expr(Box::new(Expr::Member(MemberExpr {
span: DUMMY_SP,
obj: Box::new(Expr::Ident(Ident {
span: DUMMY_SP,
sym: "_styled".into(),
optional: false,
})),
prop: MemberProp::Ident(ident),
}))),
args: vec![ExprOrSpread {
expr: Box::new(Expr::Object(lit)),
spread: None,
}],
type_args: None,
});
}
Some(TplTransform::ComponentCustom(args, lit)) => {
*n = Expr::Call(CallExpr {
span: DUMMY_SP,
callee: Callee::Expr(Box::new(Expr::Call(CallExpr {
args,
callee: Callee::Expr(Box::new(Expr::Ident(Ident {
span: DUMMY_SP,
sym: "_styled".into(),
optional: false,
}))),
span: DUMMY_SP,
type_args: None,
}))),
type_args: None,
args: vec![ExprOrSpread {
expr: Box::new(Expr::Object(lit)),
spread: None,
}],
})
}
None => {}
}
}

Expand Down

0 comments on commit 744d420

Please sign in to comment.