You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Can not declaration merging for default exported class #14080
// A *self-contained* demonstration of the problem follows...// ===== file Foo.ts =====exportdefaultclassFoo{hello(){console.log('Foo');}}// ===== file Bar.ts =====importFoofrom'./Foo';declare module './Foo'{interfaceFoo{add(x,y);}}// ERROR: 'add' does not exist in type Foo.Foo.prototype.add=function(x,y){returnx+y;};letf=newFoo();f.hello();f.add(3,4);// ERROR: 'add' does not exist in type Foo.
Reacted by Tim Buschtöns, Jackson Teather, Ian MacLeod, Adam Shaw, Pulkit Goyal, afc163, Rohit Gohri, Matt McCutchen, csha, Christian Rackerseder and 44 more
Would fixing this address my problem with defining inner classes and interfaces on a default export? I get "Merged declaration 'Outer' cannot include a default export declaration."
mymodule.ts:
export default class Outer {
//...
}
namespace Outer {
export class Inner {
//...
}
}
myconsumer.ts:
import Outer from 'mymodule';
function whatevs(innie: Outer.Inner) {
//...
}
I'm trying to keep my consumer code succinct, so I don't have code like:
import MetaOuter from 'mymodule';
function whatevs(outie: MetaOuter.Outer) {
//...
}
function whatevs(innie: MetaOuter.Inner) {
//...
}
(Actually, my preference is to do this with an inner interface in my particular situation, but I can't get it to work with an inner class either.)
(Also, in my case, static methods on subclasses of Outer produce the Inner instances, for use by external classes in characterizing instances of the Outer subclasses.)
My interim solution is to move the inner class out to its own module. I can give it a reasonable import name and still keep class references succinct. I guess I don't really need inner classes and interfaces.
On Thu, Sep 28, 2017, 10:49 Joe Lapp ***@***.***> wrote:
Ugh. Here's a prevalent use case for nested interfaces:
export default class MyController {
constructor(options?: Options) {
//...
}
}
namespace MyController {
export interface Options {
//...
}
}
Except you can't do it yet on default exports.
Are people using a different pattern? Just not using default exports?
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#14080 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAChnSJ6MAONZ-j5pwNsuxcaos8l6rNsks5sm9wpgaJpZM4MBduT>
.
Thanks Ian MacLeod (@nevir). Is the issue with defaults and refactoring that a class is less likely to have a single name across the entire application? Is that also an argument for not exporting functions directly but instead making stateless functions static methods on exported classes?
I wanted to point out that I think export-assigned (export =) classes have the same problem as default-exported classes of not being augmentable. There have been two questions (1, 2) about this on Stack Overflow in the past week. Given the lack of action on this bug, I didn't think it would be helpful to file a separate bug for export-assigned classes.
Reacted by Abraham White, Christian Rackerseder, Sean M. Vieira, Dan Manastireanu, Julien Scholz, dizayn-design and dejan9393
Is going to get fixed anytime soon? It makes correctly typing some modules, eg. markdown-it-incremental-dom, impossible. Markdown-it is an extendible Markdown compiler and some extensions add methods to its default export. Because of this bug I can't add proper typings to DefinitelyTyped and using the extension with TypeScript is a real pain.
// @types/markdown-it-incremental-domimport*asMDfrom'markdown-it';declare module 'markdown-it'{// Idea #1 (failed)interfaceMarkdownItStatic{renderToIncrementalDOM(): void;}letdefault: MarkdownItStatic;// Idea #2 (failed)declaredefault.renderToIncrementalDOM(): void;// Idea #3 (failed)interfaceMarkdownItStatic{renderToIncrementalDOM(): void;}// Idea #4 (failed)constdefault:
{renderToIncrementalDOM(): void}}
EDIT: I just realized that markdown-it is yet another type of evil – callable export = functionAndNamespace;. So the comment should read “consider a hypothetical reasonably typed module called markdown-it…”
m93a I don't think your scenario has anything to do with this issue since you aren't using a class. Your idea 3 works if I export the MarkdownItStatic interface in the first file. It isn't exported by default since the module has a default export expression (see this post), and it has to be exported in order to be augmented.
Reacted by Felix Hochgruber, Danielle Church, Aidin Abedi, Dan Manastireanu, Soichiro Miki, Aaron Ross, Joshua Shaffer, Alex, Manuel Thalmann, Renārs Vilnis and 4 more
I don't know exactly why, but it works even if in my file I import as a different name import * as ChartX from 'chart.js', so it will handle any refactoring/renames you make.
I haven't tested if this also works with default exports.
Merged declaration 'MyClassName' cannot include a default export declaration. Consider adding a separate 'export default MyClassName' declaration instead.ts(2652)
That's because /** @typedef {import('stream').Readable} */ is incomplete. Once I change it to /** @typedef {import('stream').Readable} Readable */ the error goes away. This is the reproducible with JS. I'm not sure there's a Typescript analogy.
It seems to be working with the following convoluted setup:
some-library-you-cant-modify/Foo.ts
// This is the original class. You can't augment it directly because it's a default export.exportdefaultclassFoo{existingProperty: number;}
my-library/typings/Foo.ts
importFoofrom'some-library-you-cant-modify/Foo'// just reexporting the same class, but this time it's not a default exportexport{Foo};
my-library/typings/Foo.d.ts
import{Foo}from'./Foo';declare module './Foo'{interfaceFoo{myProperty: string;// class Foo is now augmented with this added property}}
my-library/consumer.ts
// Notice I'm importing from the original library, it works fine anyway.// This means you don't have to update any existing code.importFoofrom'some-library-you-cant-modify/Foo';letfoo=newFoo();foo.existingProperty=42;// we obviously can still access original propertiesfoo.myProperty="";// the added property works fine too, no compilation error
You'll have to add your Foo.d.ts file in my-library/tsconfig.json:
{
"files": [
"typings/Foo.d.ts"
]
}
Reacted by Tomas Chmelevskij, Jeremy Li, Dmitry Dulesov, xiaohai, Jaul, rdsn, mr-sanders and Gulg
Reacted by Brian McBarron, Jeremy Li, Mark Michaelis, Alex Helfet and Bubinek
Activity
liujunxing commentedon Feb 15, 2017
If I write both export Foo and export default Foo in Foo.ts, its works (both import Foo and import {Foo} works)
mhegazy commentedon Feb 15, 2017
You can only augment "exported" declarations. Class
Foois exported asdefault, and not asFoo. so the nameFoodoes not exist outside the module.It just happens that
defaultis a reserved word, and can not used as an interface declaration name.The TS compiler needs to allow
export { Foo as default}in module augmentation.Zaibot commentedon Mar 10, 2017
Could this be related to an old bug? #2537
mhegazy commentedon Mar 10, 2017
Tobias de Groen (@Zaibot) this feature (module augmentation) did not exist back then. So no.
Zaibot commentedon Mar 13, 2017
Sorry, misread or mixed up the thread
jtlapp commentedon Sep 28, 2017
Would fixing this address my problem with defining inner classes and interfaces on a default export? I get "Merged declaration 'Outer' cannot include a default export declaration."
mymodule.ts:
myconsumer.ts:
I'm trying to keep my consumer code succinct, so I don't have code like:
(Actually, my preference is to do this with an inner interface in my particular situation, but I can't get it to work with an inner class either.)
(Also, in my case, static methods on subclasses of Outer produce the Inner instances, for use by external classes in characterizing instances of the Outer subclasses.)
jtlapp commentedon Sep 28, 2017
My interim solution is to move the inner class out to its own module. I can give it a reasonable import name and still keep class references succinct. I guess I don't really need inner classes and interfaces.
jtlapp commentedon Sep 28, 2017
P.S. How did I automatically unassign Yui (@yuit)?
jtlapp commentedon Sep 28, 2017
Ugh. Here's a prevalent use case for nested interfaces:
Except you can't do it yet on default exports.
Are people using a different pattern? Just not using default exports?
nevir commentedon Sep 29, 2017
jtlapp commentedon Sep 29, 2017
Thanks Ian MacLeod (@nevir). Is the issue with defaults and refactoring that a class is less likely to have a single name across the entire application? Is that also an argument for not exporting functions directly but instead making stateless functions static methods on exported classes?
1 remaining item
mattmccutchen commentedon Sep 17, 2018
I wanted to point out that I think export-assigned (
export =) classes have the same problem as default-exported classes of not being augmentable. There have been two questions (1, 2) about this on Stack Overflow in the past week. Given the lack of action on this bug, I didn't think it would be helpful to file a separate bug for export-assigned classes.cshaa commentedon Oct 7, 2018
Is going to get fixed anytime soon? It makes correctly typing some modules, eg.
markdown-it-incremental-dom, impossible. Markdown-it is an extendible Markdown compiler and some extensions add methods to its default export. Because of this bug I can't add proper typings to DefinitelyTyped and using the extension with TypeScript is a real pain.EDIT: I just realized that
markdown-itis yet another type of evil – callableexport = functionAndNamespace;. So the comment should read “consider a hypothetical reasonably typed module calledmarkdown-it…”mattmccutchen commentedon Oct 7, 2018
m93a I don't think your scenario has anything to do with this issue since you aren't using a class. Your idea 3 works if I
exporttheMarkdownItStaticinterface in the first file. It isn't exported by default since the module has a default export expression (see this post), and it has to be exported in order to be augmented.rdsedmundo commentedon Aug 15, 2019
Is there a way for augmenting a module exported with
export =though? I wasn't able to get it working.i.e
knex.d.ts
file.ts
hanzhangyu commentedon Oct 12, 2019
defaultis a keyword of JS. By the way, you can export a new variable temporarily.node_modules -> knex.ts
custom knex.ts
danmana commentedon May 6, 2020
I just figured out a workaround for this while trying to augment
chart.js, which is declared as a Class and namespace, and exported withexport =node_modules @types/chart.js/index.d.ts
my-typings.d.ts
usage in my-file.ts
I don't know exactly why, but it works even if in my file I import as a different name
import * as ChartX from 'chart.js', so it will handle any refactoring/renames you make.I haven't tested if this also works with default exports.
Here is the PR that I made for the chart.js augmentation DefinitelyTyped/DefinitelyTyped#44519
clshortfuse commentedon Jan 14, 2021
Was going a bit insane trying to figure our what was happening. Here's a small reproducible:
That's because
/** @typedef {import('stream').Readable} */is incomplete. Once I change it to/** @typedef {import('stream').Readable} Readable */the error goes away. This is the reproducible with JS. I'm not sure there's a Typescript analogy.neuoy commentedon Feb 25, 2022
It seems to be working with the following convoluted setup:
some-library-you-cant-modify/Foo.ts
my-library/typings/Foo.ts
my-library/typings/Foo.d.ts
my-library/consumer.ts
You'll have to add your
Foo.d.tsfile in my-library/tsconfig.json:{ "files": [ "typings/Foo.d.ts" ] }chmelevskij commentedon Apr 20, 2022
Youen (@neuoy) solution kind of worked, but for me it broke other things 😓 Like building with rollup plugin typescript
refactor(link): Introduce `index.ts` and `augmentation.ts`
zhangone233 commentedon Jul 31, 2023
When will this issue be fixed ?
GulgDev commentedon Mar 31, 2025
Are there any better workarounds than what Youen (@neuoy) suggested? I have a more complex case in which that would be harder to do.
RyanCavanaugh commentedon Aug 18, 2026
The correct way to do this is
Rejecting the augmentation in OP is the correct behavior;
export default class Foodoes not make an export namedFoo.