();
for (const [month, amount] of monthlyGHSRO) monthlyRO.set(month, [...(monthlyRO.get(month) || []), amount]);
for (const [month, amount] of monthlyOCRO) monthlyRO.set(month, [...(monthlyRO.get(month) || []), amount]);
const table = new Table();
const digits = (n: number) => (v: any) => (typeof v === 'number' ? v.toFixed(n) : v);
const sum = Array.from(monthly.values()).reduce((a, c) => a + c.reduce((a, b) => a + b, 0), 0);
const sumGHS = Array.from(monthlyGHS.values()).reduce((a, c) => a + c, 0);
const sumOC = Array.from(monthlyOC.values()).reduce((a, c) => a + c, 0);
const sumRO = Array.from(monthlyRO.values()).reduce((a, c) => a + c.reduce((a, b) => a + b, 0), 0);
for (const [key, value] of [
['months', monthly.size],
['github', sumGHS],
['OC', sumOC],
['sum', sum],
['avg', sum / monthly.size],
['recurring sum', sumRO],
['recurring avg', sumRO / monthlyRO.size],
]) {
table.row();
table.cell('key', key);
table.cell('value', value, digits(0));
}
console.log(table.toString());
const url = new URL('/', 'https://try.venz.dev');
url.searchParams.set('type', 'pivot');
url.searchParams.set('lp', 'tr');
url.searchParams.set('br', '0');
url.searchParams.set('labelX', 'month');
url.searchParams.set('labelY', 'amount ($)');
url.searchParams.append('l', 'GitHub Sponsors');
url.searchParams.append('color', '#fbfbfb');
url.searchParams.append('l', 'Open Collective');
url.searchParams.append('color', '#2487ff');
for (const [month, amount] of monthly) {
url.searchParams.append('label', month);
url.searchParams.append('data', amount.join(','));
}
console.log('\nVenz link');
const text = `${url.origin}?${decodeURIComponent(url.searchParams.toString()).replaceAll('#', '%23')}`;
console.log(text);
};
main().catch(console.error);
================================================
FILE: packages/docs/src/assets/testimonials.json
================================================
{
"data": [
{
"entities": {
"urls": [
{
"url": "cdn.bsky.app/img/dummy-02",
"display_url": "cdn.bsky.app/img/julien.deniau.me",
"media_key": "4"
}
]
},
"id": "3mdrubg3y5c2z",
"author_id": "11",
"created_at": "2026-02-01T07:57:00.000Z",
"attachments": {},
"text": "Done on a mono-repo in two times.\nWhat's impressive is that there were no false positive!\ncdn.bsky.app/img/dummy-02"
},
{
"entities": {
"urls": [
{
"url": "https://t.co/EwIeRiHaXf",
"display_url": "pic.x.com/EwIeRiHaXf",
"media_key": "6"
}
]
},
"id": "2017122504914161979",
"author_id": "16",
"created_at": "2026-01-30T06:27:00.000Z",
"text": "Good morning 🌄\nA tool I discovered lately.\nIf you care about clean codebases, this one's a gem 💎"
},
{
"entities": {
"urls": []
},
"id": "3mcky5ps3r22p",
"author_id": "12",
"created_at": "2026-01-16T20:53:09.000Z",
"text": "Knip is a cracking-looking open source tool for trimming unused JavaScript."
},
{
"entities": {
"urls": [
{
"url": "cdn.bsky.app/img/dummy-03",
"display_url": "cdn.bsky.app/img/patak.dev",
"media_key": "5"
}
]
},
"id": "3mcjtmv35wc2y",
"author_id": "15",
"created_at": "2026-01-16T09:59:29.000Z",
"attachments": {},
"text": "From the report, we could improve the situation if we stop sending unused JavaScript. @webpro.nl is doing an incredible job growing the usage of knip.dev, and that should hopefully make a dent in the future.\ncdn.bsky.app/img/dummy-03"
},
{
"entities": {
"urls": [
{
"url": "https://knip.dev/blog/for-editors-and-agents",
"expanded_url": "https://knip.dev/blog/for-editors-and-agents",
"display_url": "knip.dev/blog/for-edi..."
}
]
},
"id": "3mby3zkzhfs2t",
"author_id": "13",
"created_at": "2026-01-09T08:41:49.000Z",
"text": "The VSCode/Cursor extension is ace!\nAnd you can use the Knip MCP to create the config file.\nIt works for Svelte as well.\nKnip is really really good 🙏"
},
{
"entities": {
"urls": [
{
"url": "https://t.co/6rcslWBqIV",
"display_url": "pic.x.com/6rcslWBqIV",
"media_key": "6"
}
]
},
"id": "2007481125610914217",
"author_id": "17",
"created_at": "2026-01-03T15:56:00.000Z",
"attachments": { "media_keys": ["6"] },
"text": "This is both embarrassing and satisfying after running @Knip https://t.co/6rcslWBqIV"
},
{
"entities": {
"urls": [
{
"url": "https://t.co/hneEWMqdnm",
"expanded_url": "https://knip.dev",
"display_url": "Knip.dev"
}
]
},
"id": "2007221517562904675",
"author_id": "18",
"created_at": "2026-01-02T23:44:00.000Z",
"text": "You can't spell Opus without https://t.co/hneEWMqdnm"
},
{
"entities": {
"urls": []
},
"id": "3mbggqrihuk2r",
"author_id": "14",
"created_at": "2026-01-02T08:05:50.000Z",
"text": "Knip is amazing and you should be using it."
},
{
"entities": {
"urls": []
},
"id": "1932778410691949034",
"author_id": "10",
"created_at": "2025-06-11T14:34:00.000Z",
"text": "\"Thanks Knip!\"\neveryday,\nevery team member,\nliterally..\nknip.dev 🏅"
},
{
"entities": {
"urls": []
},
"url": "https://github.com/webpro-nl/knip/issues/1120#issuecomment-2943978262",
"author_id": "9",
"created_at": "2025-06-05T14:00:00.000Z",
"text": "Thank you so much for this incredible piece of software! Knip is one of the most impressive tools I've seen in my career."
},
{
"entities": {
"urls": [
{
"url": "https://github.com/webpro-nl/knip/pull/1022",
"expanded_url": "https://github.com/webpro-nl/knip/pull/1022",
"display_url": "https://github.com/webpro-nl/knip/pull/1022",
"title": "feat: add create-typescript-app plugin",
"description": "feat: add create-typescript-app plugin",
"unwound_url": "https://github.com/webpro-nl/knip/pull/1022"
}
]
},
"id": "3lmhgsj2n622o",
"author_id": "7",
"created_at": "2025-04-10T13:50:00.000Z",
"attachments": {},
"text": "Got my first plugin merged into Knip by @webpro.nl today! ☺️\n\nThe contribution experience was fantastic: the docs were super clear, the code was straightforward to navigate, and the helper script to generate a new plugin got me 90% of the way there. Awesome. Thanks Lars!\n\nhttps://github.com/webpro-nl/knip/pull/1022"
},
{
"entities": {
"urls": []
},
"id": "1904580358307250634",
"author_id": "6",
"created_at": "2025-03-25T18:05:00.000Z",
"attachments": {},
"text": "All of the TanStack monorepos make use of knip.dev\n\nIt's like eslint for detecting unused dependencies or even your own files/modules."
},
{
"entities": {
"urls": [
{
"url": "cdn.bsky.app/img/dummy-01",
"display_url": "cdn.bsky.app/img/opw3iapscc55nlgrx6q2yjah",
"media_key": "2"
}
]
},
"id": "3ll5ioywml223",
"author_id": "8",
"created_at": "2025-03-24T20:32:00.000Z",
"attachments": {},
"text": "The joy of adding knip.dev (by @webpro.nl) on a codebase! Right in time for spring cleaning 🧹🍃\ncdn.bsky.app/img/dummy-01"
},
{
"entities": {
"urls": []
},
"id": "1874692207472726401",
"author_id": "5",
"created_at": "2025-01-02T06:40:00.000Z",
"attachments": {},
"text": "Knip helped us delete ~300k lines of unused code at Vercel."
},
{
"entities": {
"urls": [
{
"url": "https://t.co/kA81p5gkmP",
"expanded_url": "https://www.npmjs.com/package/knip",
"display_url": "npmjs.com/package/knip",
"title": "Knip",
"description": "Find unused dependencies, exports and files in your TypeScript and JavaScript projects.",
"unwound_url": "https://www.npmjs.com/package/knip"
}
]
},
"id": "1850975288362221628",
"author_id": "5",
"created_at": "2024-10-28T19:57:00.000Z",
"attachments": {},
"text": "Knip may be the greatest tool ever conceived.\nhttps://t.co/kA81p5gkmP"
},
{
"entities": {
"urls": []
},
"id": "3k3obqi65oy2p",
"author_id": "4",
"created_at": "2023-07-29T17:33:00.000Z",
"attachments": {},
"text": "`knip` is the best code maintenance tool I've used in my 20 years of development, just an absolute work of art"
},
{
"entities": {
"urls": []
},
"id": "1808672067900092505",
"author_id": "3",
"created_at": "2024-07-04T03:19:00.000Z",
"attachments": {},
"text": "knip is great.\n\nA great example of stuff just working. It detects your environment so well and its defaults are almost too good because I didn’t realise it was doing everything automatically."
},
{
"entities": {
"urls": []
},
"id": "1808808208229707835",
"author_id": "3",
"created_at": "2024-07-04T12:20:00.000Z",
"attachments": {},
"text": "Then was like “this is how tooling should be, this has taken me an hour not a day and it’s faster and more accurate and more future proof”\n\nFive stars ⭐️⭐️⭐️⭐️⭐️"
},
{
"entities": {
"urls": []
},
"id": "1803013555907698921",
"author_id": "2",
"created_at": "2024-06-18T12:35:00.000Z",
"attachments": {},
"text": "Crazy useful tool. Thought it would be a nightmare to setup in my Next.js+Workers repo but it has crazy good automatic detection for code dependencies. In short, Knip creates a graph of code use and highlights dead code for you to snip (knip)."
},
{
"entities": {
"urls": []
},
"id": "1793913676896031033",
"author_id": "44217212",
"created_at": "2024-05-24T09:55:00.000Z",
"attachments": {},
"text": "Lars is singlehandedly building the best tool to keep your code clean and your dependencies minimal. And he's so engaging and helpful. Can't imagine going back to not using knip 👏"
},
{
"entities": {
"urls": [
{
"url": "https://t.co/dummy-01",
"display_url": "pic.twitter.com/uOuO1ch5ij",
"media_key": "1"
}
]
},
"id": "1745972825490604506",
"author_id": "1",
"created_at": "2024-01-13T01:55:00.000Z",
"attachments": { "media_keys": ["1"] },
"text": "warning: do not use a tool like knip.dev + code search to delete a ton of unused code. It works too well.\n\nI just did this in https://github.com/sourcegraph/cody/pull/2705, and my boss said if I end the week with net negative lines of code committed, I'm in deep trouble. https://t.co/dummy-01"
},
{
"entities": {
"mentions": [
{
"username": "argos_ci",
"id": "1554000987022663681"
},
{
"username": "webprolific",
"id": "218833730"
}
],
"annotations": [
{
"probability": 0.6699,
"type": "Other",
"normalized_text": "Prettier"
},
{
"probability": 0.7586,
"type": "Other",
"normalized_text": "ESLint"
}
],
"urls": [
{
"url": "https://t.co/uOuO1ch5ij",
"expanded_url": "https://twitter.com/gregberge_/status/1730180003453927560/photo/1",
"display_url": "pic.twitter.com/uOuO1ch5ij",
"media_key": "3_1730179997682552832"
}
]
},
"author_id": "22918124",
"text": "Ran knip.dev in @argos_ci, and boom 💥! Dead code detected instantly. With a good config, it can run on CI to keep projects clean. Installing this tool should be a no-brainer, like Prettier or ESLint.\n\nKudos, @webprolific! 👏 https://t.co/uOuO1ch5ij",
"id": "1730180003453927560",
"created_at": "2023-11-30T11:00:32.000Z",
"edit_history_tweet_ids": ["1730180003453927560"],
"attachments": { "media_keys": ["3_1730179997682552832"] }
},
{
"note_tweet": {
"text": "Problem: \n🚫 Your project has unused files.\n🚫 It has unused npm dependencies.\n🚫 It has unused TypeScript exports.\n\nBut you haven't noticed, because these things are hard to spot.\n\nSolution: knip\n\nI just used knip to find and resolve dozens of issues.\n\nhttps://t.co/QmN3sNlmbm",
"entities": {
"urls": [
{
"url": "https://t.co/QmN3sNlmbm",
"expanded_url": "https://github.com/webpro-nl/knip",
"display_url": "github.com/webpro-nl/knip"
}
]
}
},
"entities": {
"annotations": [
{
"probability": 0.8569,
"type": "Other",
"normalized_text": "TypeScript"
}
],
"urls": [
{
"url": "https://t.co/grbeTqMtt6",
"expanded_url": "https://github.com/webpro-nl/knip",
"display_url": "github.com/webpro-nl/knip",
"images": [
{
"url": "https://pbs.twimg.com/news_img/1729485824314548224/0xZughNs?format=png&name=orig",
"width": 1280,
"height": 640
},
{
"url": "https://pbs.twimg.com/news_img/1729485824314548224/0xZughNs?format=png&name=150x150",
"width": 150,
"height": 150
}
],
"title": "GitHub - webpro-nl/knip: ✂️ Find unused dependencies, exports and files in your JavaScript and TypeScript projects. Knip it before you ship it!",
"description": "✂️ Find unused dependencies, exports and files in your JavaScript and TypeScript projects. Knip it before you ship it! - GitHub - webpro-nl/knip: ✂️ Find unused dependencies, exports and files in yo...",
"unwound_url": "https://github.com/webpro-nl/knip"
},
{
"url": "https://t.co/qaOXBlD3eo",
"expanded_url": "https://twitter.com/i/web/status/1691460974518353920",
"display_url": "twitter.com/i/web/status/1…"
}
]
},
"author_id": "19268321",
"text": "Problem: \n🚫 Your project has unused files.\n🚫 It has unused npm dependencies.\n🚫 It has unused TypeScript exports.\n\nBut you haven't noticed, because these things are hard to spot.\n\nSolution: knip\n\nI just used knip to find and resolve dozens of issues.\n\nhttps://t.co/grbeTqMtt6 https://t.co/qaOXBlD3eo",
"id": "1691460974518353920",
"created_at": "2023-08-15T14:44:56.000Z",
"edit_history_tweet_ids": ["1691460974518353920"]
},
{
"entities": {
"mentions": [
{
"username": "webprolific",
"id": "218833730"
}
],
"annotations": [
{
"probability": 0.6322,
"type": "Other",
"normalized_text": "Knip"
}
]
},
"author_id": "3126044440",
"text": "@webprolific Knip helped me get rid of over 41k lines of code in legacy codebase 🥺🥺💕",
"id": "1729181106715632088",
"created_at": "2023-11-27T16:51:17.000Z",
"edit_history_tweet_ids": ["1729181106715632088"]
},
{
"entities": {
"mentions": [
{
"username": "contra",
"id": "973313523790082053"
}
],
"annotations": [
{
"probability": 0.8474,
"type": "Other",
"normalized_text": "Knip"
}
],
"urls": [
{
"url": "https://t.co/UgCunYQWzu",
"expanded_url": "https://twitter.com/webprolific/status/1729105865683435542",
"display_url": "twitter.com/webprolific/st…"
}
]
},
"author_id": "95668959",
"text": "Big fans of Knip at @Contra. Such an extremely well developed and maintained project https://t.co/UgCunYQWzu",
"id": "1729157761215369264",
"created_at": "2023-11-27T15:18:31.000Z",
"edit_history_tweet_ids": ["1729157761215369264"]
},
{
"entities": {
"mentions": [
{
"username": "webprolific",
"id": "218833730"
}
],
"urls": [
{
"url": "https://t.co/ZUYYdvkBJc",
"expanded_url": "https://github.com/getsentry/sentry/",
"display_url": "github.com/getsentry/sent…",
"title": "GitHub - getsentry/sentry: Developer-first error tracking and performance monitoring",
"description": "Developer-first error tracking and performance monitoring - GitHub - getsentry/sentry: Developer-first error tracking and performance monitoring",
"unwound_url": "https://github.com/getsentry/sentry/"
}
]
},
"author_id": "1632752318994153472",
"text": "@webprolific Just tried this on https://t.co/ZUYYdvkBJc - got some super useful results! Nice work.",
"id": "1727040036334424406",
"created_at": "2023-11-21T19:03:26.000Z",
"edit_history_tweet_ids": ["1727040036334424406"]
},
{
"entities": {
"mentions": [
{ "username": "TkDodo", "id": "44217212" },
{
"username": "webprolific",
"id": "218833730"
},
{
"username": "webprolific",
"id": "218833730"
}
]
},
"author_id": "1513218780167655427",
"text": "@TkDodo @webprolific I've manage to delete 6k LOC in the last 30 minutes 🫣\n\nGreat job here @webprolific 👍🏽",
"id": "1726807293583609935",
"created_at": "2023-11-21T03:38:35.000Z",
"edit_history_tweet_ids": ["1726807293583609935"]
},
{
"entities": {
"mentions": [
{
"username": "webprolific",
"id": "218833730"
}
],
"urls": [
{
"url": "https://t.co/Tg5E6C7gqa",
"expanded_url": "https://twitter.com/housecor/status/1713975785273303286",
"display_url": "twitter.com/housecor/statu…"
}
]
},
"author_id": "44217212",
"text": "knip is an amazing tool. Shoutout to @webprolific for building it 🙌.\n\nNot cleaning up correctly has a real maintenance cost. I've deleted lots of dead code - functions that were only used in tests and components that were only used in stories - all thanks to knip 🚀. https://t.co/Tg5E6C7gqa",
"id": "1714023231689031941",
"created_at": "2023-10-16T20:59:18.000Z",
"edit_history_tweet_ids": ["1714023231689031941"]
},
{
"entities": {
"annotations": [
{
"probability": 0.9562,
"type": "Other",
"normalized_text": "JavaScript"
},
{
"probability": 0.8666,
"type": "Other",
"normalized_text": "TypeScript"
}
],
"urls": [
{
"url": "https://t.co/f3fzC5UPtR",
"expanded_url": "https://github.com/webpro-nl/knip",
"display_url": "github.com/webpro-nl/knip",
"images": [
{
"url": "https://pbs.twimg.com/news_img/1729485824314548224/0xZughNs?format=png&name=orig",
"width": 1280,
"height": 640
},
{
"url": "https://pbs.twimg.com/news_img/1729485824314548224/0xZughNs?format=png&name=150x150",
"width": 150,
"height": 150
}
],
"title": "GitHub - webpro-nl/knip: ✂️ Find unused dependencies, exports and files in your JavaScript and TypeScript projects. Knip it before you ship it!",
"description": "✂️ Find unused dependencies, exports and files in your JavaScript and TypeScript projects. Knip it before you ship it! - GitHub - webpro-nl/knip: ✂️ Find unused dependencies, exports and files in yo...",
"unwound_url": "https://github.com/webpro-nl/knip"
}
]
},
"author_id": "481186762",
"text": "🛠️ Knip\n\n👉🏻 Knip finds unused dependencies, exports and files in your JavaScript and TypeScript projects.\n👉🏻 Less code and dependencies lead to improved performance, less maintenance and easier refactorings. \n\nhttps://t.co/f3fzC5UPtR",
"id": "1696221274039595363",
"created_at": "2023-08-28T18:00:40.000Z",
"edit_history_tweet_ids": ["1696221274039595363"]
},
{
"entities": {
"annotations": [
{
"probability": 0.9354,
"type": "Other",
"normalized_text": "JavaScript"
},
{
"probability": 0.7027,
"type": "Other",
"normalized_text": "TypeScript"
},
{
"probability": 0.7208,
"type": "Other",
"normalized_text": "Josh"
}
],
"urls": [
{
"url": "https://t.co/nKtvr2wEg6",
"expanded_url": "https://twitter.com/JoshuaKGoldberg/status/1693713311836115286",
"display_url": "twitter.com/JoshuaKGoldberg"
}
]
},
"author_id": "416394303",
"text": "Knip is wonderful at finding out unused code/dependencies in a legacy JavaScript/TypeScript application\n\nRecommend 💯\n\nHere's how Josh used it to remove code bloat in Centered: https://t.co/nKtvr2wEg6",
"id": "1693944495472046382",
"created_at": "2023-08-22T11:13:34.000Z",
"edit_history_tweet_ids": ["1693944495472046382"]
},
{
"entities": {
"annotations": [
{
"probability": 0.5513,
"type": "Other",
"normalized_text": "Knip"
}
],
"urls": [
{
"url": "https://t.co/L8w9V11PRL",
"expanded_url": "https://www.smashingmagazine.com/2023/08/knip-automated-tool-find-unused-files-exports-dependencies/",
"display_url": "smashingmagazine.com/2023/08/knip-a…",
"images": [
{
"url": "https://pbs.twimg.com/news_img/1727384011872366592/W-M5klkJ?format=jpg&name=orig",
"width": 1200,
"height": 675
},
{
"url": "https://pbs.twimg.com/news_img/1727384011872366592/W-M5klkJ?format=jpg&name=150x150",
"width": 150,
"height": 150
}
],
"title": "Knip: An Automated Tool For Finding Unused Files, Exports, And Dependencies — Smashing Magazine",
"description": "Most of the projects have at least a few unused files, exports, and dependencies lying around, often because it’s difficult knowing when one thing relies on another and scary removing something you’re not sure is in use. Lars Kappert shares a tool he’s been working on that offers a solution.",
"unwound_url": "https://www.smashingmagazine.com/2023/08/knip-automated-tool-find-unused-files-exports-dependencies/"
}
]
},
"author_id": "15736190",
"text": "Unused files, exports, and dependencies often clutter projects, as it’s difficult knowing when one thing relies on another and scary to remove something you’re not sure is in use. 👀\n\n↬ Meet Knip, which scours projects for these unused artifacts: https://t.co/L8w9V11PRL",
"id": "1691120132901240832",
"created_at": "2023-08-14T16:10:33.000Z",
"edit_history_tweet_ids": ["1691120132901240832"]
},
{
"entities": {
"mentions": [
{
"username": "typescript",
"id": "809233214"
}
],
"urls": [
{
"url": "https://t.co/1mRMwvjIpw",
"expanded_url": "https://effectivetypescript.com/2023/07/29/knip/",
"display_url": "effectivetypescript.com/2023/07/29/kni…",
"images": [
{
"url": "https://pbs.twimg.com/news_img/1696204795353219085/ZjmFlTVW?format=jpg&name=orig",
"width": 560,
"height": 735
},
{
"url": "https://pbs.twimg.com/news_img/1696204795353219085/ZjmFlTVW?format=jpg&name=150x150",
"width": 150,
"height": 150
}
],
"title": "Effective TypeScript › Recommendation Update: ✂️ Use knip to detect dead code and types",
"description": "TL;DR: Use ts-prune is in maintenance mode. Use knip to find dead code instead. It's great!",
"unwound_url": "https://effectivetypescript.com/2023/07/29/knip/"
}
]
},
"author_id": "21046936",
"text": "Recommendation update: use knip to find dead code, types and dependencies in your @typescript ✂️https://t.co/1mRMwvjIpw",
"id": "1685298103094554625",
"created_at": "2023-07-29T14:35:53.000Z",
"edit_history_tweet_ids": ["1685298103094554625"]
},
{
"entities": {
"annotations": [
{
"probability": 0.9661,
"type": "Other",
"normalized_text": "GitHub"
}
],
"urls": [
{
"url": "https://t.co/vnxNbiJs1I",
"expanded_url": "https://github.com/webpro-nl/knip",
"display_url": "github.com/webpro-nl/knip",
"images": [
{
"url": "https://pbs.twimg.com/news_img/1729485824314548224/0xZughNs?format=png&name=orig",
"width": 1280,
"height": 640
},
{
"url": "https://pbs.twimg.com/news_img/1729485824314548224/0xZughNs?format=png&name=150x150",
"width": 150,
"height": 150
}
],
"title": "GitHub - webpro-nl/knip: ✂️ Find unused dependencies, exports and files in your JavaScript and TypeScript projects. Knip it before you ship it!",
"description": "✂️ Find unused dependencies, exports and files in your JavaScript and TypeScript projects. Knip it before you ship it! - GitHub - webpro-nl/knip: ✂️ Find unused dependencies, exports and files in yo...",
"unwound_url": "https://github.com/webpro-nl/knip"
}
]
},
"author_id": "380616577",
"text": "This is worth checking out on GitHub. 👍 https://t.co/vnxNbiJs1I",
"id": "1581910299846012928",
"created_at": "2022-10-17T07:29:40.000Z",
"edit_history_tweet_ids": ["1581910299846012928"]
},
{
"note_tweet": {
"text": "Streamlining React projects with Knip:\n\nRecently, I employed Knip for a project, and it worked wonders! \n\nIt efficiently resolved issues with removing\n✅ Unused files, \n✅ Unused npm dependencies, \n✅ Unneeded TypeScript exports.\n\n A real time-saver for maintaining a clean and efficient codebase. 🛠️ #ReactDevelopment \n\nhttps://t.co/IbJH2fIzsE",
"entities": {
"urls": [
{
"url": "https://t.co/IbJH2fIzsE",
"expanded_url": "https://github.com/webpro-nl/knip",
"display_url": "github.com/webpro-nl/knip"
}
],
"hashtags": [{ "tag": "ReactDevelopment" }]
}
},
"entities": {
"annotations": [
{
"probability": 0.743,
"type": "Other",
"normalized_text": "Knip"
},
{
"probability": 0.7667,
"type": "Other",
"normalized_text": "Knip"
}
],
"urls": [
{
"url": "https://t.co/P39xg0qCrT",
"expanded_url": "https://twitter.com/i/web/status/1693502146128281657",
"display_url": "twitter.com/i/web/status/1…"
}
]
},
"author_id": "827145937030148097",
"text": "Streamlining React projects with Knip:\n\nRecently, I employed Knip for a project, and it worked wonders! \n\nIt efficiently resolved issues with removing\n✅ Unused files, \n✅ Unused npm dependencies, \n✅ Unneeded TypeScript exports.\n\n A real time-saver for maintaining a clean and… https://t.co/P39xg0qCrT",
"id": "1693502146128281657",
"created_at": "2023-08-21T05:55:50.000Z",
"edit_history_tweet_ids": ["1693502146128281657"]
},
{
"entities": {
"annotations": [
{
"probability": 0.9331,
"type": "Other",
"normalized_text": "JavaScript"
},
{
"probability": 0.5907,
"type": "Other",
"normalized_text": "Knip"
}
]
},
"author_id": "18191376",
"text": "Found a bunch of unused code, -dependencies, and unnecessary exports. Had just one false positive but overall pretty good. 10/10 would recommend.\n\nIf you’ve got a JavaScript package/project use Knip and remove unnecessary code. ✂️",
"id": "1692942539614028086",
"created_at": "2023-08-19T16:52:09.000Z",
"edit_history_tweet_ids": ["1692942539614028086"]
}
],
"includes": {
"media": [
{
"media_key": "1",
"url": "https://pbs.twimg.com/media/GDrwdB3aYAAu4am?format=png&name=orig",
"type": "photo"
},
{
"media_key": "2",
"url": "https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:opw3iapscc55nlgrx6q2yjah/bafkreihzszb56agbe6dtuf6xfutl65mjg5ethnrvw264d5bljbm377jora@jpeg",
"type": "photo"
},
{
"media_key": "3_1730179997682552832",
"url": "https://pbs.twimg.com/media/GALVin9a0AAsn0-.jpg",
"type": "photo"
},
{
"media_key": "4",
"url": "https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:4dsclu5hjmht3shxutxtge6h/bafkreid63o3hwvwpk6emgzj64vywusztofhz2yihuvwkywuuajn4eekvom@jpeg",
"type": "photo"
},
{
"media_key": "5",
"url": "https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:2gkh62xvzokhlf6li4ol3b3d/bafkreiahk2jbsb2stw276lkrkxrkvialqs25zrpgo2n3cigzfb7qvgfsmq@jpeg",
"type": "photo"
},
{
"media_key": "6",
"url": "https://pbs.twimg.com/media/G9wBaMFWoAAkgv5.png",
"type": "photo"
}
],
"users": [
{
"id": "11",
"username": "julien.deniau.me",
"profile_image_url": "https://cdn.bsky.app/img/avatar/plain/did:plc:4dsclu5hjmht3shxutxtge6h/bafkreiht3iqxcmelwe7oysawrdpynzevf4dt2ndzeln4f5c2izf62zm57q@jpeg",
"name": "Julien Deniau"
},
{
"id": "12",
"username": "alexharfordseo.bsky.social",
"profile_image_url": "https://cdn.bsky.app/img/avatar/plain/did:plc:y3arowkhgiexpyvmmxvquavg/bafkreiazlcx5fdxylsqtsdng3beq3ayuoc7nzyqcddeequhi7uploqcjny@jpeg",
"name": "AlexHarford-TechSEO"
},
{
"id": "13",
"username": "fubits.dev",
"profile_image_url": "https://cdn.bsky.app/img/avatar/plain/did:plc:6aglx53tojyuwxwueap5og3h/bafkreiclnob3mnfvo74an6ugrnbdikrkxcskndjojrdtkwf5vwjuu77z5u@jpeg",
"name": "Ilja"
},
{
"id": "14",
"username": "alexanderkaran.bsky.social",
"profile_image_url": "https://cdn.bsky.app/img/avatar/plain/did:plc:3n4fccuhdomkkebojjdurnco/bafkreiggltcvdoh4igf5yxvrxggysmasv5efn6hffyav2o6swxslbc3lp4@jpeg",
"name": "Alexander Karan"
},
{
"id": "15",
"username": "patak.dev",
"profile_image_url": "https://cdn.bsky.app/img/avatar/plain/did:plc:2gkh62xvzokhlf6li4ol3b3d/bafkreifgzl4e5jqlakd77ajvnilsb5tufsv24h2sxfwmitkzxrh3sk6mhq@jpeg",
"name": "patak"
},
{
"id": "16",
"username": "psparwez",
"profile_image_url": "https://unavatar.io/x/psparwez",
"name": "PS"
},
{
"id": "17",
"username": "k_shehadeh",
"profile_image_url": "https://unavatar.io/x/k_shehadeh",
"name": "Karim Shehadeh"
},
{
"id": "18",
"username": "cramforce",
"profile_image_url": "https://unavatar.io/x/cramforce",
"name": "Malte Ubl"
},
{
"id": "1",
"username": "sqs",
"profile_image_url": "https://pbs.twimg.com/profile_images/1519180377604034560/NxI03WYE_400x400.jpg",
"name": "Quinn Slack"
},
{
"id": "2",
"username": "jokull",
"profile_image_url": "https://pbs.twimg.com/profile_images/1943338796122083328/NsFCJwMd_400x400.jpg",
"name": "Jökull Solberg"
},
{
"id": "3",
"username": "Hicksyfern",
"profile_image_url": "https://pbs.twimg.com/profile_images/1559181703352094720/NFL2PBbK_400x400.jpg",
"name": "Tom Hicks"
},
{
"id": "4",
"username": "daviduzumeri.bsky.social",
"profile_image_url": "https://cdn.bsky.app/img/avatar/plain/did:plc:6ykajndqngy2kxozo37mpsgc/bafkreif2xu6bini3tpv2hfauw53ng43zbefgqol36yt4baehcbdfxahihu@jpeg",
"name": "Old Man Uzi"
},
{
"id": "5",
"username": "gary__tyr",
"profile_image_url": "https://pbs.twimg.com/profile_images/1947080916045631488/m42HwWiy_400x400.jpg",
"name": "Gary Tyr"
},
{
"id": "6",
"username": "KevinVanCott",
"profile_image_url": "https://pbs.twimg.com/profile_images/1419785698887012357/V533Pwsr_400x400.jpg",
"name": "Kevin Thomas Van Cott"
},
{
"id": "7",
"username": "joshuakgoldberg.com",
"profile_image_url": "https://cdn.bsky.app/img/avatar/plain/did:plc:hwtki3j7oghodc7h6gqnrtro/bafkreicvkrpjmc7yoviwf5vhkecgcgyn23x24ix4aqdwiikcl226c64k2q@jpeg",
"name": "Josh Goldberg 💖"
},
{
"id": "8",
"username": "beaussan.io",
"profile_image_url": "https://cdn.bsky.app/img/avatar/plain/did:plc:opw3iapscc55nlgrx6q2yjah/bafkreiaj72657msogojjdk4wr76fe3umimydon3pzzrquuj7opcftntrqa@jpeg",
"name": "Nicolas Beaussart"
},
{
"id": "9",
"username": "MidnightDesign",
"name": "Rudolph Gottesheim",
"profile_image_url": "https://avatars.githubusercontent.com/u/743172?v=4"
},
{
"id": "10",
"username": "stephaneledorze",
"name": "stephane le dorze",
"profile_image_url": "https://pbs.twimg.com/profile_images/1587561006317899777/y8cgxyI7_400x400.jpg"
},
{
"id": "22918124",
"username": "gregberge_",
"profile_image_url": "https://pbs.twimg.com/profile_images/1722358890807861248/75S7CB3G_normal.jpg",
"name": "Greg Bergé"
},
{
"id": "19268321",
"username": "housecor",
"profile_image_url": "https://pbs.twimg.com/profile_images/1963593369306750976/7gPWqEa8_400x400.jpg",
"name": "Cory House"
},
{
"id": "3126044440",
"username": "pkgacek",
"profile_image_url": "https://pbs.twimg.com/profile_images/585047168899244033/kpQ-Reb7_normal.jpg",
"name": "Piotr Gacek 🐟"
},
{
"id": "95668959",
"username": "kuizinas",
"profile_image_url": "https://pbs.twimg.com/profile_images/1978959812172894209/yQXTNMuJ_400x400.jpg",
"name": "Gajus"
},
{
"id": "1632752318994153472",
"username": "imabhiprasad",
"profile_image_url": "https://pbs.twimg.com/profile_images/1917768224612962304/7RHnN131_400x400.jpg",
"name": "Abhijeet Prasad"
},
{
"id": "1513218780167655427",
"username": "fernandoeeu_dev",
"profile_image_url": "https://pbs.twimg.com/profile_images/1513219705317822473/9CR0LjqB_normal.jpg",
"name": "Fernando"
},
{
"id": "44217212",
"username": "TkDodo",
"profile_image_url": "https://pbs.twimg.com/profile_images/1803004614372937728/OSIQ4AGx_400x400.jpg",
"name": "Dominik 🔮"
},
{
"id": "481186762",
"username": "Mokkapps",
"profile_image_url": "https://pbs.twimg.com/profile_images/1825498937911889920/FtBqvqgK_400x400.jpg",
"name": "Michael Hoffmann"
},
{
"id": "416394303",
"username": "nicoespeon",
"profile_image_url": "https://pbs.twimg.com/profile_images/1649018862527041536/9s6Nlwyj_normal.jpg",
"name": "Nicolas Carlo"
},
{
"id": "15736190",
"username": "smashingmag",
"profile_image_url": "https://pbs.twimg.com/profile_images/1669591955653685248/mTxJyTGY_normal.jpg",
"name": "Smashing Magazine 🇺🇦 🏳️🌈"
},
{
"id": "21046936",
"username": "danvdk",
"profile_image_url": "https://pbs.twimg.com/profile_images/485135064557035520/zATSd81r_normal.jpeg",
"name": "Dan Vanderkam"
},
{
"id": "380616577",
"username": "stefanjudis",
"profile_image_url": "https://pbs.twimg.com/profile_images/1465780299380502537/pptBYF44_normal.jpg",
"name": "Stefan Judis"
},
{
"id": "827145937030148097",
"username": "BKailaash",
"profile_image_url": "https://pbs.twimg.com/profile_images/839048279442718720/SiC-Q2Nc_normal.jpg",
"name": "Kailaash"
},
{
"id": "18191376",
"username": "waldekm",
"profile_image_url": "https://pbs.twimg.com/profile_images/1670040743006617602/N5VnH4YU_normal.jpg",
"name": "Waldek Mastykarz"
}
]
}
}
================================================
FILE: packages/docs/src/components/Contributors.astro
================================================
---
import { readFile } from 'node:fs/promises';
const ENVIRONMENT = import.meta.env.ENVIRONMENT;
const GITHUB_TOKEN = import.meta.env.GITHUB_TOKEN;
const isFetch = ENVIRONMENT !== 'development' && Boolean(GITHUB_TOKEN);
interface Contributor {
html_url: string;
avatar_url: string;
login: string;
}
const url = new URL('/repos/webpro-nl/knip/contributors', 'https://api.github.com');
url.searchParams.set('per_page', '100');
const getAllContributors = async (url: URL) => {
const allContributors: Contributor[] = [];
let nextUrl: string = url.href;
console.log('\n');
while (nextUrl) {
console.log(`Fetching contributors from ${nextUrl}`);
const response = await fetch(nextUrl, {
headers: {
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${GITHUB_TOKEN}`,
'X-GitHub-Api-Version': '2022-11-28',
},
});
if (!response.ok) {
console.error('GitHub API request failed:');
console.log(await response.text());
return [];
}
const contributors = await response.json();
allContributors.push(...contributors);
const linkHeader = response.headers.get('Link');
nextUrl = linkHeader?.match(/<([^>]+)>;\s*rel="next"/)?.[1] || '';
}
return allContributors;
};
const contributors: Contributor[] = isFetch
? await getAllContributors(url)
: JSON.parse(await readFile('mock/contributors.json', 'utf-8'));
if (!Array.isArray(contributors)) console.log(contributors);
---
{
(Array.isArray(contributors) ? contributors : []).map(contributor => (
))
}
================================================
FILE: packages/docs/src/components/EmojiBlastButton.astro
================================================
================================================
FILE: packages/docs/src/components/Footer.astro
================================================
---
import Default from '@astrojs/starlight/components/Footer.astro';
---
ISC License © 2024
Lars Kappert
================================================
FILE: packages/docs/src/components/Head.astro
================================================
---
import Default from '@astrojs/starlight/components/Head.astro';
const slug = Astro.locals.starlightRoute.slug;
const url = new URL(Astro.url);
url.pathname = `/og/docs${slug ? `/${slug}` : ''}.webp`;
---
================================================
FILE: packages/docs/src/components/Post.astro
================================================
---
import { formatTimestamp, type PostWithUser, replaceShortenedUrls } from '../util/post.js';
const post: PostWithUser = replaceShortenedUrls(Astro.props.data);
---
{
post && (
<>
')} />
17 ? `https://twitter.com/${post.user.username}/status/${post.id}` : `https://bsky.app/profile/${post.user.username}/post/${post.id}`}`}>
{formatTimestamp(post.created_at)}
>
)
}
================================================
FILE: packages/docs/src/components/Posts.astro
================================================
---
import { readFile } from 'node:fs/promises';
import { Card, CardGrid } from '@astrojs/starlight/components';
import type { PostResponse } from '../util/post.js';
import Post from './Post.astro';
const testimonials: PostResponse = JSON.parse(await readFile('src/assets/testimonials.json', 'utf-8'));
---
{
testimonials.data.map(testimonial => {
return (
user.id === testimonial.author_id
),
media: testimonials.includes.media?.filter(media =>
testimonial.entities.urls
?.map(url => url.media_key)
.includes(media.media_key)
),
}}
/>
);
})
}
================================================
FILE: packages/docs/src/components/Projects.astro
================================================
---
import Adobe from '../assets/projects/adobe.svg';
import AGGrid from '../assets/projects/ag-grid.svg';
import AGGrid2 from '../assets/projects/ag-grid2.svg';
import Anthropic from '../assets/projects/anthropic.svg';
import ArkType from '../assets/projects/arktype.svg';
import AstroLogo from '../assets/projects/astro.svg';
import AWS from '../assets/projects/aws.svg';
import Backstage from '../assets/projects/backstage.svg';
import Cloudflare from '../assets/projects/cloudflare.svg';
import CreateTypeScriptApp from '../assets/projects/create-typescript-app.png';
import Datadog from '../assets/projects/datadog.svg';
import ESLint from '../assets/projects/eslint.svg';
import FreeCodeCamp from '../assets/projects/freecodecamp.svg';
import Google from '../assets/projects/google.svg';
import Grafana from '../assets/projects/grafana.svg';
import Guardian from '../assets/projects/guardian.svg';
import Microsoft from '../assets/projects/microsoft.svg';
import Mocha from '../assets/projects/mocha.svg';
import Nuxt from '../assets/projects/nuxt.svg';
import Prettier from '../assets/projects/prettier.svg';
import Sanity from '../assets/projects/sanity.svg';
import SAP from '../assets/projects/sap.svg';
import Sentry from '../assets/projects/sentry.svg';
import Shopify from '../assets/projects/shopify.svg';
import SourceGraph from '../assets/projects/sourcegraph.svg';
import Stately from '../assets/projects/stately.svg';
import Storybook from '../assets/projects/storybook.svg';
import Svelte from '../assets/projects/svelte.svg';
import TanStack from '../assets/projects/tanstack.png';
import TypeScriptESLint from '../assets/projects/typescript-eslint.svg';
import Vercel from '../assets/projects/vercel.svg';
---
================================================
FILE: packages/docs/src/components/Sponsors.astro
================================================
---
const { showAll = true } = Astro.props;
---
================================================
FILE: packages/docs/src/components/SponsorsChart.astro
================================================
---
import Chart from '../assets/venz-chart.svg';
---
================================================
FILE: packages/docs/src/content/docs/blog/brief-history.md
================================================
---
title: A Brief History Of Knip
date: 2023-10-15
sidebar:
order: 8
---
_Published: 2023-10-15_
If you are fond of short lists and brief histories, then this page was written
just for you!
- 2022-10-04: The [initial commit][1]. Still so tiny at that point, but the seed
was planted. Starting out with finding unused files and exports, the name was
Exportman! 🦸
- 2022-10-09: Big plans and a rename 5 days later, the first published version
of Knip was [v0.1.2][2].
- 2022-11-22: Unused dependencies and support for workspaces and plugins in the
[first alpha of v1][3].
- 2023-01-10: Lots of testing and fixes led to [Knip v1][4].
- 2023-03-22: [Knip v2][5] saw a full rewrite of the TypeScript backend.
- 2023-10-15: [Introduction of Knip v3][6].
[1]: https://github.com/webpro-nl/knip/commit/9589dfe22608da7d89f2613383da6db5826226d2
[2]: https://github.com/webpro-nl/knip/tree/0.1.2
[3]: https://github.com/webpro-nl/knip/releases/tag/1.0.0-alpha.0
[4]: https://github.com/webpro-nl/knip/tree/1.0.0
[5]: https://github.com/webpro-nl/knip/issues/73
[6]: ./knip-v3.mdx
================================================
FILE: packages/docs/src/content/docs/blog/for-editors-and-agents.md
================================================
---
title: Knip for Editors & Agents
date: 2025-12-17
sidebar:
order: 2
---
_Published: 2025-12-17_
Three years in, Knip has found its place in [over 10.000 projects][1] and is
downloaded [over 18M times/month][2]. A long period of steady growth in usage
and stability allows Knip to become more accessible to more people. That's why
I'm excited and proud to introduce the brand new [Editor Extension][3] **and**
MCP Server. For humans and coding agents alike, Knip will help keep your
codebases tidy.
Don't forget... Knip it before you ship it!
## Editor Extension
This one is for you.
[The usual suspects][4] like red squiggles for unused exports are there. What
really moves the needle for DX with Knip's module graph is **navigation**. A
completely unique way to view & fly through codebases. Connect the dots during
development and refactors, while keeping things in check. We're starting out
with [3 key features][5]:
1. **Hover over Export** for import & usage locations
2. **Imports Tree View** for direct links to implementations
3. **Exports Tree View** for direct links to import & usage locations
The extension has a [built-in MCP Server][6] with a command and resources to
configure Knip for you, _completely automated_.
Find [Knip on the VS Code Marketplace][7] and find [Knip in the Open VSX
Registry][8].
## MCP Server
Configuring Knip has always been a major headache to many. No more. Tell your
coding agent to "configure knip" and it will RTFM so you don't have to. Using a
newer model like Opus 4.5 or GPT 5.2 results in an optimized `knip.json` file
and an uncluttered codebase.
The [MCP Server is available][9] separately and built into the VS Code
Extension.
## Language Server
The VS Code Extension and the MCP Server are powered by the new Language Server.
It's a custom server that builds the full module graph of your project, and
provides a session with a graph explorer to request all sorts of interesting
information. Queries like "where is an export imported" or "is this import part
of a circular dependency" are just scratching the surface here.
Extensions for other IDEs can be built on top. See
[language-server/README.md][10]
## Screenshots
- [Lint Findings][4]
- [Imports & Exports][5]
- [Contention][11]
- [Circular Dependencies][12]
- [Conflicts][13]
- [Branching][14]
- [VS Code Extension Settings][15]
### Lint Findings
![Lint Findings][16]
### Imports & Exports
![hover][17]
### Contention
The IDE extension shows extra issues in the tree views like circular
dependencies. We're starting out with some extra novelties like conflicting and
branched/diamond-shaped import chains.
#### Circular Dependencies
If an import is part of a circular dependency, Knip will display:
![Circular Dependencies][18]
#### Conflicts
TypeScript shows direct conflicts when importing or re-exporting the same named
export from different files. Except when the problem is more subtle and the
chain spans more than one file. Knip warns:
![Conflicts][19]
#### Branching
Branched or diamond-shaped imports chains indicate unnecessary re-exports and
complexity. They help to untangle large codebases and shrink or get rid of
barrel files. Knip warns:
![Branching][20]
### VS Code Extension Settings
![VS Code Extension Settings][21]
[1]: https://github.com/webpro-nl/knip/network/dependents
[2]: https://www.npmjs.com/package/knip
[3]: #editor-extension
[4]: #lint-findings
[5]: #imports--exports
[6]: #mcp-server
[7]: https://marketplace.visualstudio.com/items?itemName=webpro.vscode-knip
[8]: https://open-vsx.org/extension/webpro/vscode-knip
[9]: https://www.npmjs.com/package/@knip/mcp
[10]: https://github.com/webpro-nl/knip/blob/main/packages/language-server/README.md
[11]: #contention
[12]: #circular-dependencies
[13]: #conflicts
[14]: #branching
[15]: #vs-code-extension-settings
[16]: /screenshots/editors-and-agents/diagnostics.webp
[17]: /screenshots/editors-and-agents/imports-exports.webp
[18]: /screenshots/editors-and-agents/circular-dependency.webp
[19]: /screenshots/editors-and-agents/conflict.webp
[20]: /screenshots/editors-and-agents/branch.webp
[21]: /screenshots/editors-and-agents/vscode-extension-settings.webp
================================================
FILE: packages/docs/src/content/docs/blog/knip-v3.mdx
================================================
---
title: Announcing Knip v3
date: 2023-10-15
sidebar:
order: 9
---
import { Tabs, TabItem } from '@astrojs/starlight/components';
_Published: 2023-10-15_
Lots of new users got introduced to Knip, coming with clear bug reports, helpful
insights, superb reproductions and great suggestions this year. You're all a
friendly and helpful bunch! Recently I've opened a Discord channel where more
communication, collaboration, ideas and updates are happening: feel free to join
[The Knip Barn][1]!
Today, Knip has [over 140k weekly downloads on npm][2], [almost 4000 stars on
GitHub][3], and [over 500 repositories][4] using it. While numbers are just
numbers, they do add to the positive feedback I'm receiving daily. Everything
combined makes me think I'm on the right track which is very motivating to keep
working on Knip.
## So... What's Been Cooking Lately?
- Migration to a monorepo setup
- This very website built with Starlight 🌟
- Extended documentation for just about everything
- Improved JSON reporter for external integrations (e.g. [GitHub Action][5])
- Some breaking changes, but you probably don't need to make any changes
## Breaking Changes
A major bump comes with breaking changes, but most likely no changes necessary
on your end:
- Removed support for Node.js v16, Knip v3 requires at least Node.js v18.6
- Simplified [exit codes][6]
- [Production mode][7] now includes types by default (add `--exclude types` for
previous behavior)
- Removed `--ignore-internal` flag; [`@internal`][8] exports ignored in
production mode now
- The `--debug-file-filter` flag is removed
- The `jsonExt` reporter is now the default [JSON reporter][9] (the previous one
is gone)
- Moved `typescript` to `peerDependencies` (requires `>=5.0.4`)
## Installation
Try out the latest Knip v3 release today!
```shell
npm install -D knip
```
```shell
pnpm add -D knip
```
```shell
bun add -D knip
```
```shell
yarn add -D knip
```
Remember, Knip it before you ship it! Have a great day ☀️
[1]: https://discord.gg/r5uXTtbTpc
[2]: https://www.npmjs.com/package/knip
[3]: https://github.com/webpro-nl/knip/stargazers
[4]: https://github.com/webpro-nl/knip/network/dependents
[5]: https://github.com/marketplace/actions/knip-reporter
[6]: ../reference/cli.md#--no-exit-code
[7]: ../features/production-mode.md
[8]: ../reference/jsdoc-tsdoc-tags.md#internal
[9]: ../features/reporters.md#json
================================================
FILE: packages/docs/src/content/docs/blog/knip-v4.mdx
================================================
---
title: Announcing Knip v4
date: 2024-01-16
sidebar:
order: 6
---
import { Tabs, TabItem } from '@astrojs/starlight/components';
_Published: 2024-01-16_
I'm happy to announce that Knip v4 is available!
The work took over a month and the process of [slimming down to speed up][1]
ended up really well: significant faster runs and reduced memory usage. In the
meantime, v3 continued to receive more contributions, plugins and bug fixes.
## Highlights
Compared to v3, here are the highlights:
- Performance: significant speed bump (up to 80%!)
- Performance: globbing in combo with `.gitignore` is a lot more efficient
- Configuration: [built-in compilers][2] (for Astro, MDX, Svelte & Vue)
- The `ignore` option has been improved
- Internal refactoring to serialize data for future improvements like caching.
The actual performance win in your projects depends on various factors like size
and complexity.
## Major Changes
The changes have been tested against various repositories, but it's possible
that you will encounter false positives caused by the major refactoring that has
been done. If you do, [please report][3]!
### Unused Class Members
Finding unused class members is no longer enabled by default. Here's why it's
now opt-in:
- When using Knip for the first time on a large repository it can crash after a
while with an out of memory error. This is a terrible experience.
- Plenty of codebases don't use classes at all, keeping TS programs in memory is
a waste of resources.
- Many configurations already exclude `classMembers` from the output.
Enable unused class members by using the CLI argument or the configuration
option:
```shell
knip --include classMembers
```
```json
{
"include": ["classMembers"]
}
```
Now that unused class members is opt-in and better organized within Knip, it
might be interesting to start looking at opt-ins for other unused members, such
as those of types and interfaces.
By the way, enum members are "cheap" with the v4 refactor, so those are still
included by default.
### Compilers
You can remove the `compilers` option from your configuration. Since you can
override them, your custom compilers can stay where they are. This also means
that you can go back from `knip.ts` to `knip.json` if you prefer.
### Ignore Files
The `ignore` option accepted patterns like `examples/`, but if you want to
ignore the files inside this folder you should update to globs like
`examples/**`.
## What's Next?
The refactoring for this release opens the door to more optimizations, such as
caching. I'm also very excited to see how deeper integrations such as in GitHub
Actions or IDEs like VS Code or WebStorm may further develop.
Remember, if you are you using Knip at work your company can [sponsor me][4]!
## One More Thing...
An idea I've been toying with is "tagged exports". The idea is that you can tag
exports in a JSDoc comment. The tag does not need to be part of the JSDoc or
TSDoc spec. For example:
```ts
/** @custom */
export const myExport = 1;
```
Then, include or exclude such tagged exports from the report like so:
```shell
knip --experimental-tags=+custom
knip --experimental-tags=-custom,-internal
```
This way, you can either focus on or ignore specific tagged exports with tags
you define yourself. This also works for individual class or enum members.
Once this feature is intuitive and stable, the `experimental` flag will be
removed and option(s) added to the Knip configuration file. The docs are in the
[CLI reference][5].
## Let's Go!
What are you waiting for? Start using Knip v4 today!
```shell
npm install -D knip
```
```shell
pnpm add -D knip
```
```shell
bun add -D knip
```
```shell
yarn add -D knip
```
Remember, Knip it before you ship it! Have a great day ☀️
[1]: ./slim-down-to-speed-up.md
[2]: ../features/compilers.md
[3]: ../guides/issue-reproduction.md
[4]: https://github.com/sponsors/webpro
[5]: ../reference/cli.md#--tags
================================================
FILE: packages/docs/src/content/docs/blog/knip-v5.mdx
================================================
---
title: Announcing Knip v5
date: 2024-02-10
sidebar:
order: 5
---
import { Tabs, TabItem } from '@astrojs/starlight/components';
_Published: 2024-02-10_
Today brings the smallest major release so far. Tiny yet mighty!
Below are two cases to demonstrate the change in how unused exports are
reported.
## Case 1
The first case shows two exports with a namespaced import that references one of
those exports explicitly:
```ts title="knip.js"
export const version = 'v5';
export const getRocket = () => '🚀';
```
```ts title="index.js"
import * as NS from './knip.js';
console.log(NS.version);
```
In this case we see that `getRocket` is an unused export.
Previously it would go into the "Unused exports in namespaces" category
(`nsExports`). This issue has been moved to the "Unused exports" category
(`exports`).
## Case 2
The second case is similar, but only the imported namespace itself is
referenced. None of the individual exports is referenced:
```ts title="index.js"
import * as NS from './knip.js';
import send from 'stats';
send(NS);
```
Are the `version` and `getRocket` exports used? We can't know. The same is true
for the spread object pattern:
```ts title="index.js"
import * as NS from './knip.js';
const Spread = { ...NS };
```
Previously those exports would go into the "Unused exports in namespaces"
category. This is still the case, but this category is no longer enabled by
default.
## Include unused exports in namespaces
To enable this type of issues in Knip v5, add this argument to the command:
```shell
knip --include nsExports
```
Or in your configuration file:
```json title="knip.json"
{
"include": ["nsExports", "nsTypes"]
}
```
Now `version` and `getRocket` will be reported as "Unused exports in
namespaces".
Note that `nsExports` and `nsTypes` are split for more granular control.
## Handling exports in namespaced imports
You have a few options to handle namespaced imports when it comes to unused
exports.
### 1. Use named imports
Regardless of whether `nsExports` is enabled or not, it's often good practice to
replace the namespaced imports with named imports:
```ts title="index.js"
import { version, getRocket } from './knip.js';
send({ version, getRocket });
```
Whenever possible, explicit over implicit is often the better choice.
### 2. Standardized JSDoc tags
Using one of the available JSDoc tags like `@public` or `@internal`:
```ts title="knip.js"
export const version = 'v5';
/** @public */
export const getRocket = () => '🚀';
```
Assuming only imported using a namespace (like in the example cases above), this
will exclude the `getRocket` export from the report, even though it isn't
explicitly referenced.
### 3. Arbitrary JSDoc tags
Another solution is to tag individual exports arbitrarily:
```ts title="knip.js"
export const version = 'v5';
/** @launch */
export const getRocket = () => '🚀';
```
And then exclude the tag like so:
```shell
$ knip --experimental-tags=-launch
Exports in used namespace (1)
version NS unknown knip.js:1:1
```
Assuming only imported using a namespace (like in the example cases above), this
will exclude the `getRocket` export from the report, even though it isn't
explicitly referenced.
## A better default
I believe this behavior in v5 is the better default: have all exports you want
to know about in a single category, and those you probably want to ignore in
another that's disabled by default.
Before the [v4 refactoring][1], this would be a lot harder to implement. That
refactoring turns out to be a better investment than expected. Combined with a
better understanding of how people write code and use Knip, this change is a
natural iteration.
Why the major bump? It's not breaking for the large majority of users, but for
some it may be breaking. For instance when relying on the [JSON reporter][2],
other reporter output, or custom [preprocessing][3]. It's not a bug fix, it's
not a new feature, but since semver is all about setting expectations I feel the
change is large enough to warrant a major bump.
## Let's Go!
What are you waiting for? Start using Knip v5 today!
```shell
npm install -D knip
```
```shell
pnpm add -D knip
```
```shell
bun add -D knip
```
```shell
yarn add -D knip
```
Remember, Knip it before you ship it! Have a great day ☀️
[1]: ../blog/slim-down-to-speed-up.md
[2]: ../features/reporters.md#json
[3]: ../features/reporters.md#preprocessors
================================================
FILE: packages/docs/src/content/docs/blog/knip-v6.md
================================================
---
title: Announcing Knip v6
date: 2026-03-20
sidebar:
order: 1
---
_Published: 2026-03-20_
## Knip v6 is out!
This release is all about replacing the TypeScript backend entirely with
`oxc-parser` and `oxc-resolver`, and making Knip a whole lot faster!
## From TypeScript to oxc
Two years ago, the ["slim down to speed up"][1] and [Knip v4][2] work removed a
lot of overhead around TypeScript programs, made serialization and caching
practical, and improved memory efficiency a lot. But there was still a ceiling:
parsing and module resolution still depended on TypeScript APIs designed for
IDEs and language servers — not for the kind of single-pass static analysis Knip
does.
Starting today, Knip v6 parses your source files with [oxc-parser][3]. This is
more than just a parser swap for the sake of using the latest 'n greatest.
Knip has always been designed to parse each file only once, but the TypeScript
backend carried the overhead of wiring up an entire program along with the
typechecker. That's useful for IDEs keeping symbols connected, but much less so
when you only need to traverse an AST once to collect imports and exports.
The TypeScript backend made the setup as a whole harder and slower than it
needed to be, especially to keep large monorepos in check.
Now with TypeScript itself Go-ing places, replacing that backend was only a
matter of time.
Unsurprisingly, the search didn't take long: `oxc-parser` offers everything we
need and its (experimental) raw transfer is crazy fast. Massive props to
[overlookmotel][4], [Boshen][5] and all contributors for all the work on
[the oxc suite][6]!
## Performance tuning
Next to this major refactor, I've been having a ball tuning Knip's performance
further. One thing to highlight here is that a few more plugins have been
refactored to statically analyze configuration files directly, as opposed to
actually importing them (including transitive dependencies...). This includes
the ESLint ("flat config"), tsdown and tsup plugins.
## The numbers
Comparing v5 and v6 in some projects using Knip, all boosts are in the **2-4x** range:
[![venz-chart][8]][7]
Trust me, I could look at this chart all day long! The same numbers in a table:
| Project | v5.88.0 | v6.0.0 |
| ---------------- | ------: | -----: |
| [astro][9] | 4.0s | 2.0s |
| [query][10] | 3.8s | 1.7s |
| [rolldown][11] | 3.7s | 1.7s |
| [sentry][12] | 11.0s | 4.0s |
| [TypeScript][13] | 3.7s | 0.9s |
## What's new
- Did I already mention Knip got 2-4x faster?
- Support for TS namespaces (and modules), new issue type `namespaceMembers`:
```ts
export namespace MyNamespace {
export const myName = 'knip'; // we were ignored in v5,
export type MyType = string; // yet in v6 we are included
}
```
## Breaking changes
Granted, most of you won't even notice. Here's the list:
- Dropped support for Node.js v18 → Knip v6 requires Node.js v20.19.0 or newer
- Dropped issue type `classMembers`
- Dropped `--include-libs` → this is now the default and only behavior
- Dropped `--isolate-workspaces` → this is now the default and only behavior
- Dropped `--experimental-tags` → use [`--tags`][14]
- In [reporter functions][15], `issues.files` is consistent with other issue shapes. Removed `issues._files`.
- In the [JSON reporter][16], issues are consistently arrays for any issue type. Removed root `files`.
## Editor Extensions
[Editor extensions][17] benefit from the core upgrades, for being faster and more
memory-efficient. Regardless of new extension releases, the local version of
Knip will be detected and used. Upgrade `knip` in your dependencies when you're
ready.
## What about classMembers?
I feel you. Even Knip itself was using it. Until today.
The problem is that the implementation relies on the JS-based `ts.LanguageService`
API that exposes the `findReferences` method. TypeScript v6 is the last JS-based
release, and TypeScript v7 is a full rewrite in Go. I am left wondering if it
ever will be feasible and practical to build such features using primitives
(i.e. not via LSP) in a JS-based CLI (references: [microsoft/typescript-go#455][18],
[@typescript/api][19]). Knip was already pretty unique for even trying this in
a CLI tool.
Not that many projects seem to be using it either:
[github.com search for "classMembers path\:knip.json"][20].
If your project relies on it, feel free to open an issue on GitHub or contact me
and maybe we can work something out. Maybe a separate dedicated tool could work,
or extended support for Knip v5.
## Upgrade today
```sh
npm install -D knip@latest
```
## Deep closing thoughts...
Remember, Knip it before you ship it! Have a great day ☀️
[1]: ./slim-down-to-speed-up.md
[2]: ./knip-v4.mdx
[3]: https://oxc.rs/docs/guide/usage/parser
[4]: https://github.com/overlookmotel
[5]: https://github.com/Boshen
[6]: https://oxc.rs
[7]: https://try.venz.dev/?type=bar&labelX=Knip&labelY=duration+(s)&label=astro&label=query&label=rolldown&label=sentry&label=typescript&l=v5.88.0&l=v6.0.0&data=4*2&data=3.8*1.7&data=3.7*1.7&data=11*4&data=3.7*0.9
[8]: https://cdn.venz.dev/i/chart.svg?pad=0&type=bar&labelX=Knip&labelY=duration+(s)&label=astro&label=query&label=rolldown&label=sentry&label=typescript&l=v5.88.0&l=v6.0.0&data=4*2&data=3.8*1.7&data=3.7*1.7&data=11*4&data=3.7*0.9&theme=dark
[9]: https://github.com/withastro/astro
[10]: https://github.com/TanStack/query
[11]: https://github.com/rolldown/rolldown
[12]: https://github.com/getsentry/sentry
[13]: https://github.com/microsoft/TypeScript
[14]: ../reference/configuration.md#tags
[15]: ../features/reporters.md#custom-reporters
[16]: ../features/reporters.md#json
[17]: ../reference/integrations.md
[18]: https://github.com/microsoft/typescript-go/discussions/455
[19]: https://github.com/microsoft/typescript-go/tree/main/_packages/api
[20]: https://github.com/search?q=classMembers%20path%3Aknip.json&type=code
================================================
FILE: packages/docs/src/content/docs/blog/migration-to-v1.md
================================================
---
title: Migration to v1
---
_2023-01-04_
When coming from version v0.13.3 or before, there are some breaking changes:
- The `entryFiles` and `projectFiles` options have been renamed to `entry` and
`project`.
- The `--dev` argument and `dev: true` option are gone, this is now the default
mode (see [production mode][1]).
- Workspaces have been moved from the root of the config to the `workspaces` key
(see [workspaces][2]).
- The `--dir` argument has been renamed to `--workspace`.
## Example
A configuration like this in v0.13.3 or before...
```json
{
"entryFiles": ["src/index.ts"],
"projectFiles": ["src/**/*.ts", "!**/*.spec.ts"],
"dev": {
"entryFiles": ["src/index.ts", "src/**/*.spec.ts", "src/**/*.e2e.ts"],
"projectFiles": ["src/**/*.ts"]
}
}
```
...should become this for v1...
```json
{
"entry": ["src/index.ts!"],
"project": ["src/**/*.ts!"]
}
```
Much cleaner, right? For some more details:
- The `dev` property for the `--dev` flag is now the default mode.
- Use `--production` to analyze only the `entry` and `project` files suffixed
with `!`.
- The glob patterns for both types of test files (`*.spec.ts` and `*.e2e.ts`)
are no longer needed:
- Regular test files like `*.test.js` and `*.spec.ts` etc. are automatically
handled by Knip.
- The `*.e2e.ts` files is configured with the Cypress or other plugin. Note
that Cypress uses `*.cy.ts` for spec files, but this could be overridden
like so:
```json
{
"entry": "src/index.ts!",
"project": "src/**/*.ts!",
"cypress": {
"entry": "src/**/*.e2e.ts"
}
}
```
[1]: ../features/production-mode.md
[2]: ../features/monorepos-and-workspaces.md
================================================
FILE: packages/docs/src/content/docs/blog/release-notes-v2.md
================================================
---
title: Release Notes v2
sidebar:
order: 10
---
_2023-03-22_
## Breaking changes
When coming from v1, there are no breaking changes in terms of configuration.
## Changes
There are some changes regarding CLI arguments and output:
- Knip now runs on every \[workspace]\[1] automatically (except for the ones in
`ignoreWorkspaces: []`).
- The "Unlisted or unresolved dependencies" is split in "Unlisted dependencies"
and "Unresolved imports".
- Bug fixes and increased correctness impact output (potentially causing CI to
now succeed or fail).
## New features
Rewriting a major part of Knip's core from scratch allows for some new exciting
features:
- **Performance**. Files are read only once, and their ASTs are traversed only
once. Projects of any size will notice the difference. Total running time for
some projects decreases with 90%.
- **Compilers**. You can now include other file types such as `.mdx`, `.vue` and
`.svelte` in the analysis.
Internally, the `ts-morph` dependency is replaced by `typescript` itself.
## Other improvements
- Improved support for workspaces.
- Improved module resolutions, self-referencing imports, and other things you
don't want to worry about.
- Configure `ignoreDependencies` and `ignoreBinaries` at the workspace level.
- Simplified plugins model: plugin dependency finder may now return any type of
dependency in a single array: npm packages, local workspace packages, local
files, etc. (module and path resolution are handled outside the plugin).
- Many bugfixes.
================================================
FILE: packages/docs/src/content/docs/blog/slim-down-to-speed-up.md
================================================
---
title: Slim down to speed up
date: 2023-12-14
sidebar:
order: 7
---
_Published: 2023-12-14_
**tl;dr;** Memory usage is up to 50% lower, runs are up to 60% faster and you
can start using v4 canary today. No "unused class members" for the time being,
but this feature is planned to be restored.
## Introduction
Honestly, performance has always been a challenge for Knip. A longstanding
bottleneck has finally been eliminated and Knip is going to be a lot faster.
Skip straight to the bottom to install v4 canary and try it out! Or grab
yourself a nice drink and read on if you're interested in where we are coming
from, and where we are heading.
## Projects & Workspaces
From the start, Knip has relied on TypeScript for its robust parser for
JavaScript and TypeScript files. And on lots of machinery important to Knip,
like module resolution and accurately finding references to exported values.
Parts of it can be customized, such as the (virtual) file system and the module
resolver.
In TypeScript terms, a "project" is like a workspace in a monorepo. Same as each
workspace has a `package.json`, each project has a `tsconfig.json`. The
`ts.createProgram()` method is used to create a program based on a
`tsconfig.json` and the machinery starts to read and parse source code files,
resolve modules, and so on.
Up until v2, when Knip wanted to find unused things in a monorepo, all programs
for all workspaces were loaded into memory. Workspaces often depend on each
other, so Knip couldn't load one project, analyze it and dispose it. This way,
connections across workspaces would be lost.
## Shared Workspaces
Knip v2 said goodbye to this approach and implemented its own TypeScript backend
(after using `ts-morph` for this). Based on the compatibility of
`compilerOptions`, workspaces were merged into shared programs whenever
possible. Having less programs in memory led to significant performance
improvements. Yet ultimately it was still a stopgap, since everything was still
kept in memory for the duration of the process.
"Why does everything need to stay in memory?", you may wonder. The answer is
that Knip uses `findReferences` at the end of the process. Knip relied on this
TypeScript Language Server method for everything that's not easy to find. More
about that later in [the story of findReferences][1]
## Serialization
Fortunately, everything that's imported and exported from source files
(including things like members of namespaces and enums) can be found relatively
easily during AST traversal. This way, references to exports don't have to be
"traced back" later on.
It's mostly class members that are harder to find due to their dynamic nature.
Without these, all information can be serialized for storage and retrieval (in
memory or on disk). Slimming down by taking class members out of the equation
simplifies things a lot and paves the way for all sorts of improvements.
## We Have To Slim Down
The relevant part in the linting process can be summarized in 5 steps:
1. Collect entry files and feed them to TypeScript
2. Read files, resolve modules, and create ASTs
3. Traverse ASTs and collect imports & exports
4. Match exports against imports to determine what's unused
5. Find references to hard-to-find exported values and members
If we would hold on to reporting unused class members, then especially steps 2
and 5 are hard to decouple. The program and the language service containing the
source files used to eventually trace back references can't really be decoupled.
So class members had to go. Sometimes you have to slim down to keep moving. One
step back, two steps forward.
If you rely on this feature, fear not. I plan to bring it back before the final
v4, but possibly behind a flag.
## What's In Store?
So with this out of the way, everything becomes a lot clearer and we can finally
really start thinking about significant memory and performance improvements. So
what's in store here? A lot!
- We no longer need to keep everything in memory, so workspaces are read and
disposed in isolation, one at a time. Memory usage will be spread out more
even. This does not make it faster, but reducing "out of memory" issues is
definitely a Good Thing™️ in my book.
- Knip could recover from unexpected exits and continue from the last completed
workspace.
- The imports and exports are in a format that can be serialized for storage and
retrieval. This opens up interesting opportunities, such as local caching on
disk, skipping work in subsequent runs, remote caching, and so on.
- Handling workspaces in isolation and serialization result in parallelization
becoming a possibility. This becomes essential, as module resolution and AST
creation and traversal are now the slowest parts of the process and are not
easy to optimize significantly (unless perhaps switching to e.g Rust).
- No longer relying on `findReferences` speeds up the export/import matching
part part significantly. So far I've seen **improvements of up to 60% on total
runtime**, and my guess is that some larger codebases may profit even more.
- The serialization format is still being explored and there is no caching yet,
but having the steps more decoupled is another Good Thing™️ that future me
should be happy about.
## Back It Up, Please
I heard you. Here's some example data. You can get it directly from Knip using
the `--performance` flag when running it on any codebase. Below we have some
data after linting the [Remix monorepo][2].
### Knip v3
```sh
$ knip --performance
Name size min max median sum
----------------------------- ---- ------ ------- ------- -------
findReferences 223 0.55 2252.35 8.46 5826.95
createProgram 2 50.78 1959.92 1005.35 2010.70
getTypeChecker 2 5.04 667.45 336.24 672.48
getImportsAndExports 396 0.00 7.19 0.11 104.46
Total running time: 9.7s (mem: 1487.39MB)
```
### Knip v4
```sh
$ knip --performance
...
Name size min max median sum
----------------------------- ---- ------ ------- ------- -------
createProgram 2 54.36 2138.45 1096.40 2192.81
getTypeChecker 2 7.40 664.83 336.12 672.23
getImportsAndExports 396 0.00 36.36 0.16 224.37
getSymbolAtLocation 2915 0.00 29.71 0.00 65.63
Total running time: 4.3s (mem: 729.67MB)
```
### Takeaways
The main takeaways here:
- In v3,`findReferences` is where Knip potentially spends most of its time
- In v4, total running time is down over 50%
- In v4, memory usage is down 50% (calculated using
`process.memoryUsage().heapUsage`)
- In v4, `getImportsAndExports` is more comprehensive to compensate for the
absence of `findReferences` - more on that below
Remember, unused class members are no longer reported by default in v4.
## The story of `findReferences`
Did I mention Knip uses `findReferences`...? Knip relied on it for everything
that's not easy to find. Here's an example of an export/import match that **is**
easy to find:
```ts title="import.ts"
import { MyThing } from './thing.ts';
```
```ts title="export.ts"
export const MyThing = 'cool';
```
In v2 and v3, Knip collects many of such easy patterns. Other patterns are
harder to find with static analysis. This is especially true for class members.
Let's take a look at the next example:
```ts title="MyClass.ts"
class MyClass {
constructor() {
this.method();
}
method() {}
do() {}
}
export const OtherName = MyClass;
```
```ts title="instance.ts"
import * as MyNamespace from './MyClass.ts';
const { OtherName } = MyNamespace;
const instance = new OtherName();
instance.do();
```
Without a call or `new` expression to instantiate `OtherName`, its `method`
member would not be used (since the constructor would not be executed). To
figure this out using static analysis goes a long way. Through export
declarations, import declarations, aliases, initializers, call expressions...
the list goes on and on. Yet all this magic is exactly what happens when you use
"Find all references" or "Go to definition" in VS Code.
Knip used `findReferences` extensively, but it's what makes a part of Knip
rather slow. TypeScript needs to wire things up (through
`ts.createLanguageService` and `program.getTypeChecker`) before it can use this,
and then it tries hard to find all references to anything you throw at it. It
does this very well, but the more class members, enum members and namespaced
imports your codebase has, the longer it inevitably takes to complete the
process.
Besides letting go of class members, a slightly more comprehensive AST traversal
is required to compensate for the absence of `findReferences` (it's the
`getImportsAndExports` function in the metrics above). I'd like to give you an
idea of what "more comprehensive" means here.
In the following example, `referencedExport` was stored as export from
`namespace.ts`, but it was not imported directly as such:
```ts title="namespace.ts"
export const referencedExport = () => {};
```
```ts title="index.ts"
import * as NS from './namespace.ts';
NS.referencedExport();
```
Previously, Knip used `findReferences()` to "trace back" the usage of the
exported `referencedExport`.
The gist of the optimization is to pre-determine all imports and exports. During
AST traversal of `index.ts` , Knip sees that `referencedExport` is attached to
the imported `NS` namespace, and stores that as an imported identifier of
`namespace.ts`. When matching exports against imports, this lookup comes at no
extra cost. Additionally, this can be stored as strings, so it can be serialized
too. And that means it can be cached.
Knip already did this for trivial cases as shown in the first example of this
article. This has now been extended to cover more patterns. This is also what
needs to be tested more extensively before v4 can be released. Its own test
suite and the projects in the integration tests are already covered so we're
well on our way.
For the record, `findReferences` is an absolute gem of functionality provided by
TypeScript. Knip is still backed by TypeScript, and tries to speed things up by
shaking things off. In the end it's all about trade-offs.
## Let's Go!
You can start using Knip v4 today, feel free to try it out! You might find a
false positive that wasn't there in v3, please [report this][3].
```sh
npm install -D knip@canary
```
Remember, Knip it before you ship it! Have a great day ☀️
[1]: #the-story-of-findreferences
[2]: https://github.com/remix-run/remix
[3]: https://github.com/webpro-nl/knip/issues
================================================
FILE: packages/docs/src/content/docs/blog/state-of-knip.md
================================================
---
title: The State of Knip
date: 2025-02-28
sidebar:
order: 3
---
_Published: 2025-02-28_
Honestly, Knip was a bit of a "cursed" project from the get-go. Getting anywhere
near a level of being broadly-ish valuable requires a good amount of
~~foolishness~~ determination, and it has always been clear it would stay far
from perfect. It's telling that most of [similar projects][1] have been
abandoned.
And even though Knip is in its infancy, this update is meant as a sign we feel
we're still on to something. External indicators include increased usage looking
at numbers such as dependent repositories on GitHub and weekly downloads on npm,
and bug reports about increasingly less rudimentary issues.
## Two Cases
For those interested, let's take a look at two cases that hopefully give an
impression of how Knip works under the hood and the level of issues we're
currently dealing with. It's assumed you already have a basic understanding of
Knip (otherwise please consider to read at least [entry files][2] and
[plugins][3] first).
### Case 1: Next.js
Let's say this default configuration represents, greatly simplified, [the
default `entry` patterns][4] for projects using Next.js:
```json
{
"next": {
"entry": ["next.config.ts", "src/pages/**/*.tsx"]
}
}
```
Those files will be searched for and then statically analyzed to collect
`import` statements and find other local files and external dependencies. This
is the generic way Knip handles all source files.
However, the game changes if the project uses the following Next.js
configuration:
```ts title="next.config.ts"
const nextConfig = {
pageExtensions: ['page.tsx'],
};
export default nextConfig;
```
Next.js will now look for files matching `src/pages/**/*.page.tsx` instead (note
the subtle change of the glob pattern). Knip should respect this to find used
and unused files properly.
Moving the burden to users for them to either not notice at all and get
incorrect results, or having to override the `next.entry` patterns and include
`src/pages/**/*.page.tsx` isn't good DX. Knip should take care of it.
To get the configuration object and the value of `pageExtensions`, Knip has to
actually load and execute `next.config.ts` ¹... and trouble is right around the
corner:
```ts title="next.config.ts"
const nextConfig = {
pageExtensions: ['page.tsx'],
env: {
BASE_URL: process.env.BASE_URL.toLowerCase(),
},
};
export default nextConfig;
```
```shell
$ knip
💥 LoaderError: Error loading next.config.ts
💥 Reason: Cannot read properties of undefined (reading 'toLowerCase')
```
Obviously a contrived example, but the gist is that lots of tooling
configuration expects environment variables to be defined. But when running Knip
there might not be a mechanism to set those. Clearly a breaking change when Knip
starts doing this, only for Next.js projects with a configuration file that
doesn't read environment variables safely (or has other contextual
dependencies).
By the way, [the ESLint v9 plugin][5] has a similar issue.
¹ Another approach could be to statically analyze the `next.config.ts`
configuration file. That would require some additional efforts and get us only
so far, but is definitely useful in some cases and on the radar.
**EDIT:** This has been solved in the Next.js plugin in v5.48.0.
### Case 2: Knip does that?!
To further bring down user configuration and the number of false positives, the
system required more components. New components have been introduced to keep
improving and nail it for an increasing number of projects. This case is an
illustration of some of those components.
Let's just dive into this example and find out what's happening:
```json title="package.json"
{
"scripts": {
"test": "yarn --cwd packages/frontend vitest -c vitest.components.config.ts"
}
}
```
Orchestration is necessary between various components within Knip, such as:
- Plugins, the Vitest plugin parses `vitest.components.config.ts`
- Custom CLI argument parsing for executables, e.g. `yarn --cwd [dir]` and
`vitest --config [file]`
- The workspace graph, to see `packages/frontend` is a descendant workspace of
the root workspace
Patterns like in the script above do not occur only in `package.json` files, but
could be anywhere. Here's a similar example in a GitHub Actions workflow:
```yaml title=".github/workflows/test.yml"
jobs:
integration:
runs-on: ubuntu-latest
steps:
- run: playwright test -c playwright.e2e.config.ts
working-directory: e2e
```
The pattern is very similar, because Knip needs to assign a configuration file
to a specific workspace (assuming there's one in `./e2e`) and apply the Vitest
configuration to that particular workspace with its own set of directory and
entry file patterns.
An essential part of Knip is to build up the module graph for source files. With
the configuration files still in mind, this is the pattern Knip follows towards
this goal:
- Find configuration files at default and custom locations
- Assign them to the right workspace
- Run plugins in their own workspace to take entry file patterns from the
configuration objects
- Load and parse configuration files to get referenced dependencies
The referenced dependencies are stored in the `DependencyDeputy` class to
eventually determine what dependencies are unused or missing in `package.json`
in each workspace.
Both the configuration and entry files are then used to start building up the
module graph.
## Comprehensive
Discussing the two cases briefly covers only part of the whole process. This
might give a sense of the reason why Knip is pretty comprehensive. After all,
building the module graph for internal source files to find unused files and
exports requires the list of external dependencies including internal
workspaces. And on the other hand, a complete module graph is required to find
unused or missing external dependencies.
The comprehensiveness also requires a range of components in the system, such as
the aforementioned ones, [compilers for popular frameworks][6] and a [script
parser][7], and other affordances such as [auto-fix][8].
That said, code organization could be improved to make it more accessible for
contributions and, for instance, expose programmatic APIs to use the generated
module graph outside of Knip. Additionally, existing plugins can better take
advantage of existing components in the system, and new plugins can be developed
to further reduce user configuration and false positives.
## The End
That's all for today, thanks for reading! Have a great one, and don't forget:
Knip it before you ship it! ✂️
[1]: ../explanations/comparison-and-migration.md
[2]: ../explanations/entry-files.md
[3]: ../explanations/plugins.md
[4]: ../reference/plugins/next.md#default-configuration
[5]: ../reference/plugins/eslint.md#eslint-v9
[6]: ../features/compilers.md
[7]: ../features/script-parser.md
[8]: ../features/auto-fix.mdx
================================================
FILE: packages/docs/src/content/docs/blog/two-years.mdx
================================================
---
title: Two Years
date: 2024-10-04
sidebar:
order: 4
---
_Published: 2024-10-04_
import EmojiBlastButton from '../../../components/EmojiBlastButton.astro';
import Projects from '../../../components/Projects.astro';
import Sponsors from '../../../components/Sponsors.astro';
Exactly two years ago the first commit was pushed to GitHub and the first
version of Knip was published to the npm registry. The name was initially
[Exportman][1]! We've come a loooong way... The JavaScript ecosystem is highly
dynamic and I've been crazy enough to even start, try and keep up with it! But
here we are.
October 4th is World Animal Day, so there was really no choice but bring in the
crazy mascot that early adopters may remember:
![Crazy cow with orange scissors in Van Gogh style][2]
Today we celebrate an unknown but CRAZY amount of clutter removed from so many
codebases with Knip's help. Every single day I see many of those little red
blocks for thousands of lines of deleted code and dependencies. Call me crazy,
but to me this is pure joy and never gets old! 🟩 🟥 🟥 🟥 🟥
## Smiling faces
The actual amount of code and dependencies removed and the number of smiling
faces this brings is what matters most, but also remain a good mystery. Clearly
more and more projects add Knip to their projects and CI workflows to keep
ever-growing codebases tidy. It's wonderful to see if Knip plays its part in
today's ecosystem to help with that. Thanks for bearing with me, here's to a lot
more little red blocks in your PRs! 🟩 🟥 🟥 🟥 🟥
## Updates
Why not throw in some freshly cooked updates in [v5.31.0][3] for you while we're
at it:
- [The auto-fix feature][4] has been completely revamped, it's much better and a
lot more comprehensive! You have to see it to believe it.
- Knip has upgraded to [Jiti v2][5], resolving a bunch of known issues when
loading configuration files authored in TypeScript and ESM, such as:
```
Cannot use 'import.meta' outside a module
await is only valid in async functions and the top level bodies of modules
Unexpected identifier 'Promise'
Reflect.metadata is not a function
```
And that pesky "CJS build of Vite's Node API is deprecated" warning is finally
gone!
Thanks to everyone involved in making this happen, it's truly much appreciated.
## Stable
If you haven't tried Knip recently, it's worth taking another look! Version 5
was released 8 months ago, and even though there were no breaking changes, it
includes many enhancements. In fact, Knip has been largely stable since version
3, which came out a year ago. Many releases have a compound effect, as Knip has
kept the pace for two years now.
## Projects using Knip
This list of projects using Knip to keep their codebases tidy is something I
couldn't be more proud of:
:::section{.columns.min200}
:::
And so many more on and off the radar. Very, very cool!
## Sponsors
Last but not least, eternal gratitude for all the sponsors that have been
supporting me along the way. THANK YOU, THANK YOU, THANK YOU!
And eh.. gotta take my chances: how about [joining this awesome club][6]?
:::section{.columns.min300.mt}
:::
## Acknowledgements
Thanks to Joshua Goldberg for [emoji-blast][7]! 🎉
[1]: https://www.npmjs.com/package/exportman/v/0.0.1
[2]: /cow-with-orange-scissors-van-gogh-style.webp
[3]: https://github.com/webpro-nl/knip/releases/tag/5.31.0
[4]: ../features/auto-fix.mdx
[5]: https://github.com/unjs/jiti
[6]: /sponsors
[7]: https://www.emojiblast.dev
================================================
FILE: packages/docs/src/content/docs/explanations/comparison-and-migration.md
================================================
---
title: Comparison & Migration
---
First of all, Knip owes a lot to the projects on this page and they've all been
inspirational in their own way. For best results, Knip has [a vision embracing
comprehensiveness][1] which is larger in scope than any of the alternatives. So
if any of those tools has the right scope for your requirements, then by all
means, use what suits you best. Note that most projects are no longer
maintained.
All tools have in common that they have less features and don't support the
concept of [monorepos/workspaces][2]. Feel free to send in projects that Knip
does not handle better, Knip loves to be challenged!
## Migration
A migration consists of deleting the dependency and its configuration file and
[getting started with Knip][3]. You should end up with less configuration.
## Comparison
### depcheck
> [Depcheck][4] is a tool for analyzing the dependencies in a project to see:
> how each dependency is used, which dependencies are useless, and which
> dependencies are missing from package.json.
The project has plugins (specials), yet not as many as Knip has and they're not
as advanced. It also supports compilers (parsers) for non-standard files.
The following commands are similar:
```sh
depcheck
knip --dependencies
```
**Project status**: The project is archived and recommends Knip.
### unimported
> Find and fix dangling files and unused dependencies in your JavaScript
> projects.
[unimported][5] is fast and works well. It works in what Knip calls "production
mode" exclusively. If you're fine with a little bit of configuration and don't
want or need to deal with non-production items (such as `devDependencies` and
test files), then this might work well for you.
The following commands are similar:
```sh
unimported
knip --production --dependencies --files
```
**Project status**: The project is archived and recommends Knip.
### ts-prune
> Find unused exports in a typescript project. 🛀
[ts-prune][6] aims to find potentially unused exports in your TypeScript project
with zero configuration.
The following commands are similar:
```sh
ts-prune
knip --include exports,types,nsExports,nsTypes
```
Use `knip --exports` to also include enum and namespace members.
**Project status**: The project is archived and recommends Knip.
### ts-unused-exports
> [ts-unused-exports][7] finds unused exported symbols in your Typescript
> project
The following commands are similar:
```sh
ts-unused-exports
knip --include exports,types,nsExports,nsTypes
```
Use `knip --exports` to also include enum and namespace members.
### tsr
> Remove unused code from your TypeScript Project
[tsr][8] (previously `ts-remove-unused`) removes unused exports, and works based
on a single `tsconfig.json` file (`includes` and `excludes`) and requires no
configuration. It removes the `export` keyword or the whole export declaration.
**Project status**: The project is archived and recommends Knip.
## Related projects
Additional alternative and related projects include:
- [deadfile][9]
- [DepClean][10]
- [dependency-check][11]
- [find-unused-exports][12]
- [next-unused][13]
- [npm-check][14]
- [renoma][15]
In general, the [e18e.dev][16] website and in particular the [Cleanup][17]
section is a great resource when dealing with technical debt.
[1]: ./why-use-knip.md#comprehensive
[2]: ../features/monorepos-and-workspaces.md
[3]: ../overview/getting-started.mdx
[4]: https://github.com/depcheck/depcheck
[5]: https://github.com/smeijer/unimported
[6]: https://github.com/nadeesha/ts-prune
[7]: https://github.com/pzavolinsky/ts-unused-exports
[8]: https://github.com/line/tsr
[9]: https://github.com/M-Izadmehr/deadfile
[10]: https://github.com/mysteryven/depclean
[11]: https://github.com/dependency-check-team/dependency-check
[12]: https://github.com/jaydenseric/find-unused-exports
[13]: https://github.com/pacocoursey/next-unused
[14]: https://github.com/dylang/npm-check
[15]: https://github.com/bluwy/renoma
[16]: https://e18e.dev
[17]: https://e18e.dev/guide/cleanup.html
================================================
FILE: packages/docs/src/content/docs/explanations/entry-files.md
================================================
---
title: Entry Files
sidebar:
order: 1
---
Entry files are the starting point for Knip to determine what files are used in
the codebase. More entry files lead to increased coverage of the codebase. This
also leads to more dependencies to be discovered. This page explains how Knip
and its plugins try to find entry files so you don't need to configure them
yourself.
## Default entry file patterns
For brevity, the [default configuration][1] on the previous page mentions only
`index.js` and `index.ts`, but the default set of file names and extensions is
actually a bit larger:
- `index`, `main` and `cli`
- `js`, `mjs`, `cjs`, `jsx`, `ts`, `mts`, `cts` and `tsx`
This means files like `main.cjs` and `src/cli.ts` are automatically added as
entry files. Here's the default configuration in full:
```json
{
"entry": [
"{index,cli,main}.{js,cjs,mjs,jsx,ts,cts,mts,tsx}",
"src/{index,cli,main}.{js,cjs,mjs,jsx,ts,cts,mts,tsx}"
],
"project": ["**/*.{js,cjs,mjs,jsx,ts,cts,mts,tsx}!"]
}
```
Next to the default locations, Knip looks for `entry` files in other places. In
a monorepo, this is done for each workspace separately.
The values you set override the default values, they are not merged.
Also see [FAQ: Where does Knip look for entry files?][2]
## Plugins
Plugins often add entry files. For instance, if the Remix, Storybook and Vitest
plugins are enabled in your project, they'll add additional entry files. See
[the next page about plugins][3] for more details about this.
## Scripts in package.json
The `package.json` is scanned for entry files. The `main`, `bin`, and `exports`
fields may contain entry files. The `scripts` are also parsed to find entry
files and dependencies. See [Script Parser][4] for more details.
## Ignored files
Knip respects `.gitignore` files. By default, ignored files are not added as
entry files. This behavior can be disabled by using the [`--no-gitignore`][5]
flag on the CLI.
## Configuring project files
See [configuring project files][6] for guidance on tuning `entry` and `project`
and when to use `ignore`.
[1]: ../overview/configuration.md#defaults
[2]: ../reference/faq.md#where-does-knip-look-for-entry-files
[3]: ./plugins.md
[4]: ../features/script-parser.md
[5]: ../reference/cli.md#--no-gitignore
[6]: ../guides/configuring-project-files.md
================================================
FILE: packages/docs/src/content/docs/explanations/plugins.md
================================================
---
title: Plugins
sidebar:
order: 2
---
This page describes why Knip uses plugins and the difference between `config`
and `entry` files.
Knip has an extensive and growing [list of built-in plugins][1]. Feel free to
[write a plugin][2] so others can benefit too!
## What does a plugin do?
Plugins are enabled if the related package is listed in the list of dependencies
in `package.json`. For instance, if `astro` is listed in `dependencies` or
`devDependencies`, then the Astro plugin is enabled. And this means that this
plugin will:
- Handle [configuration files][3] like `astro.config.mjs`
- Add [entry files][4] such as `src/pages/**/*.astro`
- Define [command-line arguments][5]
## Configuration files
Knip uses [entry files][6] as starting points to scan your source code and
resolve other internal files and external dependencies. The module graph can be
statically resolved through the `require` and `import` statements in those
source files. However, configuration files reference external dependencies in
various ways. Knip uses a plugin for each tool to parse configuration files and
find those dependencies.
### Example: ESLint
In the first example we look at [the ESLint plugin][7]. The default `config`
file patterns include `.eslintrc.json`. Here's a minimal example:
```json title=".eslintrc.json"
{
"extends": ["airbnb", "prettier"],
"plugins": ["@typescript-eslint"]
}
```
Configuration files like this don't `import` or `require` anything, but they do
require the referenced dependencies to be installed.
In this case, the plugin will return three dependencies:
- `eslint-config-airbnb`
- `eslint-config-prettier`
- `@typescript-eslint/eslint-plugin`
Knip will then look for missing dependencies in `package.json` and report those
as unlisted. And vice versa, if there are any ESLint plugins listed in
`package.json`, but unused, those will be reported as well.
### Example: Vitest
The second example uses [the Vitest plugin][8]. Here's a minimal example of a
Vitest configuration file:
```ts title="vitest.config.ts"
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
coverage: {
provider: 'istanbul',
},
environment: 'happy-dom',
},
});
```
The Vitest plugin reads this configuration and returns two dependencies:
- `@vitest/coverage-istanbul`
- `vitest-environment-happy-dom`
Knip will look for missing and unused dependencies in `package.json` and report
accordingly.
Some tools allow configuration to be stored in `package.json`, that's why some
plugins contain `package.json` in the list of `config` files.
:::tip[Summary]
Plugins load configuration files to find referenced dependencies, and determine
unused and unlisted dependencies.
:::
## Entry files
Many plugins have default `entry` files configured. When the plugin is enabled,
Knip will add entry files as configured by the plugin to resolve used files and
dependencies.
For example, if `next` is listed as a dependency in `package.json`, the Next.js
plugin will automatically add multiple patterns as entry files, such as
`pages/**/*.{js,jsx,ts,tsx}`. If `vitest` is listed, the Vitest plugin adds
`**/*.{test,test-d,spec,spec-d}.ts` as entry file patterns. Most plugins have
entry files configured, so you don't have to.
It's mostly plugins for meta frameworks and test runners that have `entry` files
configured.
:::tip[Plugins result in less configuration]
Plugins uses entry file patterns as defined in your configuration file of these
tools. So you don't need to repeat this in your Knip configuration.
:::
For example, let's say your Playwright configuration contains the following:
```ts title="playwright.config.ts"
import type { PlaywrightTestConfig } from '@playwright/test';
const config: PlaywrightTestConfig = {
testDir: 'integration',
testMatch: ['**/*-test.ts'],
};
export default config;
```
The Playwright plugin will read this configuration file and return those entry
patterns (`integration/**/*-test.ts`). Knip will then not use the default entry
patterns.
You can still override this behavior in your Knip configuration:
```json title="knip.json"
{
"playwright": {
"entry": "src/**/*.integration.ts"
}
}
```
This should not be necessary though. Please consider opening a pull request or a
bug report if any plugin is not behaving as expected.
:::tip[Summary]
Plugins try hard to automatically add the correct entry files.
:::
## Entry files from config files
Entry files are part of plugin configuration (as described in the previous
section). Yet plugins can also return additional entry files after parsing
configuration files. Below are some examples of configuration files parsed by
plugins to return additional entry files. The goal of these examples is to give
you an idea about the various ways Knip and its plugins try to find entry files
so you don't need to configure them yourself.
### Angular
The Angular plugin parses the Angular configuration file. Here's a fragment:
```json title="angular.json"
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"projects": {
"knip-angular-example": {
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/knip-angular-example",
"main": "src/main.ts",
"tsConfig": "tsconfig.app.json"
}
}
}
}
}
}
```
This will result in `src/main.ts` being added as an entry file (and
`@angular-devkit/build-angular` as a referenced dependency).
Additionally, the Angular plugin returns `tsconfig.app.json` as a configuration
file for the TypeScript plugin.
### GitHub Actions
This plugin parses workflow YAML files. This fragment contains three `run`
scripts:
```yml title=".github/workflows/deploy.yml"
jobs:
integration:
runs-on: ubuntu-latest
steps:
- run: npm install
- run: node scripts/build.js
- run: node --loader tsx scripts/deploy.ts
- run: playwright test -c playwright.web.config.ts
working-dir: e2e
```
From these scripts, the `scripts/build.js` and `scripts/deploy.ts` files will be
added as entry files by the GitHub Actions plugin.
Additionally, the file `e2e/playwright.web.config.ts` is detected and will be
handed over as a Playwright configuration file.
Read more about this in [command-line arguments][5].
### webpack
Let's take a look at this example webpack configuration file:
```js title="webpack.config.js"
module.exports = env => {
return {
entry: {
main: './src/app.ts',
vendor: './src/vendor.ts',
},
module: {
rules: [
{
test: /\.(woff|ttf|ico|woff2|jpg|jpeg|png|webp)$/i,
use: 'base64-inline-loader',
},
],
},
};
};
```
The webpack plugin will parse this and add `./src/app.ts` and `./src/vendor.ts`
as entry files. It will also add `base64-inline-loader` as a referenced
dependency.
:::tip[Summary]
In your config files, plugins can find additional entry files and also other
config files recursively.
:::
## Bringing it all together
Sometimes a configuration file is a JavaScript or TypeScript file that imports
dependencies, but also contains configuration that needs to be parsed by a
plugin to find additional dependencies.
Let's take a look at this example Vite configuration file:
```ts title="vite.config.ts"
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig(async ({ mode, command }) => {
return {
plugins: [react()],
test: {
setupFiles: ['./setup-tests.ts'],
environment: 'happy-dom',
coverage: {
provider: 'c8',
},
},
};
});
```
This file imports `vite` and `@vitejs/plugin-react` directly, but also
indirectly references the `happy-dom` and `@vitest/coverage-c8` packages.
The Vite plugin of Knip will **dynamically** load this configuration file and
parse the exported configuration. But it's not aware of the `vite` and
`@vitejs/plugin-react` imports. This is why such `config` files are also
automatically added as `entry` files for Knip to **statically** resolve the
`import` and `require` statements.
Additionally, `./setup-tests.ts` will be added as an `entry` file.
:::note
When plugins dynamically load configuration files, conditional dependencies may
not be detected if the condition evaluates differently during analysis. See
[conditional or dynamic dependencies][9] for details and workarounds.
:::
## Command-Line Arguments
Plugins may define the arguments where Knip should look for entry files,
configuration files and dependencies. We've already seen some examples above:
```sh
node --loader tsx scripts/deploy.ts
playwright test -c playwright.web.config.ts
```
Please see [script parser][10] for more details.
## Config File Location
If configuration files aren't in their default location and they are not
referenced through some script like `vite -c ./dir/vite.config.ts`, then make
sure to tell Knip about it. Two examples:
```json title="knip.jsonc"
{
"playwright": { "config": ["e2e/playwright.config.ts"] },
"vite": "packages/*/vite.config.ts" // shorthand without `config` and array notation
}
```
This is common in projects where a directory like `packages/lib` is not an
actual workspace with a `package.json` file. Also see [integrated monorepos][11]
for similar cases.
## Summary
:::tip[Summary]
Plugins are configured with two distinct types of files:
- `config` files are dynamically loaded and parsed by the plugin
- `entry` files are added to the module graph
- Both can recursively lead to additional entry files, config files and
dependencies
:::
[1]: ../reference/plugins.md
[2]: ../writing-a-plugin/index.md
[3]: #configuration-files
[4]: #entry-files
[5]: #command-line-arguments
[6]: ./entry-files.md
[7]: ../reference/plugins/eslint.md
[8]: ../reference/plugins/vitest.md
[9]: ../guides/handling-issues.mdx#conditional-or-dynamic-dependencies
[10]: ../features/script-parser.md
[11]: ../features/integrated-monorepos.md
================================================
FILE: packages/docs/src/content/docs/explanations/why-use-knip.md
================================================
---
title: Why use Knip?
sidebar:
order: 3
---
The value of removing clutter is clear, but finding it manually is tedious. This
is where Knip comes in: comprehensive and accurate results at any scale.
:::tip[TL;DR]
Knip finds and fixes unused dependencies, exports and files.
Deep analysis from [fine-grained entry points][1] based on the actual frameworks
and tooling in [(mono)repos][2] for accurate and actionable results. Advanced
features for maximum coverage:
- [Custom module resolution][3]
- [Configuration file parsers][4]
- [Advanced shell script parser][5]
- [Built-in and custom compilers][6]
- [Auto-fix most issues][7]
:::
## Less is more
There are plenty of reasons to delete unused files, dependencies and "dead
code":
- Easier maintenance: things are easier to manage when there's less of it.
- Improved performance: startup time, build time and/or bundle size can be
negatively impacted when unused code, files and/or dependencies are included.
Relying on tree-shaking when bundling code helps, but it's not a silver
bullet.
- Easier onboarding: there should be no doubts about whether files, dependencies
and exports are actually in use or not. Especially for people new to the
project and/or taking over responsibilities this is harder to grasp.
- Prevent regressions: tools like TypeScript, ESLint and Prettier do all sorts
of checks and linting to report violations and prevent regressions. Knip does
the same for dependencies, exports and files that are obsolete.
- Keeping dead code around has a negative value on readability, as it can be
misleading and distracting. Even if it serves no purpose it will need to be
maintained (source: [Safe dead code removal → YAGNI][8]).
- Also see [Why are unused dependencies a problem?][9] and [Why are unused
exports a problem?][10].
## Automation
Code and dependency management is usually not the most exciting task for most of
us. Knip's mission is to automate finding clutter. This is such a tedious job if
you were to do it manually, and where would you even start? Knip applies many
techniques and heuristics to report what you need and save a lot of time.
:::tip
Knip not only finds clutter, it can also [remove clutter][7]!
Use Knip next to a linter like ESLint or Biome: after removing unused variables
inside files, Knip might find even more unused code. Rinse and repeat!
:::
## Comprehensive
You can use alternative tools that do the same. However, the advantage of a
strategy that addresses all of dependencies, exports and files is in their
synergy:
- Utilizing plugins to find their dependencies includes the capacity to find
additional entry and configuration files. This results in more resolved and
used files. Better coverage gives better insights into unused files and
exports.
- Analyzing more files reveals more unused exports and dependency usage,
refining the list of both unused and unlisted dependencies.
- This approach is amplified in a monorepo setting. In fact, files and internal
dependencies can recursively reference each other (across workspaces).
## Greenfield or Legacy
Installing Knip in greenfield projects ensures the project stays neat and tidy
from the start. Add it to your CI workflow and prevent any regressions from
entering the codebase.
:::tip
Use Knip in a CI environment to prevent future regressions.
:::
In large and/or legacy projects, Knip may report false positives and require
some configuration. It aims to be a great assistant when cleaning up parts of
the project or doing large refactors. Even a list of results with a few false
positives is many times better and faster than if you were to do it manually.
## Unobtrusive
Knip does not introduce new syntax for you to learn. This may sound obvious, but
consider comments like the following:
```js
// eslint-disable-next-line
// prettier-ignore
// @ts-expect-error
```
Maybe you wonder why Knip does not have similar comments like `// knip-ignore`
so you can get rid of false positives? A variety of reasons:
1. A false positive may be a bug in Knip, and should be reported, not dismissed.
2. Instead of proprietary comments, use [standardized annotations][11] that also
serve as documentation.
3. In the event you want to remove Knip, just uninstall `knip` without having to
remove useless comments scattered throughout the codebase.
Tip: use `@lintignore` in JSDoc comments, so other linters can use the same.
[1]: ./entry-files.md
[2]: ../features/monorepos-and-workspaces.md
[3]: ../reference/faq.md#why-doesnt-knip-use-an-existing-module-resolver
[4]: ./plugins.md#configuration-files
[5]: ../features/script-parser.md
[6]: ../features/compilers.md
[7]: ../features/auto-fix.mdx
[8]: https://jfmengels.net/safe-dead-code-removal/#yagni-you-arent-gonna-need-it
[9]: ../typescript/unused-dependencies.md#why-are-unused-dependencies-a-problem
[10]: ../typescript/unused-exports.md#why-are-unused-exports-a-problem
[11]: ../reference/jsdoc-tsdoc-tags.md
================================================
FILE: packages/docs/src/content/docs/features/auto-fix.mdx
================================================
---
title: Auto-fix
---
import { Tabs, TabItem } from '@astrojs/starlight/components';
import { Badge } from '@astrojs/starlight/components';
Run Knip as you normally would, and if the report looks good then run it again
with the `--fix` flag to let Knip automatically apply fixes. It fixes the
following [issue types][1]:
- Remove `export` keyword for unused exports, re-exports, and exported types
- Remove `export default` keywords for unused default exports
- Remove unused enum and namespace members
- Remove unused `dependencies` and `devDependencies` from `package.json`
- Remove unused files
- Remove unused catalog entries
:::caution
Use a VCS (version control system) like Git to review and undo changes as
necessary.
:::
## Flags
### Fix
Add the `--fix` flag to remove unused exports and dependencies:
```sh
knip --fix
```
Add `--allow-remove-files` to allow Knip to remove unused files:
```sh
knip --fix --allow-remove-files
```
Use `--fix-type` to fix only specific issue types:
- `dependencies`
- `exports`
- `types`
- `files`
- `catalog`
Example:
```sh
knip --fix-type exports,types
knip --fix-type exports --fix-type types # same as above
```
### Format
Add `--format` to format the modified files using the formatter and
configuration in your project. Supports Biome, deno fmt, dprint and Prettier
(using [Formatly][2]):
```sh
knip --fix --format
```
## Demo
## Post-fix
After Knip has fixed issues, there are four things to consider:
### 1. Use a formatter
Use a tool like Prettier or Biome if the code needs formatting. Knip removes the
minimum amount of code while leaving it in a working state.
:::tip
Add the `--format` flag to format the modified files using the formatter and
configuration in your project.
:::
### 2. Unused variables
Use a tool like ESLint or Biome to find and remove unused imports and variables
inside files. Even better, try [remove-unused-vars][3] to remove unused
variables within files.
This may result in more deleted code, and Knip may then find more unused code.
Rinse and repeat!
### 3. Unused dependencies
Verify changes in `package.json` and update dependencies using your package
manager.
```shell
npm install
```
```shell
pnpm install
```
```shell
bun install
```
```shell
yarn
```
### 4. Install unlisted dependencies
If Knip reports unlisted dependencies or binaries, they should be installed
using the package manager in the project, for example:
```shell
npm install unlisted-package
```
```shell
pnpm add unlisted-package
```
```shell
bun add unlisted-package
```
```shell
yarn add unlisted-package
```
## Example results
### Exports
The `export` keyword for unused exports is removed:
```diff title="module.ts"
-export const unused = 1;
-export default class MyClass {}
+const unused = 1;
+class MyClass {}
```
The `default` keyword was also removed here.
Knip removes the whole or part of export declarations:
```diff title="module.ts"
type Snake = 'python' | 'anaconda';
const Owl = 'Hedwig';
const Hawk = 'Tony';
-export type { Snake };
-export { Owl, Hawk };
+;
+;
```
### Re-exports
Knip removes the whole or part of re-exports:
```diff title="file.js"
-export { Cat, Dog } from './pets';
-export { Lion, Elephant } from './jungle';
+export { Elephant } from './jungle'
```
Also across any chain of re-exports:
```diff
export const Hawk = 'Tony';
-export const Owl = 'Hedwig';
+const Owl = 'Hedwig';
```
```diff
export * from './module.js';
```
```diff
-export { Hawk, Owl } from './barrel.js';
+export { Hawk } from './barrel.js'
```
### Export assignments
Knip removes individual exported items in "export assignments", but does not
remove the entire export declaration if it's empty:
```diff title="file.js"
-export const { a, b } = fn();
+export const { } = fn();
-export const [c, d] = [c, d];
+export const [, ] = [c, d];
```
Reason: the right-hand side of the assignment might have side-effects. It's not
safe to always remove the whole declaration. This could be improved in the
future (feel free to open an issue/RFC).
### Enum members
Unused members of enums are removed:
```diff title="file.ts"
export enum Directions {
North = 1,
East = 2,
- South = 3,
West = 4,
}
```
### CommonJS
Knip supports CommonJS and removes unused exports:
```diff title="common.js"
-module.exports = { identifier, unused };
+module.exports = { identifier, };
-module.exports.UNUSED = 1;
-module.exports['ACCESS'] = 1;
+
+
```
Warning: the right-hand side of such an assignment might have side-effects. Knip
currently removes the whole declaration (feel free to open an issue/RFC).
### Dependencies
Unused dependencies are removed from `package.json`:
```diff title="package.json"
{
"name": "my-package",
"dependencies": {
- "rimraf": "*",
- "unused-dependency": "*"
+ "rimraf": "*"
},
- "devDependencies": {
- "unreferenced-package": "5.3.3"
- }
+ "devDependencies": {}
}
```
### Catalog entries
Unused [catalog][4] entries are removed from `pnpm-workspace.yaml`:
```diff title="pnpm-workspace.yaml"
packages:
- 'packages/*'
catalog:
react: ^18.0.0
- unused-package: ^1.0.0
```
Catalogs in `package.json` are supported as well.
## What's not included
Operations that auto-fix does not (yet) perform and why:
- Add unlisted (dev) dependencies to `package.json` (should it go into
`dependencies` or `devDependencies`? For monorepos in current workspace or
root?)
- Add unlisted binaries (which package and package version contains the used
binary?)
- Fix duplicate exports (which one should be removed?)
[1]: ../reference/issue-types.md
[2]: https://github.com/JoshuaKGoldberg/formatly
[3]: https://github.com/webpro-nl/remove-unused-vars
[4]: https://pnpm.io/catalogs
================================================
FILE: packages/docs/src/content/docs/features/compilers.md
================================================
---
title: Compilers
---
Projects may have source files that are not JavaScript or TypeScript, and thus
require compilation (or transpilation, or pre-processing, you name it). Files
like `.mdx`, `.astro`, `.vue` and `.svelte` may also import other source files
and external dependencies. So ideally, these files are included when linting the
project. That's why Knip supports compilers.
## Built-in compilers
Knip has built-in "compilers" for the following file extensions:
- `.astro`
- `.css` (only enabled by `tailwindcss`)
- `.mdx`
- `.prisma`
- `.sass` + `.scss`
- `.svelte`
- `.vue`
Knip does not include real compilers for those files, but regular expressions to
collect `import` statements. This is fast, requires no dependencies, and enough
for Knip to build the module graph.
On the other hand, real compilers may expose their own challenges in the context
of Knip. For instance, the Svelte compiler keeps `exports` intact, while they
might represent component properties. This results in those exports being
reported as unused by Knip.
The built-in functions seem to do a decent job, but override them however you
like.
Compilers are enabled only if certain dependencies are found. If that's not
working for your project, set `true` and enable any compiler manually:
```ts title="knip.ts"
export default {
compilers: {
mdx: true,
},
};
```
## Custom compilers
Built-in compilers can be overridden, and additional compilers can be added.
Since compilers are functions, the Knip configuration file must be a dynamic
`.js` or `.ts` file.
### Interface
The compiler function interface is straightforward. Text in, text out:
```ts
(source: string, filename: string) => string;
```
This may also be an `async` function.
:::tip[Note]
Compilers will automatically have their extension added as a default extension
to Knip. This means you don't need to add something like `**/*.{ts,vue}` to the
`entry` or `project` file patterns manually.
:::
### Examples
- [CSS][1]
- [MDX][2]
- [Svelte][3]
- [Vue][4]
#### CSS
Here's an example, minimal compiler for CSS files:
```ts title="knip.ts"
export default {
compilers: {
css: (text: string) => [...text.matchAll(/(?<=@)import[^;]+/g)].join('\n'),
},
};
```
You may wonder why the CSS compiler is not included by default. It's currently
not clear if it should be included. And if so, what would be the best way to
determine it should be enabled, and what syntax(es) it should support. Note that
Tailwind CSS and SASS/SCSS compilers are included.
#### MDX
Another example, in case the built-in MDX compiler is not enough:
```ts
import { compile } from '@mdx-js/mdx';
export default {
compilers: {
mdx: async text => (await compile(text)).toString(),
},
};
```
#### Svelte
In a Svelte project, the compiler is automatically enabled. Override and use
Svelte's compiler for better results if the built-in "compiler" is not enough:
```ts
import type { KnipConfig } from 'knip';
import { compile } from 'svelte/compiler';
export default {
compilers: {
svelte: (source: string) => compile(source, {}).js.code,
},
} satisfies KnipConfig;
```
#### Vue
In a Vue project, the compiler is automatically enabled. Override and use Vue's
parser for better results if the built-in "compiler" is not enough:
```ts
import type { KnipConfig } from 'knip';
import {
parse,
type SFCScriptBlock,
type SFCStyleBlock,
} from 'vue/compiler-sfc';
function getScriptBlockContent(block: SFCScriptBlock | null): string[] {
if (!block) return [];
if (block.src) return [`import '${block.src}'`];
return [block.content];
}
function getStyleBlockContent(block: SFCStyleBlock | null): string[] {
if (!block) return [];
if (block.src) return [`@import '${block.src}';`];
return [block.content];
}
function getStyleImports(content: string): string {
return [...content.matchAll(/(?<=@)import[^;]+/g)].join('\n');
}
const config = {
compilers: {
vue: (text: string, filename: string) => {
const { descriptor } = parse(text, { filename, sourceMap: false });
return [
...getScriptBlockContent(descriptor.script),
...getScriptBlockContent(descriptor.scriptSetup),
...descriptor.styles.flatMap(getStyleBlockContent).map(getStyleImports),
].join('\n');
},
},
} satisfies KnipConfig;
export default config;
```
[1]: #css
[2]: #mdx
[3]: #svelte
[4]: #vue
================================================
FILE: packages/docs/src/content/docs/features/integrated-monorepos.md
================================================
---
title: Integrated Monorepos
sidebar:
order: 3
---
Some repositories have a single `package.json`, but consist of multiple projects
with configuration files across the repository. A good example is the [Nx
integrated monorepo style][1].
:::tip
An integrated monorepo is a single workspace.
:::
## Entry Files
The default entrypoints files might not be enough. Here's an idea that might fit
this type of monorepo:
```json title="knip.json"
{
"entry": ["{apps,libs}/**/src/index.{ts,tsx}"],
"project": ["{apps,libs}/**/src/**/*.{ts,tsx}"]
}
```
## Plugins
Let's assume some of these projects are applications ("apps") which have their
own ESLint configuration files and Cypress configuration and test files. This
may result in those files getting reported as unused, and consequently also the
dependencies they import and refer to.
In that case, we could configure the ESLint and Cypress plugins like this:
```json title="knip.json"
{
"eslint": {
"config": ["{apps,libs}/**/.eslintrc.json"]
},
"cypress": {
"entry": ["apps/**/cypress.config.ts", "apps/**/cypress/e2e/*.spec.ts"]
}
}
```
Adapt the file patterns to your project, and the relevant `config` and `entry`
files and dependencies should no longer be reported as unused.
## Internal Workspace Dependencies
A note about repositories with multiple `package.json` files and **internal**
workspace packages: it is recommended to list all dependencies in each consuming
`package.json`, allowing Knip to do fine-grained reporting of both unused and
unlisted dependencies.
An alternative is to `ignoreDependencies: ["@internal/*"]`.
[1]: https://nx.dev/getting-started/tutorials/integrated-repo-tutorial
================================================
FILE: packages/docs/src/content/docs/features/monorepos-and-workspaces.md
================================================
---
title: Monorepos & Workspaces
sidebar:
order: 2
---
Workspaces are handled out-of-the-box by Knip.
Workspaces are sometimes also referred to as package-based monorepos, or as
packages in a monorepo. Knip uses the term workspace exclusively to indicate a
directory that has a `package.json`.
## Configuration
Here's example configuration with custom `entry` and `project` patterns:
```json title="knip.json"
{
"workspaces": {
".": {
"entry": "scripts/*.js",
"project": "scripts/**/*.js"
},
"packages/*": {
"entry": "{index,cli}.ts",
"project": "**/*.ts"
},
"packages/cli": {
"entry": "bin/cli.js"
}
}
}
```
:::tip
Run Knip without any configuration to see if and where custom `entry` and/or
`project` files are necessary per workspace.
:::
Each workspace has the same [default configuration][1].
The root workspace is named `"."` under `workspaces` (like in the example
above).
:::caution
In a project with workspaces, the `entry` and `project` options at the root
level are ignored. Use the workspace named `"."` for those (like in the example
above).
:::
## Workspaces
Knip reads workspaces from four possible locations:
1. The `workspaces` array in `package.json` (npm, Bun, Yarn, Lerna)
2. The `packages` array in `pnpm-workspace.yaml` (pnpm)
3. The `workspaces.packages` array in `package.json` (legacy)
4. The `workspaces` object in Knip configuration
The `workspaces` in Knip configuration (4) not already defined in the root
`package.json` or `pnpm-workspace.yaml` (1, 2, 3) are added to the analysis.
:::caution
A workspace must have a `package.json` file.
:::
For projects with only a root `package.json`, please see [integrated
monorepos][2].
## Additional workspaces
If a workspaces is not configured as such in `package.json#workspaces` (or
`pnpm-workspace.yaml`) it can be added to the Knip configuration manually. Add
their path to the `workspaces` configuration object the same way as
`"packages/cli": {}` in the example above.
## Source mapping
See [Source Mapping][3].
## Additional options
The following options are available inside workspace configurations:
- [ignore][4]
- [ignoreBinaries][5]
- [ignoreDependencies][6]
- [ignoreMembers][7]
- [ignoreUnresolved][8]
- [includeEntryExports][9]
[Plugins][10] can be configured separately per workspace.
Use `--debug` for verbose output and see the workspaces Knip includes, their
configurations, enabled plugins, glob options and resolved files.
## Filter workspaces
Use the `--workspace` (or `-W`) argument to select one or more workspaces:
```sh
knip --workspace packages/my-lib
```
The filter supports multiple formats:
```sh
knip --workspace @myorg/my-lib # Package name
knip --workspace '@myorg/*' # Package name glob
knip --workspace packages/my-lib # Directory path
knip --workspace './apps/*' # Directory glob
```
Combine selectors to include or exclude workspaces:
```sh
knip --workspace @myorg/* --workspace '!@myorg/legacy'
knip --workspace './apps/*' --workspace '@shared/utils'
```
This will include the target workspace(s), but also ancestor and dependent
workspaces. For two reasons:
- Ancestor workspaces may list dependencies in `package.json` the linted
workspace uses.
- Dependent workspaces may reference exports from the linted workspace.
To lint the workspace in isolation, there are two options:
- Combine the `workspace` argument with [strict production mode][11].
- Run Knip from inside the workspace directory.
[1]: ../overview/configuration.md#defaults
[2]: ./integrated-monorepos.md
[3]: ./source-mapping.md
[4]: ../reference/configuration.md#ignore
[5]: ../reference/configuration.md#ignorebinaries
[6]: ../reference/configuration.md#ignoredependencies
[7]: ../reference/configuration.md#ignoremembers
[8]: ../reference/configuration.md#ignoreunresolved
[9]: ../reference/configuration.md#includeentryexports
[10]: ../reference/configuration.md#plugins
[11]: ./production-mode.md#strict-mode
================================================
FILE: packages/docs/src/content/docs/features/production-mode.md
================================================
---
title: Production Mode
sidebar:
order: 1
---
The default mode for Knip is comprehensive and targets all project code,
including configuration files, test files, Storybook stories, and so on. Test
files usually import production files. This prevents production files or their
exports from being reported as unused, while sometimes both of them can be
deleted. Knip features a "production mode" to focus only on the code that you
ship.
## Configuration
To tell Knip what is production code, add an exclamation mark behind each
`pattern!` that represents production code:
```json title="knip.json"
{
"entry": ["src/index.ts!", "build/script.js"],
"project": ["src/**/*.ts!", "build/*.js"]
}
```
Depending on file structure and enabled plugins, you might not need to modify
your configuration at all.
Run Knip with the `--production` flag:
```sh
knip --production
```
Here's what's included in production mode:
- Only `entry` and `project` patterns suffixed with `!`
- Only production `entry` file patterns exported by plugins (such as Next.js and
Remix)
- Only the `start` script (of `package.json#scripts`)
- Ignore exports with the [`@internal` tag][1]
:::note
The production run does not replace the default run. Depending on your needs you
can run either of them or both separately. Usually both modes can share the same
configuration.
:::
To see the difference between default and production mode in great detail, use
the `--debug` flag and inspect what entry and project files are used, and the
plugins that are enabled. For instance, in production mode this shows that files
such as tests and Storybook files (stories) are excluded from the analysis.
In case files like mocks and test helpers are reported as unused files, use
negated patterns to exclude those files in production mode:
```json title="knip.json"
{
"entry": ["src/index.ts!"],
"project": ["src/**/*.ts!", "!src/test-helpers/**!"]
}
```
Also see [configuring project files][2] to align `entry` and `project` with
production mode.
## Strict Mode
In production mode, only `dependencies` (not `devDependencies`) are considered
when finding unused or unlisted dependencies.
Additionally, the `--strict` flag can be added to:
- Verify isolation: workspaces should use strictly their own `dependencies`
- Include `peerDependencies` when finding unused or unlisted dependencies
- Report type-only imports listed in `dependencies`
```sh
knip --production --strict
```
Using `--strict` implies `--production`, so the latter can be omitted.
## Types
Add `--exclude types` if you don't want to include types in the report:
```sh
knip --production --exclude types
```
[1]: ../reference/jsdoc-tsdoc-tags.md#internal
[2]: ../guides/configuring-project-files.md
================================================
FILE: packages/docs/src/content/docs/features/reporters.md
================================================
---
title: Reporters & Preprocessors
---
## Built-in Reporters
Knip provides the following built-in reporters:
- `codeowners`
- `compact`
- [`disclosure`][1]
- [`github-actions`][2]
- [`json`][3]
- [`markdown`][4]
- [`codeclimate`][5]
- `symbols` (default)
Example usage:
```sh
knip --reporter compact
```
### JSON
The built-in `json` reporter output is meant to be consumed by other tools. It
reports in JSON format with unused `files` and `issues` as an array with one
object per file structured like this:
```json
{
"issues": [
{
"file": "package.json",
"owners": ["@org/admin"],
"dependencies": [{ "name": "jquery", "line": 5, "col": 6, "pos": 71 }],
"devDependencies": [{ "name": "lodash", "line": 9, "col": 6, "pos": 99 }],
"unlisted": [{ "name": "react" }, { "name": "@org/unresolved" }],
"exports": [],
"types": [],
"duplicates": []
},
{
"file": "src/Registration.tsx",
"owners": ["@org/owner"],
"dependencies": [],
"devDependencies": [],
"binaries": [],
"unresolved": [
{ "name": "./unresolved", "line": 8, "col": 23, "pos": 407 }
],
"exports": [{ "name": "unusedExport", "line": 1, "col": 14, "pos": 13 }],
"types": [
{ "name": "unusedEnum", "line": 3, "col": 13, "pos": 71 },
{ "name": "unusedType", "line": 8, "col": 14, "pos": 145 }
],
"enumMembers": [
{
"namespace": "MyEnum",
"name": "unusedMember",
"line": 13,
"col": 3,
"pos": 167
},
{
"namespace": "MyEnum",
"name": "unusedKey",
"line": 15,
"col": 3,
"pos": 205
}
],
"duplicates": ["Registration", "default"]
}
]
}
```
The keys match the [reported issue types][6]. Example usage:
```sh
knip --reporter json
```
### GitHub Actions
Use the GitHub Actions reporter in a workflow for annotations in pull requests.
Example usage:
```sh
knip --reporter github-actions
```
Changed files in pull requests will now contain inline annotations for lint
findings.
### Markdown
The built-in `markdown` reporter output is meant to be saved to a Markdown file.
This allows following the changes in issues over time. It reports issues in
Markdown tables separated by issue types as headings, for example:
```md
# Knip report
## Unused files (1)
- src/unused.ts
## Unlisted dependencies (2)
| Name | Location | Severity |
| :-------------- | :---------------- | :------- |
| unresolved | src/index.ts:8:23 | error |
| @org/unresolved | src/index.ts:9:23 | error |
## Unresolved imports (1)
| Name | Location | Severity |
| :----------- | :----------------- | :------- |
| ./unresolved | src/index.ts:10:12 | error |
```
### Disclosure
This reporter is useful for sharing large reports. Groups of issues are rendered
in a closed state initially. The reporter renders this:
````text
$ knip --reporter disclosure
Unused files (2)
```
unused.ts
dangling.js
```
Unused dependencies (2)
```
my-package package.json:17:5
unused-dep package.json:20:5
```
````
The above can be copy-pasted where HTML and Markdown is supported, such as a
GitHub issue or pull request, and renders like so:
Unused files (2)
```
unused.ts
dangling.js
```
Unused dependencies (2)
```
my-package package.json:17:5
unused-dep package.json:20:5
```
### CodeClimate
The built-in `codeclimate` reporter generates output in the Code Climate Report
JSON format. Example usage:
```text
$ knip --reporter codeclimate
[
{
"type": "issue",
"check_name": "Unused exports",
"description": "isUnused",
"categories": ["Bug Risk"],
"location": {
"path": "path/to/file.ts",
"positions": {
"begin": {
"line": 6,
"column": 1
}
}
}
"severity": "major",
"fingerprint": "e9789995c1fe9f7d75eed6a0c0f89e84",
}
]
```
## Custom Reporters
When the provided built-in reporters are not sufficient, a custom local reporter
can be implemented or an external reporter can be used. Multiple reporters can
be used at once by repeating the `--reporter` argument.
The results are passed to the function from its default export and can be used
to write issues to `stdout`, a JSON or CSV file, or sent to a service. It
supports a local JavaScript or TypeScript file or an external dependency.
### Local
The default export of the reporter should be a function with this interface:
```ts
type Reporter = async (options: ReporterOptions): void;
type ReporterOptions = {
report: Report;
issues: Issues;
counters: Counters;
configurationHints: ConfigurationHints;
isDisableConfigHints: boolean;
isTreatConfigHintsAsErrors: boolean;
cwd: string;
isProduction: boolean;
isShowProgress: boolean;
options: string;
};
```
The data can then be used to write issues to `stdout`, a JSON or CSV file, or
sent to a service.
Here's a most minimal reporter example:
```ts title="./my-reporter.ts"
import type { Reporter } from 'knip';
const reporter: Reporter = function (options) {
console.log(options.issues);
console.log(options.counters);
};
export default reporter;
```
Example usage:
```sh
knip --reporter ./my-reporter.ts
```
### External
Pass `--reporter [pkg-name]` to use an external reporter. The default exported
function of the `main` script (default: `index.js`) will be invoked with the
`ReporterOptions`, just like a local reporter.
## Preprocessors
A preprocessor is a function that runs after the analysis is finished. It
receives the results from the analysis and should return data in the same
shape/structure (unless you pass it to only your own reporter).
The data goes through the preprocessors before the final data is passed to the
reporters. There are no built-in preprocessors. Just like reporters, use e.g.
`--preprocessor ./my-preprocessor` from the command line (can be repeated).
The default export of the preprocessor should be a function with this interface:
```ts
type Preprocessor = async (options: ReporterOptions) => ReporterOptions;
```
Like reporters, you can use local JavaScript or TypeScript files and external
npm packages as preprocessors.
Example preprocessor:
```ts title="./preprocess.ts"
import type { Preprocessor } from 'knip';
const preprocess: Preprocessor = function (options) {
// modify options.issues and options.counters
return options;
};
export default preprocess;
```
Example usage:
```sh
knip --preprocessor ./preprocess.ts
```
[1]: #disclosure
[2]: #github-actions
[3]: #json
[4]: #markdown
[5]: #codeclimate
[6]: ../reference/issue-types.md
================================================
FILE: packages/docs/src/content/docs/features/rules-and-filters.md
================================================
---
title: Rules & Filters
sidebar:
order: 5
---
Use rules or filters to customize Knip's output. This has various use cases, a
few examples:
- Temporarily focus on a specific issue type.
- You don't want to see unused `type`, `interface` and `enum` exports reported.
- Specific issue types should be printed, but not counted against the total
error count.
If you're looking to handle one-off exceptions, also see [JSDoc tags][1].
## Filters
You can `--include` or `--exclude` any of the reported issue types to slice &
dice the report to your needs. Alternatively, they can be added to the
configuration (e.g. `"exclude": ["dependencies"]`).
Use `--include` to report only specific issue types. The following example
commands do the same:
```sh
knip --include files --include dependencies
knip --include files,dependencies
```
Or the other way around, use `--exclude` to ignore the types you're not
interested in:
```sh
knip --include files --exclude enumMembers,duplicates
```
Also see the [list of issue types][2].
### Shorthands
Knip has shortcuts to include only specific issue types.
1. The `--dependencies` flag includes:
- `dependencies` (and `devDependencies` + `optionalPeerDependencies`)
- `unlisted`
- `binaries`
- `unresolved`
- `catalog`
2. The `--exports` flag includes:
- `exports`
- `types`
- `enumMembers`
- `namespaceMembers`
- `duplicates`
3. The `--files` flag is a shortcut for `--include files`
## Rules
Use `rules` in the configuration to customize the issue types that count towards
the total error count, or to exclude them altogether.
| Value | Default | Printed | Counted | Description |
| :-------- | :-----: | :-----: | :-----: | :-------------------------------- |
| `"error"` | ✓ | ✓ | ✓ | Similar to the `--include` filter |
| `"warn"` | - | ✓ | - | Printed in faded/gray color |
| `"off"` | - | - | - | Similar to the `--exclude` filter |
Example:
```json title="knip.json"
{
"rules": {
"files": "warn",
"duplicates": "off"
}
}
```
Also see the [issue types overview][2].
NOTE: If the `dependencies` issue type is included, the `devDependencies` and
`optionalPeerDependencies` types can still be set to `"warn"` separately.
The rules are modeled after the ESLint `rules` configuration, and could be
extended in the future.
## Rules or filters?
Filters are meant to be used as command-line flags, rules allow for more
fine-grained configuration.
- Rules are more fine-grained since they also have "warn".
- Rules could be extended in the future.
- Filters can be set in configuration and from CLI (rules only in
configuration).
- Filters have shorthands (rules don't have this).
[1]: ../reference/jsdoc-tsdoc-tags.md
[2]: ../reference/issue-types.md
================================================
FILE: packages/docs/src/content/docs/features/script-parser.md
================================================
---
title: Script Parser
---
Knip parses shell commands and scripts to find additional dependencies, entry
files and configuration files in various places:
- In [`package.json`][1]
- In [CLI arguments][2]
- In [scripts][3]
- In [source code][4]
Shell scripts can be read and statically analyzed, but they're not executed.
## package.json
The `main`, `bin`, `exports` and `scripts` fields may contain entry files. Let's
take a look at this example:
```json title="package.json"
{
"name": "my-package",
"main": "index.js",
"exports": {
"./lib": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
},
"bin": {
"program": "bin/cli.js"
},
"scripts": {
"build": "rollup src/entry.ts",
"start": "node --loader tsx server.ts"
}
}
```
From this example, Knip automatically adds the following files as entry files:
- `index.js`
- `./dist/index.mjs`
- `./dist/index.cjs`
- `bin/cli.js`
- `src/entry.ts`
- `server.ts`
### Excluded files
Knip would not add the `exports` if the `dist` folder is matching a pattern in a
relevant `.gitignore` file or `ignore` option.
Knip does not add scripts without a standard extension. For instance, the
`bin/tool` file might be a valid executable for Node.js, but wouldn't be added
or parsed by Knip.
### CLI Arguments
When parsing the `scripts` of `package.json` and other files, Knip detects
various types of inputs. Some examples:
- The first positional argument is usually an entry file
- Configuration files are often in the `-c` or `--config` argument
- The `--require`, `--loader` or `--import` arguments are often dependencies
```json
{
"name": "my-lib",
"scripts": {
"start": "node --import tsx/esm run.ts",
"bundle": "tsup -c tsup.lib.config.ts",
"type-check": "tsc -p tsconfig.app.json"
}
}
```
The `"start"` script will have `tsx` marked as a referenced dependency, and adds
`run.ts` as an entry file.
Additionally, the following files are detected as configuration files:
- `tsup.lib.config.ts` - to be handled by the tsup plugin
- `tsconfig.app.json` - to be handled by the TypeScript plugin
Such executables and their arguments are all defined in plugins separately for
fine-grained results.
## Scripts
Plugins may also use the script parser to extract entry files and dependencies
from commands. A few examples:
- GitHub Actions: workflow files may contain `run` commands (e.g.
`.github/workflows/ci.yml`)
- Husky & Lefthook: Git hooks such as `.git/hooks/pre-push` contain scripts;
also `lefthook.yml` has `run` commands
- Lint Staged: configuration values are all commands
- Nx: task executors and `nx:run-commands` executors in `project.json` contains
scripts
- Release It: `hooks` contain commands
Plugins can also return configuration files. Some examples:
- The Angular plugin detects `options.tsConfig` as a TypeScript config file
- The GitHub Actions plugin parses `run` commands which may contain
configuration file paths
## Source Code
When Knip is walking the abstract syntax trees (ASTs) of JavaScript and
TypeScript source code files, it looks for imports and exports. But there's a
few more (rather obscure) things that Knip detects in the process. Below are
examples of additional scripts Knip parses to find entry files and dependencies.
### bun
If the `bun` dependency is imported in source code, Knip considers the contents
of `$` template tags to be scripts:
```ts
import { $ } from 'bun';
await $`bun boxen I ❤ unicorns`;
await $`boxen I ❤ unicorns`;
```
Parsing the script results in the `boxen` binary (the `boxen-cli` dependency) as
referenced (twice).
### execa
If the `execa` dependency is imported in source code, Knip considers the
contents of `$` template tags to be scripts:
```ts
await $({ stdio: 'inherit' })`c8 node hydrate.js`;
```
Parsing the script results in `hydrate.js` added as an entry file and the `c8`
binary/dependency as referenced.
### zx
If the `zx` dependency is imported in source code, Knip considers the contents
of `$` template tags to be scripts:
```ts
await $`node scripts/parse.js`;
```
This will add `scripts/parse.js` as an entry file.
[1]: #packagejson
[2]: #cli-arguments
[3]: #scripts
[4]: #source-code
================================================
FILE: packages/docs/src/content/docs/features/source-mapping.md
================================================
---
title: Source Mapping
sidebar:
order: 4
---
Knip is mostly interested in source code. Analyzing build artifacts hurts
performance and often leads to false positives, as they potentially contain
bundled code and unresolvable imports.
That's why Knip tries to map such build artifacts back to their original source
files and analyze those instead. This is done based on `tsconfig.json` settings.
## Example 1: package.json
Let's look at an example case with `package.json` and `tsconfig.json` files, and
see how "dist" files are mapped to "src" files.
```jsonc title="package.json"
{
"name": "my-workspace",
"main": "index.js",
"exports": {
".": "./src/entry.js",
"./feat": "./lib/feat.js",
"./public": "./dist/app.js",
"./public/*": "./dist/*.js",
"./public/*.js": "./dist/*.js",
"./dist/internal/*": null,
},
}
```
With this TypeScript configuration:
```json title="tsconfig.json"
{
"compilerOptions": {
"baseUrl": "src",
"outDir": "dist"
}
}
```
- `./src/entry.js` is not in an `outDir` folder, so it's added as an entry file
- `./lib/feat.js` is not in an `outDir` folder, so it's added as an entry file
- `./dist/app.js` is in a `dist` folder and mapped to `./src/app.{js,ts}` (¹)
- `./dist/*.js` is in a `dist` folder and mapped to `./src/**/*.{js,ts}` (¹)
- `./dist/internal/*` is translated to `./dist/internal/**` and files in this
directory and deeper are ignored when globbing entry files
(¹) full extensions list is actually: `js`, `mjs`, `cjs`, `jsx`, `ts`, `tsx`,
`mts`, `cts`
In `--debug` mode, look for "Source mapping" to see this in action.
:::tip
Using `./dist/*.js` means that all files matching `./src/**/*.{js,ts}` are added
as entry files. By default, unused exports of entry files are not reported. Use
[includeEntryExports][1] to include them.
:::
## Example 2: monorepo
Let's say we have this module in a monorepo that imports `helper` from another
workspace in the same monorepo:
```ts title="index.js"
import { helper } from '@org/shared';
```
The target workspace `@org/shared` has this `package.json`:
```json title="package.json"
{
"name": "@org/shared",
"main": "dist/index.js"
}
```
The module resolver will resolve `@org/shared` to `dist/index.js`. That file is
usually compiled and git-ignored, while Knip wants the source file instead.
:::tip
You may need to compile build artifacts to `outDir` first before Knip can
successfully apply source mapping for internal references in a monorepo.
:::
If the target workspace has a `tsconfig.json` file with an `outDir` option, Knip
will try to map the "dist" file to the "src" file. Then if `src/index.ts`
exists, Knip will use that file instead of `dist/index.js`.
Currently this only works based on `tsconfig.json`, in the future more source
mappings may be added.
[1]: ../reference/configuration.md#includeentryexports
================================================
FILE: packages/docs/src/content/docs/guides/configuring-project-files.md
================================================
---
title: Configuring Project Files
sidebar:
order: 1
---
The `entry` and `project` file patterns are the first and most important
options. Getting those right is essential to help you delete more code and make
Knip faster:
- Start with defaults. Only add targeted `entry` overrides when needed.
- Use `project` patterns (with negations) to define the scope for Knip.
- Use production mode to exclude tests and other non-production files.
- Use `ignore` only to suppress issues in specific files; it does not exclude
files from analysis.
Let's dive in and expand on all of these.
## Entry files
Avoid adding too many files as `entry` files:
1. Knip does not report [unused exports][1] in entry files by default.
2. Proper `entry` and `project` patterns allow Knip to find unused files and
exports.
## Unused files
Files are reported as unused if they are in the set of `project` files, but are
not resolved from the `entry` files:
```
unused files = project files - (entry files + resolved files)
```
See [entry files][2] to see where Knip looks for entry files. Fine-tune `entry`
and adjust `project` to fit your codebase.
:::tip
Use negated `project` patterns to precisely include/exclude files for unused
files detection.
Use `ignore*` to suppress specific issues in matching files; it does not exclude
files from analysis.
:::
## Negated patterns
When there are too many files in the analysis, start here.
For instance, routes are entry files except those prefixed with an underscore:
```json
{
"entry": ["src/routes/*.ts", "!src/routes/_*.ts"]
}
```
Some files are not part of your source and are reported as unused (false
positives)? Use negated `project` patterns:
```json
{
"entry": ["src/index.ts"],
"project": ["src/**/*.ts", "!src/exclude/**"]
}
```
❌ Don't use `ignore` for generated artifacts:
```json title="knip.json"
{
"entry": ["src/index.ts", "scripts/*.ts"],
"ignore": ["build/**", "dist/**", "src/generated.ts"]
}
```
✅ Do define your project boundaries:
```json title="knip.json"
{
"entry": ["src/index.ts", "scripts/*.ts"],
"project": ["src/**", "scripts/**"],
"ignore": ["src/generated.ts"]
}
```
Why this is better:
- `project` defines what belongs to the codebase, so build outputs are excluded
from analysis and unused file detection
- `ignore` is for the few files that should be analyzed but contain exceptions
- Improves performance by analyzing fewer files
## Ignore issues in specific files
Use `ignore` when a specific analyzed file is not handled properly by Knip or
intentionally contains unused exports (e.g. generated files exporting
"everything"):
```json
{
"entry": ["src/index.ts"],
"project": ["src/**/*.ts"],
"ignore": ["src/generated.ts"]
}
```
Also see [ignoreExportsUsedInFile][3] for a more targeted approach.
## Production Mode
Default mode includes tests and other non-production files in the analysis. To
focus on production code, use [production mode][4].
Don't try to exclude tests via `ignore` or negated `project` patterns. That's
inefficient and ineffective due to entries added by plugins. Use production mode
instead.
❌ Don't do this:
```json
{
"ignore": ["**/*.test.js"]
}
```
Why not: `ignore` hides issues from the report; it does not exclude files from
analysis.
❌ Also don't do this:
```json
{
"entry": ["index.ts", "!**/*.test.js"]
}
```
Why not: plugins add test files as `entry` files; you can't and shouldn't
override that globally.
❌ Or this:
```json
{
"project": ["**/*.ts", "!**/*.spec.ts"]
}
```
Why not: `project` is for unused file detection; negating test files is
ineffective because they are `entry` files.
✅ Do this instead:
```shell
knip --production
```
To fine-tune the resulting production file set, for instance to exclude test
helper files that still show as unused, use the exclamation mark suffix on
production patterns:
```json
{
"entry": ["src/index.ts!"],
"project": ["src/**/*.ts!", "!src/test-helpers/**!"]
}
```
Remember to keep adding the exclamation mark `suffix!` for production file
patterns.
:::tip
Use the exclamation mark (`!`) on both ends (`!`) to exclude files in production
mode.
:::
## Defaults & Plugins
To reiterate, the default `entry` and `project` files for each workspace:
```json
{
"entry": [
"{index,cli,main}.{js,cjs,mjs,jsx,ts,cts,mts,tsx}",
"src/{index,cli,main}.{js,cjs,mjs,jsx,ts,cts,mts,tsx}"
],
"project": ["**/*.{js,cjs,mjs,jsx,ts,cts,mts,tsx}!"]
}
```
Next to this, there are other places where [Knip looks for entry files][2].
Additionally, [plugins have plenty of entry files configured][5] that are
automatically added as well.
[1]: ../typescript/unused-exports.md
[2]: ../explanations/entry-files.md
[3]: ../reference/configuration.md#ignoreexportsusedinfile
[4]: ../features/production-mode.md
[5]: ../explanations/plugins.md#entry-files
================================================
FILE: packages/docs/src/content/docs/guides/contributing.md
================================================
---
title: Contributing to Knip
---
Here are some ways to contribute to Knip:
- Spread the word!
- [Star the project][1]
- [File an issue with a reproduction][2]
- [Pull requests are welcome][3]
[Writing a plugin][4] is a great and fun way to get started.
The [CONTRIBUTING.md][3] and [DEVELOPMENT.md][5] guides should get you up and
running quickly.
The main goal of Knip is to keep projects clean & tidy. Everything that
contributes to that goal is welcome!
[1]: https://github.com/webpro-nl/knip
[2]: ./issue-reproduction.md
[3]: https://github.com/webpro-nl/knip/blob/main/.github/CONTRIBUTING.md
[4]: ../writing-a-plugin/index.md
[5]: https://github.com/webpro-nl/knip/blob/main/.github/DEVELOPMENT.md
================================================
FILE: packages/docs/src/content/docs/guides/handling-issues.mdx
================================================
---
title: Handling Issues
sidebar:
order: 3
---
import { Tabs, TabItem } from '@astrojs/starlight/components';
Issues reported by Knip may contain false positives, but also tons of useful
information. Getting the most out of Knip may require some configuration.
Go over the issue types one by one. For instance, reducing the number of unused
files will also reduce the number of unused dependencies.
1. [Unused files][1]
2. [Unused dependencies][2]
3. [Unresolved imports][3]
4. [Unused exports][4]
:::tip
Try the [Knip Editor Extension][5]! Let your coding agent use the built-in
MCP Server and create a custom `knip.json` for you.
:::
## Unused files
Getting the list of unused files right trickles down into the other issue types
as well, so we start here. Files are reported as unused if they are in the set
of `project` files, but not in the set of files resolved from the `entry` files:
```
unused files = project files - (entry files + resolved files)
```
:::tip
Addressing reported [Configuration Hints][6] first might help significantly,
especially when handling unused files.
:::
Common causes for unused files include:
- [Missing generated files][7]
- [Dynamic import specifiers][8]
- [Unsupported arguments in scripts][9]
- [Unsupported file formats][10]
- [Missing plugin][11]
- [Incomplete plugin][12]
- [TypeScript path aliases in monorepos][13]
- [Relative paths across workspaces][14]
- [Integrated monorepos][15]
- [Auto-mocking or auto-imports][16]
In most cases you can add `entry` patterns manually.
Use `--files` to [filter the report][17] and focus only on unused files:
```sh
knip --files
```
This works with other issue types as well. For instance, use `--dependencies` to
focus only on dependencies and exclude issues related to unused files and
exports.
:::caution
Don't add unused files to the `ignore` option before reading [configuring
project files][18]. Learn why and when to use `entry`, `project`, production
mode and `ignore*` patterns for better results and performance.
:::
### Missing generated files
For certain features, Knip needs to run after relevant files are generated. For
instance, [source mapping][19] in a monorepo may require files to be built into
`dist` folders first. And generated files in the `src` directory may import
other files. For instance, the `src/routeTree.gen.ts` file generated by
`@tanstack/router` must exist so Knip can find the imported route files.
**Solution**: compile and/or generate the relevant files first so Knip can
resolve and find the source files.
If Knip still reports false positives, you may need to strategically add an
`entry` file manually.
### Dynamic import specifiers
Dynamic import specifiers aren't resolved, such as:
```ts
const entry = await import(path.join(baseDir, 'entry.ts'));
```
**Solution**: add `entry.ts` to `entry` patterns.
### Unsupported arguments in scripts
Some tooling command arguments aren't recognized:
```json
{
"name": "my-lib",
"version": "1.0.0",
"scripts": {
"build": "unknown-build-cli --entry production.ts"
}
}
```
**Solution**: add `production.ts` to `entry` patterns.
This works the same for any script, also those in GitHub Actions workflows or
Git hooks. See [script parser][20] for more details about Knip's script parser.
### Unsupported file formats
Entry files referenced in HTML files (e.g. `