depshred 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +237 -200
- package/package.json +1 -1
- package/src/cli.js +2 -2
- package/src/interactive.js +58 -15
- package/src/locator.js +185 -7
- package/src/registry.js +2 -2
- package/src/reporter.js +1 -1
- package/src/rules/alt-rules.js +20 -2
package/README.md
CHANGED
|
@@ -1,201 +1,238 @@
|
|
|
1
|
-
<p align="center">
|
|
2
|
-
<img src="assets/depshred.webp" width="200" alt="depshred logo">
|
|
3
|
-
</p>
|
|
4
|
-
|
|
5
|
-
<h1 align="center">depshred</h1>
|
|
6
|
-
|
|
7
|
-
<p align="center">
|
|
8
|
-
<em>Clean up your node_modules. Stop installing stuff Node already has.</em>
|
|
9
|
-
</p>
|
|
10
|
-
|
|
11
|
-
<p align="center">
|
|
12
|
-
<img src="https://img.shields.io/badge/run_with-npx_depshred-111111?style=flat-square" alt="Run with npx">
|
|
13
|
-
<img src="https://img.shields.io/badge/node.js-18%2B_%7C_20%2B_%7C_22%2B-111111?style=flat-square" alt="Node Support">
|
|
14
|
-
<img src="https://img.shields.io/badge/version-1.0.
|
|
15
|
-
<img src="https://img.shields.io/badge/license-MIT-111111?style=flat-square" alt="MIT license">
|
|
16
|
-
</p>
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
Ever look at your `node_modules` folder and wonder why it takes up half your hard drive?
|
|
21
|
-
|
|
22
|
-
When you follow tutorials or ask AI to write code, it is super easy to end up with 30 different packages installed for things modern Node can already do by itself. Stuff like `dotenv`, `uuid`, or `rimraf` do not need to be in your project anymore. Node already knows how to do all of that.
|
|
23
|
-
|
|
24
|
-
`depshred` looks through your project, finds packages you do not actually need, and shows you how to swap them for simple code that comes with Node.
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
## Quick start
|
|
29
|
-
|
|
30
|
-
You do not even need to install anything first. Just open your terminal inside your project folder and run:
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
npx depshred
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
If your project lives in another folder, just tell it where:
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
npx depshred ./path/to/your-project
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Using pnpm or bun instead of npm? That works too:
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
pnpm dlx depshred
|
|
46
|
-
bunx depshred
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
## What it looks like
|
|
52
|
-
|
|
53
|
-
Here is what you see when you run it in your terminal:
|
|
54
|
-
|
|
55
|
-
<p align="center">
|
|
56
|
-
<img src="assets/view01.webp" width="100%" alt="depshred terminal interface">
|
|
57
|
-
</p>
|
|
58
|
-
|
|
59
|
-
You get an easy list you can move through with your arrow keys:
|
|
60
|
-
|
|
61
|
-
```text
|
|
62
|
-
+--------------------------------------------------------------------------+
|
|
63
|
-
| depshred v1.0.
|
|
64
|
-
| Target: ./my-project |
|
|
65
|
-
+--------------------------------------------------------------------------+
|
|
66
|
-
| Total Scanned: 24 dependencies |
|
|
67
|
-
| |
|
|
68
|
-
| [CRITICAL] 2 packages have native Node.js replacements (saves ~182 KB) |
|
|
69
|
-
| [UNUSED] 1 package has 0 imports (run: npm uninstall rimraf) |
|
|
70
|
-
| [WARN] 1 package is dormant >3y (0 sub-deps, self-contained) |
|
|
71
|
-
| [OK] 20 packages are lean and actively maintained |
|
|
72
|
-
+--------------------------------------------------------------------------+
|
|
73
|
-
|
|
74
|
-
Select a dependency to inspect (Use arrow keys, Enter to inspect):
|
|
75
|
-
> [CRITICAL] dotenv Native Node.js built-in available (saves ~72 KB)
|
|
76
|
-
[CRITICAL] uuid Native Node.js built-in available (saves ~110 KB)
|
|
77
|
-
[UNUSED] rimraf 0 imports found in project source code
|
|
78
|
-
[WARN] ms Dormant >3 years | 0 sub-dependencies (self-contained)
|
|
79
|
-
[OK] fastify Active, schema-compiled and lean
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
---
|
|
83
|
-
|
|
84
|
-
## What it checks for
|
|
85
|
-
|
|
86
|
-
When you run `depshred`, it checks three main things:
|
|
87
|
-
|
|
88
|
-
1. **Stuff Node already has built in**
|
|
89
|
-
Do you really need `dotenv` when Node can load `.env` files on its own? Do you need `uuid` when Node has `crypto.randomUUID()`? `depshred` finds every file and exact line number where you use these, and shows you the quick built-in way to replace them.
|
|
90
|
-
|
|
91
|
-
2. **Packages you installed but never use**
|
|
92
|
-
Ever ran `npm install` for a test or a guide, forgot about it, and left it sitting in your `package.json`? `depshred` scans your whole project. If you never imported it anywhere, it tells you straight up: `npm uninstall <package>`.
|
|
93
|
-
|
|
94
|
-
3. **Old or abandoned packages**
|
|
95
|
-
It checks npm to see how big each package is and whether anyone has touched it in the last 3 years. If a package is small, has zero other packages inside it, and just works, `depshred` lets you know so you do not have to worry.
|
|
96
|
-
|
|
97
|
-
```text
|
|
98
|
-
Your package.json
|
|
99
|
-
|
|
|
100
|
-
+------------------+------------------+
|
|
101
|
-
| | |
|
|
102
|
-
v v v
|
|
103
|
-
1. Already in Node? 2. Used in code? 3. npm check
|
|
104
|
-
(dotenv, uuid, etc.) (0 imports found) (file size, age)
|
|
105
|
-
| | |
|
|
106
|
-
v v v
|
|
107
|
-
Use Node built-ins Safe to delete Good to know
|
|
108
|
-
(saves disk space) (npm uninstall) (see how big it is)
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
|
123
|
-
|
|
|
124
|
-
| `--
|
|
125
|
-
|
|
|
126
|
-
|
|
|
127
|
-
|
|
|
128
|
-
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/gozajpeg/depshred/master/assets/depshred.webp" width="200" alt="depshred logo">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">depshred</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<em>Clean up your node_modules. Stop installing stuff Node already has.</em>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<img src="https://img.shields.io/badge/run_with-npx_depshred-111111?style=flat-square" alt="Run with npx">
|
|
13
|
+
<img src="https://img.shields.io/badge/node.js-18%2B_%7C_20%2B_%7C_22%2B-111111?style=flat-square" alt="Node Support">
|
|
14
|
+
<img src="https://img.shields.io/badge/version-1.0.2-111111?style=flat-square" alt="version 1.0.0">
|
|
15
|
+
<img src="https://img.shields.io/badge/license-MIT-111111?style=flat-square" alt="MIT license">
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
Ever look at your `node_modules` folder and wonder why it takes up half your hard drive?
|
|
21
|
+
|
|
22
|
+
When you follow tutorials or ask AI to write code, it is super easy to end up with 30 different packages installed for things modern Node can already do by itself. Stuff like `dotenv`, `uuid`, or `rimraf` do not need to be in your project anymore. Node already knows how to do all of that.
|
|
23
|
+
|
|
24
|
+
`depshred` looks through your project, finds packages you do not actually need, and shows you how to swap them for simple code that comes with Node.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
You do not even need to install anything first. Just open your terminal inside your project folder and run:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx depshred
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
If your project lives in another folder, just tell it where:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx depshred ./path/to/your-project
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Using pnpm or bun instead of npm? That works too:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pnpm dlx depshred
|
|
46
|
+
bunx depshred
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## What it looks like
|
|
52
|
+
|
|
53
|
+
Here is what you see when you run it in your terminal:
|
|
54
|
+
|
|
55
|
+
<p align="center">
|
|
56
|
+
<img src="https://raw.githubusercontent.com/gozajpeg/depshred/master/assets/view01.webp" width="100%" alt="depshred terminal interface">
|
|
57
|
+
</p>
|
|
58
|
+
|
|
59
|
+
You get an easy list you can move through with your arrow keys:
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
+--------------------------------------------------------------------------+
|
|
63
|
+
| depshred v1.0.2 (Node v20.12.0) |
|
|
64
|
+
| Target: ./my-project |
|
|
65
|
+
+--------------------------------------------------------------------------+
|
|
66
|
+
| Total Scanned: 24 dependencies |
|
|
67
|
+
| |
|
|
68
|
+
| [CRITICAL] 2 packages have native Node.js replacements (saves ~182 KB) |
|
|
69
|
+
| [UNUSED] 1 package has 0 imports (run: npm uninstall rimraf) |
|
|
70
|
+
| [WARN] 1 package is dormant >3y (0 sub-deps, self-contained) |
|
|
71
|
+
| [OK] 20 packages are lean and actively maintained |
|
|
72
|
+
+--------------------------------------------------------------------------+
|
|
73
|
+
|
|
74
|
+
Select a dependency to inspect (Use arrow keys, Enter to inspect):
|
|
75
|
+
> [CRITICAL] dotenv Native Node.js built-in available (saves ~72 KB)
|
|
76
|
+
[CRITICAL] uuid Native Node.js built-in available (saves ~110 KB)
|
|
77
|
+
[UNUSED] rimraf 0 imports found in project source code
|
|
78
|
+
[WARN] ms Dormant >3 years | 0 sub-dependencies (self-contained)
|
|
79
|
+
[OK] fastify Active, schema-compiled and lean
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## What it checks for
|
|
85
|
+
|
|
86
|
+
When you run `depshred`, it checks three main things:
|
|
87
|
+
|
|
88
|
+
1. **Stuff Node already has built in**
|
|
89
|
+
Do you really need `dotenv` when Node can load `.env` files on its own? Do you need `uuid` when Node has `crypto.randomUUID()`? `depshred` finds every file and exact line number where you use these, and shows you the quick built-in way to replace them.
|
|
90
|
+
|
|
91
|
+
2. **Packages you installed but never use**
|
|
92
|
+
Ever ran `npm install` for a test or a guide, forgot about it, and left it sitting in your `package.json`? `depshred` scans your whole project. If you never imported it anywhere, it tells you straight up: `npm uninstall <package>`.
|
|
93
|
+
|
|
94
|
+
3. **Old or abandoned packages**
|
|
95
|
+
It checks npm to see how big each package is and whether anyone has touched it in the last 3 years. If a package is small, has zero other packages inside it, and just works, `depshred` lets you know so you do not have to worry.
|
|
96
|
+
|
|
97
|
+
```text
|
|
98
|
+
Your package.json
|
|
99
|
+
|
|
|
100
|
+
+------------------+------------------+
|
|
101
|
+
| | |
|
|
102
|
+
v v v
|
|
103
|
+
1. Already in Node? 2. Used in code? 3. npm check
|
|
104
|
+
(dotenv, uuid, etc.) (0 imports found) (file size, age)
|
|
105
|
+
| | |
|
|
106
|
+
v v v
|
|
107
|
+
Use Node built-ins Safe to delete Good to know
|
|
108
|
+
(saves disk space) (npm uninstall) (see how big it is)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## What packages can depshred replace?
|
|
115
|
+
|
|
116
|
+
To be completely honest: no tool can replace every single package in the world. Node does not have a built-in 3D engine to replace `three.js` or a built-in database to replace `pg`.
|
|
117
|
+
|
|
118
|
+
Instead, `depshred` targets the **most common sources of accidental bloat**: legacy packages that modern Node already includes for free, and heavy monolithic libraries with modern lightweight replacements.
|
|
119
|
+
|
|
120
|
+
### 1. Modern Node.js built-ins (Zero packages needed)
|
|
121
|
+
|
|
122
|
+
| Legacy Package | Built-in Node Feature | Supported in | What replaces it |
|
|
123
|
+
| :--- | :--- | :--- | :--- |
|
|
124
|
+
| **`dotenv`** | `--env-file` | Node 20.6+ | `node --env-file=.env app.js` |
|
|
125
|
+
| **`uuid`** | `crypto.randomUUID()` | Node 14.17+ / 16.7+ | `crypto.randomUUID()` |
|
|
126
|
+
| **`rimraf`** | `fs.rmSync()` / `fs.rm()` | Node 14.14+ | `fs.rmSync(path, { recursive: true, force: true })` |
|
|
127
|
+
| **`mkdirp`** | `fs.mkdirSync()` / `fs.mkdir()` | Node 10.12+ | `fs.mkdirSync(path, { recursive: true })` |
|
|
128
|
+
| **`nodemon`** | `node --watch` | Node 18.11+ | `node --watch app.js` |
|
|
129
|
+
| **`axios`** | `fetch()` | Node 18+ | Native `fetch` and `AbortController` |
|
|
130
|
+
| **`node-fetch`** | `fetch()` | Node 18+ | Native `fetch` and `AbortController` |
|
|
131
|
+
| **`glob`** | `fs.glob()` | Node 22+ | Native file matching in `node:fs` |
|
|
132
|
+
| **`querystring`** | `URLSearchParams` | Node 10+ | Built-in `URLSearchParams` |
|
|
133
|
+
|
|
134
|
+
### 2. Lightweight modern alternatives
|
|
135
|
+
|
|
136
|
+
| Heavy Package | Suggested Alternative | What you save |
|
|
137
|
+
| :--- | :--- | :--- |
|
|
138
|
+
| **`zod`** | `valibot` or `arktype` | Saves ~5.5 MB unpacked (~90% bundle size reduction) |
|
|
139
|
+
| **`moment`** | `dayjs` or native `Intl` | Saves ~4 MB unpacked |
|
|
140
|
+
| **`lodash`** | `es-toolkit` or modern JS | Saves ~1.5 MB unpacked |
|
|
141
|
+
| **`chalk`** | `picocolors` | Saves ~100 KB and drops dependency chain |
|
|
142
|
+
| **`classnames`** | `clsx` | Drop-in API, faster, zero dependencies |
|
|
143
|
+
| **`bcrypt`** | `bcryptjs` or native `crypto` | Removes heavy C++ `node-gyp` build tools |
|
|
144
|
+
| **`deep-equal`** | `fast-deep-equal` | Sub-1KB, faster comparisons |
|
|
145
|
+
|
|
146
|
+
> *Everything else in your project is still scanned for 0-import unused packages, heavy sizes (>5MB), and unmaintained/abandoned code (>3 years old).*
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Extra options
|
|
151
|
+
|
|
152
|
+
Need to run it inside a script or change how it works? Here are the flags you can use:
|
|
153
|
+
|
|
154
|
+
| Flag | What it does |
|
|
155
|
+
| :--- | :--- |
|
|
156
|
+
| `--check` | Checks your project and stops with an error if it finds bad or useless packages. Great for GitHub Actions. |
|
|
157
|
+
| `--strict` | Like `--check`, but also stops on small warnings. |
|
|
158
|
+
| `--json` | Prints results as JSON so you can read them with code. |
|
|
159
|
+
| `--no-interactive` | Skips the arrow-key menu and just prints a plain table. |
|
|
160
|
+
| `--path <folder>` | Checks a different folder instead of where you are right now. |
|
|
161
|
+
| `--dormant-years <num>` | How many years without updates before calling a package old (defaults to 3). |
|
|
162
|
+
| `--ignore <names>` | Comma-separated names of packages you want to skip. |
|
|
163
|
+
| `--init-skill [app]` | Adds instructions to your AI editor so it stops installing packages you do not need. |
|
|
164
|
+
| `--help`, `-h` | Shows all available flags. |
|
|
165
|
+
| `--version`, `-v` | Shows the version number. |
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Teach your AI editor to stop installing junk
|
|
170
|
+
|
|
171
|
+
If you use AI coding tools like Cursor, Claude, Windsurf, or GitHub Copilot, you have probably seen them run `npm install` for every single little thing. Most of the time, Node already has what they are trying to download.
|
|
172
|
+
|
|
173
|
+
You can teach your AI helper to check first before installing:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
# Pick your editor from a menu:
|
|
177
|
+
npx depshred --init-skill
|
|
178
|
+
|
|
179
|
+
# Or pick your editor directly:
|
|
180
|
+
npx depshred --init-skill cursor # For Cursor (.cursor/rules)
|
|
181
|
+
npx depshred --init-skill copilot # For GitHub Copilot (.github)
|
|
182
|
+
npx depshred --init-skill windsurf # For Windsurf (.windsurf/rules)
|
|
183
|
+
npx depshred --init-skill claude # For Claude Code (CLAUDE.md)
|
|
184
|
+
npx depshred --init-skill cline # For Cline (.clinerules)
|
|
185
|
+
npx depshred --init-skill antigravity # For Antigravity (.agents/skills)
|
|
186
|
+
npx depshred --init-skill agents # For general agents (AGENTS.md)
|
|
187
|
+
npx depshred --init-skill all # Sets up all of them at once
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Want to see all the skill files and how they work? Check out [SKILLS.md](SKILLS.md).
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Save your settings
|
|
195
|
+
|
|
196
|
+
If there is a package you want `depshred` to leave alone, you can save your preferences right inside your `package.json`:
|
|
197
|
+
|
|
198
|
+
```json
|
|
199
|
+
{
|
|
200
|
+
"depshred": {
|
|
201
|
+
"ignore": ["fast-deep-equal"],
|
|
202
|
+
"dormantYears": 4
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## What projects work with it?
|
|
210
|
+
|
|
211
|
+
Pretty much any modern Node.js project:
|
|
212
|
+
|
|
213
|
+
- **Any framework:** Express, Fastify, Next.js, NestJS, Koa, or plain Node scripts.
|
|
214
|
+
- **Any module style:** Both `import` (ES Modules) and `require` (CommonJS).
|
|
215
|
+
- **JavaScript and TypeScript:** `.js`, `.ts`, `.jsx`, `.tsx`, `.mjs`, `.cjs`.
|
|
216
|
+
- **Projects with multiple folders:** Works with monorepos using the `--path` flag.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Questions you might have
|
|
221
|
+
|
|
222
|
+
**Does it change my code automatically?**
|
|
223
|
+
No. `depshred` never touches your code behind your back. It shows you the file, the line number, and the built-in Node replacement. You decide what to change and when.
|
|
224
|
+
|
|
225
|
+
**Does it need a database or send my code to some server?**
|
|
226
|
+
No. Everything runs right on your computer. It only talks to the public npm website to check package sizes and update dates. It saves that info in your computer's temporary folder for a day so it stays fast.
|
|
227
|
+
|
|
228
|
+
**What if an old package works fine and does not need updates?**
|
|
229
|
+
Not every old package is broken. Some small tools are simply finished and do not need changes. `depshred` checks if the package has zero sub-packages. If it is small and self-contained, it gives you a soft heads-up, not a red error. You can also add it to your ignore list.
|
|
230
|
+
|
|
231
|
+
**Does it work with Python or Go?**
|
|
232
|
+
No, this is built only for Node.js and npm projects.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## License
|
|
237
|
+
|
|
201
238
|
[MIT](LICENSE).
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -33,7 +33,7 @@ ${pc.bold('EXAMPLES:')}
|
|
|
33
33
|
npx depshred
|
|
34
34
|
npx depshred --check
|
|
35
35
|
npx depshred --init-skill cursor
|
|
36
|
-
npx depshred ./
|
|
36
|
+
npx depshred ./path/to/your-project --json
|
|
37
37
|
`;
|
|
38
38
|
|
|
39
39
|
export async function run(rawArgs = []) {
|
|
@@ -77,7 +77,7 @@ export async function run(rawArgs = []) {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
if (values.version) {
|
|
80
|
-
console.log('depshred v1.0.
|
|
80
|
+
console.log('depshred v1.0.2');
|
|
81
81
|
return;
|
|
82
82
|
}
|
|
83
83
|
|
package/src/interactive.js
CHANGED
|
@@ -29,7 +29,7 @@ export async function runInteractive(auditData) {
|
|
|
29
29
|
process.stdout.write('\x1b[2J\x1b[3J\x1b[H');
|
|
30
30
|
|
|
31
31
|
console.log(pc.bold(pc.white('+--------------------------------------------------------------------------+')));
|
|
32
|
-
console.log(pc.bold(pc.white(`| depshred v1.0.
|
|
32
|
+
console.log(pc.bold(pc.white(`| depshred v1.0.2 (Target Node: ${targetNode})`)));
|
|
33
33
|
console.log(pc.bold(pc.white(`| Project: ${projectName} (${totalDependencies} dependencies scanned)`)));
|
|
34
34
|
console.log(pc.bold(pc.white('+--------------------------------------------------------------------------+')));
|
|
35
35
|
console.log(pc.red(`| [CRITICAL] ${summary.critical} native replacements (saves ~${formatBytes(totalSaveableBytes)})`));
|
|
@@ -41,27 +41,70 @@ export async function runInteractive(auditData) {
|
|
|
41
41
|
|
|
42
42
|
if (inspecting) {
|
|
43
43
|
const item = results[selectedIndex];
|
|
44
|
-
console.log(pc.bold(
|
|
45
|
-
console.log(
|
|
46
|
-
console.log(
|
|
47
|
-
console.log(`
|
|
44
|
+
console.log(pc.bold(pc.white('+--------------------------------------------------------------------------+')));
|
|
45
|
+
console.log(pc.bold(pc.white(`| INSPECTING: ${item.name} (${item.tier})`)));
|
|
46
|
+
console.log(pc.bold(pc.white('+--------------------------------------------------------------------------+')));
|
|
47
|
+
console.log(`Summary: ${item.details}`);
|
|
48
|
+
console.log(`Unpacked Size: ${formatBytes(item.size)} | Sub-dependencies: ${item.dependenciesCount}`);
|
|
49
|
+
console.log(`Type: ${item.isDev ? 'Development dependency (devDependencies)' : 'Production dependency (dependencies)'}`);
|
|
48
50
|
console.log('');
|
|
49
51
|
|
|
52
|
+
// Critical items: show Before/After diff and uninstall command
|
|
53
|
+
if (item.tier === 'CRITICAL') {
|
|
54
|
+
if (item.diff) {
|
|
55
|
+
console.log(pc.bold('Replacement Solution (Modern Node.js Built-in):'));
|
|
56
|
+
console.log(pc.green(item.diff));
|
|
57
|
+
console.log('');
|
|
58
|
+
}
|
|
59
|
+
console.log(pc.bold('Action:'));
|
|
60
|
+
console.log(pc.cyan(` npm uninstall ${item.name}`));
|
|
61
|
+
console.log(pc.dim(` Saves ~${formatBytes(item.size)} unpacked disk space.`));
|
|
62
|
+
console.log('');
|
|
63
|
+
}
|
|
50
64
|
|
|
65
|
+
// Unused items: show clear uninstall command and ignore instructions
|
|
66
|
+
if (item.tier === 'UNUSED') {
|
|
67
|
+
console.log(pc.bold('Recommended Action:'));
|
|
68
|
+
console.log(pc.cyan(` npm uninstall ${item.name}`));
|
|
69
|
+
console.log(pc.dim(` Zero imports or config references found in project.`));
|
|
70
|
+
console.log(pc.dim(` To permanently ignore, add to package.json: "depshred": { "ignore": ["${item.name}"] }`));
|
|
71
|
+
console.log('');
|
|
72
|
+
}
|
|
51
73
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
console.log(pc.
|
|
74
|
+
// Warning items: show lighter alternatives or dormancy details
|
|
75
|
+
if (item.tier === 'WARN') {
|
|
76
|
+
if (item.altRule) {
|
|
77
|
+
console.log(pc.bold('Recommended Lighter Alternative:'));
|
|
78
|
+
console.log(` Suggested: ${pc.cyan(item.altRule.lighter)}`);
|
|
79
|
+
console.log(` Savings: ${pc.green(item.altRule.savings)}`);
|
|
80
|
+
console.log(` Details: ${item.altRule.description}`);
|
|
81
|
+
console.log('');
|
|
82
|
+
} else if (item.size > 5 * 1024 * 1024) {
|
|
83
|
+
console.log(pc.bold('Size Optimization Note:'));
|
|
84
|
+
console.log(pc.yellow(` Heavy package (${formatBytes(item.size)}). Check if dynamic import() or tree-shaking can reduce bundle weight.`));
|
|
85
|
+
console.log('');
|
|
56
86
|
}
|
|
57
|
-
|
|
58
|
-
|
|
87
|
+
|
|
88
|
+
if (item.yearsDormant && item.yearsDormant >= 3) {
|
|
89
|
+
console.log(pc.bold('Maintenance Assessment:'));
|
|
90
|
+
if (item.selfContained) {
|
|
91
|
+
console.log(pc.dim(` Last update >${item.yearsDormant} years ago, but 0 sub-dependencies (self-contained and stable).`));
|
|
92
|
+
} else {
|
|
93
|
+
console.log(pc.yellow(` Last update >${item.yearsDormant} years ago with ${item.dependenciesCount} sub-dependencies.`));
|
|
94
|
+
}
|
|
95
|
+
console.log('');
|
|
59
96
|
}
|
|
60
|
-
console.log('');
|
|
61
97
|
}
|
|
62
98
|
|
|
63
|
-
|
|
64
|
-
|
|
99
|
+
// Show where imports or references were found
|
|
100
|
+
if (item.imports && item.imports.length > 0) {
|
|
101
|
+
console.log(pc.bold(`References in project (${item.imports.length}):`));
|
|
102
|
+
for (const imp of item.imports.slice(0, 8)) {
|
|
103
|
+
console.log(pc.dim(` - ${imp.file}:${imp.line} -> ${imp.snippet}`));
|
|
104
|
+
}
|
|
105
|
+
if (item.imports.length > 8) {
|
|
106
|
+
console.log(pc.dim(` ... and ${item.imports.length - 8} more references`));
|
|
107
|
+
}
|
|
65
108
|
console.log('');
|
|
66
109
|
}
|
|
67
110
|
|
|
@@ -136,4 +179,4 @@ export async function runInteractive(auditData) {
|
|
|
136
179
|
process.stdin.resume();
|
|
137
180
|
render();
|
|
138
181
|
});
|
|
139
|
-
}
|
|
182
|
+
}
|
package/src/locator.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
2
|
-
import { join, relative, extname } from 'node:path';
|
|
2
|
+
import { join, relative, extname, basename } from 'node:path';
|
|
3
3
|
|
|
4
|
-
const SUPPORTED_EXTENSIONS = new Set(['.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx']);
|
|
4
|
+
const SUPPORTED_EXTENSIONS = new Set(['.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx', '.vue', '.svelte']);
|
|
5
5
|
const IGNORED_DIRECTORIES = new Set([
|
|
6
6
|
'node_modules',
|
|
7
7
|
'.git',
|
|
@@ -17,6 +17,25 @@ const IGNORED_DIRECTORIES = new Set([
|
|
|
17
17
|
'.windsurf'
|
|
18
18
|
]);
|
|
19
19
|
|
|
20
|
+
const CONFIG_FILE_PATTERNS = [
|
|
21
|
+
/^\.babelrc(\.json|\.js|\.cjs|\.mjs)?$/,
|
|
22
|
+
/^babel\.config(\.json|\.js|\.cjs|\.mjs)?$/,
|
|
23
|
+
/^\.eslintrc(\.json|\.js|\.cjs|\.yaml|\.yml)?$/,
|
|
24
|
+
/^eslint\.config(\.js|\.mjs|\.cjs)?$/,
|
|
25
|
+
/^\.prettierrc(\.json|\.js|\.cjs|\.yaml|\.yml)?$/,
|
|
26
|
+
/^prettier\.config(\.js|\.mjs|\.cjs)?$/,
|
|
27
|
+
/^tsconfig(\..+)?\.json$/,
|
|
28
|
+
/^jsconfig(\..+)?\.json$/,
|
|
29
|
+
/^postcss\.config(\.js|\.cjs|\.mjs|\.json)?$/,
|
|
30
|
+
/^tailwind\.config(\.js|\.cjs|\.mjs|\.ts)?$/,
|
|
31
|
+
/^vite\.config(\.js|\.ts|\.mjs)?$/,
|
|
32
|
+
/^webpack\.config(\.js|\.cjs|\.mjs|\.ts)?$/,
|
|
33
|
+
/^rollup\.config(\.js|\.mjs)?$/,
|
|
34
|
+
/^next\.config(\.js|\.mjs|\.ts)?$/,
|
|
35
|
+
/^jest\.config(\.js|\.ts|\.json)?$/,
|
|
36
|
+
/^vitest\.config(\.js|\.ts)?$/
|
|
37
|
+
];
|
|
38
|
+
|
|
20
39
|
/**
|
|
21
40
|
* Normalizes an import specifier to its root npm package name.
|
|
22
41
|
* e.g. "lodash/map" -> "lodash", "@scoped/pkg/deep" -> "@scoped/pkg"
|
|
@@ -80,10 +99,34 @@ async function getSourceFiles(dir) {
|
|
|
80
99
|
}
|
|
81
100
|
|
|
82
101
|
/**
|
|
83
|
-
*
|
|
102
|
+
* Find root-level config files in a directory.
|
|
103
|
+
*/
|
|
104
|
+
async function getRootConfigFiles(dir) {
|
|
105
|
+
const configFiles = [];
|
|
106
|
+
let entries;
|
|
107
|
+
try {
|
|
108
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
109
|
+
} catch {
|
|
110
|
+
return configFiles;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for (const entry of entries) {
|
|
114
|
+
if (entry.isFile()) {
|
|
115
|
+
const name = entry.name.toLowerCase();
|
|
116
|
+
if (CONFIG_FILE_PATTERNS.some((pat) => pat.test(name))) {
|
|
117
|
+
configFiles.push(entry.name);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return configFiles;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Scan project source files and config files to locate package imports and detect unused dependencies.
|
|
84
126
|
*
|
|
85
127
|
* @param {string} rootDir - Root directory of the project
|
|
86
128
|
* @param {string[]} declaredDependencies - List of package names from package.json
|
|
129
|
+
* @param {object} scripts - package.json scripts object
|
|
87
130
|
* @returns {Promise<{ importsMap: Map<string, Array<{file: string, line: number, snippet: string}>>, unusedPackages: string[], scannedFilesCount: number }>}
|
|
88
131
|
*/
|
|
89
132
|
export async function locateImports(rootDir, declaredDependencies = [], scripts = {}) {
|
|
@@ -91,7 +134,142 @@ export async function locateImports(rootDir, declaredDependencies = [], scripts
|
|
|
91
134
|
const importsMap = new Map();
|
|
92
135
|
const importedSet = new Set();
|
|
93
136
|
|
|
94
|
-
//
|
|
137
|
+
// Rule 1: Always ignore depshred itself (never flag depshred as unused within a project)
|
|
138
|
+
if (declaredDependencies.includes('depshred')) {
|
|
139
|
+
importedSet.add('depshred');
|
|
140
|
+
importsMap.set('depshred', [{
|
|
141
|
+
file: 'package.json',
|
|
142
|
+
line: 1,
|
|
143
|
+
snippet: 'depshred CLI dependency auditor'
|
|
144
|
+
}]);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Rule 2: Ambient TypeScript definitions (@types/*) are used by compiler, never imported in source code
|
|
148
|
+
for (const pkg of declaredDependencies) {
|
|
149
|
+
if (pkg.startsWith('@types/')) {
|
|
150
|
+
importedSet.add(pkg);
|
|
151
|
+
if (!importsMap.has(pkg)) importsMap.set(pkg, []);
|
|
152
|
+
importsMap.get(pkg).push({
|
|
153
|
+
file: 'tsconfig.json',
|
|
154
|
+
line: 1,
|
|
155
|
+
snippet: 'TypeScript ambient type definition'
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Rule 3: Bundler, linter, and build configuration plugins
|
|
161
|
+
for (const pkg of declaredDependencies) {
|
|
162
|
+
if (
|
|
163
|
+
pkg.startsWith('@vitejs/plugin-') ||
|
|
164
|
+
pkg.startsWith('vite-plugin-') ||
|
|
165
|
+
pkg.startsWith('@rollup/plugin-') ||
|
|
166
|
+
pkg.startsWith('rollup-plugin-') ||
|
|
167
|
+
pkg.startsWith('@rolldown/plugin-') ||
|
|
168
|
+
pkg.endsWith('-webpack-plugin') ||
|
|
169
|
+
pkg.startsWith('eslint-config-') ||
|
|
170
|
+
pkg.startsWith('eslint-plugin-') ||
|
|
171
|
+
pkg.startsWith('@eslint/') ||
|
|
172
|
+
pkg.startsWith('prettier-plugin-')
|
|
173
|
+
) {
|
|
174
|
+
importedSet.add(pkg);
|
|
175
|
+
if (!importsMap.has(pkg)) importsMap.set(pkg, []);
|
|
176
|
+
importsMap.get(pkg).push({
|
|
177
|
+
file: 'package.json',
|
|
178
|
+
line: 1,
|
|
179
|
+
snippet: 'Bundler or linter build plugin'
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Rule 4: Compiler engines and toolchain infrastructure
|
|
185
|
+
// Babel toolchain detection: @babel/core, plugins, presets, react-compiler
|
|
186
|
+
const hasBabelTool = declaredDependencies.some((p) =>
|
|
187
|
+
p.includes('babel') || p === '@vitejs/plugin-react' || p.includes('react-compiler')
|
|
188
|
+
);
|
|
189
|
+
if (hasBabelTool) {
|
|
190
|
+
for (const pkg of declaredDependencies) {
|
|
191
|
+
if (
|
|
192
|
+
pkg === '@babel/core' ||
|
|
193
|
+
pkg === '@babel/runtime' ||
|
|
194
|
+
pkg.startsWith('babel-plugin-') ||
|
|
195
|
+
pkg.startsWith('@babel/plugin-') ||
|
|
196
|
+
pkg.startsWith('babel-preset-') ||
|
|
197
|
+
pkg.startsWith('@babel/preset-') ||
|
|
198
|
+
pkg.includes('plugin-babel')
|
|
199
|
+
) {
|
|
200
|
+
importedSet.add(pkg);
|
|
201
|
+
if (!importsMap.has(pkg)) importsMap.set(pkg, []);
|
|
202
|
+
importsMap.get(pkg).push({
|
|
203
|
+
file: 'package.json',
|
|
204
|
+
line: 1,
|
|
205
|
+
snippet: 'Babel compiler infrastructure / plugin'
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// PostCSS toolchain detection: postcss, autoprefixer, tailwindcss
|
|
212
|
+
const hasPostcssTool = declaredDependencies.some((p) =>
|
|
213
|
+
p.includes('postcss') || p === 'tailwindcss' || p === 'autoprefixer'
|
|
214
|
+
);
|
|
215
|
+
if (hasPostcssTool) {
|
|
216
|
+
for (const pkg of declaredDependencies) {
|
|
217
|
+
if (pkg === 'postcss' || pkg.startsWith('postcss-')) {
|
|
218
|
+
importedSet.add(pkg);
|
|
219
|
+
if (!importsMap.has(pkg)) importsMap.set(pkg, []);
|
|
220
|
+
importsMap.get(pkg).push({
|
|
221
|
+
file: 'package.json',
|
|
222
|
+
line: 1,
|
|
223
|
+
snippet: 'PostCSS compiler infrastructure / plugin'
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Rule 5: Scan root configuration files for package names and plugin/preset references
|
|
230
|
+
const rootConfigFiles = await getRootConfigFiles(rootDir);
|
|
231
|
+
for (const configName of rootConfigFiles) {
|
|
232
|
+
let content;
|
|
233
|
+
try {
|
|
234
|
+
content = await readFile(join(rootDir, configName), 'utf8');
|
|
235
|
+
} catch {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
for (const pkg of declaredDependencies) {
|
|
240
|
+
if (importedSet.has(pkg)) continue;
|
|
241
|
+
|
|
242
|
+
// Exact package match (quoted or word boundary)
|
|
243
|
+
const escaped = pkg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
244
|
+
const directRegex = new RegExp(`['"]${escaped}['"]|\\b${escaped}\\b`);
|
|
245
|
+
|
|
246
|
+
// Shorthand matches for common config tools
|
|
247
|
+
let shorthandMatch = false;
|
|
248
|
+
if (configName.includes('eslint')) {
|
|
249
|
+
const eslintPlugin = pkg.replace(/^eslint-plugin-/, '');
|
|
250
|
+
const eslintConfig = pkg.replace(/^eslint-config-/, '');
|
|
251
|
+
if (eslintPlugin !== pkg && new RegExp(`['"]${eslintPlugin}['"]`).test(content)) shorthandMatch = true;
|
|
252
|
+
if (eslintConfig !== pkg && new RegExp(`['"]${eslintConfig}['"]`).test(content)) shorthandMatch = true;
|
|
253
|
+
} else if (configName.includes('babel')) {
|
|
254
|
+
const babelPreset = pkg.replace(/^(@babel\/preset-|babel-preset-)/, '');
|
|
255
|
+
const babelPlugin = pkg.replace(/^(@babel\/plugin-|babel-plugin-)/, '');
|
|
256
|
+
if (babelPreset !== pkg && new RegExp(`['"]${babelPreset}['"]`).test(content)) shorthandMatch = true;
|
|
257
|
+
if (babelPlugin !== pkg && new RegExp(`['"]${babelPlugin}['"]`).test(content)) shorthandMatch = true;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (directRegex.test(content) || shorthandMatch) {
|
|
261
|
+
importedSet.add(pkg);
|
|
262
|
+
if (!importsMap.has(pkg)) importsMap.set(pkg, []);
|
|
263
|
+
importsMap.get(pkg).push({
|
|
264
|
+
file: configName,
|
|
265
|
+
line: 1,
|
|
266
|
+
snippet: `Config reference in ${configName}`
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Rule 6: Scan package.json scripts for CLI tool references (e.g. nodemon, rimraf, tsc, prettier)
|
|
95
273
|
for (const [scriptName, scriptCmd] of Object.entries(scripts)) {
|
|
96
274
|
if (typeof scriptCmd !== 'string') continue;
|
|
97
275
|
for (const pkg of declaredDependencies) {
|
|
@@ -111,6 +289,7 @@ export async function locateImports(rootDir, declaredDependencies = [], scripts
|
|
|
111
289
|
}
|
|
112
290
|
}
|
|
113
291
|
|
|
292
|
+
// Rule 7: Scan project source files for import / require / export from
|
|
114
293
|
for (const filePath of sourceFiles) {
|
|
115
294
|
let content;
|
|
116
295
|
try {
|
|
@@ -124,7 +303,6 @@ export async function locateImports(rootDir, declaredDependencies = [], scripts
|
|
|
124
303
|
|
|
125
304
|
for (let i = 0; i < lines.length; i++) {
|
|
126
305
|
const line = lines[i];
|
|
127
|
-
// Skip empty or comment-only lines
|
|
128
306
|
const trimmed = line.trim();
|
|
129
307
|
if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) {
|
|
130
308
|
continue;
|
|
@@ -152,7 +330,7 @@ export async function locateImports(rootDir, declaredDependencies = [], scripts
|
|
|
152
330
|
}
|
|
153
331
|
}
|
|
154
332
|
|
|
155
|
-
// Detect unused packages: declared in package.json but never
|
|
333
|
+
// Detect unused packages: declared in package.json but never referenced anywhere
|
|
156
334
|
const unusedPackages = declaredDependencies.filter((pkg) => !importedSet.has(pkg));
|
|
157
335
|
|
|
158
336
|
return {
|
|
@@ -160,4 +338,4 @@ export async function locateImports(rootDir, declaredDependencies = [], scripts
|
|
|
160
338
|
unusedPackages,
|
|
161
339
|
scannedFilesCount: sourceFiles.length
|
|
162
340
|
};
|
|
163
|
-
}
|
|
341
|
+
}
|
package/src/registry.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { tmpdir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
|
|
@@ -66,7 +66,7 @@ export async function fetchPackageMetadata(packageName, timeoutMs = 4000) {
|
|
|
66
66
|
signal: controller.signal,
|
|
67
67
|
headers: {
|
|
68
68
|
Accept: 'application/json',
|
|
69
|
-
'User-Agent': 'depshred-audit/1.0.
|
|
69
|
+
'User-Agent': 'depshred-audit/1.0.2'
|
|
70
70
|
}
|
|
71
71
|
});
|
|
72
72
|
|
package/src/reporter.js
CHANGED
|
@@ -9,7 +9,7 @@ export function printScorecard(auditData, options = {}) {
|
|
|
9
9
|
|
|
10
10
|
console.log('');
|
|
11
11
|
console.log(pc.bold(pc.white('+--------------------------------------------------------------------------+')));
|
|
12
|
-
console.log(pc.bold(pc.white(`| depshred v1.0.
|
|
12
|
+
console.log(pc.bold(pc.white(`| depshred v1.0.2 (Target Node: ${targetNode})`)));
|
|
13
13
|
console.log(pc.bold(pc.white(`| Project: ${projectName} (${totalDependencies} dependencies scanned)`)));
|
|
14
14
|
console.log(pc.bold(pc.white('+--------------------------------------------------------------------------+')));
|
|
15
15
|
|
package/src/rules/alt-rules.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
2
|
* Modern Lightweight Package Alternatives Catalog.
|
|
3
3
|
* Objective replacements for heavy or legacy packages.
|
|
4
4
|
*/
|
|
5
5
|
export const ALT_RULES = [
|
|
6
|
+
{
|
|
7
|
+
name: 'zod',
|
|
8
|
+
lighter: 'valibot',
|
|
9
|
+
description: 'Zod is ~5.9MB unpacked (~14KB gzipped). Replace with valibot (modular, sub-1KB per schema) or arktype for 10x smaller bundles.',
|
|
10
|
+
savings: '~5.5MB unpacked (~90% bundle reduction)'
|
|
11
|
+
},
|
|
6
12
|
{
|
|
7
13
|
name: 'moment',
|
|
8
14
|
lighter: 'dayjs',
|
|
@@ -21,10 +27,22 @@ export const ALT_RULES = [
|
|
|
21
27
|
description: 'Large dependency chain in v4/v5. Replace with picocolors (sub-7KB, 0 sub-dependencies).',
|
|
22
28
|
savings: '~100KB'
|
|
23
29
|
},
|
|
30
|
+
{
|
|
31
|
+
name: 'classnames',
|
|
32
|
+
lighter: 'clsx',
|
|
33
|
+
description: 'Legacy classnames utility. Replace with clsx (drop-in API, faster, zero dependencies).',
|
|
34
|
+
savings: '~50KB'
|
|
35
|
+
},
|
|
24
36
|
{
|
|
25
37
|
name: 'bcrypt',
|
|
26
38
|
lighter: 'bcryptjs',
|
|
27
39
|
description: 'Requires node-gyp C++ compilation toolchain. Replace with pure JS bcryptjs or native crypto.scrypt.',
|
|
28
40
|
savings: 'Eliminates native build toolchain dependency'
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'deep-equal',
|
|
44
|
+
lighter: 'fast-deep-equal',
|
|
45
|
+
description: 'Legacy recursive comparison. Replace with fast-deep-equal (sub-1KB, faster) or native assert.deepStrictEqual.',
|
|
46
|
+
savings: '~80% faster and 0 sub-dependencies'
|
|
29
47
|
}
|
|
30
|
-
];
|
|
48
|
+
];
|