Skip to content

Comments

feat(transformer): support styled components plugin#12066

Merged
graphite-app[bot] merged 1 commit intomainfrom
07-04-feat_transformer_support_styled_components_plugin
Jul 10, 2025
Merged

feat(transformer): support styled components plugin#12066
graphite-app[bot] merged 1 commit intomainfrom
07-04-feat_transformer_support_styled_components_plugin

Conversation

@Dunqing
Copy link
Member

@Dunqing Dunqing commented Jul 4, 2025

close: #11876

This plugin adds comprehensive support for styled-components with server-side rendering, style minification, and enhanced debugging capabilities. The implementation is ported from the official Babel plugin to ensure compatibility and feature parity.

Current Limitations

⚠️ Import Support: This plugin currently only supports styled-components imported via ES modules (import statements). CommonJS imports using require("styled-components") are not yet supported.

Feature Support

Options:

✅ Fully Supported:

  • displayName: Adds display names for debugging
  • fileName: Controls filename prefixing in display names
  • ssr: Adds unique component IDs for server-side rendering
  • transpileTemplateLiterals: Converts template literals to function calls
  • minify: Minifies CSS content in template literals
  • namespace: Adds namespace prefixes to component IDs
  • meaninglessFileNames: Controls which filenames are considered meaningless

⚠️ Partially Supported:

  • pure: Only supports call expressions, not tagged template expressions (bundler limitation)

❌ Not Yet Implemented:

  • cssProp: JSX css prop transformation
  • topLevelImportPaths: Custom import path handling

Testing

The test suite is adapted from the official Babel plugin tests with modifications to integrate with our testing infrastructure.

Note: Some test outputs differ from the original due to our different hashing algorithm for component IDs. This is intentional and follows the same approach used by SWC's implementation.

@github-actions github-actions bot added A-transformer Area - Transformer / Transpiler C-enhancement Category - New feature or request labels Jul 4, 2025
Copy link
Member Author

Dunqing commented Jul 4, 2025


How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:

  • 0-merge - adds this PR to the back of the merge queue
  • hotfix - for urgent hot fixes, skip the queue and merge this PR next

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.

@codspeed-hq
Copy link

codspeed-hq bot commented Jul 4, 2025

CodSpeed Instrumentation Performance Report

Merging #12066 will not alter performance

Comparing 07-04-feat_transformer_support_styled_components_plugin (19b97c0) with main (4c35f4a)

Summary

✅ 34 untouched benchmarks

@Dunqing Dunqing force-pushed the 07-04-feat_transformer_support_styled_components_plugin branch 3 times, most recently from 8bcb0f1 to 85dd4eb Compare July 9, 2025 06:52
@Dunqing Dunqing marked this pull request as ready for review July 9, 2025 06:56
@Dunqing Dunqing requested a review from overlookmotel as a code owner July 9, 2025 06:56
@Dunqing Dunqing force-pushed the 07-04-feat_transformer_support_styled_components_plugin branch from 85dd4eb to 3fee05b Compare July 9, 2025 07:42
@Dunqing Dunqing force-pushed the 07-04-feat_transformer_support_styled_components_plugin branch from 3fee05b to 44bd320 Compare July 9, 2025 10:06
@overlookmotel
Copy link
Member

I've pushed a commit to enable this plugin in benchmarks, so we can see what impact it has.

@overlookmotel
Copy link
Member

Not too bad!

bench

Copy link
Member

@overlookmotel overlookmotel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried to review this properly, but it's hard because I don't understand what any of it is doing! I know we're keen to get this merged ASAP, but afterwards could you possibly add some comments, and I'll read through it again?

Please feel free to merge in the meantime. I'll do a few follow-up PRs with suggested refactoring and perf, and my comments below could also be addressed in follow-ups.

Another thought: Are we able to disable this plugin on files which aren't JSX/TSX? Or does it need to run on JS files too? The loop through all top-level statements is probably quite costly for long files, and it'd be ideal to skip it if we can.

Comment on lines +600 to +616
fn base36_encode<'a>(mut num: u64, ctx: &TraverseCtx<'a>) -> &'a str {
const BASE36_BYTES: &[u8] = b"abcdefghijklmnopqrstuvwxyz0123456789";

num %= 36_u64.pow(6); // 36^6, to ensure the result is <= 6 characters long.

let mut result = Vec::with_capacity(6);

while num != 0 {
result.push(BASE36_BYTES[(num % 36) as usize]);
num /= 36;
}

ctx.ast.allocator.alloc_str(
// SAFETY: the bytes are valid UTF-8 as they are ASCII characters.
unsafe { std::str::from_utf8_unchecked(&result) },
)
}
Copy link
Member

@overlookmotel overlookmotel Jul 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason for using base 36 here? Couldn't we use base 64 (A-Z, a-z, 0-9, _, $)?

Division by numbers which aren't a power of 2 is quite expensive, and it'd give a larger range of possibilities in 6 chars (less chance of collisions).

Also, building the string with InlineString would be be more performant than Vec. Like in mangler:

https://github.com/oxc-project/oxc/blob/fe843806ece40a19adca3a7c6366abfb6f8662bb/crates/oxc_mangler/src/base54.rs

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

@overlookmotel overlookmotel Jul 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a big thing! Was just asking if there was a reason because base36 is unusual. Odd choice from styled-components authors!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Dunqing I realized now that maybe there is a reason. Maybe class names are case insensitive in some browsers?

@Dunqing
Copy link
Member Author

Dunqing commented Jul 9, 2025

Not too bad!

bench

I guess we don't have a benchmark that can cover all the features that this plugin has, so we don't see much performance change.

@Dunqing
Copy link
Member Author

Dunqing commented Jul 9, 2025

Another thought: Are we able to disable this plugin on files which aren't JSX/TSX? Or does it need to run on JS files too? The loop through all top-level statements is probably quite costly for long files, and it'd be ideal to skip it if we can.

It needs to run in all files. One possible case that we need to consider is that they would like to define all of the styled components in a utility file (.js or .ts), and use them in the JSX/TSX files.

Another serious performance hit if we need to support CJS, we need to go through the whole AST to find whether there is a const variable = require('styled-components'). This is the reason I don't support it at this stage.

@graphite-app graphite-app bot added the 0-merge Merge with Graphite Merge Queue label Jul 10, 2025
@graphite-app
Copy link
Contributor

graphite-app bot commented Jul 10, 2025

Merge activity

close: #11876

This plugin adds comprehensive support for styled-components with server-side rendering, style minification, and enhanced debugging capabilities. The implementation is ported from the [official Babel plugin](https://github.com/styled-components/babel-plugin-styled-components) to ensure compatibility and feature parity.

## Current Limitations

⚠️ **Import Support**: This plugin currently only supports styled-components imported via ES modules (`import` statements). CommonJS imports using `require("styled-components")` are not yet supported.

## Feature Support

### Options:
 **✅ Fully Supported:**
 - `displayName`: Adds display names for debugging
 - `fileName`: Controls filename prefixing in display names
 - `ssr`: Adds unique component IDs for server-side rendering
 - `transpileTemplateLiterals`: Converts template literals to function calls
 - `minify`: Minifies CSS content in template literals
 - `namespace`: Adds namespace prefixes to component IDs
 - `meaninglessFileNames`: Controls which filenames are considered meaningless

 **⚠️ Partially Supported:**
 - `pure`: Only supports call expressions, not tagged template expressions (bundler limitation)

 **❌ Not Yet Implemented:**
 - `cssProp`: JSX css prop transformation
 - `topLevelImportPaths`: Custom import path handling

## Testing

The test suite is adapted from the [official Babel plugin tests](https://github.com/styled-components/babel-plugin-styled-components) with modifications to integrate with our testing infrastructure.

**Note**: Some test outputs differ from the original due to our different hashing algorithm for component IDs. This is intentional and follows the same approach used by SWC's implementation.
@graphite-app graphite-app bot force-pushed the 07-04-feat_transformer_support_styled_components_plugin branch from 94ec753 to 19b97c0 Compare July 10, 2025 00:39
graphite-app bot pushed a commit that referenced this pull request Jul 10, 2025
Follow-on after #12066. Tiny refactor. Move clippy exception to cover just the place it's needed, rather than the whole file.
graphite-app bot pushed a commit that referenced this pull request Jul 10, 2025
Follow-on after #12066.

`helpers` was an `FxHashMap<String, SymbolId>`, but we know all the possible keys, and there's only 6 of them. So use a small array `[Option<SymbolId>; 6]` instead, and convert from strings to indexes into that array via a `StyledComponentsHelper` enum.

Array (24 bytes) is smaller than an `FxHashMap`, and involves no allocations, as well as avoiding hashing strings.

Note: Using an enum rather than an untyped `usize` index, as with an enum, compiler can statically prove indexing into the array can never be out of bounds, so it skips bounds checks.
@Dunqing
Copy link
Member Author

Dunqing commented Jul 10, 2025

Consider this stack of PR to be huge, so I am merging this stack and following your suggestion and finishing some missing work in the follow-up PRs later.

graphite-app bot pushed a commit that referenced this pull request Jul 10, 2025
Follow-on after #12066. Pure refactor. Just make the code less deeply nested.
graphite-app bot pushed a commit that referenced this pull request Jul 10, 2025
Follow-on after #12066. Pure refactor. Simplify and shorten code.
@graphite-app graphite-app bot merged commit 19b97c0 into main Jul 10, 2025
25 checks passed
@graphite-app graphite-app bot deleted the 07-04-feat_transformer_support_styled_components_plugin branch July 10, 2025 00:47
@graphite-app graphite-app bot removed the 0-merge Merge with Graphite Merge Queue label Jul 10, 2025
@overlookmotel
Copy link
Member

Another serious performance hit if we need to support CJS, we need to go through the whole AST to find whether there is a const variable = require('styled-components'). This is the reason I don't support it at this stage.

Argh! Maybe we need to include require in the module record that parser produces, and use that in transformer.

I suppose we could more easily support require('styled-components') at top level only - which is the common use case.

graphite-app bot pushed a commit that referenced this pull request Jul 10, 2025
…sions is part of `ComputedMemberExpression` (#12181)

close: #12066 (comment)

We haven't supported transform `ComputedMemberExpression`, because I found that Babel plugin support for `styled['div']` is also incomplete. Anyway, short-circuiting for this is still a good improvement.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-transformer Area - Transformer / Transpiler C-enhancement Category - New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

transformer: babel-plugin-styled-components

2 participants