-
Notifications
You must be signed in to change notification settings - Fork 609
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[api-extractor] improve the .d.ts rollup to handle augmented types #1709
Comments
Hi! We are having the exact same problem. We heavily use global types extensions and this is a blocker to implement api-extractor. Will this be fixed? If there is not enough band-width, can I get some guidance of how could I contribute a solution. Thanks! |
Following the Module Augmentation pattern does not work either. We use this patter a lot for plugin based logic where a plugin can extend a centralized interfaced to expose optional options for a common function. In this case for any extra options required by the plugin in the file I tried to go down into the Example:// modules ./core.ts
export interface Extras {}
export function foo(options: Extras) {
// ...
} // modules ./plugin.ts
declare module './core' {
export interface Extras {
someCoolExtra?: string;
}
} // modules ./index.ts
export * from './core';
export * from './plugin'; |
Could you provide a more complete example? The above code doesn't seem to compile for me. // TS2436: Ambient module declaration cannot specify relative module name
declare module './core' {
// TS2306: File 'plugin.ts' is not a module
export * from './plugin'; |
Sorry about the oversimplified example. Here is a working repo example: https://github.com/manrueda/api-extractor-module-augmentation |
Thanks @manrueda , this was super helpful! I added some fixes to the repro in PR manrueda/api-extractor-module-augmentation#1 . For your example, it seems like we want the .d.ts rollup to look like this: export declare interface Extras {
coreProp?: boolean;
}
export declare interface Extras {
pluginProp?: boolean;
} or even better, like this: export declare interface Extras {
coreProp?: boolean;
pluginProp?: boolean;
} Today, API Extractor's analyzer starts from the export { foo, Extras } ...and follows aliases to get to the " export declare interface Extras {
coreProp?: boolean;
} It then essentially copy+pastes that declaration into the .d.ts rollup file. Suppose hypothetically that your symbol had multiple "merged declarations" in that file, like this: core.ts export declare interface Extras {
coreProp?: boolean;
}
export declare interface Extras {
moreStuff?: boolean;
} In that case, the analyzer would make a single But with augmented types, there's a new challenge: The type of a symbol can be different from the perspective of different files. In your example, the Playing around with this in API Extactor, I could not find any This raises a concern about uniqueness -- if the same symbol appears in different contexts, which augmentation should API Extractor choose? For exported symbols, I think the answer is use the entry point file (index.ts). For forgotten exports (or intentionally unexported types), I'm not sure there is a well-defined answer, and maybe it would be better to ignore augmentation for that case. A second challenge is how to make the .d.ts rollup. The compiler engine seems to treat We could just say that augmented // core.ts
export interface Extras {
coreProp?: boolean;
}
// plugin.ts
declare module './core' {
export function Extras(): void;
} And so is this: // core.ts
export enum Extras {
coreProp = 1
}
// plugin.ts
declare module './core' {
export namespace Extras { }
} Thinking about this... I feel like we need to find/invent some compiler black magic that lets us retrieve the original |
BTW looking back at @yangmingshan 's original issue description, it seems that his request involed globally-scoped augmented declarations, which is a fairly different problem. Today API Extractor doesn't even support non-augmented globals. (Global variables are frankly a not a great way to define an API, so there hasn't been a lot of interest in doing the work.) In vuejs/core#652 (comment) it looks like they realized that Thus I'm going to repurpose this issue to focus on @manrueda 's repro instead. |
Thanks @octogonz! I was using global augmented types to have my extensibility feature but I am changing to do module based to have a cleaner API. I expand the example repo with another example that I want to have that is extending a type from an NPM package. manrueda/api-extractor-module-augmentation@e25c974 Not sure if that makes thing way more complicated or not. I used an interface from @microsoft/api-extractor just for example purpuses. |
It does. 😁 But I had totally not thought about that possibility. It is an enlightening case to consider, even if we don't support it initially.
BTW if someone wants to pitch in, it would be super helpful for someone to figure out how to enumerate the |
Something that I just remember (and why I end up in the global augmentation issue) it's that we have a less important case where we extend the TypeScript build it |
Is there any progress on this? Are there any known workarounds? |
I just write a simple script to read // api-extractor doesn't support `declare global`. This function is a workaround for it.
// See also https://github.com/microsoft/rushstack/issues/1709
async function copyDeclare(pkg: Package) {
const sourceFiles = await glob(['**/*.ts', '**/*.tsx'], {
cwd: path.join(pkg.dir, 'src'),
absolute: true,
});
const chunks: string[] = [];
for (const filePath of sourceFiles) {
const content = await readFile(filePath, { encoding: 'utf-8' });
const lines = content.split('\n');
const startIndex = lines.indexOf('declare global {');
if (startIndex === -1) {
continue;
}
chunks.push(lines.slice(startIndex).join('\n'));
}
if (chunks.length === 0) {
return;
}
const code = `\n${chunks.join('\n\n')}`;
const destFiles = await glob(['_tsup-dts-rollup*'], {
cwd: path.join(pkg.dir, 'dist'),
absolute: true,
});
for (const filePath of destFiles) {
const content = await readFile(filePath, { encoding: 'utf-8' });
await writeFile(filePath, content + code);
}
} |
In case this is helpful to anyone. I'm using api-extractor through vite-plugin-dts with As a workaround for this issue, I moved all my module augmentations to a single file ( import dtsPlugin from "vite-plugin-dts";
import { appendFile, readFile } from "fs/promises";
export default defineConfig({
plugins: [
dtsPlugin({
rollupTypes: true,
async afterBuild() {
const filterFile = await readFile("./src/type-extensions.ts");
await appendFile("./dist/facilmap-leaflet.d.ts", filterFile);
},
}), |
I followed @cdauth's suggestion, which has been a helpful workaround. however, i settled on a I am augmenting so i ended up with something like this:
and otherwise the vite config is as described. thanks for that suggestion, it really helped my situation |
This issue proposes to improve API Extractor's analysis to handle augmented types, as illustrated in this repro from @manrueda:
https://github.com/manrueda/api-extractor-module-augmentation
In TypeScript, types can get "augmented" when the original declaration is extended locally in a file, via declaration merging. For example, we might have an entry point like this:
index.ts
Original description involving global augmentation
Is this a feature or a bug?
Please describe the actual behavior.
.d.ts rollup generation will remove global type extension.
If the issue is a bug, how can we reproduce it? Please provide detailed steps and include a GitHub branch if applicable. Your issue will get resolved faster if you can make it easy to investigate.
Source code:
Generated code:
What is the expected behavior?
Generated code should keep global type extension.
If this is a bug, please provide the tool version, Node.js version, and OS.
The text was updated successfully, but these errors were encountered: