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.
| Plugin | Enabler packages |
|---|---|
| Next.js | next |
| Nuxt | nuxt, @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 |
| Gatsby | gatsby |
| Astro | astro |
| Angular | @angular/core, ng-packagr |
| Ionic | @ionic/angular |
| React Router | @react-router/dev |
| RedwoodSDK | rwsdk (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 Native | react-native |
| Expo | expo |
| Firebase | firebase |
| NestJS | @nestjs/core |
| nest-commander | nest-commander (credits the run method of each class that extends CommandRunner) |
| AdonisJS | @adonisjs/core |
| Docusaurus | @docusaurus/core |
| Nitro | nitropack |
| Capacitor | @capacitor/core, @capacitor/cli |
| Sanity | sanity, @sanity/ |
| VitePress | vitepress |
| next-intl | next-intl |
| Relay | relay-runtime, react-relay, relay-compiler |
| Electron | electron, electron-builder, @electron-forge/cli, electron-vite |
| Qwik | @builder.io/qwik, @builder.io/qwik-city, @qwik.dev/core, @qwik.dev/router |
| i18next | i18next, react-i18next, vue-i18n, next-i18next |
| Wuchale | wuchale, @wuchale/vite-plugin, or wuchale.config.js |
| Convex | convex; the safe in-project directory from convex.json#functions becomes the functions entry root when configured |
| Lit | lit, lit-element, @lit/reactive-element (native Custom Elements lifecycle methods are credited without a Lit dependency) |
| Lexical | lexical, @lexical/ (credits custom node lifecycle methods on DecoratorNode, ElementNode, TextNode subclasses) |
| Ember | ember-source, ember-cli, @embroider/core, @embroider/compat, @glimmer/component |
| Expo Router | expo-router |
| Flue | @flue/runtime (app, database, Cloudflare and agent modules, with flue.config.* overrides) |
| Supabase | supabase, 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 |
| Contentlayer | contentlayer, contentlayer2, next-contentlayer, next-contentlayer2, or contentlayer.config.{ts,js,mts,mjs} present |
| Fumadocs | fumadocs-mdx, fumadocs-core, fumadocs-ui, or source.config.* present |
| Mintlify | mint, mintlify, or docs.json / mint.json present |
| Velite | velite, or velite.config.{ts,mts,cts,js,mjs,cjs} present |
| rspress | rspress, @rspress/ (credits the @theme / @theme-original virtual modules) |
| Obsidian | obsidian |
| Browser extension | webextension-polyfill, @types/chrome, @types/firefox-webext-browser |
| WXT | wxt, @wxt-dev/ |
| VS Code extension | vscode, @types/vscode, @vscode/test-electron |
| Plugin | Enabler packages |
|---|---|
| Vite | vite, rolldown-vite |
| Webpack | webpack |
| Rspack | @rspack/core, @rspack/cli |
| Rsbuild | @rsbuild/core |
| Rollup | rollup |
| Rolldown | rolldown |
| Tsup | tsup |
| Tsdown | tsdown |
| pkg-utils | @sanity/pkg-utils |
| Parcel | parcel, @parcel/ |
| Module Federation | @module-federation/enhanced, @module-federation/modern-js, @module-federation/nextjs-mf, @module-federation/node, @module-federation/rsbuild-plugin, @module-federation/rspack, @module-federation/runtime, @module-federation/vite, @module-federation/webpack-bundler-runtime, @originjs/vite-plugin-federation |
| Plugin | Enabler packages |
|---|---|
| Vitest | vitest or config file present |
| Rstest | @rstest/core |
| Jest | jest |
| Playwright | @playwright/test, or playwright with a playwright.config.ts or playwright.config.js file in the package root |
| Cypress | cypress |
| Mocha | mocha |
| Jasmine | jasmine (reads spec/support/jasmine.* or a jasmine --config file) |
| Ava | ava |
| tap | tap |
| tsd | tsd |
| Storybook | storybook, @storybook/ |
| Karma | karma |
| Cucumber | @cucumber/cucumber |
| WebdriverIO | webdriverio, @wdio/ |
| k6 | k6, or *.k6.{js,ts,mjs,cjs,mts,cts} files present |
| Stryker | @stryker-mutator/core, stryker, or config file present |
| Plugin | Enabler packages |
|---|---|
| ESLint | eslint, @eslint/js, or config file present |
| Biome | @biomejs/biome |
| Stylelint | stylelint |
| Prettier | prettier |
| Oxfmt | oxfmt |
| Oxlint | oxlint |
| markdownlint | markdownlint, markdownlint-cli, markdownlint-cli2 |
| CSpell | cspell |
| Remark | remark, remark-cli |
| textlint | textlint (credits rule, filter and plugin packages by short name) |
| Plugin | Enabler packages |
|---|---|
| TypeScript | typescript |
| Babel | @babel/core |
| SWC | @swc/core, @swc/cli |
| Plugin | Enabler packages |
|---|---|
| Tailwind | tailwindcss, @tailwindcss/postcss |
| PostCSS | postcss |
| UnoCSS | unocss, @unocss/* |
| PandaCSS | @pandacss/dev |
| Plugin | Enabler packages |
|---|---|
| Prisma | prisma, @prisma/client |
| Drizzle | drizzle-orm |
| Knex | knex |
| TypeORM | typeorm |
| Kysely | kysely, kysely-ctl |
| Plugin | Enabler packages |
|---|---|
| Turborepo | turbo |
| Nx | nx |
| Kibana | a kibana.jsonc manifest present |
| Changesets | @changesets/cli |
| Syncpack | syncpack |
| pnpm | pnpm-workspace.yaml or pnpm-lock.yaml present |
| Plugin | Enabler packages |
|---|---|
| semantic-release | semantic-release |
| Commitlint | @commitlint/cli |
| Commitizen | commitizen |
| commit-and-tag-version | commit-and-tag-version, standard-version |
| Vercel | vercel, @vercel/config |
| Wrangler | wrangler |
| Railway | railway (credits the commands of each service() in .railway/railway.ts) |
| Serverless Framework | serverless, or a root serverless.* config (function handlers are entry points) |
| osls | osls (reads the same serverless.* config) |
| Alchemy | alchemy (stack files and the Worker entries that a stack declares). See Alchemy stacks |
| OpenNext Cloudflare | @opennextjs/cloudflare, or an opennextjs-cloudflare command in a package.json script |
| Sentry | @sentry/nextjs, @sentry/react, @sentry/node, @sentry/browser |
| Danger | danger |
| Plugin | Enabler packages |
|---|---|
| husky | husky |
| lint-staged | lint-staged |
| lefthook | lefthook, @evilmartians/lefthook |
| simple-git-hooks | simple-git-hooks |
| Plugin | Enabler packages |
|---|---|
| GraphQL Codegen | @graphql-codegen/cli |
| TypeDoc | typedoc |
| openapi-ts | openapi-typescript |
| Plop | plop |
| SVGO | svgo |
| SVGR | @svgr/core, @svgr/cli, @svgr/webpack, @svgr/rollup, @svgr/vite |
| size-limit | size-limit (credits @size-limit/* and size-limit-* presets and plugins) |
| c8 | c8 |
| nyc | nyc |
| MSW | msw |
| nodemon | nodemon |
| PM2 | pm2 |
| dependency-cruiser | dependency-cruiser |
| Hardhat | hardhat |
| Bun | bun-types, @types/bun |
| Deno | deno.json or deno.jsonc present |
| napi-rs | A napi field with targets in package.json, plus optionalDependencies |
| Varlock | varlock, @varlock/, or .env.schema present |
| mise | mise.toml, .mise.toml, .config/mise.toml, .config/mise/config.toml, or a mise command in a package.json script (credits task run commands) |
| TypeScript content mapper | A typescript.contentMapper field in package.json (the exec script is an entry point) |
| OpenCode | opencode.json, .opencode/, or @opencode-ai/ (credits auto-loaded plugin files and opencode.json plugin dependencies) |
| OpenTelemetry | Any @opentelemetry/ package (credits the SDK interface methods of samplers, processors, exporters and propagators). See OpenTelemetry SDK classes |
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:
| Plugin | What it extracts |
|---|---|
| ESLint | Plugin imports, shared configs, custom rule paths, import resolver packages |
| Vitest | Reporters, coverage provider, typecheck checker, browser provider from vitest.config.ts |
| Vite | Entry points, resolve.alias mappings, plugin imports from vite.config.ts and from named configs such as vite.config.lib.ts. The SWC plugins in the plugins option of @vitejs/plugin-react-swc and the Babel plugins and presets in the babelOptions of @wyw-in-js/vite count as used dependencies |
| Jest | Setup files, transform paths, module mappers, and test patterns from jest.config.*, jest-*.config.*, and jest.*.config.*. See Jest configs |
| tsd | Declaration test directory from package.json#tsd.directory |
| Storybook | Story globs, addon imports from .storybook/main.ts. Fallow resolves each stories pattern against the .storybook/ directory, as Storybook does. See Storybook stories |
| Tailwind | Content paths, plugin imports from tailwind.config.js |
| Webpack | Entry 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 |
| TypeScript | Path aliases, project references from tsconfig.json |
| Babel | Plugin and preset imports from babel.config.js |
| Rollup | Entry points, plugin imports from rollup.config.js and from named configs such as rollup.config.esm.js |
| Rolldown | Entry points, external deps, plugin imports from rolldown.config.ts |
| PostCSS | Plugin imports from postcss.config.js |
| Nuxt | Modules, 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 |
| SvelteKit | kit.alias mappings, param matchers from svelte.config.js |
| Next.js | App Router convention exports (page, layout, loading, error, route), route segment config, Pages Router exports, middleware, proxy, instrumentation, mdx-components, transpilePackages. The packages in experimental.swcPlugins count as used dependencies |
| NestJS | The schematics package that collection names in nest-cli.json counts as a used dependency |
| Rsbuild | Entry 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 |
| Rspack | Entry 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 |
| Remark | Plugin imports from .remarkrc.js |
| Angular | Styles, 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) |
| Nx | Executor references, main entry points, tsconfig paths, and the commands of nx:run-commands targets from project.json. See Nx run-commands targets |
| GraphQL Codegen | Plugin imports and documents globs from codegen*.{ts,js,cjs,mjs} and graphql.config.*. Each documents glob is an entry pattern, because the codegen run reads these files without an import |
| TypeORM | migrations globs from a DataSource config. See TypeORM migrations |
| Kibana | For each kibana.jsonc with "type": "plugin": public/index when plugin.browser, server/index when plugin.server, common/index, and the index of each plugin.extraPublicDirs directory. Scout Playwright configs and global setup files. setup, start and stop on classes that implement Plugin, PrebootPlugin or AsyncPlugin |
| Hardhat | Plugin imports and side-effect requires from hardhat.config.ts |
| Contentlayer | contentDirPath, same-file defineDocumentType filePathPattern entries, and config imports from contentlayer.config.* |
| Velite | defineCollection 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.* |
| UnoCSS | Preset and transformer imports from uno.config.ts |
| Wuchale | Adapter imports from wuchale.config.js; static custom JavaScript config files referenced from @wuchale/vite-plugin in vite.config.* |
| Module Federation | exposes 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.
Jest configs
The Jest plugin reads every Jest config in a package:
jest.config.*jest-*.config.*, for examplejest-integration.config.tsjest.*.config.*, for examplejest.e2e.config.ts
Each config adds its own test patterns. A config with its own testMatch or testRegex does not remove the default test patterns of the other configs.
Fallow converts a simple testRegex to a glob:
testRegex | Glob |
|---|---|
\.integration-spec\.ts$ | **/*.integration-spec.ts |
test/integration/.*\.integration-spec\.ts$ | test/integration/**/*.integration-spec.ts |
When fallow cannot convert a regex, add the test files to entry.
A --config file that a script passes to jest is also an entry point, for example jest --config ./jest-integration.config.ts.
Nx run-commands targets
Fallow reads the command and commands of each nx:run-commands target in a project.json file as a script. For example, tsx scripts/seed.ts makes scripts/seed.ts an entry point, and jest --config ./jest-integration.config.ts makes the Jest config an entry point.
- Fallow replaces the
{projectRoot}and{workspaceRoot}tokens. - Fallow also reads a command in the
configurationsof the target. - A command that calls another target of the same project, such as
nx ts-node -- src/run.ts, uses the command of that target. - Fallow reads a target only when it runs in the project directory, for example with
cwdset to{projectRoot}. A target withoutcwdin a nested project runs from the workspace root, so Fallow skips it. Fallow also skips a target with acwdthat points to a different directory, because its relative paths can point to the wrong files.
TypeORM migrations
Fallow reads the migrations option of a DataSource config in data-source.{ts,js}, *.data-source.{ts,js}, *.datasource.{ts,js}, and ormconfig.{ts,js,json}. Each glob becomes an entry pattern, because TypeORM loads the migration files at runtime without an import.
// src/data-source.ts
export const dataSource = new DataSource({
type: "postgres",
migrations: [`${isJest ? "src/" : "dist/"}migrations/*.{ts,js}`],
});
When a template selects between two paths, fallow uses the path that starts with src/. When no path starts with src/, fallow changes a dist/ path to the same path under src/.
Fallow does not read the entities and subscribers options. TypeORM uses only the decorated classes of these files. An entry point for each file would also hide the other unused exports and members of the file.
Classes and enums that a library reads by reflection
Some libraries read every member of a class or an enum. Project code does not name these members, so fallow counts them as used in these cases:
- ORM entities. An entity class that you pass to
getRepository,getCustomRepository,getTreeRepository,getMongoRepository, orcreateQueryBuilderuses all of its members. The type argument of a repository getter, as ingetRepository<Order>(), counts too. - ORM find calls. An entity class that you pass to
find,findBy,findOne,findOneBy,findOneOrFail,findOneByOrFail,findAndCount, orfindAndCountByuses all of its members. This applies only when the file imports fromtypeorm,@nestjs/typeorm, or a@mikro-orm/package. - GraphQL enums. An enum that you pass to
registerEnumTypefrom@nestjs/graphqlortype-graphqluses all of its values.
An entity class that code uses only as a type can still have unused properties. See Entity properties that code uses only as a type.
OpenTelemetry SDK classes
The OpenTelemetry SDK calls the methods of the samplers, processors, exporters and propagators that you register. Your code does not call these methods, so fallow counts them as used in these cases:
| Class heritage | Members that count as used |
|---|---|
implements Sampler | shouldSample, toString |
implements SpanProcessor | onStart, onEnding, onEnd, shutdown, forceFlush |
implements SpanExporter | export, shutdown, forceFlush |
implements PushMetricExporter | export, forceFlush, selectAggregationTemporality, selectAggregation, shutdown |
extends MetricReader | onInitialized, onForceFlush, onShutdown |
implements LogRecordProcessor | onEmit, enabled, shutdown, forceFlush |
implements LogRecordExporter | export, shutdown, forceFlush |
implements ContextManager | active, with, bind, enable, disable |
implements TextMapPropagator | inject, extract, fields |
implements TextMapGetter | keys, get |
implements TextMapSetter | set |
Other members of these classes stay reportable as unused-class-members.
Alchemy stacks
Alchemy reads a stack from alchemy.run.ts, or from the file that a script passes to the CLI (alchemy deploy ./stacks/app.ts). No project code imports a stack file, so fallow uses these files as entry points:
- Each
alchemy.run.ts,.mts,.cts,.js,.mjsor.cjsfile, at any depth. - Each stack file that a
package.jsonscript passes toalchemy deploy,dev,destroyorplan, also with--config. - The bundle entry that a resource names in its
mainproperty, for examplemain: "./src/worker.ts"ormain: new URL("./src/index.ts", import.meta.url).pathname.
A stack runs at deploy time, so a stack file is a support entry, not a production entry. The Worker code that a stack imports stays reachable with --production.
The Alchemy CLI loads @effect/platform-node or @effect/platform-bun with a dynamic import(), so fallow does not report these packages as unused dependencies.
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 readsroot. A relative entry, and a Module Federationexposestarget, resolve against that directory. - An entry without an extension, such as
./libor./src/app, matches the source file (./lib.ts) or the directory index (./lib/index.ts). This also applies whencontextorrootis set. - A Module Federation
exposestarget and theremotesscope of a webpack config inconfig/,build/orwebpack/resolve against the package root, the same as webpack does withoutcontext.
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?optionsof each loader do not change the target. - Each loader package, such as
raw-loader, counts as a used dependency. A loader indevDependenciesis 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,
defaultincluded, 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-loaderand 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 asworker-loader,css-loaderorbabel-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 forclient-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-loaderand similar. The import loads the same way asnew Worker(new URL('./work.js', import.meta.url)). A cycle through the import is reported by default, andcircularDependencies.ignoreLazyImportsskips it.fallow list --entry-weightcounts 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.jsorpkg/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!, sopkg/a!b.jsis the loaderpkg/aforb.js. - Only inline loaders change an import. A loader that a rule in
module.rulesof 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, theoneOforder) use JavaScript regular expressions and computed paths, and many projects build the rules in code, for example withwebpack-merge, in thewebpackfunction ofnext.config.js, or in StorybookwebpackFinal. 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
ignoreUnresolvedImportsentry 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 ofunused-file. Its exports followincludeEntryExports, 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 reportimport("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 theimportandpackageNamefields 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 nearestpackage.jsonat or above the config), so a sibling workspace that declares the same package and does not use it still reports it. Asharedentry for a package thatpackage.jsondoes not declare creates no finding. Asharedvalue 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
appendPluginshook or a Next.jswebpack(config)hook. - A helper module that the bundler config imports with a relative
importorrequire, such asconfig/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 constin the same file (export const mfConfig = { ... }), and a plainconst(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
importof a sibling config file, including a default import and a named import. - A relative
requireof a sibling config file, one step deep. Arequireof a package stays silent. - An object spread over a same-file binding (
{ ...base }) andObject.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. exposestargets and theremotesscope of a webpack config underconfig/,build/orwebpack/, resolved against the package root, as webpack does withoutcontext. 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
requireof 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-unreadablediagnostic with thespreadreason for each Federation key the readable part does not declare. When the options declare no Federation key at all, a barefederation(...)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 }])registerscheckout, and a literalaliaswhen the object has one.loadRemote('checkout/Button')registerscheckout. A scoped name keeps two segments (@scope/remote).init({ remotes })andcreateInstance({ remotes })register the remotes that theirremotesoption names. Fallow also reads theregisterRemotesandloadRemotemethods 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-fileon a file that anexposesentry makes an entry point lists each config that exposes it.--trace-dependencyon aremotesalias 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.
reasonisnot-object-literal,array-form,spread,unreadable-entriesordynamic-argument. For a runtime call,keyisregisterRemotes,loadRemote,initorcreateInstance. More values can come later, so do not treat this list as closed.- The plugin is
module-federationfor a standalone config file and for a runtime call. It iswebpack,rspack,rsbuild,viteornextjswhen 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 convention directories
Fallow scans plugins/, middleware/ and modules/ the same way Nuxt does: one level deep. A file is an entry point when it is a direct child of the directory, or the index file of a direct child directory. For example, plugins/bar.ts and plugins/foo/index.ts are entry points. plugins/foo/helper.ts and plugins/foo/deep/index.ts are not, so they report as unused when nothing imports them.
- A file in the
pluginslist ofnuxt.configis an entry point at any depth. - The files in
modules/<name>/runtime/stay entry points, because a module registers them throughresolver.resolve()strings that fallow does not follow. - The same rules apply under
app/and in each local layer. Under a configuredsrcDir, they apply toplugins/andmiddleware/.
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.confignames it inextends, 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 everylayers/<name>directory that has anuxt.config, without anextendsentry.
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
autoImportson, its components and composables are auto-import sources. An unreferenced layer component then reports asunused-file. - Its own
nuxt.configtakes part in theautoImportssurface classification, alongside the root config. The advisory that fallow prints for a customcomponents:orimports: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.nameresolves by its directory name. - An
extendslayer without a$meta.namegets 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.vuefiles. A string reference such asresolveComponent('Name')can render a global component. - Files in
components/content/of the project and of each local layer, when@nuxt/contentis inmodules. 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/.storybookandstories: ["../../../packages/ui/src/**/*.docs.tsx"]marks the matching files inpackages/uias 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 pattern can use the extglob groups
@(..),?(..),+(..),*(..)and!(..). A@(ts|tsx)group, as in the Storybook config template, matches like{ts,tsx}. A negated group excludes files:../src/**/!(*.dev).stories.@(js|ts)does not matchbutton.dev.stories.ts. See Extglob groups in tool globs. - Fallow reads the object form
{ directory, files, titlePrefix }.directoryresolves against the.storybook/directory, andfilesapplies under it. Withoutfiles, the Storybook default**/*.@(mdx|stories.@(js|jsx|mjs|ts|tsx))applies. - Fallow also reads the
.storybook/mainconfig of a workspace when the root package uses Storybook too.
Extglob groups in tool globs
Fallow reads the extglob groups @(..), ?(..), +(..), *(..) and !(..) in these tool globs:
- Storybook
storiespatterns - Playwright
testMatch - Vitest
test.includeandtest.exclude - Rstest
includeandexclude - Jasmine
spec_filesandhelpers import.meta.globpatterns, see Dynamic import resolution
The glob matcher cannot repeat a group, so fallow changes each group into a glob that it can match:
- A negated group
!(..)in an include glob excludes the files that match its alternatives. - A
+(..)group in an include glob matches one or more repetitions. Fallow reads+(ts|js)as{ts,js}*. Thus*.test.+(ts|js)also matchesa.test.jsxanda.test.tsx, so a few more files can become entry points. - An exclude glob matches a
+(..)group once and a*(..)group zero times or once. An exclude glob with a!(..)group removes no file. Thus an exclude never removes a file that the tool keeps. - A Playwright
testMatchglob with a group makes only the matching files entry points. The match ignores case, as before. - A Vitest plain
(a|b)group matches one of its alternatives.