Aller au contenu

IDE Navigation Between Projects — Declaration Maps

Why this change

In a monorepo where shared packages (@bricks-common/helpers, @bricks-common/api-communication, @bricks-common-front/helpers) are consumed as pre-built artifacts via tsup, IDEs like VS Code and Cursor resolve "Go to Definition" to the compiled .d.ts files inside dist/. This means developers land on auto-generated type stubs instead of the actual source code, making it hard to read implementations, set breakpoints, or quickly navigate across package boundaries.

TypeScript's declaration maps (declarationMap: true) solve this by emitting .d.ts.map files alongside declarations. These map each declaration back to its original .ts source, so "Go to Definition" jumps straight to the real source file.

However, tsup's built-in dts: true option bundles all declarations into a single rolled-up .d.ts file — which destroys the 1:1 mapping needed for declaration maps to work. The fix is to split the build into two steps: let tsup handle the JavaScript bundling, and let tsc handle type declarations with proper source mapping.

What changed

1. Split build: JS bundling (tsup) + type declarations (tsc)

Each affected package's build script was changed from a single tsup invocation to a two-step pipeline:

- "build": "tsup"
+ "build": "pnpm run build:js && pnpm run build:types",
+ "build:js": "tsup",
+ "build:types": "tsc -b tsconfig.build.json --force"

Note : @bricks-common-front/helpers utilise tsc -p tsconfig.build.json (mode mono-projet, sans -b ni --force). La forme tsc -b … --force ci-dessus s'applique aux deux paquets both/ (@bricks-common/helpers et @bricks-common/api-communication).

  • build:js — tsup bundles the JavaScript output (ESM + CJS) as before, but with dts: false so it no longer emits declarations.
  • build:typestsc emits declarations (.d.ts) and declaration maps (.d.ts.map) preserving the per-file structure that IDEs need.

2. tsup: disabled built-in declaration generation

In each tsup.config.ts:

- dts: true,
+ dts: false,

tsup's dts: true rolls up all declarations into a single file, which breaks declaration map source links. Disabling it delegates declaration emit to tsc.

3. Réglages d'émission des déclarations (dans tsconfig.build.json)

Les réglages requis pour que tsc émette des declaration maps propres vivent dans le tsconfig.build.json de chaque paquet (voir section 4), pas dans tsconfig.json. Pour les deux paquets both/, tsconfig.json reste un fichier de pures références (files: [], include: [], references: [...]) ; pour @bricks-common-front/helpers, tsconfig.json conserve noEmit: true.

"noEmit": false,
"declarationMap": true,
"emitDeclarationOnly": true,
"outDir": "./dist",
"rootDir": "./src",
"tsBuildInfoFile": "./node_modules/.cache/tsconfig.build.tsbuildinfo",
Option Purpose
declarationMap: true Emits .d.ts.map files that map declarations back to source .ts files.
emitDeclarationOnly: true Only emits .d.ts and .d.ts.map — no JS (tsup handles that).
outDir / rootDir Controls where declarations land and preserves the directory structure in dist/.
tsBuildInfoFile Stores incremental build info inside node_modules/.cache/ to keep the working tree clean.

4. tsconfig.build.json: dedicated config for type builds

A new tsconfig.build.json was added to each package. Il porte les réglages d'émission des déclarations et exclut les fichiers de test du résultat. Les deux paquets both/ étendent tsconfig.base.json (quatre niveaux plus haut) ; seul @bricks-common-front/helpers étend ./tsconfig.json :

{
  "extends": "../../../../tsconfig.base.json", // front/helpers étend "./tsconfig.json"
  "compilerOptions": {
    "noEmit": false,
    "declarationMap": true,
    "emitDeclarationOnly": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "tsBuildInfoFile": "./node_modules/.cache/tsconfig.build.tsbuildinfo"
  },
  "include": ["src/**/*"],
  "exclude": ["dist", "node_modules", "**/*.test.ts", "**/*.spec.ts", "**/__test__/**", "**/__tests__/**"]
}

This prevents test files and mocks from leaking into the published type declarations while keeping the base tsconfig.json usable for type-checking and IDE support across all files (including tests).

5. TypeScript project references

api-communication/tsconfig.build.json declares a reference to helpers:

"references": [{ "path": "../helpers/tsconfig.build.json" }]

This tells tsc -b to build helpers first when building api-communication, respecting the dependency order (api-communication depends on helpers). It also enables cross-project "Go to Definition" navigation.

Packages affected

Package Path
@bricks-common/helpers projects/common/both/helpers
@bricks-common/api-communication projects/common/both/api-communication
@bricks-common-front/helpers projects/common/front/helpers

Impact on the monorepo

For developers

  • "Go to Definition" now lands on real source code instead of compiled .d.ts stubs — across all three shared packages.
  • No workflow change required: pnpm build continues to work as before; the split is internal to each package's build script.
  • Incremental builds: tsc -b with composite: true uses .tsbuildinfo files, making repeated type builds faster.

For CI / builds

  • Build time may increase marginally because type declarations are now a separate tsc step rather than inline tsup. In practice this is offset by incremental caching (tsBuildInfoFile).
  • The dist/ output of each package now contains per-file .d.ts + .d.ts.map pairs instead of a single rolled-up index.d.ts. Consumers that reference "types": "./dist/index.d.ts" in package.json are unaffected because tsc still emits an index.d.ts entry point.

For future shared packages

When creating a new shared package in projects/common/, follow the same pattern:

  1. Set dts: false in tsup.config.ts.
  2. Create a tsconfig.build.json (étendant tsconfig.base.json pour un paquet both/, ou ./tsconfig.json pour un paquet front/) avec declarationMap, emitDeclarationOnly, outDir, rootDir, tsBuildInfoFile et qui exclut les fichiers de test.
  3. Split the build script into build:js (tsup) and build:types (tsc -b tsconfig.build.json --force pour un paquet both/, tsc -p tsconfig.build.json pour un paquet front/).
  4. If the package depends on another shared package, add a references entry in tsconfig.build.json.