Skip to content

zerobytes/granular-codemods

Repository files navigation

@granularjs/codemods

jscodeshift-powered codemods that automatically translate React code to @granularjs/core + @granularjs/jsx.

These power the granular migrate command (shipped via @granularjs/cli), but you can also run individual transforms standalone via granular-codemod.

Install

npm install --save-dev @granularjs/codemods

Use the high-level migration CLI

Install the umbrella CLI and run granular migrate:

npm install -g @granularjs/cli   # or: npx @granularjs/cli ...

# Basic: writes the migrated copy to "<source>-granular" next to your source
granular migrate ./my-react-app

# Explicit output path
granular migrate ./my-react-app --out ./my-granular-app

# Preview only (no files touched, no destination created)
granular migrate ./my-react-app --dry-run

# Overwrite an existing destination
granular migrate ./my-react-app --out ./out --force

The migration is always non-destructive: the source folder is never modified. The CLI clones the project to the destination first (skipping node_modules, dist, build caches, etc.) and runs every codemod plus dependency/config rewrites against the clone. A MIGRATION_REPORT.md is written to the destination, with diff instructions to compare against the source.

Steps the migration runs (use --steps / --skip to control them): discover, clone, deps, config, codemods, lint, audit, report.

Use individual transforms

npx granular-codemod useState-to-state ./src
npx granular-codemod array-map-to-list ./src/components/List.jsx
npx granular-codemod react-imports ./src

Available transforms

Reactivity

Transform What it does
useState-to-state const [x, setX] = useState(0)const x = state(0); rewrites setX(v) and setX(prev => …). Surviving shorthand references to the setter (e.g. {x, setX}) are expanded to {x, setX: x.set}.
useRef-to-state const ref = useRef(null)const ref = state(null); rewrites ref.current reads/writes.
useMemo-to-derive useMemo(() => expr, [deps])derive(() => expr) (deps inferred reactively).
useEffect-to-after useEffect(fn, [a, b])after(a, b).change((a, b) => fn(...)). When deps are simple identifiers and the callback has no params, the dep names are bound as the callback parameters so the body keeps using each dep as a plain value. Empty/missing deps become a one-shot call with a TODO.
useCallback-remove useCallback(fn, deps)fn.
useContext-to-context createContext(default)context(default); useContext(Ctx)Ctx.state(); <Ctx.Provider value={x}>{children}</Ctx.Provider>Ctx.scope(x).serve(children).
setState-updater Catch-all for setX(prev => …) patterns left over after the previous transforms.

Rendering

Transform What it does
array-map-to-list arr.map(x => <Item …/>)list(arr, x => <Item …/>, { key }).
conditional-jsx-to-when {cond && <X/>} and {cond ? <X/> : <Y/>} inside JSX → when(cond, () => <X/>, …) when cond is a known reactive source. Ambiguous cases get a TODO comment.
react-imports Removes react / react-dom imports; rewrites createRoot(el).render(<App/>) and ReactDOM.render(<App/>, el) to bootstrap(<App/>, el).

Project config

Transform What it does
tsconfig Sets compilerOptions.jsx = "react-jsx" and compilerOptions.jsxImportSource = "@granularjs/jsx" in tsconfig.json.
vite-config Removes @vitejs/plugin-react; adds esbuild.jsx = 'automatic' and esbuild.jsxImportSource = '@granularjs/jsx'.
package-json Removes React deps; adds @granularjs/core and @granularjs/jsx.

Programmatic API

const { runTransformOnSource, runAll } = require('@granularjs/codemods/runner');

const out = runTransformOnSource('useState-to-state', source, { path: 'Counter.jsx' });

License

Apache-2.0

About

Codemods to migrate React projects to @granularjs/core + @granularjs/jsx (jscodeshift)

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors