Skip to content

Replace rollup-plugin-dts with dts-bundle-generator#370

Merged
gh-worker-dd-mergequeue-cf854d[bot] merged 13 commits into
masterfrom
yoann/dts-bundle-generator
May 22, 2026
Merged

Replace rollup-plugin-dts with dts-bundle-generator#370
gh-worker-dd-mergequeue-cf854d[bot] merged 13 commits into
masterfrom
yoann/dts-bundle-generator

Conversation

@yoannmoinet

@yoannmoinet yoannmoinet commented May 15, 2026

Copy link
Copy Markdown
Member

What and why?

Replace rollup-plugin-dts with dts-bundle-generator for bundling the published packages' TypeScript declarations. The previous setup hit DOM-lib vs @types/node conflicts when bundling types that pulled in browser APIs (e.g. the RUM Browser SDK types) and was flagged as slow in the rollup config.

This unblocks #144 (exposing the RUM plugin in the root README and dropping its hideFromRootReadme flag). #144 wants consumers of the published bundler plugins to be able to configure RUM without installing @datadog/browser-rum themselves — which requires RumInitConfiguration and RumPublicApi to be bundled into the shipped .d.ts. Under rollup-plugin-dts the bundle failed on DOM-vs-Node lib conflicts the moment those SDK types entered the graph, so #144 carried a // TODO: Find a way to bundle in types in the plugin. and a fallback path that re-declared the SDK types by hand. With this PR's two-pass + inlinedLibraries setup, the SDK types are inlined cleanly into the published .d.ts, and #144 can drop the workaround and use Assign<RumInitConfiguration, …> directly.

How?

Two-pass bundling (packages/tools/src/dtsBundlePlugin.mjs)

  • Pass 1 — TypeScript API emits .d.ts for every workspace file reachable from the entry, using the root tsconfig (no DOM lib, so no @types/node conflict).
  • Pass 2dts-bundle-generator bundles against the emitted .d.ts files with DOM lib enabled. Because every reachable file is now a declaration (entry + @dd/* redirected via paths), dts-bundle-generator hits its allFilesAreDeclarations shortcut and skips its own compilation — so the lib conflict never resurfaces.
  • Temp artifacts (emitted .d.ts, the bundle tsconfig) land in <CWD>/.dts-tmp-<package>/ so TypeScript resolves external packages via the project's node_modules by walking up — no symlink hacks. The whole mkdir → emit → bundle → write flow is wrapped in try/finally, so a failure cleans up.
  • Lives in its own module so the dts-specific deps (dts-bundle-generator, typescript) don't leak into rollupConfig.mjs's import graph.

Workspace @dd/* paths

  • Declared once in the root tsconfig.json (compilerOptions.paths). Both passes extend the root config and inherit them; pass 2 rewrites them to point at the emitted .d.ts files inside the temp dir.
  • A new yarn cli integrity check derives the expected mapping from each workspace's package.json exports and reconciles it with the root tsconfig — idempotent when correct, auto-fixes drift, always writes alphabetically sorted.

Plugin buildPlugin config

  • Each plugin's package.json now carries its build-time config under a single buildPlugin block (alongside the pre-existing hideFromRootReadme):
    • buildPlugin.inlinedLibraries: npm packages whose types should be folded into the bundled .d.ts rather than left as imports (currently @datadog/browser-core + @datadog/browser-rum-core, declared on @dd/rum-plugin).
    • buildPlugin.toBuild: sub-build descriptors that used to live at the top level of package.json.
  • The dts plugin unions every plugin's inlinedLibraries at build time — no shared rollup-config edits when adding a future plugin that needs to inline another library.

Library configuration at the dts-bundle-generator call site

  • inlinedLibraries: union of plugin-declared values.
  • importedLibraries: every peer/runtime dep that isn't inlined — preserved as import statements.
  • output.exportReferencedTypes: false — keeps internally-referenced SDK types (Site, BufferedObservable, Encoder, …) as declare rather than export. Without this, dts-bundle-generator's default behaviour was leaking ~280 internal types as part of our public API surface; the documented surface is now ~10 names (VitePluginOptions, datadogVitePlugin, the per-plugin *Types lookups, helpers, version).

RUM SDK types

  • packages/plugins/rum/src/browserSdkTypes.ts re-exports RumPublicApi and RumInitConfiguration from @datadog/browser-rum-core directly (instead of the top-level @datadog/browser-rum). This matches what's inlined and avoids a typeof datadogRum indirection that confused the bundler.

Yarn patch on dts-bundle-generator

  • Adds a null check in TypesUsageEvaluator for cases where getImportExportReferencedSymbol returns undefined (triggered by postcss.d.mts re-exporting from ./postcss.js under bundler-mode resolution).
  • Verified empirically that the patch is still required even without inlinedLibraries: the evaluator walks types from importedLibraries and other transitive sources too, so the bug is reachable through vite / rollup peer-dep type graphs.

yoannmoinet and others added 13 commits May 15, 2026 10:22
Uses a two-pass approach to avoid DOM lib vs @types/node conflicts:
- Pass 1: TypeScript API emits .d.ts for all workspace files without DOM lib
- Pass 2: dts-bundle-generator bundles against emitted .d.ts files with DOM lib,
  skipping its own compilation pass (allFilesAreDeclarations shortcut)

Includes a Yarn patch for dts-bundle-generator to add a missing null check
in TypesUsageEvaluator when getImportExportReferencedSymbol returns undefined
(triggered by postcss.d.mts exporting from ./postcss.js in bundler mode).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Emit temp .d.ts files and tsconfigs to os.tmpdir() to avoid cluttering CWD
- Symlink project node_modules into the temp dir so TypeScript can resolve external packages from emitted files
- Use absolute paths for tsconfig extends since the temp configs live outside the project
- Configure inlinedLibraries (@datadog/browser-rum-core, @datadog/browser-core) vs importedLibraries (peer/runtime deps) for dts-bundle-generator
- Update LICENSES-3rdparty.csv to swap rollup-plugin-dts for dts-bundle-generator
- Emit temp files inside the project (`.dts-tmp-<pkg>/`) so TypeScript
  finds node_modules by walking up — drops the symlink hack.
- Hoist `dts-bundle-generator` and `typescript` to top-level imports.
- Drop the `generated` flag; Rollup calls `closeBundle` once per bundle.
- Wrap the whole mkdir → emit → bundle flow in `try/finally` so an
  error mid-build doesn't leak temp dirs.
- Co-locate both tsconfigs inside the temp dir, single cleanup target.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Skip pass 1's tsconfig file write — feed the parsed root config to
  `ts.parseJsonConfigFileContent` directly and merge overrides in the
  `createProgram` call.
- Drop redundant `emitOnlyDtsFiles=true` (already implied by
  `emitDeclarationOnly`) and the explicit `createCompilerHost`
  (`createProgram` builds its own).
- Hoist `entrySrcPath` / `entryDtsPath` and drop `|| {}` defensives on
  `peerDependencies`/`dependencies` since the typedef declares them.
- Tighten the doc block and inline notes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dts-bundle-generator's default `exportReferencedTypes: true` was
preserving the `export` modifier on every type it inlined from
`@datadog/browser-rum-core` / `@datadog/browser-core`, leaking ~280
internal types (Site, BufferedObservable, Encoder, TraceContextInjection,
…) as part of our public API.

Setting it to `false` keeps the documented surface (`VitePluginOptions`,
`datadogVitePlugin`, `*Types`, `helpers`) exported and demotes everything
else to `declare`, so structural lookups like `RumTypes['RumInitConfiguration']`
still work but `import { Site } from '@datadog/vite-plugin'` no longer
compiles.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move the @dd/* `paths` map from a runtime `buildDdPaths()` fs walk in
the rollup config to a declarative block in the root tsconfig.json.
Both dts passes already extend the root config, so pass 1 inherits
the paths and pass 2 reads them off the parsed root config to compute
the .d.ts variant.

- Drops ~42 lines of node_modules-walking logic.
- Makes the workspace structure visible at a glance.
- Aligns paths with what tsc / typecheck already see.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Derives the expected @dd/* `paths` map from each workspace's
package.json `exports` and reconciles it with the root tsconfig.json.
The check is idempotent — if the existing paths match, the file is
not rewritten — and auto-fixes when a workspace is added/renamed/removed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Compare the existing paths block as-is — any out-of-order entry
triggers a rewrite so the on-disk file is always alphabetically
sorted.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- `toBuild` is now nested under each plugin's `buildPlugin` block,
  alongside the existing `hideFromRootReadme`.
- New `buildPlugin.inlinedLibraries` field lets plugins declare
  which npm packages should be inlined into the bundled .d.ts.
- `getDtsBundlePlugin` collects the union of `inlinedLibraries`
  from every plugin manifest at build time, replacing the
  hardcoded list in the rollup config.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move `getDtsBundlePlugin` and its `collectInlinedLibraries` /
`buildDtsPaths` helpers from `rollupConfig.mjs` into a sibling
`dtsBundlePlugin.mjs`. `rollupConfig.mjs` now just imports the
plugin factory; the dts-specific deps (`dts-bundle-generator`,
`typescript`) only load on the consumer side.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@yoannmoinet yoannmoinet marked this pull request as ready for review May 19, 2026 14:58
@yoannmoinet yoannmoinet requested review from a team as code owners May 19, 2026 14:58
@yoannmoinet yoannmoinet requested review from michaeldfoley and removed request for a team May 19, 2026 14:58
@yoannmoinet

Copy link
Copy Markdown
Member Author

@codex review
@cursor review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@gh-worker-dd-mergequeue-cf854d gh-worker-dd-mergequeue-cf854d Bot merged commit 0490391 into master May 22, 2026
5 checks passed
@gh-worker-dd-mergequeue-cf854d gh-worker-dd-mergequeue-cf854d Bot deleted the yoann/dts-bundle-generator branch May 22, 2026 19:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants