@granularjs/codemods
v0.2.1
Published
Codemods to migrate React projects to @granularjs/core + @granularjs/jsx. Powered by jscodeshift.
Maintainers
Readme
@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/codemodsUse 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 --forceThe 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 ./srcAvailable 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
