Skip to content

Add parent-selector CSS namespace option (parity with @quickbaseoss/babel-plugin-styled-components-css-namespace) #616

@norbertsuski

Description

@norbertsuski

Feature request

@swc/plugin-styled-components already accepts a namespace option. As implemented today (12.9.0), namespace prefixes the styled-components componentId hash to avoid cross-instance collisions in micro-frontend setups. That's useful, but it is a different problem from what the babel ecosystem covers with @quickbaseoss/babel-plugin-styled-components-css-namespace, which emits a parent CSS selector before every styled-component class — .foo becomes .<ns> .foo — increasing specificity and isolating styles from legacy CSS that lives in the host page.

I'd like an additional option (or a separate plugin) that produces the parent-selector behavior at the swc layer. Sketch:

{
  "experimental": {
    "plugins": [
      ["@swc/plugin-styled-components", {
        "displayName": false,
        "ssr": true,
        "minify": true,
        "namespace": "myapp",            // existing: componentId prefix
        "cssNamespace": "myapp"          // proposed: emit `.myapp .foo`
      }]
    ]
  }
}

Use case

We run 11 micro-frontend React apps mounted into a single portal host page that also serves legacy non-styled-components CSS. Three of the apps depend on the babel parent-selector plugin today to isolate their styles from the host stylesheet. Migrating all 11 apps off babel-loader to swc-loader is held up by exactly these three apps — the other 8 already run on swc successfully (~3.8× faster cold build, -44% main bundle size in our pilot).

The plugin's parent-selector behavior is small and well-specified:

  • For every styled.X and styled(X) invocation, wrap the runtime-injected CSS so that all selectors are prefixed by .<cssNamespace> (note the trailing space — descendant combinator, not concatenation).
  • No componentId mutation — that's covered by the existing namespace option.
  • No effect on styled-components global styles or createGlobalStyle (existing babel plugin scopes only component-level styles).

Why a separate option (not overloading namespace)

  • The two behaviors are independent and frequently want different values per app.
  • The existing namespace value affects bundle output bytes deterministically; layering parent-selector emission on top of the same key would silently change behavior for current users.
  • Easy to deprecate the babel plugin once a 1:1 swc analog exists, since adopters can flip with a config change.

What I tried

  • The current namespace option (does not emit parent selectors — verified by grep on production bundle: zero matches for .<ns> pattern in swc output, 97+ matches in babel-built equivalent app).
  • Routing affected files to a separate babel-loader rule via webpack — works but defeats the speed win and keeps babel-loader + babel-plugin-styled-components + @quickbaseoss/babel-plugin-styled-components-css-namespace in the dep tree for the whole monorepo.

Versions

  • @swc/core: 1.15.33
  • @swc/plugin-styled-components: 12.9.0
  • swc-loader: 0.2.7
  • styled-components: 5.3.3
  • Node: 22.x
  • OS: macOS / Linux CI runners

Happy to contribute

If the maintainers point at where in the Rust plugin source the per-component CSS emission lives, I can attempt a draft PR. Reference implementation (babel): https://github.com/QuickBase/babel-plugin-styled-components-css-namespace/blob/master/index.js (small file, ~60 LOC).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions