Skip to content

Commit 9b72c4e

Browse files
authored
Scaffold and test cases (#3)
* Scaffold test cases and repo setup * Typecheck fixtures + change adapter setup * Format * Move files * Setup lefthook
1 parent b517753 commit 9b72c4e

105 files changed

Lines changed: 8759 additions & 1 deletion

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
workflow_dispatch:
9+
10+
jobs:
11+
verify:
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- name: Checkout
16+
uses: actions/checkout@v4
17+
18+
- name: Setup pnpm
19+
uses: pnpm/action-setup@v4
20+
21+
- name: Setup node
22+
uses: actions/setup-node@v4
23+
with:
24+
node-version: 22
25+
cache: "pnpm"
26+
27+
- name: Install dependencies
28+
run: pnpm install
29+
30+
- name: Verify changes
31+
run: pnpm run ci
32+
33+
- name: Build
34+
run: pnpm run build

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,13 @@ dist
137137
# Vite logs files
138138
vite.config.js.timestamp-*
139139
vite.config.ts.timestamp-*
140+
141+
# Storybook build output
142+
storybook-static
143+
playwright-report
144+
145+
# Playwright MCP
146+
.playwright-mcp/
147+
148+
# Screenshots
149+
*.png

.storybook/main.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import type { StorybookConfig } from "@storybook/react-vite";
2+
import stylex from "@stylexjs/unplugin";
3+
4+
const config: StorybookConfig = {
5+
stories: ["../test-cases/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
6+
framework: {
7+
name: "@storybook/react-vite",
8+
options: {},
9+
},
10+
viteFinal: async (config) => {
11+
config.plugins = config.plugins || [];
12+
13+
// Add StyleX plugin for processing .stylex.ts files
14+
config.plugins.unshift(
15+
stylex.vite({
16+
dev: true,
17+
unstable_moduleResolution: {
18+
type: "commonJS",
19+
rootDir: process.cwd(),
20+
},
21+
}),
22+
);
23+
24+
// Force esbuild to treat .ts files as tsx with automatic JSX runtime
25+
config.esbuild = {
26+
...config.esbuild,
27+
loader: "tsx",
28+
include: /\.(ts|tsx|js|jsx)$/,
29+
jsx: "automatic",
30+
};
31+
32+
// Also configure optimizeDeps for pre-bundling
33+
config.optimizeDeps = {
34+
...config.optimizeDeps,
35+
esbuildOptions: {
36+
...config.optimizeDeps?.esbuildOptions,
37+
loader: {
38+
".ts": "tsx",
39+
},
40+
jsx: "automatic",
41+
},
42+
};
43+
44+
return config;
45+
},
46+
};
47+
48+
export default config;

.storybook/preview.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { Preview } from "@storybook/react";
2+
3+
const preview: Preview = {
4+
parameters: {
5+
layout: "padded",
6+
},
7+
};
8+
9+
export default preview;

AGENT.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
CLAUDE.md

CLAUDE.md

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
Codemod to transform styled-components to StyleX using jscodeshift.
8+
9+
## Tech Stack
10+
11+
- **Runtime**: Node.js >=22.20
12+
- **Package Manager**: pnpm >=10.22.0
13+
- **Language**: TypeScript (ESM)
14+
- **Build**: tsdown
15+
- **Test**: vitest
16+
- **Lint**: oxlint
17+
- **Visual Testing**: Storybook 10
18+
- **Codemod Framework**: jscodeshift
19+
20+
## Commands
21+
22+
```bash
23+
pnpm install # Install dependencies
24+
pnpm build # Build with tsdown
25+
pnpm test # Run tests (watch mode)
26+
pnpm test:run # Run tests once
27+
pnpm typecheck # Type check with tsc
28+
pnpm lint # Lint with oxlint
29+
pnpm run ci # Run lint + typecheck + test
30+
pnpm storybook # Start Storybook dev server (port 6006)
31+
```
32+
33+
## Project Structure
34+
35+
```
36+
src/
37+
├── index.ts # Main exports
38+
├── transform.ts # Transform implementation
39+
├── transform.test.ts # Test runner (auto-discovers test cases)
40+
├── run.ts # Programmatic runner (runTransform)
41+
└── adapter.ts # Adapter interface and default adapter
42+
43+
test-cases/
44+
├── *.input.tsx # Input files (styled-components)
45+
├── *.output.tsx # Expected output files (StyleX)
46+
├── *.stylex.ts # StyleX theme variables (defineVars)
47+
├── *.warnings.json # Expected warnings for test cases
48+
├── lib/ # Helper files for test cases
49+
│ ├── helpers.ts # styled-components helpers (color, truncate)
50+
│ ├── helpers.stylex.ts # StyleX version of helpers
51+
│ └── colors.stylex.ts # StyleX color variables
52+
└── TestCases.stories.tsx # Auto-discovering Storybook stories
53+
54+
.storybook/
55+
├── main.ts # Storybook config (Vite, React, StyleX)
56+
└── preview.ts # Storybook preview config
57+
```
58+
59+
## Adding Test Cases
60+
61+
Create matching `.input.tsx` and `.output.tsx` files in `test-cases/`. Tests auto-discover all pairs and fail if any file is missing its counterpart.
62+
63+
**Test categories:**
64+
65+
- **File pairing**: Verifies all test cases have matching input/output files
66+
- **Output linting**: Runs oxlint on all output files to ensure valid code
67+
- **Transform tests** (skipped): Will verify transform produces expected output once implemented
68+
69+
**Note**: The transform is currently a stub that adds TODO comments. The transform tests are skipped until implementation is complete. Output files represent the expected transformation results.
70+
71+
## Storybook Visual Testing
72+
73+
Storybook renders all test cases side-by-side (input with styled-components, output with StyleX) to visually verify the transformation produces identical styles.
74+
75+
- **Auto-discovery**: Test cases are automatically discovered from `test-cases/*.input.tsx` and `*.output.tsx` files
76+
- **Side-by-side view**: Each test case shows "Input (styled-components)" and "Output (StyleX)" panels
77+
- **"All" story**: Shows all test cases on a single page at `http://localhost:6006/?path=/story/test-cases--all`
78+
79+
Run `pnpm storybook` to start the dev server and visually compare transformations.
80+
81+
## Visual Inspection with Playwright MCP
82+
83+
Use the Playwright MCP to inspect test case rendering:
84+
85+
1. Start Storybook: `pnpm storybook`
86+
2. Navigate to `http://localhost:6006/?path=/story/test-cases--all`
87+
3. Use Playwright MCP to take screenshots and verify input/output match visually
88+
89+
The "All" story shows every test case side-by-side, making it easy to compare styled-components input with StyleX output.
90+
91+
## Adapter System
92+
93+
The codemod uses an adapter system for value transformations, allowing customization of how styled-components values are converted to StyleX.
94+
95+
### Programmatic Usage
96+
97+
Create a script to run the transform with a custom adapter:
98+
99+
```typescript
100+
// run-transform.ts
101+
import { runTransform } from "styled-components-to-stylex-codemod";
102+
import type { Adapter } from "styled-components-to-stylex-codemod";
103+
104+
const myAdapter: Adapter = {
105+
transformValue({ path, defaultValue, valueType }) {
106+
// Return StyleX-compatible value
107+
// valueType is 'theme' | 'helper' | 'interpolation'
108+
return `themeVars.${path.replace(/\./g, "_")}`;
109+
},
110+
getImports() {
111+
return ["import { themeVars } from './theme.stylex';"];
112+
},
113+
getDeclarations() {
114+
return [];
115+
},
116+
};
117+
118+
await runTransform({
119+
files: "src/**/*.tsx",
120+
adapter: myAdapter,
121+
dryRun: true, // Set to false to write changes
122+
parser: "tsx",
123+
});
124+
```
125+
126+
Run with: `npx tsx run-transform.ts`
127+
128+
### Default Adapter
129+
130+
The default adapter converts theme values to CSS custom properties:
131+
132+
```typescript
133+
// Input: props.theme.colors.primary
134+
// Output: 'var(--colors-primary, #defaultValue)'
135+
```
136+
137+
## StyleX Requirements
138+
139+
Output files must use valid StyleX syntax:
140+
141+
- **No CSS shorthands**: Use `backgroundColor` not `background`, `borderWidth`/`borderStyle`/`borderColor` not `border`
142+
- **defineVars must be exported**: Theme variables must be in separate `.stylex.ts` files with named exports
143+
- **createTheme needs base vars**: `stylex.createTheme(baseVars, overrides)` requires two arguments
144+
145+
## Test Cases (from styled-components docs)
146+
147+
| Test Case | Pattern |
148+
| ------------------------------- | -------------------------------------------------------------------- |
149+
| `basic` | `styled.h1`, `styled.section` |
150+
| `adapting-props` | Props interpolation with `${props => ...}` |
151+
| `extending-styles` | `styled(Component)` inheritance |
152+
| `pseudo-selectors` | `&:hover`, `&:focus`, `&::before` |
153+
| `keyframes` | `keyframes` + animation |
154+
| `attrs` | `.attrs()` for default props |
155+
| `theming` | `ThemeProvider` + `props.theme` |
156+
| `css-helper` | `css` helper for shared styles |
157+
| `as-prop` | Polymorphic `as` prop |
158+
| `global-styles` | `createGlobalStyle` |
159+
| `media-queries` | `@media (min-width: ...)` responsive styles |
160+
| `nesting` | Child selectors `> *`, `&:not(:first-child)` |
161+
| `component-selector` | `${Link}:hover &` referencing other components |
162+
| `style-objects` | Object syntax `styled.div({...})` |
163+
| `conditional-styles` | Short-circuit `${props => props.x && '...'}` |
164+
| `styled-component` | `styled(CustomComponent)` with className |
165+
| `transient-props` | `$prefix` props to prevent DOM forwarding |
166+
| `refs` | Ref forwarding to styled components |
167+
| `use-theme` | `useTheme` hook for accessing theme |
168+
| `with-theme` | `withTheme` HOC for class components |
169+
| `sibling-selectors` | Adjacent `& + &` and general `& ~ &` sibling selectors |
170+
| `specificity` | Double ampersand `&&` and `&&&` for specificity boost |
171+
| `descendant-component-selector` | `${Child} { ... }` parent styling child component |
172+
| `forwarded-as` | `forwardedAs` prop for passing `as` through HOCs |
173+
| `function-theme` | `theme={fn}` function that receives parent theme |
174+
| `adhoc-theme` | Per-instance `theme` prop override |
175+
| `attribute-selectors` | `&[disabled]`, `&[type="text"]`, `&[href^="https"]` |
176+
| `css-variables` | `var(--custom-property)` CSS custom properties |
177+
| `css-calc` | `calc()` expressions in styles |
178+
| `multiple-animations` | Combining multiple keyframes animations |
179+
| `important` | `!important` declarations (removed in output) |
180+
| `universal-selector` | `& *` universal descendant selector |
181+
| `complex-selectors` | Multiple/compound selectors `&:hover, &:focus` |
182+
| `string-interpolation` | Static string interpolations `${variable}` |
183+
| `should-forward-prop` | `.withConfig({ shouldForwardProp })` prop filtering |
184+
| `with-config` | `.withConfig({ displayName, componentId })` |
185+
| `helpers` | Helper functions: `color()` theme accessor, `truncate()` CSS snippet |
186+
187+
## Transformation Goals
188+
189+
The codemod should handle conversions like:
190+
191+
- `styled.div` / `styled(Component)``stylex.create()` + `stylex.props()`
192+
- Template literal CSS → StyleX object syntax
193+
- Dynamic props/interpolations → StyleX variants or dynamic styles
194+
- `keyframes``stylex.keyframes()`
195+
- Theme values → CSS variables or `stylex.createTheme()`
196+
- `css` helper → Plain style objects
197+
- `.attrs()` → Inline props on element

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,14 @@
11
# styled-components-to-stylex-codemod
2-
styled-components to StyleX codemod
2+
3+
Transform styled-components to StyleX.
4+
5+
## Usage
6+
7+
```bash
8+
npx styled-components-to-stylex src/
9+
npx styled-components-to-stylex --dry src/ # dry run
10+
```
11+
12+
## License
13+
14+
MIT

lefthook.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
output:
2+
- summary
3+
- success
4+
- failure
5+
6+
pre-commit:
7+
parallel: true
8+
jobs:
9+
- name: lint
10+
run: pnpm lint:fix {staged_files}

oxlint.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
3+
"rules": {
4+
"no-unused-vars": "warn",
5+
"no-console": "warn",
6+
"eqeqeq": "error"
7+
},
8+
"ignorePatterns": [
9+
"dist/**",
10+
"node_modules/**",
11+
"coverage/**",
12+
"test-cases/**",
13+
"storybook-static/**"
14+
]
15+
}

0 commit comments

Comments
 (0)