From efa59eefb15cdaa0be409576f97fe28f4413be81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjam=C3=ADn=20Vicente?= Date: Sun, 8 Nov 2026 19:45:37 -0300 Subject: [PATCH 1/2] fix(vite): add Ivy members to .d.ts chunks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dts plugin only augmented declaration outputs of type `asset`, but rolldown-plugin-dts (and so tsdown) emits declarations as chunks. The published `.d.ts` then had no `ɵfac` / `ɵcmp` members, and Angular rejected the library's standalone components (NG2012: "Component imports must be standalone components, directives, pipes, or must be NgModules"). Augment `.d.ts` chunks through their `code`, and assets through their `source` as before. Fixes #681 --- napi/angular-compiler/test/dts.test.ts | 22 ++++++++++++++++++ napi/angular-compiler/vite-plugin/index.ts | 26 ++++++++++++++-------- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/napi/angular-compiler/test/dts.test.ts b/napi/angular-compiler/test/dts.test.ts index 3337a997ae..3834a9b9c6 100644 --- a/napi/angular-compiler/test/dts.test.ts +++ b/napi/angular-compiler/test/dts.test.ts @@ -526,6 +526,28 @@ describe('angular() dts plugin (#104)', () => { expect(out).toContain('static ɵcmp: i0.ɵɵComponentDeclaration { + const { transform, dts } = getPlugins('partial') + await runTransform(transform) + + const js = 'export class LibButtonComponent {}\n' + const bundle = { + 'index.d.mts': { + type: 'chunk' as const, + fileName: 'index.d.mts', + code: 'export declare class LibButtonComponent {\n}\n', + }, + 'index.mjs': { type: 'chunk' as const, fileName: 'index.mjs', code: js }, + } + await runGenerateBundle(dts, bundle) + + const out = bundle['index.d.mts'].code + expect(out).toContain('import * as i0 from "@angular/core";') + expect(out).toContain('static ɵfac: i0.ɵɵFactoryDeclaration { const { transform, dts } = getPlugins('full') await runTransform(transform) diff --git a/napi/angular-compiler/vite-plugin/index.ts b/napi/angular-compiler/vite-plugin/index.ts index 0b4230fefe..2a0b660e42 100644 --- a/napi/angular-compiler/vite-plugin/index.ts +++ b/napi/angular-compiler/vite-plugin/index.ts @@ -1777,17 +1777,25 @@ export function angular(options: PluginOptions = {}): Plugin[] { const declarations = Array.from(byClassName.values()) for (const file of Object.values(bundle)) { - if (file.type !== 'asset') continue if (!/\.d\.[cm]?ts$/.test(file.fileName)) continue - const source = - typeof file.source === 'string' - ? file.source - : Buffer.from(file.source).toString('utf-8') - - const augmented = injectDtsDeclarations(source, declarations) - if (augmented !== source) { - file.source = augmented + // rolldown-plugin-dts (and so tsdown) emits declarations as + // chunks; other generators emit them as assets. + if (file.type === 'chunk') { + const augmented = injectDtsDeclarations(file.code, declarations) + if (augmented !== file.code) { + file.code = augmented + } + } else if (file.type === 'asset') { + const source = + typeof file.source === 'string' + ? file.source + : Buffer.from(file.source).toString('utf-8') + + const augmented = injectDtsDeclarations(source, declarations) + if (augmented !== source) { + file.source = augmented + } } } }, From ffa622a22d094882b76e591336f757a0b52c0299 Mon Sep 17 00:00:00 2001 From: LongYinan Date: Mon, 9 Nov 2026 12:17:20 +0800 Subject: [PATCH 2/2] docs(vite): list which declaration outputs get Ivy members The README listed vite-plugin-dts and tsc as generators whose `.d.ts` get augmented, but both write their files outside the bundle, after `generateBundle`. tsdown's `.d.cts` for a `cjs` format is built in a separate pass without user plugins. Say so, and fix the rolldown-plugin-dts import (`{ dts }`). Also read and write the chunk / asset text in one path, and test the shared-chunk shape rolldown-plugin-dts emits (`declare class X` + `export { X as t }`). Co-Authored-By: Claude Opus 5.5 --- napi/angular-compiler/README.md | 17 ++++++++++------ napi/angular-compiler/test/dts.test.ts | 20 +++++++++++++++++++ napi/angular-compiler/vite-plugin/index.ts | 23 +++++++++++----------- 3 files changed, 43 insertions(+), 17 deletions(-) diff --git a/napi/angular-compiler/README.md b/napi/angular-compiler/README.md index 1e4d74d7c8..bf36c00838 100644 --- a/napi/angular-compiler/README.md +++ b/napi/angular-compiler/README.md @@ -286,7 +286,7 @@ type-checking against your library. ```typescript // vite.config.ts — Angular library build import { angular } from '@oxc-angular/vite' -import dts from 'rolldown-plugin-dts' // or vite-plugin-dts / tsdown +import { dts } from 'rolldown-plugin-dts' export default defineConfig({ plugins: [angular({ compilationMode: 'partial' }), dts()], @@ -295,11 +295,16 @@ export default defineConfig({ ``` The plugin does **not** generate the base `.d.ts` itself — a declaration -generator (`rolldown-plugin-dts`, `vite-plugin-dts`, `tsdown`, or `tsc`) must -produce them. The Angular members are then spliced into those files during -`generateBundle`. The injected members reference `i0` (the `@angular/core` -namespace), and the plugin adds `import * as i0 from "@angular/core";` to any -`.d.ts` it augments. +generator must produce them in the bundle, as `rolldown-plugin-dts` and +`tsdown` (`dts: true`) do. The Angular members are then spliced into those +files during `generateBundle`. The injected members reference `i0` (the +`@angular/core` namespace), and the plugin adds +`import * as i0 from "@angular/core";` to any `.d.ts` it augments. + +Declarations written outside the bundle are not augmented: `vite-plugin-dts` +and `tsc` write their files themselves, and tsdown builds the `.d.cts` of a +`cjs` format in a separate pass without user plugins. Use `format: 'esm'` with +tsdown. ## Vite Plugin Architecture diff --git a/napi/angular-compiler/test/dts.test.ts b/napi/angular-compiler/test/dts.test.ts index 3834a9b9c6..762fa369b5 100644 --- a/napi/angular-compiler/test/dts.test.ts +++ b/napi/angular-compiler/test/dts.test.ts @@ -548,6 +548,26 @@ describe('angular() dts plugin (#104)', () => { expect(bundle['index.mjs'].code).toBe(js) }) + it('augments shared .d.ts chunks that export the class under another name', async () => { + const { transform, dts } = getPlugins('partial') + await runTransform(transform) + + // The shape rolldown-plugin-dts gives a chunk shared by several entries. + const bundle = { + 'shared-abc.d.mts': { + type: 'chunk' as const, + fileName: 'shared-abc.d.mts', + code: 'declare class LibButtonComponent {\n}\nexport { LibButtonComponent as t };\n', + }, + } + await runGenerateBundle(dts, bundle) + + const out = bundle['shared-abc.d.mts'].code + expect(out).toContain('import * as i0 from "@angular/core";') + expect(out).toContain('static ɵcmp: i0.ɵɵComponentDeclaration { const { transform, dts } = getPlugins('full') await runTransform(transform) diff --git a/napi/angular-compiler/vite-plugin/index.ts b/napi/angular-compiler/vite-plugin/index.ts index 2a0b660e42..c49d0e203c 100644 --- a/napi/angular-compiler/vite-plugin/index.ts +++ b/napi/angular-compiler/vite-plugin/index.ts @@ -1780,22 +1780,23 @@ export function angular(options: PluginOptions = {}): Plugin[] { if (!/\.d\.[cm]?ts$/.test(file.fileName)) continue // rolldown-plugin-dts (and so tsdown) emits declarations as - // chunks; other generators emit them as assets. + // chunks; generators that call `emitFile` emit them as assets. + let source: string if (file.type === 'chunk') { - const augmented = injectDtsDeclarations(file.code, declarations) - if (augmented !== file.code) { - file.code = augmented - } - } else if (file.type === 'asset') { - const source = + source = file.code + } else { + source = typeof file.source === 'string' ? file.source : Buffer.from(file.source).toString('utf-8') + } - const augmented = injectDtsDeclarations(source, declarations) - if (augmented !== source) { - file.source = augmented - } + const augmented = injectDtsDeclarations(source, declarations) + if (augmented === source) continue + if (file.type === 'chunk') { + file.code = augmented + } else { + file.source = augmented } } },