Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 22 additions & 0 deletions apps/landing/src/app/(detail)/docs/LeftMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,28 @@ export function LeftMenu() {
Core Concepts
</MenuItem>
<MenuItem to="/docs/features">Features</MenuItem>
<MenuItem
subMenu={[
{
to: '/docs/migration/overview',
children: 'Overview',
},
{
to: '/docs/migration/styled-components',
children: 'styled-components & Emotion',
},
{
to: '/docs/migration/vanilla-extract',
children: 'vanilla-extract',
},
{
to: '/docs/migration/stylex',
children: 'StyleX',
},
]}
>
Migration
</MenuItem>
<MenuItem
subMenu={[
{
Expand Down
91 changes: 91 additions & 0 deletions apps/landing/src/app/(detail)/docs/migration/overview/page.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
export const metadata = {
title: 'Migration',
alternates: {
canonical: '/docs/migration/overview',
},
}

# Migrating from another CSS-in-JS library

Devup UI reads styled-components, Emotion, vanilla-extract and StyleX source directly. You do not rewrite call sites, and you do not keep the original library installed — the build rewrites the imports and compiles every call into static CSS.

## What happens to your code

```tsx
// what you wrote
import styled from 'styled-components'

const Card = styled.div`
color: red;
`
```

```tsx
// what ships — no styled-components, no runtime
const Card = ({ style, className, ...rest }) => (
<div
{...rest}
className={['a', className].filter(Boolean).join(' ')}
style={style}
/>
)
```

```css
.a {
color: red;
}
```

The import is not merely re-pointed at `@devup-ui/react`: the extractor consumes the call and removes the specifier, so the import statement disappears from the output entirely.

## Two passes

1. **Import rewrite** — `import { style } from '@vanilla-extract/css'` becomes `import { css as style } from '@devup-ui/react'`. Only names with a real Devup UI counterpart move; the rest stay put so their own types keep working.
2. **Extraction** — `style({ ... })` is compiled to a class name, the specifier is dropped, and the now-empty import is deleted.

Because of the second pass, the intermediate `css as style` alias never reaches your bundle.

## Enabling it

Every plugin turns the default aliases on automatically:

```ts
// vite.config.ts
import { DevupUI } from '@devup-ui/vite-plugin'

export default DevupUI({})
```

The defaults cover `styled-components`, `@emotion/styled`, `@emotion/react` and `@vanilla-extract/css`. StyleX needs no alias — `@stylexjs/stylex` is recognised directly.

Opt a package out when you want to keep using the real thing:

```ts
DevupUI({
importAliases: {
'styled-components': false,
},
})
```

## Uninstalling the original package

Once the imports are rewritten you can drop the dependency. TypeScript still has to resolve the specifier you wrote, so the plugins generate `<distDir>/compat.d.ts` with ambient declarations for the aliases you have enabled:

```ts
/// <reference types="@devup-ui/react/compat/styled-components" />
/// <reference types="@devup-ui/react/compat/emotion" />
/// <reference types="@devup-ui/react/compat/stylex" />
/// <reference types="@devup-ui/react/compat/vanilla-extract" />
```

That file lands next to `theme.d.ts` inside your project, so `tsconfig.json` picks it up with no extra configuration. Disabled aliases are left out on purpose — an ambient declaration wins over an installed package, and a library you opted out of must keep its own types.

## Where the absorbed APIs live

Anything that exists only to absorb another library — `ThemeProvider`, `createGlobalStyle`, `ServerStyleSheet`, and friends — is exported from `@devup-ui/react/compat`, never from `@devup-ui/react`. Using Devup UI directly means never seeing them, and it lets the compat entry keep the original spelling: its `useTheme` cannot collide with Devup UI's own `useTheme`.

```tsx
// rewritten for you
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
export const metadata = {
title: 'Migration',
alternates: {
canonical: '/docs/migration/styled-components',
},
}

# styled-components & Emotion

Both libraries are covered by the same default aliases, and every spelling of `styled` compiles to a static component.

## Supported call shapes

```tsx
import styled from 'styled-components'

const A = styled.div`
color: red;
`
const B = styled('div')`
color: red;
`
const C = styled.div({ bg: 'red' })
const D = styled('div')({ bg: 'red' })
const E = styled(Component)({ bg: 'red' })
const F = styled('div', { bg: 'red' })
```

Namespace imports work too. `@emotion/styled` and `styled-components` export a single callable, so the namespace binding _is_ that value and it is bound straight to the Devup UI export:

```tsx
import * as Emotion from '@emotion/styled'
// → import { styled as Emotion } from '@devup-ui/react'

const Card = Emotion.div`color: red;`
```

## Theme interpolation

`ThemeProvider` redefines CSS variables on a `display: contents` wrapper, so it scopes the theme to its subtree without touching layout. Interpolations that read the theme are resolved at build time — no runtime lookup:

```tsx
const Card = styled.div`
color: ${(p) => p.theme.brand};
border: 1px solid ${({ theme }) => theme.colors.line};
`
```

```css
.a {
color: var(--brand);
}
.b {
border: 1px solid var(--colors-line);
}
```

Nested themes flatten with a hyphen, so `theme.colors.line` and `--colors-line` always agree. The object form resolves identically:

```tsx
const Card = styled('div')({ color: (p) => p.theme.brand })
```

Reading the theme in JS gives the same references back:

```tsx
import { useTheme } from 'styled-components'

function Divider() {
const theme = useTheme()
// `${theme.colors.line}` === 'var(--colors-line)'
return <hr style={{ borderColor: `${theme.colors.line}` }} />
}
```

## Global styles

`createGlobalStyle` keeps its render site — the rules are lifted into the stylesheet and the call collapses to a component that renders nothing:

```tsx
const GlobalStyle = createGlobalStyle`body { margin: 0; }`
// → const GlobalStyle = () => null
```

Emotion's `<Global styles={...} />` behaves the same way: the `styles` prop is extracted and the element stops rendering markup.

## The rest of the surface

| API | Result |
| ---------------------------------------- | -------------------------------------------------------------------------------- |
| `css`, `keyframes` | Devup UI equivalents |
| `ThemeProvider`, `useTheme`, `withTheme` | `@devup-ui/react/compat`, CSS-variable backed |
| `createGlobalStyle`, `Global` | extracted, render nothing |
| `ServerStyleSheet`, `StyleSheetManager` | inert — Devup UI already emits a real stylesheet, so there is nothing to collect |
| `isStyledComponent` | always `false` |
| `ClassNames`, `CacheProvider` | no equivalent; stays on its own package with a build warning |
98 changes: 98 additions & 0 deletions apps/landing/src/app/(detail)/docs/migration/stylex/page.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
export const metadata = {
title: 'Migration',
alternates: {
canonical: '/docs/migration/stylex',
},
}

# StyleX

`@stylexjs/stylex` needs no alias configuration — the extractor recognises the package directly, through the default import, a namespace import, or named imports.

## Styles and props

`create()` becomes a namespace-to-class-name object and `props()` becomes the attributes to spread:

```tsx
import stylex from '@stylexjs/stylex'

const styles = stylex.create({
base: { display: 'inline-block' },
})
const colors = stylex.create({ red: { color: 'red' }, blue: { color: 'blue' } })

const el = <div {...stylex.props(styles.base, colors[color])} />
```

```tsx
const styles = { base: 'a' }
const colors = { red: 'b', blue: 'c' }

const el = <div {...{ className: `a ${colors[color] || ''}` }} />
```

Computed access such as `colors[color]` is resolved against the object the `create()` call was rewritten into, so every generated atom stays reachable. `stylex.attrs()` behaves the same but emits `class` instead of `className`.

Accepted argument shapes:

```tsx
stylex.props(styles.base) // member access
stylex.props(styles['base']) // literal key
stylex.props(colors[color]) // runtime key
stylex.props([styles.a, styles.b]) // StyleXArray, nestable
stylex.props(on && styles.active) // conditional
stylex.props(styles?.base) // optional chaining
```

## Variables and themes

```tsx
const colors = stylex.defineVars({ primary: 'blue', secondary: 'grey' })
const dark = stylex.createTheme(colors, { primary: 'navy' })
const styles = stylex.create({ box: { color: colors.primary } })

const el = <div {...stylex.props(dark, styles.box)} />
```

```css
:root {
--a: blue;
--b: grey;
}
.c {
--a: navy;
}
.d {
color: var(--a);
}
```

```tsx
const colors = { primary: 'var(--a)', secondary: 'var(--b)' }
const dark = 'c'
const styles = { box: 'd' }
const el = <div {...{ className: 'c d' }} />
```

`createThemeContract()` produces the same references without publishing a `:root` block, and `defineConsts()` inlines its values into the `create()` calls that read them.

## At-rules

```tsx
const fallback = stylex.positionTry({ top: '0', insetBlockEnd: 'auto' })
const transition = stylex.viewTransitionClass({ animationDuration: '300ms' })
```

```css
@position-try --a {
top: 0;
inset-block-end: auto;
}
.b {
animation-duration: 300ms;
}
```

## Value helpers

`firstThatWorks()` emits its fallbacks in CSS order, `types.*()` unwraps to the inner value, and `include()` merges another namespace's classes. Values may be plain literals, condition objects (`{ default, ':hover', '@media ...' }`), or arrow functions, which become CSS variables bound at the call site.
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
export const metadata = {
title: 'Migration',
alternates: {
canonical: '/docs/migration/vanilla-extract',
},
}

# vanilla-extract

## Stylesheet files

Inside `.css.ts` / `.css.js` — the only place vanilla-extract allows its APIs — Devup UI evaluates the module and replaces every call with its generated output. The whole surface works there: `style`, `globalStyle`, `keyframes`, `styleVariants`, `fontFace`, `createVar`, `fallbackVar`, `createTheme`, `createThemeContract`, `layer`, `createContainer`.

```ts
// theme.css.ts
import { createTheme, createThemeContract, style } from '@vanilla-extract/css'

const vars = createThemeContract({ colors: { bg: null } })
export const light = createTheme(vars, { colors: { bg: 'white' } })
export const box = style({ background: vars.colors.bg })
```

## Ordinary modules

`style` and `globalStyle` also resolve in `.ts` / `.tsx`, mapped onto their Devup UI counterparts:

```tsx
import { globalStyle, style } from '@vanilla-extract/css'

export const a = style({ color: 'red' })
globalStyle('body', { margin: 0 })
```

```tsx
// output — no import left
export const a = 'a'
```

`globalStyle(selector, rules)` keeps vanilla-extract's two-argument shape; the extractor folds the selector back into the object `globalCss` takes.

The remaining APIs stay on `@vanilla-extract/css` outside a stylesheet file, because evaluating a module that also contains React components is not possible. A build warning names them:

```
[devup-ui] WARNING: '@vanilla-extract/css' keeps styleVariants, createVar because
devup-ui has no equivalent export, so the package stays a runtime dependency.
```

Moving those calls into a `.css.ts` file — where vanilla-extract wants them anyway — removes the dependency.

## Namespace imports

A namespace stands for many named exports whose Devup UI counterparts are renamed (`style` → `css`), which a namespace access cannot express, so it is left alone:

```ts
// unchanged
```

Use named imports to get the rewrite.
4 changes: 4 additions & 0 deletions e2e/exported-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ export const EXPECTED_EXPORTED_ROUTES = [
'/docs/figma-and-theme-integration/devup-figma-plugin',
'/docs/figma-and-theme-integration/devup-json',
'/docs/installation',
'/docs/migration/overview',
'/docs/migration/styled-components',
'/docs/migration/stylex',
'/docs/migration/vanilla-extract',
'/docs/overview',
'/docs/quick-start',
'/showcase',
Expand Down
Loading
Loading