Skip to content

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.

Knip has built-in “compilers” for the following file extensions:

  • .astro
  • .css (only enabled by tailwindcss)
  • .mdx
  • .prisma
  • .sass + .scss
  • .less
  • .styl + .stylus
  • .svelte
  • .tsrx
  • .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. The tradeoff is that unused exports inside these files go unreported.

Real compilers recover those exports, but bring their own challenges. The Svelte compiler, for instance, keeps exports intact even when they represent component properties, so Knip reports them as unused. When you need that fidelity, override any built-in with the framework’s own compiler (see the Svelte and Vue examples below). For example, to report unused exports in .tsrx files.

Compilers are enabled only when a related dependency is found. The .tsrx compiler when a @tsrx/* package is installed, for example. If that detection doesn’t work for your project, set true to enable any compiler manually:

knip.ts
export default {
compilers: {
mdx: true,
},
};

The default project patterns include compiler extensions automatically. When you override project, list them explicitly:

knip.ts
export default {
project: ['src/**/*.{ts,tsx,css}'],
};

Files outside project are not analyzed, so their imports don’t count. A narrowed pattern like ['src/**/*.{ts,tsx}'] silently drops everything a stylesheet imports, which is a common source of false positives: @import 'tailwindcss' in an excluded .css file gets tailwindcss reported as an unused dependency. Knip reports a configuration hint when a compiled extension is excluded this way.

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.

The compiler function interface is straightforward. Text in, text out:

(source: string, filename: string) => string;

This may also be an async function.

Here’s an example, minimal compiler for CSS files:

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, SASS/SCSS, Less and Stylus compilers are included.

Another example, in case the built-in MDX compiler is not enough:

import { compile } from '@mdx-js/mdx';
export default {
compilers: {
mdx: async text => (await compile(text)).toString(),
},
};

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:

import type { KnipConfig } from 'knip';
import { compile } from 'svelte/compiler';
export default {
compilers: {
svelte: (source: string) => compile(source, {}).js.code,
},
} satisfies KnipConfig;

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:

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;

To track any file extension that’s not meant to compile to JavaScript, register a no-op compiler:

knip.ts
export default {
compilers: { template: () => '' },
};

ISC License © 2026Lars Kappert