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
161 changes: 161 additions & 0 deletions pages/userland-migrations/axios-to-whatwg-fetch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
---
authors: brunocroh, AugustinMauroy
---

# Axios to WHATWG Fetch

Migrates code from the [Axios](https://axios-http.com) HTTP client to the [WHATWG Fetch](https://fetch.spec.whatwg.org) API that is natively available in Node.js as the global [`fetch`](https://nodejs.org/docs/latest/api/globals.html#fetch), reducing dependencies and improving performance. It rewrites every Axios request helper — `axios.request()`, `axios.get()`, `axios.delete()`, `axios.head()`, `axios.options()`, `axios.post()`, `axios.put()`, `axios.patch()`, `axios.postForm()`, `axios.putForm()`, and `axios.patchForm()` — and recognizes default ESM imports, aliased imports, CommonJS `require()` calls, and dynamic `import()`. Once all call sites are converted, it also removes the `axios` and `@types/axios` entries from `package.json`.

## Usage

Run this codemod with:

```sh
npx codemod @nodejs/axios-to-whatwg-fetch
```

## Examples

### GET request

A plain `axios.get()` becomes a `fetch()` call with a shim that keeps the `response.data` property working.

```diff
-import axios from "axios";
const base = "https://dummyjson.com/todos";

-const all = await axios.get(base);
+const all = await fetch(base)
+ .then(async (res) => Object.assign(res, { data: await res.json() }))
+ .catch(() => null);
console.log("\nGET /todos ->", all.status);
console.log(`Preview: ${all.data.todos.length} todos`);
```

### POST request with a JSON body

The `data` argument of `axios.post()` is serialized with `JSON.stringify()` and passed as the `body` option.

```diff
-import axios from 'axios';
const base = 'https://dummyjson.com/todos/add';

-const todoCreated = await axios.post(base, {
- todo: 'Use DummyJSON in the project',
- completed: false,
- userId: 5,
-});
+const todoCreated = await fetch(base, {
+ method: "POST",
+ body: JSON.stringify({
+ todo: 'Use DummyJSON in the project',
+ completed: false,
+ userId: 5,
+ })
+})
+ .then(async (resp) => Object.assign(resp, { data: await resp.json() }))
+ .catch(() => null);
console.log('\nPOST /todos ->', todoCreated);
```

### Form submission

`axios.postForm()` (and the `putForm`/`patchForm` variants) send the payload as `URLSearchParams`.

```diff
-import axios from 'axios';
const base = 'https://dummyjson.com/forms';

-const created = await axios.postForm(`${base}/submit`, {
- title: 'Form Demo',
- completed: false,
-});
+const created = await fetch(`${base}/submit`, {
+ method: "POST",
+ body: new URLSearchParams({
+ title: 'Form Demo',
+ completed: false,
+ })
+})
+ .then(async (resp) => Object.assign(resp, { data: await resp.json() }))
+ .catch(() => null);
console.log(created);
```

### `axios.request()` with a config object

The `url`, `method`, and `data` properties of the config object are mapped onto the `fetch()` call.

```diff
-import axios from 'axios';
-
const base = 'https://dummyjson.com/todos/1';

-const customRequest = await axios.request({
- url: base,
- method: 'PATCH',
- data: {
- todo: 'Updated todo',
- completed: true,
- },
-});
+const customRequest = await fetch(base, {
+ method: "PATCH",
+ body: JSON.stringify({
+ todo: 'Updated todo',
+ completed: true,
+ })
+})
+ .then(async (resp) => Object.assign(resp, { data: await resp.json() }))
+ .catch(() => null);
console.log('\nREQUEST /todos/1 ->', customRequest);
```

### CommonJS `require()`

CommonJS modules are handled the same way, and the now-unused `require('axios')` binding is removed.

```diff
-const axios = require('axios');

function fetchAllTodos() {
- return axios.get('https://dummyjson.com/todos');
+ return fetch('https://dummyjson.com/todos')
+ .then(async (res) => Object.assign(res, { data: await res.json() }))
+ .catch(() => null);
}

module.exports = { fetchAllTodos };
```

## Notes

- A `fetch` response exposes its payload through `res.json()` rather than a `data` property, so each converted call is followed by `.then(async (res) => Object.assign(res, { data: await res.json() }))` to keep existing `response.data` accesses working.
- Converted calls end with `.catch(() => null)`, so a failed request resolves to `null` instead of rejecting. Also note that unlike Axios, `fetch` does not reject on HTTP error statuses (4xx/5xx), so error-handling code built around Axios rejections should be reviewed manually.
- Safety first: if any Axios call in a file uses an unsupported configuration option, the entire file is left untouched and a warning with the source location is printed, preserving the original behavior.
- After the transformation, the codemod detects your package manager and removes the `axios` and `@types/axios` dependencies from `package.json`.

### Limitations

The codemod skips files whose Axios calls use any of the following configuration options, because they have no direct `fetch` equivalent:

- `beforeRedirect`
- `cancelToken`
- `decompress`
- `httpAgent`
- `httpsAgent`
- `maxBodyLength`
- `maxContentLength`
- `maxRedirects`
- `paramsSerializer`
- `signal`
- `socketPath`
- `timeout`
- `transformRequest`
- `transformResponse`
- `validateStatus`
- `withCredentials`

It also does not cover Axios features outside of the direct request helpers, such as interceptors, cancel tokens, or instance configuration created with `axios.create()`.

<!-- sync_to_learn: true -->
75 changes: 75 additions & 0 deletions pages/userland-migrations/chalk-to-util-styletext.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
---
authors: richiemccoll
---

# Chalk to `util.styleText()`

Migrates usage of the `chalk` npm package to the Node.js built-in `util.styleText()` API. Replaces the `chalk` import with `{ styleText }` from `node:util` and rewrites all chalk method calls accordingly. Chained chalk styles are converted to an array of style strings.

## Usage

Run this codemod with:

```sh
npx codemod @nodejs/chalk-to-util-styletext
```

## Examples

### Example 1

Basic color methods (ESM default import)

```diff
-import chalk from "chalk";
+import { styleText } from "node:util";

-console.log(chalk.red("Error message"));
-console.log(chalk.green("Success message"));
-console.log(chalk.blue("Info message"));
+console.log(styleText("red", "Error message"));
+console.log(styleText("green", "Success message"));
+console.log(styleText("blue", "Info message"));
```

### Example 2

Chained styles

```diff
-import chalk from "chalk";
+import { styleText } from "node:util";

-console.log(chalk.red.bold("Error: Operation failed"));
-console.log(chalk.green.underline("Success: All tests passed"));
-console.log(chalk.yellow.bgBlack("Warning: Deprecated API usage"));
+console.log(styleText(["red", "bold"], "Error: Operation failed"));
+console.log(styleText(["green", "underline"], "Success: All tests passed"));
+console.log(styleText(["yellow", "bgBlack"], "Warning: Deprecated API usage"));
```

### Example 3

CommonJS `require`

```diff
-const chalk = require("chalk");
+const { styleText } = require("node:util");

-const error = chalk.red("Error");
-const warning = chalk.yellow("Warning");
-const info = chalk.blue("Info");
+const error = styleText("red", "Error");
+const warning = styleText("yellow", "Warning");
+const info = styleText("blue", "Info");

console.log(error, warning, info);
```

## Notes

### Limitations

Chalk methods that have no direct `util.styleText` equivalent — including `hex()`, `rgb()`, `ansi256()`, `bgAnsi256()`, `visible()`, and `new chalk.Chalk()` — are skipped. A warning is printed for each unsupported call, and those call sites are left unchanged for manual review.

<!-- sync_to_learn: true -->
101 changes: 101 additions & 0 deletions pages/userland-migrations/correct-ts-specifiers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
---
authors: JakobJingleheimer
---

# Correct TypeScript Specifiers

Transforms import specifiers from the old `tsc` (TypeScript's compiler) requirement of using `.js` file extensions in source-code to import files that are actually TypeScript; the corrected specifiers enable source-code to be runnable by standards-compliant software like Node.js. This is a one-and-done process, and the updated source-code should be committed to your version control (eg git); thereafter, source-code import statements should be authored compliant with the ECMAScript (JavaScript) standard.

Supported cases:

- no file extension → `.cts`, `.mts`, `.js`, `.ts`, `.d.cts`, `.d.mts`, or `.d.ts`
- `.cjs` → `.cts`, `.mjs` → `.mts`, `.js` → `.ts`
- `.js` → `.d.cts`, `.d.mts`, or `.d.ts`
- [Package.json subpath imports](https://nodejs.org/api/packages.html#subpath-imports)
- [tsconfig paths](https://www.typescriptlang.org/tsconfig/#paths) (via [`@nodejs-loaders/alias`](https://github.com/JakobJingleheimer/nodejs-loaders/blob/main/packages/alias?tab=readme-ov-file))
- In order to subsequently run code via node, you will need to add this (or another) loader to your own project. Or, switch to [subimports](https://nodejs.org/api/packages.html#subpath-imports).
- Commonjs-like directory specifiers

## Usage

> [!CAUTION]
> This will change your source-code. Commit any unsaved changes before running this package.

> [!IMPORTANT]
> [`--experimental-import-meta-resolve`](https://nodejs.org/api/cli.html#--experimental-import-meta-resolve) MUST be enabled; the feature is not really experimental—it's nonstandard because it's not relevant for browsers.

Run this codemod with:

```sh
NODE_OPTIONS="--experimental-import-meta-resolve" \
npx codemod @nodejs/correct-ts-specifiers
```

### Monorepos

For best results, run this _within_ each workspace of the monorepo.

```text
project-root/
├ workspaces/
├ foo/ ←--------- RUN HERE
├ …
├ package.json
└ tsconfig.json
└ bar/ ←--------- RUN HERE
├ …
├ package.json
└ tsconfig.json
└ utils/ ←--------- RUN HERE
├ qux.js
└ zed.js
```

## Examples

```diff
import { URL } from 'node:url';

import { bar } from '@dep/bar';
import { foo } from 'foo';

-import { Bird } from './Bird';
+import { Bird } from './Bird/index.ts';
import { Cat } from './Cat.ts';
-import { Dog } from '…/Dog/index.mjs';
+import { Dog } from '…/Dog/index.mts';
import { baseUrl } from '#config.js';
-import { qux } from './qux.js';
+import { qux } from './qux.js/index.ts';

-export { Zed } from './zed';
+export type { Zed } from './zed.d.ts';

-const nil = await import('./nil.js');
+const nil = await import('./nil.ts');
```

> [!TIP]
> Those using `tsc` to compile will need to enable [`rewriteRelativeImportExtensions`](https://www.typescriptlang.org/tsconfig/#rewriteRelativeImportExtensions); using `tsc` for only type-checking (ex via a lint/test step like `npm run test:types`) needs [`allowImportingTsExtensions`](https://www.typescriptlang.org/tsconfig/#allowImportingTsExtensions) (and some additional compile options—see the cited documentation);

## Notes

This package does not just blindly find & replace file extensions within specifiers: It confirms that the replacement specifier actually exists; in ambiguous cases (such as two files with the same basename in the same location but different relevant file extensions like `/tmp/foo.js` and `/tmp/foo.ts`), it logs an error, skips that specifier, and continues processing.

> [!CAUTION]
> This package does not confirm that imported modules contain the desired export(s). This _shouldn't_ actually ever result in a problem because ambiguous cases are skipped (so if there is a problem, it existed before the migration started). Merely running your source-code after the migration completes will confirm all is well (if there are problems, node will error, citing the problems).

> [!TIP]
> Node.js requires the `type` keyword be present on type imports. For own code, this package usually handles that. However, in some cases and for node modules, it does not. Robust tooling already exists that will automatically fix this, such as
>
> - [`use-import-type` via biome](https://biomejs.dev/linter/rules/use-import-type/)
> - [`typescript/no-import-type-side-effects` via oxlint](https://oxc.rs/docs/guide/usage/linter/rules/typescript/no-import-type-side-effects)
> - [`consistent-type-imports` via typescript-lint](https://typescript-eslint.io/rules/consistent-type-imports)
>
> If your source code needs that, first run this codemod and then one of those fixers.

### Limitations

When both a `.js` file and a corresponding `.ts` file exist at the same path, the codemod cannot determine which one the specifier refers to. In that case it logs an error, leaves the specifier unchanged, and continues processing the rest of the file.

<!-- sync_to_learn: true -->
Loading
Loading