Skip to content
Fallow home
All docs pages

Built-in plugins

Get accurate results with zero configuration. Built-in plugins detect Next.js, Vite, Jest, Tailwind, ESLint, and many other tools, and tell fallow which files and exports each tool uses.

Built-in plugins give you accurate results without configuration. Without a plugin, fallow can report a route file, a config file, or a tool dependency as unused, because no import reaches it.

Fallow has built-in plugins for popular TypeScript and JavaScript tools. Each plugin tells fallow the conventions of one framework or tool: entry points, config files, used exports, and tooling dependencies.

Most plugins activate automatically when the relevant package is in your package.json. Some plugins (ESLint, Vitest) also activate when a config file is present in the workspace root, even without the package. This covers a monorepo that installs the tool at the root and gives the workspace packages their own config files.

PluginEnabler packages
Next.jsnext
Nuxtnuxt, @nuxt/kit
Pinia@pinia/nuxt
Remix@remix-run/node, @remix-run/react, @remix-run/cloudflare, @remix-run/cloudflare-pages, @remix-run/deno
SvelteKit@sveltejs/kit
Gatsbygatsby
Astroastro
Angular@angular/core, ng-packagr
Ionic@ionic/angular
React Router@react-router/dev
RedwoodSDKrwsdk (keeps src/worker.{ts,tsx,js,jsx,mts,mjs} reachable in rwsdk/vite apps)
TanStack Router@tanstack/react-router, @tanstack/solid-router, @tanstack/start, @tanstack/react-start, @tanstack/solid-start, @tanstack/virtual-file-routes
React Nativereact-native
Expoexpo
Firebasefirebase
NestJS@nestjs/core
AdonisJS@adonisjs/core
Docusaurus@docusaurus/core
Nitronitropack
Capacitor@capacitor/core, @capacitor/cli
Sanitysanity, @sanity/
VitePressvitepress
next-intlnext-intl
Relayrelay-runtime, react-relay, relay-compiler
Electronelectron, electron-builder, @electron-forge/cli, electron-vite
Qwik@builder.io/qwik, @builder.io/qwik-city, @qwik.dev/core, @qwik.dev/router
i18nexti18next, react-i18next, vue-i18n, next-i18next
Wuchalewuchale, @wuchale/vite-plugin, or wuchale.config.js
Convexconvex; the safe in-project directory from convex.json#functions becomes the functions entry root when configured
Litlit, lit-element, @lit/reactive-element (native Custom Elements lifecycle methods are credited without a Lit dependency)
Lexicallexical, @lexical/ (credits custom node lifecycle methods on DecoratorNode, ElementNode, TextNode subclasses)
Emberember-source, ember-cli, @embroider/core, @embroider/compat, @glimmer/component
Expo Routerexpo-router
Supabasesupabase, or supabase/config.toml / supabase/functions/ present (treats supabase/functions/*/index.* as runtime entry roots)
Content Collections@content-collections/core, @content-collections/vite, @content-collections/next, @content-collections/solid-start, @content-collections/remix-vite, @content-collections/qwik, @content-collections/vinxi
Contentlayercontentlayer, contentlayer2, next-contentlayer, next-contentlayer2, or contentlayer.config.{ts,js,mts,mjs} present
Fumadocsfumadocs-mdx, fumadocs-core, fumadocs-ui, or source.config.* present
Mintlifymint, mintlify, or docs.json / mint.json present
Velitevelite, or velite.config.{ts,mts,cts,js,mjs,cjs} present
rspressrspress, @rspress/ (credits the @theme / @theme-original virtual modules)
Obsidianobsidian
Browser extensionwebextension-polyfill, @types/chrome, @types/firefox-webext-browser
WXTwxt, @wxt-dev/
VS Code extensionvscode, @types/vscode, @vscode/test-electron
Plugins with deep config parsing

These plugins do more than detect a package. They parse your config files with the Oxc AST parser and extract entry points, dependencies, aliases, and other settings automatically:

PluginWhat it extracts
ESLintPlugin imports, shared configs, custom rule paths, import resolver packages
VitestReporters, coverage provider, typecheck checker, browser provider from vitest.config.ts
ViteEntry points, resolve.alias mappings, plugin imports from vite.config.ts
JestSetup files, transform paths, module mappers from jest.config.js
tsdDeclaration test directory from package.json#tsd.directory
StorybookStory globs, addon imports from .storybook/main.ts. Fallow resolves each stories pattern against the .storybook/ directory, as Storybook does. See Storybook stories
TailwindContent paths, plugin imports from tailwind.config.js
WebpackEntry points including descriptor/context forms, resolve.alias mappings, loader/plugin imports from webpack.config.js. An entry value that names a package instead of a file credits that dependency, with the resource query included (webpack-hot-middleware/client?reload=true credits webpack-hot-middleware). A glob-shaped value stays an entry pattern. Fallow also reads a config under config/, build/ or webpack/ when it exports a webpack configuration; see config directories. Inline loader requests (!raw-loader!./file.js) resolve to their resource; see inline loader requests
TypeScriptPath aliases, project references from tsconfig.json
BabelPlugin and preset imports from babel.config.js
RollupEntry points, plugin imports from rollup.config.js
RolldownEntry points, external deps, plugin imports from rolldown.config.ts
PostCSSPlugin imports from postcss.config.js
NuxtModules, plugins, server routes, alias, imports.dirs, components, srcDir from nuxt.config.ts. Fallow reads the config of each root separately: the project root and every workspace root. The custom components: or imports: shape of one app decides the autoImports verdict for that root only. See local layers
SvelteKitkit.alias mappings, param matchers from svelte.config.js
Next.jsApp Router convention exports (page, layout, loading, error, route), route segment config, Pages Router exports, middleware, proxy, instrumentation, mdx-components, transpilePackages
RsbuildEntry points, plugin imports from rsbuild.config.ts. A source.entry value that names a package credits that dependency, with the resource query included. A glob-shaped value stays an entry pattern. root applies to a relative entry, the same way context applies for Rspack
RspackEntry points, plugin imports from rspack.config.js. An entry value that names a package credits that dependency, with the resource query included. A glob-shaped value stays an entry pattern. context applies to a relative entry. An entry without an extension matches the source file or the directory index
RemarkPlugin imports from .remarkrc.js
AngularStyles, scripts, main, polyfills from angular.json build targets. The ng-packagr lib.entryFile in ng-package.json or ng-package.prod.json (default src/public_api.ts, resolved relative to the config directory) becomes a library public API entry point. Fallow also scans nested secondary-entry-point configs in the package subtree. @Component({ templateUrl, styleUrl }) decorator references create graph edges. Standalone .html templates and inline template: literals both add synthetic <template> complexity findings (@if/@for/@switch/@defer, *ngIf, bound attributes, interpolations). To suppress them, use <!-- fallow-ignore-file complexity --> (external) or // fallow-ignore-next-line complexity above the @Component decorator (inline)
NxExecutor references, main entry points, tsconfig paths from project.json
HardhatPlugin imports and side-effect requires from hardhat.config.ts
ContentlayercontentDirPath, same-file defineDocumentType filePathPattern entries, and config imports from contentlayer.config.*
VelitedefineCollection pattern globs joined to the top-level root (default content), generated .velite output, a non-default output.data dir, and config imports from velite.config.*
UnoCSSPreset and transformer imports from uno.config.ts
WuchaleAdapter imports from wuchale.config.js; static custom JavaScript config files referenced from @wuchale/vite-plugin in vite.config.*
Module Federationexposes targets, remotes aliases and shared packages from module-federation.config.* and from a Federation plugin call in a webpack, rspack, rsbuild, vite or next.config.* file. Remotes from literal runtime calls such as registerRemotes and loadRemote. See Module Federation

Run fallow list --plugins to see which plugins are active in your project.

Active plugins:
  - nextjs
  - vite
  - vitest
  - playwright
  - storybook
  - oxlint
  - typescript
  - tailwind
  - drizzle

If your framework is not in the list, write a custom plugin. Custom plugins support any internal or third-party tool, with declarative JSON or TOML.

Webpack config directories

Fallow also reads a webpack config that lives under config/, build/ or webpack/ instead of the package root. Rspack and rsbuild configs stay at the package root; they do not have this directory rule.

A file under config/ or webpack/ counts as a webpack config when it exports a webpack configuration: an object with a webpack config key other than name or dependencies (also returned from a function), an array of configurations, or a webpack-merge call (merge, mergeWithCustomize, mergeWithRules, or the imported package). A helper file such as config/webpack.paths.js does not export a configuration, so fallow still reports it as unused when nothing imports it.

build/ also holds build output, so fallow reads a narrower name there: only webpack.<target>.{ts,js,mjs,cjs}, such as build/webpack.prod.js, and only when it passes the same export check. Fallow never reads build/webpack.config.*, because that name is more likely to be a compiled or stale copy.

Fallow probes these directories beside a root config, so build/webpack.prod.js is read together with a root webpack.config.js.

Entries resolve like the bundler resolves them:

  • Rspack reads context, and rsbuild reads root. A relative entry, and a Module Federation exposes target, resolve against that directory.
  • An entry without an extension, such as ./lib or ./src/app, matches the source file (./lib.ts) or the directory index (./lib/index.ts). This also applies when context or root is set.
  • A Module Federation exposes target and the remotes scope of a webpack config in config/, build/ or webpack/ resolve against the package root, the same as webpack does without context.

Webpack inline loader requests

An import can name its loaders inline, such as require('!raw-loader?esModule=false!./shim.js'). Fallow reads these requests in every project, also when the webpack plugin is not active:

  • The request resolves to its resource, the last segment (./shim.js). The !, !! and -! prefixes and the ?options of each loader do not change the target.
  • Each loader package, such as raw-loader, counts as a used dependency. A loader in devDependencies is not reported as a dev dependency that production code uses.
  • A loader replaces the exports of its resource. A loader import therefore uses every value export of the resource, default included, and fallow does not report these exports as unused. A type export of the resource (a type alias or an interface) follows the same rule as a type export of a file that a dynamic import pattern matches: only an import that names it uses it.
  • A text loader returns the text, the bytes or a URL of the file: raw-loader, file-loader, url-loader, text-loader and similar. The resource counts as used, but its own imports do not. They do not keep other files in use, and they are not production imports. A loader that runs the file, such as worker-loader, css-loader or babel-loader, keeps the imports of the resource.
  • A text loader import does not run the resource, so it does not cross an architecture boundary, it does not carry server code into a "use client" file for client-server-leak, and a re-export through a loader is not a client or server origin of a mixed client/server barrel. A package file behind a text loader (raw-loader!pkg/notes.txt) is read at build time: the package is a used dependency, but not a dev dependency that production code imports.
  • A worker loader runs the resource in another thread: worker-loader, sharedworker-loader, worklet-loader, workerize-loader, comlink-loader, service-worker-loader and similar. The import loads the same way as new Worker(new URL('./work.js', import.meta.url)). A cycle through the import is reported by default, and circularDependencies.ignoreLazyImports skips it. fallow list --entry-weight counts the resource as out-of-thread code, not as startup code.
  • A ! is also valid in a file name. A request without a prefix, such as ./we!rd.js or pkg/a!b.js, resolves as a plain path first when that file exists, also an installed package file. When the file does not exist, for example in a clone without installed packages, fallow reads the request the way webpack does: webpack splits every request on !, so pkg/a!b.js is the loader pkg/a for b.js.
  • Only inline loaders change an import. A loader that a rule in module.rules of a webpack config applies, such as { test: /\.worker\.js$/, use: 'worker-loader' }, gives no worker or text loader semantics. The rule conditions (test, include, exclude, issuer, resourceQuery, the oneOf order) use JavaScript regular expressions and computed paths, and many projects build the rules in code, for example with webpack-merge, in the webpack function of next.config.js, or in Storybook webpackFinal. A static read would miss or misapply rules, and a misapplied text loader rule would hide the imports of every file that it matches. The configured loader packages still count as used dependencies.
  • When the resource does not resolve, the unresolved import shows the full request, so an ignoreUnresolvedImports entry must use the full request.

Module Federation

With Module Federation, fallow keeps exposed modules out of the unused-file report, does not report remote imports as unlisted dependencies, and does not report shared packages as unused dependencies.

Fallow reads exposes, remotes, and shared from module-federation.config.{ts,js,mjs,cjs,mts,cts}. It also reads them from a Federation plugin call in a webpack, rspack, rsbuild, vite or next.config.* file. Fallow recognizes these plugin calls: ModuleFederationPlugin, NextFederationPlugin, moduleFederationPlugin, pluginModuleFederation and federation. Fallow reads such a call wherever it is in the config program. A call counts only when its first argument is an object that declares exposes, remotes, or shared.

  • exposes: each target becomes a runtime entry point. The exposed module, and everything it reaches, stays out of unused-file. Its exports follow includeEntryExports, like the exports of any other entry point. A target that names a bare module request instead of a local path ("./utils": "shared-utils") credits that dependency, but only in the package that owns the config.
  • remotes: fallow treats each alias and its subpaths as provided by the remote container, so it does not report import("checkout/Button") as an unlisted dependency. The alias holds only under the directory of the config that declared it. A sibling workspace package that declares no remote still reports the dependency.
  • shared: each package that the option names counts as used, so it does not report as an unused dependency. Fallow reads the object form (shared: { react: { singleton: true } }), the array form (shared: ['react', 'react-dom']), an object element in the array form, and the import and packageName fields of an entry descriptor. A key with a trailing / credits the same package. A relative key shares a project module and credits no package. The credit applies only to the package that owns the config (the nearest package.json at or above the config), so a sibling workspace that declares the same package and does not use it still reports it. A shared entry for a package that package.json does not declare creates no finding. A shared value that fallow cannot read gives no credit and records no diagnostic.

Fallow reads a plugin call in these positions:

  • A nested plugin array, and a plugin list that a variable holds.
  • A tool-specific key such as tools.rspack.plugins.
  • A hook body, such as an rsbuild appendPlugins hook or a Next.js webpack(config) hook.
  • A helper module that the bundler config imports with a relative import or require, such as config/module-federation.js. Fallow reads the helper one import deep, inside the project root, as if the call was in the config. The diagnostics and the trace sources name the helper file.

Fallow also reads the plugin options through one step of indirection:

  • An export const in the same file (export const mfConfig = { ... }), and a plain const (const mfConfig = { ... }). The program must have one binding of that name, and no code may write to the binding or to one of its members.
  • A TypeScript non-null assertion on the binding, such as new ModuleFederationPlugin(mfConfig!).
  • A computed callee, such as new container['ModuleFederationPlugin']({ ... }).
  • A relative import of a sibling config file, including a default import and a named import.
  • A relative require of a sibling config file, one step deep. A require of a package stays silent.
  • An object spread over a same-file binding ({ ...base }) and Object.assign({}, base).
  • The array form of exposes. A string element is both the public name and the module request. An object element uses the object reader.
  • exposes targets and the remotes scope of a webpack config under config/, build/ or webpack/, resolved against the package root, as webpack does without context. See config directories.

Every config reader that resolves an options object, not only Module Federation, follows export const config = { ... }; export default config the same way.

Fallow does not read:

  • The array form of remotes. A bundler derives the request scope of an element from the whole container location, so the alias is not a bare specifier.
  • A value that is not a static object literal, an unreadable require of a package, and an array element that holds glob syntax, a nested array, or a value that is not a string.
  • A spread over a binding fallow cannot resolve. This records a plugin-config-unreadable diagnostic with the spread reason for each Federation key the readable part does not declare. When the options declare no Federation key at all, a bare federation(...) call records nothing, because other libraries use that name too.

Runtime remotes

A remote can also come from a call to the Federation runtime. Fallow reads these calls in a file that imports them from @module-federation/runtime or @module-federation/enhanced/runtime, by name, by a renamed import, or through a namespace import. A local function with the same name registers nothing.

  • registerRemotes([{ name: 'checkout', entry }]) registers checkout, and a literal alias when the object has one.
  • loadRemote('checkout/Button') registers checkout. A scoped name keeps two segments (@scope/remote).
  • init({ remotes }) and createInstance({ remotes }) register the remotes that their remotes option names. Fallow also reads the registerRemotes and loadRemote methods of the instance that they return.

A remote from a runtime call follows the same rule as a remotes entry: the alias and its subpaths are provided only inside the workspace that holds the file. Fallow also reads these calls in the <script> blocks of a .vue or .svelte file. The blocks count as one file, so an import in one block covers a call in another.

Fallow reads only a static literal argument. A call whose argument is not a static literal, such as loadRemote(`${scope}/${module}`), records a plugin-config-unreadable diagnostic on the source file, with the function name as key and the reason dynamic-argument.

Trace the Federation source

--trace-file and --trace-dependency name the Federation config when it is the reason for the result:

  • --trace-file on a file that an exposes entry makes an entry point lists each config that exposes it.
  • --trace-dependency on a remotes alias lists each config that declares the alias, and says that a remote container provides the name, not an npm package.

The human output adds one Source: line for each config. The JSON output has a sources array with { kind, plugin, config, key } entries, for example { "kind": "module-federation", "plugin": "webpack", "config": "packages/cart/webpack.config.js", "key": "exposes" }. For a remote that only a runtime call names, config is the source file and key is the function name. A trace with no Federation involvement has no sources array. The MCP trace_file and trace_dependency tools have the same field.

Limits and diagnostics

Fallow reads the config syntactically. It fetches no remote container and validates nothing about the remote. Fallow does not check the availability of the remote, its exports, its versions, or its shared singletons.

When fallow cannot read a declaration in full, it adds a plugin-config-unreadable entry to workspace_diagnostics[] and prints a warning on stderr. The entry names the plugin, the key, and the reason, with the config file in path. For a runtime call, path is the source file.

  • reason is not-object-literal, array-form, spread, unreadable-entries or dynamic-argument. For a runtime call, key is registerRemotes, loadRemote, init or createInstance. More values can come later, so do not treat this list as closed.
  • The plugin is module-federation for a standalone config file and for a runtime call. It is webpack, rspack, rsbuild, vite or nextjs when fallow read the options from the config of that plugin.
  • Two unreadable keys in one file give two entries.
  • The entry has degrades_analysis, so both CI integrations that fallow ships report it under degraded inputs.
fallow: Plugin 'module-federation': `exposes` in 'module-federation.config.ts' is not a static object literal, so the targets are not registered as entry points. Name the exposed files in `dynamicallyLoaded`.
fallow: Plugin 'webpack': `remotes` in 'webpack.config.js' uses the array form, which is not read yet, so the aliases are not treated as provided by a remote container. Name the aliases in `ignoreDependencies`, or declare them as the keys of an object literal, whose values may be computed.

For an exposed file that fallow cannot see, use dynamicallyLoaded. For a file that is a project entry point of its own, use entry. Put a remote alias in ignoreDependencies. Note that ignoreDependencies also hides unused-dependency findings for that name.

Nuxt local layers

Nuxt lets a project extend other Nuxt codebases, called layers. Fallow scans a local layer the same way it scans the project root.

A directory is a local layer when it holds a nuxt.config file (ts, js, mjs, cjs, mts or cts), and either:

  • A nuxt.config names it in extends, as a relative path. This includes the project root config and the config of any local layer already found, so a layer chain (a layer that itself extends another local layer) resolves too.
  • It sits under layers/<name>, beside the root config. Fallow scans every layers/<name> directory that has a nuxt.config, without an extends entry.

A local layer without a nuxt.config is not a layer, so its files keep reporting as unused when nothing references them.

For each local layer:

  • Its convention files (pages, layouts, components, composables, utils, server, plugins) are entry points, the same as the project root.
  • Its root files (app.vue, app.config.*) are always used.
  • With autoImports on, its components and composables are auto-import sources. An unreferenced layer component then reports as unused-file.
  • Its own nuxt.config takes part in the autoImports surface classification, alongside the root config. The advisory that fallow prints for a custom components: or imports: shape names the config file that decided that surface.

#layers/<name>/ resolves to a local layer by its $meta.name:

  • A layers/<name> directory without a $meta.name resolves by its directory name.
  • An extends layer without a $meta.name gets no alias, so an import through #layers/<name>/ stays unresolved.

A remote layer (a github: or gh: reference) or a package layer (an npm name) stays a dependency reference only. Fallow does not scan its files.

Fallow reads the modules of a package layer, so a module that the layer registers is active for the app. For example, with extends: ['docus'], or another package layer that lists @nuxt/content in its own modules, the components/content/ files and the content.config.* file of the app stay entry points. Fallow reads the nuxt.config of the layer from the nearest node_modules/<name>. docus activates @nuxt/content also when it is not installed. Fallow does not follow the extends of a package layer. A local layer that registers @nuxt/content also marks the content.config.* file of the app as used.

Nuxt auto-import scope

With autoImports on, an auto-import name marks as used only the files of the app that renders or calls it, and of the layers that this app extends. In a monorepo, a <Card /> in one app does not keep the unused Card.vue of another app. A run with autoImports on can therefore report more unused files than before.

  • A layer that an app extends by a relative path outside the app, or by the package name of a workspace, shares its names with that app in both directions. The app uses the components and Pinia stores of the layer. A layer layout can render a component that the app overrides.
  • Two apps that extend the same layer do not see the names of each other.

These component files stay entry points, because fallow cannot see how they render:

  • Files in components/global/ and *.global.vue files. A string reference such as resolveComponent('Name') can render a global component.
  • Files in components/content/ of the project and of each local layer, when @nuxt/content is in modules. Markdown content renders these components, and fallow does not read Markdown.

nuxt-og-image renders the template that a string names, so no import or template tag reaches the file. A defineOgImage('Docs.takumi') or defineOgImageComponent('Docs') call marks the matching file as used, under an OgImage/, og-image/, OgImageCommunity/, or OgImageTemplate/ components directory. The name match is the one that nuxt-og-image uses: dot segments join in PascalCase, and the renderer suffix (.satori, .browser, .takumi) and the directory prefix do not count. A template that no call names with a static string still reports as unused.

Storybook stories

Storybook reads each stories pattern in .storybook/main relative to the directory of that config file. Fallow does the same, so a pattern such as ../src/**/*.mdx matches the story files that Storybook loads.

  • A pattern can point into another workspace of the project. For example, a central docs app with apps/docs/.storybook and stories: ["../../../packages/ui/src/**/*.docs.tsx"] marks the matching files in packages/ui as used.
  • Fallow drops a pattern that leaves the project root.
  • Storybook reads a leading / as an absolute filesystem path. A pattern such as /src/** outside the project therefore marks nothing as used.
  • A @(ts|tsx) group, as in the Storybook config template, matches like {ts,tsx}. A nested @(..) group matches too. Other extglob forms, such as !(..), match nothing.
  • Fallow reads the object form { directory, files, titlePrefix }. directory resolves against the .storybook/ directory, and files applies under it. Without files, the Storybook default **/*.@(mdx|stories.@(js|jsx|mjs|ts|tsx)) applies.
  • Fallow also reads the .storybook/main config of a workspace when the root package uses Storybook too.

See also