Skip to content

Commit

Permalink
feat: support style injection for styled-components
Browse files Browse the repository at this point in the history
  • Loading branch information
arlyon committed Dec 23, 2022
1 parent 4ea9bdd commit 79ba0ff
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 44 deletions.
10 changes: 8 additions & 2 deletions examples/nextjs-styled-components/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { TailwindStyle } from "stailwc";
import { createGlobalStyle } from "styled-components";
// import { TailwindStyle } from "stailwc";

// global styles are currently broken in styled-components
// see: https://github.com/swc-project/swc/issues/6233#issuecomment-1363806515
const Global = createGlobalStyle`a,hr{color:inherit;}progress,sub,sup{vertical-align:baseline;}blockquote,body,dd,dl,fieldset,figure,h1,h2,h3,h4,h5,h6,hr,menu,ol,p,pre,ul{margin:0;}fieldset,legend,menu,ol,ul{padding:0;}*,::after,::before{box-sizing:border-box;border:0 solid #e5e7eb;--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scroll-snap-strictness:proximity;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgb(59 130 246 / 0.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;}::after,::before{--tw-content:"";}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";}body{line-height:inherit;}hr{height:0;border-top-width:1px;}abbr:where([title]){-webkit-text-decoration:underline dotted;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit;}a{-webkit-text-decoration:inherit;text-decoration:inherit;}b,strong{font-weight:bolder;}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em;}small{font-size:80%;}sub,sup{font-size:75%;line-height:0;position:relative;}sub{bottom:-.25em;}sup{top:-.5em;}table{text-indent:0;border-color:inherit;border-collapse:collapse;}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0;}button,select{text-transform:none;}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:transparent;background-image:none;}:-moz-focusring{outline:auto;}:-moz-ui-invalid{box-shadow:none;}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto;}[type=search]{-webkit-appearance:textfield;outline-offset:-2px;}::-webkit-search-decoration{-webkit-appearance:none;}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit;}summary{display:-webkit-box;display:-webkit-list-item;display:-ms-list-itembox;display:list-item;}menu,ol,ul{list-style:none;}textarea{resize:vertical;}input::-webkit-input-placeholder{opacity:1;color:#9ca3af;}input::-moz-placeholder{opacity:1;color:#9ca3af;}input:-ms-input-placeholder{opacity:1;color:#9ca3af;}textarea::-webkit-input-placeholder{opacity:1;color:#9ca3af;}textarea::-moz-placeholder{opacity:1;color:#9ca3af;}textarea:-ms-input-placeholder{opacity:1;color:#9ca3af;}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af;}[role=button],button{cursor:pointer;}:disabled{cursor:default;}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle;}img,video{max-width:100%;height:auto;}[hidden]{display:none;}::-webkit-backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scroll-snap-strictness:proximity;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgb(59 130 246 / 0.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;}::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scroll-snap-strictness:proximity;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgb(59 130 246 / 0.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;}[type=checkbox]:checked,[type=checkbox]:checked:focus,[type=checkbox]:checked:hover,[type=radio]:checked,[type=radio]:checked:focus,[type=radio]:checked:hover{border-color:transparent;background-color:currentColor;}[type=checkbox],[type=file]{border-radius:0;}[multiple],[type=date],[type=datetime-local],[type=email],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week],select,textarea{-webkit-appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow:0 0 #0000;}[multiple]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=email]:focus,[type=month]:focus,[type=number]:focus,[type=password]:focus,[type=search]:focus,[type=tel]:focus,[type=text]:focus,[type=time]:focus,[type=url]:focus,[type=week]:focus,select:focus,textarea:focus{outline:transparent solid 2px;outline-offset:2px;--tw-ring-inset:var(--tw-empty, /*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#2563eb;}input::-webkit-input-placeholder{color:#6b7280;opacity:1;}input::-moz-placeholder{color:#6b7280;opacity:1;}input:-ms-input-placeholder{color:#6b7280;opacity:1;}textarea::-webkit-input-placeholder{color:#6b7280;opacity:1;}textarea::-moz-placeholder{color:#6b7280;opacity:1;}textarea:-ms-input-placeholder{color:#6b7280;opacity:1;}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1;}::-webkit-datetime-edit-fields-wrapper{padding:0;}::-webkit-date-and-time-value{min-height:1.5em;}::-webkit-datetime-edit,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-meridiem-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-year-field{padding-top:0;padding-bottom:0;}select{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");-webkit-background-position:right .5rem center;background-position:right .5rem center;background-repeat:no-repeat;-webkit-background-size:1.5em 1.5em;background-size:1.5em 1.5em;padding-right:2.5rem;print-color-adjust:exact;}[multiple]{background-image:initial;-webkit-background-position:initial;background-position:initial;background-repeat:unset;-webkit-background-size:initial;background-size:initial;padding-right:.75rem;print-color-adjust:unset;}[type=checkbox],[type=radio]{-webkit-appearance:none;-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;appearance:none;padding:0;print-color-adjust:exact;display:inline-block;vertical-align:middle;background-origin:border-box;-webkit-user-select:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;height:1rem;width:1rem;color:#2563eb;background-color:#fff;border-color:#6b7280;border-width:1px;--tw-shadow:0 0 #0000;}[type=radio]{border-radius:100%;}[type=checkbox]:focus,[type=radio]:focus{outline:transparent solid 2px;outline-offset:2px;--tw-ring-inset:var(--tw-empty, /*!*/ /*!*/);--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);}[type=checkbox]:checked,[type=radio]:checked{-webkit-background-size:100% 100%;background-size:100% 100%;-webkit-background-position:center;background-position:center;background-repeat:no-repeat;}[type=checkbox]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");}[type=radio]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e");}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e");border-color:transparent;background-color:currentColor;-webkit-background-size:100% 100%;background-size:100% 100%;-webkit-background-position:center;background-position:center;background-repeat:no-repeat;}[type=checkbox]:indeterminate:focus,[type=checkbox]:indeterminate:hover{border-color:transparent;background-color:currentColor;}[type=file]{background:unset;border-color:inherit;border-width:0;padding:0;font-size:unset;line-height:inherit;}[type=file]:focus{outline:ButtonText solid 1px;outline:-webkit-focus-ring-color auto 1px;}`;

export default function App({ Component, pageProps }) {
return (
<>
<TailwindStyle />
{/* <TailwindStyle /> */}
<Global />
<Component {...pageProps} />
</>
);
Expand Down
2 changes: 1 addition & 1 deletion src/engine/styled_components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use swc_core::{
use crate::TransformVisitor;

impl<'a> TransformVisitor<'a> {
pub fn styled_components_global(&mut self, span: Span, n: &mut JSXOpeningElement, atom: Atom) {
pub fn styled_components_global(&mut self, span: Span, n: &mut JSXOpeningElement) {
n.name = JSXElementName::Ident(Ident::new("Global".into(), span));
}
}
122 changes: 92 additions & 30 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ use swc_core::{
},
ecma::{
ast::{
ArrayLit, CallExpr, Callee, Expr, ExprOrSpread, Ident, ImportDecl,
ArrayLit, BindingIdent, CallExpr, Callee, Decl, Expr, ExprOrSpread, Ident, ImportDecl,
ImportNamedSpecifier, ImportSpecifier, JSXAttr, JSXAttrName, JSXAttrOrSpread,
JSXAttrValue, JSXElementName, JSXExpr, JSXExprContainer, JSXOpeningElement, Lit,
MemberExpr, MemberProp, Module, ModuleDecl, ModuleItem, ObjectLit, Program, Str,
TaggedTpl, Tpl, TplElement,
MemberExpr, MemberProp, Module, ModuleDecl, ModuleItem, ObjectLit, Pat, Program, Stmt,
Str, TaggedTpl, Tpl, TplElement, VarDecl, VarDeclKind, VarDeclarator,
},
atoms::Atom,
visit::{as_folder, FoldWith, VisitMut, VisitMutWith},
Expand All @@ -46,7 +46,7 @@ pub struct AppConfig<'a> {
pub engine: Engine,
}

#[derive(Deserialize, Debug)]
#[derive(Deserialize, Debug, Eq, PartialEq, Clone, Copy)]
#[serde(rename_all = "kebab-case")]
pub enum Engine {
Emotion,
Expand Down Expand Up @@ -244,15 +244,17 @@ impl<'a> VisitMut for TransformVisitor<'a> {
/// b) extract any tw attributes and transform them into a css declarations
fn visit_mut_jsx_opening_element(&mut self, n: &mut JSXOpeningElement) {
if self.tw_style_imported && let JSXElementName::Ident(i) = &n.name && i.sym.eq("TailwindStyle") {
let atom: Atom = css::format_css(
true,
self.config.theme.font_family.get("sans").map(|v| v.as_slice()).unwrap_or(&[]),
self.config.theme.font_family.get("mono").map(|v| v.as_slice()).unwrap_or(&[])
).into();


match self.engine {
Engine::Emotion => self.emotion_global(i.span, n, atom),
Engine::StyledComponents => self.styled_components_global(i.span, n, atom),
Engine::Emotion => {
let atom: Atom = css::format_css(
true,
self.config.theme.font_family.get("sans").map(|v| v.as_slice()).unwrap_or(&[]),
self.config.theme.font_family.get("mono").map(|v| v.as_slice()).unwrap_or(&[])
).into();
self.emotion_global(i.span, n, atom)
},
Engine::StyledComponents => self.styled_components_global(i.span, n),
}
}

Expand Down Expand Up @@ -468,7 +470,8 @@ impl<'a> VisitMut for TransformVisitor<'a> {

/// Visit the import declarations, and mark whether tw_style is imported.
fn visit_mut_module(&mut self, n: &mut Module) {
n.body.drain_filter(|stmt| {

for stmt in &mut n.body {
if let ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
src, specifiers, ..
})) = stmt
Expand All @@ -480,26 +483,40 @@ impl<'a> VisitMut for TransformVisitor<'a> {
_ => false,
});

if src.value.eq("stailwc") && has_style_import {
self.tw_style_imported = true;
return true;
if !src.value.eq("stailwc") || !has_style_import {
continue;
}
}

false
});
self.tw_style_imported = true;

if self.tw_style_imported {
match self.engine {
Engine::Emotion => {
// no-op
}
Engine::StyledComponents => {
// import createGlobalStyle and create the Global object
n.body
.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
match self.engine {
Engine::Emotion => {
*stmt = ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
src: Box::new(Str {
raw: Some("styled-components".into()),
raw: None,
value: "@emotion/react".into(),
span: DUMMY_SP,
}),
span: DUMMY_SP,
specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
span: DUMMY_SP,
local: Ident::new("css".into(), DUMMY_SP),
is_type_only: false,
imported: None,
}), ImportSpecifier::Named(ImportNamedSpecifier {
span: DUMMY_SP,
local: Ident::new("Global".into(), DUMMY_SP),
is_type_only: false,
imported: None,
})],
asserts: None,
type_only: false,
}));
},
Engine::StyledComponents => { // import createGlobalStyle and create the Global object
*stmt = ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
src: Box::new(Str {
raw: None,
value: "styled-components".into(),
span: DUMMY_SP,
}),
Expand All @@ -512,11 +529,56 @@ impl<'a> VisitMut for TransformVisitor<'a> {
})],
asserts: None,
type_only: false,
})))
}));
},
}
}
}

if self.engine == Engine::StyledComponents && self.tw_style_imported {
let atom = css::format_css(
true,
self.config.theme.font_family.get("sans").map(|v| v.as_slice()).unwrap_or(&[]),
self.config.theme.font_family.get("mono").map(|v| v.as_slice()).unwrap_or(&[])
).into();

n.body
.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl {
span: DUMMY_SP,
kind: VarDeclKind::Const,
declare: false,
decls: vec![VarDeclarator {
span: DUMMY_SP,
definite: true,
name: Pat::Ident(BindingIdent {
id: Ident::new("Global".into(), DUMMY_SP),
type_ann: None,
}),
init: Some(Box::new(Expr::Call(CallExpr {
span: DUMMY_SP,
callee: Callee::Expr(Box::new(Expr::Ident(Ident::new(
"createGlobalStyle".into(),
DUMMY_SP,
)))),
args: vec![ExprOrSpread {
spread: None,
expr: Box::new(Expr::Tpl(Tpl {
span: DUMMY_SP,
exprs: vec![],
quasis: vec![TplElement {
span: DUMMY_SP,
tail: true,
cooked: None,
raw: atom,
}],
})),
}],
type_args: None,
}))),
}],
})))));
};

n.visit_mut_children_with(self);
}
}
Expand Down

0 comments on commit 79ba0ff

Please sign in to comment.