Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

transform styled dom component usage for improved performance #154

Open
wants to merge 76 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
bbdcdc8
add styled dom runtime helpers
jantimon Aug 15, 2024
60058eb
[autofix.ci] apply automated fixes
autofix-ci[bot] Aug 15, 2024
54c5f4f
Merge remote-tracking branch 'origin/main' into feature/styled-dom
jantimon Nov 29, 2024
2f684a6
Add new test which contains styled.button as well as styled(Component…
Tyderion Dec 2, 2024
74ce3bd
Replace styled.button with __yak_button while making sure the styled …
Tyderion Dec 2, 2024
99ce92d
Update test results (styled.button => __yak_button)
Tyderion Dec 2, 2024
dc2e14b
remove dbg! statements
Tyderion Dec 2, 2024
89e4ca2
Some cleanup
Tyderion Dec 2, 2024
a95e34c
Add test which contains multiple components of the same type
Tyderion Dec 2, 2024
f13fff8
Fix duplicate imports by using set, also order imports alphabetically
Tyderion Dec 2, 2024
f517585
Update tests with fixes
Tyderion Dec 2, 2024
f18289d
Support all members
Tyderion Dec 2, 2024
5944fd1
Update all snapshots
Tyderion Dec 2, 2024
7437773
cargo fmt
jantimon Dec 2, 2024
db90fc1
clippy
jantimon Dec 2, 2024
aa80605
update fixtures
jantimon Dec 2, 2024
ab2b3f9
remove proxy
jantimon Dec 2, 2024
704bfbc
[autofix.ci] apply automated fixes
autofix-ci[bot] Dec 2, 2024
fedef27
[autofix.ci] apply automated fixes (attempt 2/3)
autofix-ci[bot] Dec 2, 2024
1794d3c
[autofix.ci] apply automated fixes (attempt 3/3)
autofix-ci[bot] Dec 2, 2024
104517f
use styled directly
jantimon Dec 2, 2024
f5b49ab
[autofix.ci] apply automated fixes
autofix-ci[bot] Dec 2, 2024
d5f216a
changeset
jantimon Dec 2, 2024
735c0bd
Merge branch 'main' into feature/styled-dom
jantimon Dec 4, 2024
409ccc2
simplify matches
Tyderion Dec 2, 2024
0258214
Cleanup
Tyderion Dec 2, 2024
d446d4d
add test to handle unknown elements
Tyderion Dec 2, 2024
becf291
transform only known elements to explicit yak import
Tyderion Dec 2, 2024
839297b
call styled with string literal of the unknown elements
Tyderion Dec 2, 2024
e38df44
Update test with attrs to correctly use direct import
Tyderion Dec 2, 2024
a70e4f2
add some comments
Tyderion Dec 2, 2024
a6f9164
wip
Tyderion Dec 2, 2024
a2d42f4
Formatting
Tyderion Dec 5, 2024
4037cb1
Add new test
Tyderion Dec 5, 2024
a2a9d3b
Fix simple attr case
Tyderion Dec 5, 2024
92ec409
Update test
Tyderion Dec 5, 2024
932b7de
WOè
Tyderion Dec 5, 2024
a99c617
Improvements
Tyderion Dec 5, 2024
5a3f9f2
don't manually eliminate dead code
Tyderion Dec 5, 2024
4ef40d2
Don't sort imports
Tyderion Dec 5, 2024
973e79d
Merge branch 'main' into feature/styled-dom
Tyderion Dec 6, 2024
f32a716
Store yak component imports in YakImports
Tyderion Dec 6, 2024
980ac43
Update tests (imports reordered)
Tyderion Dec 6, 2024
bb33f34
Simplify check
Tyderion Dec 6, 2024
3eb4cb8
Simplify more
Tyderion Dec 6, 2024
65bc66a
Cleanup
Tyderion Dec 6, 2024
ba19759
Fixes
Tyderion Dec 6, 2024
164c662
Cleanup
Tyderion Dec 6, 2024
5a94970
Merge branch 'main' into feature/styled-dom
Tyderion Dec 6, 2024
52ee804
Add new tests
Tyderion Dec 6, 2024
736a510
Simplify function call transformation
Tyderion Dec 6, 2024
b08c789
Cleanup
Tyderion Dec 6, 2024
c5900a0
Move function out of object because no self reference is needed
Tyderion Dec 6, 2024
37663a0
Cleanup
Tyderion Dec 6, 2024
f7682ae
mark as pure
jantimon Dec 6, 2024
86566db
next 15.0.4
jantimon Dec 6, 2024
63cd1e1
Fix tests
Tyderion Dec 6, 2024
8b1b50d
[autofix.ci] apply automated fixes
autofix-ci[bot] Dec 6, 2024
1e75acf
Fix type of exported styled
Tyderion Dec 9, 2024
c2f86b8
[autofix.ci] apply automated fixes
autofix-ci[bot] Dec 9, 2024
22e6176
group yak internal imports
Tyderion Dec 9, 2024
1bdd141
Update tests
Tyderion Dec 9, 2024
e276ebe
Cleanup
Tyderion Dec 9, 2024
d96a1a3
fix snapshots
Tyderion Dec 9, 2024
71b2c3a
Remove unused output.stderr
Tyderion Dec 9, 2024
24831b7
Update snapshots
Tyderion Dec 9, 2024
6905651
Improve docs
Tyderion Dec 9, 2024
666d8fd
Format
Tyderion Dec 9, 2024
60322d5
remove unnecessary loop
Tyderion Dec 9, 2024
37859b7
Cleanup
Tyderion Dec 9, 2024
cf733b8
Merge branch 'main' into feature/styled-dom
jantimon Dec 9, 2024
388336e
Merge branch 'main' into feature/styled-dom
jantimon Dec 10, 2024
315606a
[autofix.ci] apply automated fixes
autofix-ci[bot] Dec 10, 2024
8da0e39
Fix and improve tests
Tyderion Dec 10, 2024
8e88ed1
[autofix.ci] apply automated fixes
autofix-ci[bot] Dec 10, 2024
b613991
Merge branch 'main' into feature/styled-dom
jantimon Dec 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/next-yak/runtime/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ export { useTheme, YakThemeProvider } from "next-yak/context";
// runtime internals (helpers which get injected by the compiler)
export { unitPostFix as __yak_unitPostFix } from "./internals/unitPostFix.js";
export { mergeCssProp as __yak_mergeCssProp } from "./internals/mergeCssProp.js";

// export shorthand for DOM styled components (e.g. for styled.div)
export * from "./styledDom.js";
44 changes: 23 additions & 21 deletions packages/next-yak/runtime/styled.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,13 @@ type Attrs<
> = Partial<TOut> | AttrsFunction<TBaseProps, TIn, TOut>;

//
// The `styled()` and `styled.` API
// The `styled()` API without `styled.` syntax
//
// The API design is inspired by styled-components:
// https://github.com/styled-components/styled-components/blob/main/packages/styled-components/src/constructors/styled.tsx
// https://github.com/styled-components/styled-components/blob/main/packages/styled-components/src/models/StyledComponent.ts
//
const StyledFactory = <T,>(Component: HtmlTags | FunctionComponent<T>) =>
export const StyledFactory = <T,>(Component: HtmlTags | FunctionComponent<T>) =>
Object.assign(yakStyled(Component), {
attrs: <
TAttrsIn extends object = {},
Expand Down Expand Up @@ -257,26 +257,28 @@ type StyledLiteral<T> = <TCSSProps>(
* `;
* ```
*/
export const styled = new Proxy(
StyledFactory as typeof StyledFactory & {
[Tag in HtmlTags]: StyledLiteral<JSX.IntrinsicElements[Tag]> & {
attrs: <
TAttrsIn extends object = {},
TAttrsOut extends AttrsMerged<
JSX.IntrinsicElements[Tag],
TAttrsIn
> = AttrsMerged<JSX.IntrinsicElements[Tag], TAttrsIn>,
>(
attrs: Attrs<JSX.IntrinsicElements[Tag], TAttrsIn, TAttrsOut>,
) => StyledLiteral<Substitute<JSX.IntrinsicElements[Tag], TAttrsIn>>;
};
},
{
get(target, TagName: keyof JSX.IntrinsicElements) {
return target(TagName);
export const styled =
// The proxy adds the styled.div, styled.button, etc. syntax
new Proxy(
StyledFactory as typeof StyledFactory & {
[Tag in HtmlTags]: StyledLiteral<JSX.IntrinsicElements[Tag]> & {
attrs: <
TAttrsIn extends object = {},
TAttrsOut extends AttrsMerged<
JSX.IntrinsicElements[Tag],
TAttrsIn
> = AttrsMerged<JSX.IntrinsicElements[Tag], TAttrsIn>,
>(
attrs: Attrs<JSX.IntrinsicElements[Tag], TAttrsIn, TAttrsOut>,
) => StyledLiteral<Substitute<JSX.IntrinsicElements[Tag], TAttrsIn>>;
};
},
},
);
{
get(target, TagName: keyof JSX.IntrinsicElements) {
return target(TagName);
},
},
);

/**
* Remove all entries that start with a $ sign
Expand Down
141 changes: 141 additions & 0 deletions packages/next-yak/runtime/styledDom.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { StyledFactory } from "./styled.js";
/// Internal API to create styled components
/// Optimization for faster rendering and smaller bundle size in production
/// thanks to better minification and dead code elimination
///
/// List taken from https://github.com/styled-components/styled-components/blob/e0019ba666fab4b5aaa2bff71ba6ad0005a299fd/packages/styled-components/src/utils/domElements.ts#L90
export const __yak_a = /*#__PURE__*/ StyledFactory("a");
export const __yak_abbr = /*#__PURE__*/ StyledFactory("abbr");
export const __yak_address = /*#__PURE__*/ StyledFactory("address");
export const __yak_area = /*#__PURE__*/ StyledFactory("area");
export const __yak_article = /*#__PURE__*/ StyledFactory("article");
export const __yak_aside = /*#__PURE__*/ StyledFactory("aside");
export const __yak_audio = /*#__PURE__*/ StyledFactory("audio");
export const __yak_b = /*#__PURE__*/ StyledFactory("b");
export const __yak_base = /*#__PURE__*/ StyledFactory("base");
export const __yak_bdi = /*#__PURE__*/ StyledFactory("bdi");
export const __yak_bdo = /*#__PURE__*/ StyledFactory("bdo");
export const __yak_big = /*#__PURE__*/ StyledFactory("big");
export const __yak_blockquote = /*#__PURE__*/ StyledFactory("blockquote");
export const __yak_body = /*#__PURE__*/ StyledFactory("body");
export const __yak_br = /*#__PURE__*/ StyledFactory("br");
export const __yak_button = /*#__PURE__*/ StyledFactory("button");
export const __yak_canvas = /*#__PURE__*/ StyledFactory("canvas");
export const __yak_caption = /*#__PURE__*/ StyledFactory("caption");
export const __yak_cite = /*#__PURE__*/ StyledFactory("cite");
export const __yak_code = /*#__PURE__*/ StyledFactory("code");
export const __yak_col = /*#__PURE__*/ StyledFactory("col");
export const __yak_colgroup = /*#__PURE__*/ StyledFactory("colgroup");
export const __yak_data = /*#__PURE__*/ StyledFactory("data");
export const __yak_datalist = /*#__PURE__*/ StyledFactory("datalist");
export const __yak_dd = /*#__PURE__*/ StyledFactory("dd");
export const __yak_del = /*#__PURE__*/ StyledFactory("del");
export const __yak_details = /*#__PURE__*/ StyledFactory("details");
export const __yak_dfn = /*#__PURE__*/ StyledFactory("dfn");
export const __yak_dialog = /*#__PURE__*/ StyledFactory("dialog");
export const __yak_div = /*#__PURE__*/ StyledFactory("div");
export const __yak_dl = /*#__PURE__*/ StyledFactory("dl");
export const __yak_dt = /*#__PURE__*/ StyledFactory("dt");
export const __yak_em = /*#__PURE__*/ StyledFactory("em");
export const __yak_embed = /*#__PURE__*/ StyledFactory("embed");
export const __yak_fieldset = /*#__PURE__*/ StyledFactory("fieldset");
export const __yak_figcaption = /*#__PURE__*/ StyledFactory("figcaption");
export const __yak_figure = /*#__PURE__*/ StyledFactory("figure");
export const __yak_footer = /*#__PURE__*/ StyledFactory("footer");
export const __yak_form = /*#__PURE__*/ StyledFactory("form");
export const __yak_h1 = /*#__PURE__*/ StyledFactory("h1");
export const __yak_h2 = /*#__PURE__*/ StyledFactory("h2");
export const __yak_h3 = /*#__PURE__*/ StyledFactory("h3");
export const __yak_h4 = /*#__PURE__*/ StyledFactory("h4");
export const __yak_h5 = /*#__PURE__*/ StyledFactory("h5");
export const __yak_h6 = /*#__PURE__*/ StyledFactory("h6");
export const __yak_header = /*#__PURE__*/ StyledFactory("header");
export const __yak_hgroup = /*#__PURE__*/ StyledFactory("hgroup");
export const __yak_hr = /*#__PURE__*/ StyledFactory("hr");
export const __yak_html = /*#__PURE__*/ StyledFactory("html");
export const __yak_i = /*#__PURE__*/ StyledFactory("i");
export const __yak_iframe = /*#__PURE__*/ StyledFactory("iframe");
export const __yak_img = /*#__PURE__*/ StyledFactory("img");
export const __yak_input = /*#__PURE__*/ StyledFactory("input");
export const __yak_ins = /*#__PURE__*/ StyledFactory("ins");
export const __yak_kbd = /*#__PURE__*/ StyledFactory("kbd");
export const __yak_keygen = /*#__PURE__*/ StyledFactory("keygen");
export const __yak_label = /*#__PURE__*/ StyledFactory("label");
export const __yak_legend = /*#__PURE__*/ StyledFactory("legend");
export const __yak_li = /*#__PURE__*/ StyledFactory("li");
export const __yak_link = /*#__PURE__*/ StyledFactory("link");
export const __yak_main = /*#__PURE__*/ StyledFactory("main");
export const __yak_map = /*#__PURE__*/ StyledFactory("map");
export const __yak_mark = /*#__PURE__*/ StyledFactory("mark");
export const __yak_menu = /*#__PURE__*/ StyledFactory("menu");
export const __yak_menuitem = /*#__PURE__*/ StyledFactory("menuitem");
export const __yak_meta = /*#__PURE__*/ StyledFactory("meta");
export const __yak_meter = /*#__PURE__*/ StyledFactory("meter");
export const __yak_nav = /*#__PURE__*/ StyledFactory("nav");
export const __yak_noscript = /*#__PURE__*/ StyledFactory("noscript");
export const __yak_object = /*#__PURE__*/ StyledFactory("object");
export const __yak_ol = /*#__PURE__*/ StyledFactory("ol");
export const __yak_optgroup = /*#__PURE__*/ StyledFactory("optgroup");
export const __yak_option = /*#__PURE__*/ StyledFactory("option");
export const __yak_output = /*#__PURE__*/ StyledFactory("output");
export const __yak_p = /*#__PURE__*/ StyledFactory("p");
export const __yak_param = /*#__PURE__*/ StyledFactory("param");
export const __yak_picture = /*#__PURE__*/ StyledFactory("picture");
export const __yak_pre = /*#__PURE__*/ StyledFactory("pre");
export const __yak_progress = /*#__PURE__*/ StyledFactory("progress");
export const __yak_q = /*#__PURE__*/ StyledFactory("q");
export const __yak_rp = /*#__PURE__*/ StyledFactory("rp");
export const __yak_rt = /*#__PURE__*/ StyledFactory("rt");
export const __yak_ruby = /*#__PURE__*/ StyledFactory("ruby");
export const __yak_s = /*#__PURE__*/ StyledFactory("s");
export const __yak_samp = /*#__PURE__*/ StyledFactory("samp");
export const __yak_script = /*#__PURE__*/ StyledFactory("script");
export const __yak_section = /*#__PURE__*/ StyledFactory("section");
export const __yak_select = /*#__PURE__*/ StyledFactory("select");
export const __yak_small = /*#__PURE__*/ StyledFactory("small");
export const __yak_source = /*#__PURE__*/ StyledFactory("source");
export const __yak_span = /*#__PURE__*/ StyledFactory("span");
export const __yak_strong = /*#__PURE__*/ StyledFactory("strong");
export const __yak_style = /*#__PURE__*/ StyledFactory("style");
export const __yak_sub = /*#__PURE__*/ StyledFactory("sub");
export const __yak_summary = /*#__PURE__*/ StyledFactory("summary");
export const __yak_sup = /*#__PURE__*/ StyledFactory("sup");
export const __yak_table = /*#__PURE__*/ StyledFactory("table");
export const __yak_tbody = /*#__PURE__*/ StyledFactory("tbody");
export const __yak_td = /*#__PURE__*/ StyledFactory("td");
export const __yak_textarea = /*#__PURE__*/ StyledFactory("textarea");
export const __yak_tfoot = /*#__PURE__*/ StyledFactory("tfoot");
export const __yak_th = /*#__PURE__*/ StyledFactory("th");
export const __yak_thead = /*#__PURE__*/ StyledFactory("thead");
export const __yak_time = /*#__PURE__*/ StyledFactory("time");
export const __yak_tr = /*#__PURE__*/ StyledFactory("tr");
export const __yak_track = /*#__PURE__*/ StyledFactory("track");
export const __yak_u = /*#__PURE__*/ StyledFactory("u");
export const __yak_ul = /*#__PURE__*/ StyledFactory("ul");
export const __yak_use = /*#__PURE__*/ StyledFactory("use");
export const __yak_var = /*#__PURE__*/ StyledFactory("var");
export const __yak_video = /*#__PURE__*/ StyledFactory("video");
export const __yak_wbr = /*#__PURE__*/ StyledFactory("wbr");
export const __yak_circle = /*#__PURE__*/ StyledFactory("circle");
export const __yak_clipPath = /*#__PURE__*/ StyledFactory("clipPath");
export const __yak_defs = /*#__PURE__*/ StyledFactory("defs");
export const __yak_ellipse = /*#__PURE__*/ StyledFactory("ellipse");
export const __yak_foreignObject = /*#__PURE__*/ StyledFactory("foreignObject");
export const __yak_g = /*#__PURE__*/ StyledFactory("g");
export const __yak_image = /*#__PURE__*/ StyledFactory("image");
export const __yak_line = /*#__PURE__*/ StyledFactory("line");
export const __yak_linearGradient =
/*#__PURE__*/ StyledFactory("linearGradient");
export const __yak_marker = /*#__PURE__*/ StyledFactory("marker");
export const __yak_mask = /*#__PURE__*/ StyledFactory("mask");
export const __yak_path = /*#__PURE__*/ StyledFactory("path");
export const __yak_pattern = /*#__PURE__*/ StyledFactory("pattern");
export const __yak_polygon = /*#__PURE__*/ StyledFactory("polygon");
export const __yak_polyline = /*#__PURE__*/ StyledFactory("polyline");
export const __yak_radialGradient =
/*#__PURE__*/ StyledFactory("radialGradient");
export const __yak_rect = /*#__PURE__*/ StyledFactory("rect");
export const __yak_stop = /*#__PURE__*/ StyledFactory("stop");
export const __yak_svg = /*#__PURE__*/ StyledFactory("svg");
export const __yak_text = /*#__PURE__*/ StyledFactory("text");
export const __yak_tspan = /*#__PURE__*/ StyledFactory("tspan");
50 changes: 49 additions & 1 deletion packages/yak-swc/yak_swc/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use css_in_js_parser::{find_char, parse_css, to_css, CommentStateType};
use css_in_js_parser::{Declaration, ParserState};
use rustc_hash::FxHashMap;
use itertools::Itertools;
use rustc_hash::{FxHashMap, FxHashSet};
use serde::Deserialize;
use std::path::Path;
use std::vec;
Expand Down Expand Up @@ -85,6 +86,7 @@ where
/// Used to check if the current program is using next-yak
/// to idenftify css-in-js expressions
yak_library_imports: YakImportVisitor,
yak_transformed_library_imports: FxHashSet<Id>,
/// Variable Name to Unique CSS Identifier Mapping\
/// e.g. const Rotation = keyframes`...` -> Rotation\
/// e.g. const Button = styled.button`...` -> Button\
Expand Down Expand Up @@ -115,6 +117,7 @@ where
current_exported: false,
variables: VariableVisitor::new(),
yak_library_imports: YakImportVisitor::new(),
yak_transformed_library_imports: FxHashSet::default(),
naming_convention: NamingConvention::new(filename.clone(), dev_mode),
variable_name_selector_mapping: FxHashMap::default(),
expression_replacement: None,
Expand Down Expand Up @@ -511,12 +514,46 @@ where
for item in module.body.iter_mut() {
if let ModuleItem::ModuleDecl(ModuleDecl::Import(import_declaration)) = item {
if import_declaration.src.value == "next-yak/internal" {
if !self.yak_transformed_library_imports.is_empty() {
// yak styled import got transformed into specific components
// Now we remove the generic styled import and add all explicit imports
import_declaration.specifiers.retain(|import_specifier| {
match import_specifier {
ImportSpecifier::Named(ImportNamedSpecifier { local, .. }) => {
if local.sym == atom!("styled") {
return false;
}
return true;
},
_ => true
}
});
let asdf = self.yak_transformed_library_imports.clone();
dbg!(format!("💩💩💩💩💩💩💩set contains: {asdf:?}"));
//Add all transformed imports
import_declaration.specifiers.extend(
self
.yak_transformed_library_imports
.iter()
.sorted_by_key(|ident| ident.0.clone())
.map(|(sym, ctxt)| {
ImportSpecifier::Named(ImportNamedSpecifier {
span: DUMMY_SP,
local: Ident { span: DUMMY_SP, ctxt: *ctxt, sym: sym.clone(), optional: false },
imported: None,
is_type_only: false,
})
})
);
}

// Add utility functions
import_declaration.specifiers.extend(
self
.yak_library_imports
.get_yak_utility_import_declaration(),
);

break;
}
}
Expand All @@ -532,6 +569,14 @@ where
last_import_index = i + 1;
}
}

for item in module.body.iter_mut() {
if let ModuleItem::ModuleDecl(ModuleDecl::Import(import_declaration)) = item {
if import_declaration.src.value == "next-yak/internal" {
}
}
}

module.body.insert(
last_import_index,
ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
Expand Down Expand Up @@ -810,6 +855,9 @@ where
&self.current_declaration,
runtime_css_variables,
);
if let Some(id) = transform_result.import {
self.yak_transformed_library_imports.insert(id);
}

if is_top_level {
self.current_declaration = vec![];
Expand Down
Loading