Skip to content

fix(compiler): resolve the swc source root per application - #3589

Merged
kamilmysliwiec merged 2 commits into
nestjs:masterfrom
thekhegay:fix/swc-monorepo-source-root
Sep 24, 2026
Merged

kamilmysliwiec merged 2 commits into
nestjs:masterfrom
thekhegay:fix/swc-monorepo-source-root

Conversation

@thekhegay

Copy link
Copy Markdown
Contributor

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

[x] Bugfix
[ ] Feature
[ ] Code style update (formatting, local variables)
[ ] Refactoring (no functional changes, no api changes)
[ ] Build related changes
[ ] CI related changes
[ ] Other... Please describe:

What is the current behavior?

Issue Number: N/A

After upgrading to 12.0.5 I tried swc on a two-app monorepo — demo is the
default project, api sits beside it. nest build api --builder swc prints
Successfully compiled: 5 files and exits 0, but what lands in
dist/apps/api is the default app:

dist/apps/api/apps/demo/src/app.controller.js
dist/apps/api/apps/demo/src/app.controller.spec.js
dist/apps/api/apps/demo/src/app.module.js
dist/apps/api/apps/demo/src/app.service.js
dist/apps/api/apps/demo/src/main.js

nest start api --builder swc then fails with
Cannot find module .../dist/apps/api/main.

(The workspace nest new generates pins "builder": "rspack" in
nest-cli.json, so --builder swc on the command line is what selects swc —
it is not the flag picking the wrong builder.)

swcDefaultsFactory reads configuration.sourceRoot straight off the root of
nest-cli.json. In a monorepo that key is the default project's source root,
while outDir comes from the selected application's tsconfig — so swc gets one
app's sources and another app's output directory.

What is the new behavior?

sourceRoot is resolved with getValueOrDefault and the application name, the
lookup base-compiler and assets-manager already use, so filenames and the
stripLeadingPaths derived from it follow the app being built. A project that
declares no sourceRoot of its own still falls back to the root value.
runTypeChecker read the same root key and now resolves it the same way.

Same command on the same workspace:

dist/apps/api/apps/api/src/api.controller.js
dist/apps/api/apps/api/src/api.module.js
dist/apps/api/apps/api/src/api.service.js
dist/apps/api/apps/api/src/main.js

and nest start api --builder swc boots ApiModule.

Tests: three in swc-defaults.spec.ts (the named app's sourceRoot wins over
the root one, stripLeadingPaths follows it, a project without its own
sourceRoot still falls back), one in swc-compiler.spec.ts for the forked
type checker, and one e2e in build.command.e2e-spec.ts that builds the second
app of a scaffolded monorepo with swc and asserts the default app's modules are
not in its dist. An existing assertion in swc-compiler.spec.ts gained a
fourth argument, since swcDefaultsFactory now takes the application name.

Does this PR introduce a breaking change?

[x] Yes
[ ] No

stripLeadingPaths now derives from the app's own source root, so a workspace
that pins root-level builder.options.filenames while its app rootDir is its
own src gets a different output layout. Measured on a monorepo whose root
sourceRoot is apps/demo/src, with
compilerOptions.builder.options.filenames: ["apps/api/src"] and
apps/api/tsconfig.app.json setting "rootDir": "src", nest build api:

before   dist/apps/api/apps/api/src/main.js
after    dist/apps/api/api/src/main.js

Where the root sourceRoot already is the built app's, nothing moves.

Other information

  • In watch mode runTypeChecker forks forked-type-checker.js with the source
    root, and that argument is where the CLI plugin metadata file goes. With a
    plugin configured, nest build api --builder swc --watch --type-check died on
    ENOENT ... apps/api/apps/demo/src/metadata.ts; it now writes
    apps/api/src/metadata.ts.

Switching the same workspace to nest build with no project name builds the
default app correctly, so this only bites the named ones.

thekhegay and others added 2 commits September 23, 2026 15:06
In a monorepo, `nest build <app> --builder swc` compiled the default
project's sources into the named application's outDir and exited 0, so
`nest start <app> --builder swc` then failed with "Cannot find module
dist/apps/<app>/main". swcDefaultsFactory read the root "sourceRoot",
which is the default project's, while outDir came from the selected
application's tsconfig.

Resolve "sourceRoot" through getValueOrDefault and the application name,
the way base-compiler and assets-manager already do. Watch mode read the
same root key and forked the type checker with it, which pointed the CLI
plugin metadata at a directory that does not exist; it now resolves the
same way.
@swc/cli strips only the first path segment, so a monorepo source root
such as apps/api/src was emitted as api/src/main.js, which nest start
cannot resolve. Skip stripping for nested source roots so the output
follows the <outDir>/<sourceRoot>/<entryFile> layout nest start expects.
@kamilmysliwiec
kamilmysliwiec merged commit cc90ce9 into nestjs:master Sep 24, 2026
1 check passed
kamilmysliwiec pushed a commit that referenced this pull request Sep 25, 2026
In watch mode with --type-check, the swc compiler forks the type checker
with the root "compilerOptions.plugins" only. In a monorepo an application
can declare its own plugins under "projects.<app>.compilerOptions.plugins",
and the non-watch path already loads them per application through
loadPlugins(), so `nest build api --builder swc --type-check` generated
the CLI plugin metadata while `nest start api --builder swc --type-check
--watch` reported "Found 0 issues" and generated none.

Resolve the plugins through getValueOrDefault with the application name,
the same way #3589 did for "sourceRoot" one argument above.
@thekhegay
thekhegay deleted the fix/swc-monorepo-source-root branch October 21, 2026 09:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants